From 5a6be06053427c6373049a9cf37bf12c0020f311 Mon Sep 17 00:00:00 2001 From: Michael Born Date: Fri, 13 Mar 2026 12:07:47 -0400 Subject: [PATCH 01/15] Enable request overrides at the *Builder level and on individual HyperClient method calls --- models/AliasBuilder.cfc | 2 +- models/BaseModel.cfc | 45 ++ models/Document.cfc | 2 +- models/ILMPolicyBuilder.cfc | 2 +- models/IndexBuilder.cfc | 2 +- models/Pipeline.cfc | 2 +- models/SearchBuilder.cfc | 2 +- models/io/HyperClient.cfc | 435 +++++++++++++----- models/io/HyperPool.cfc | 17 +- .../tests/specs/unit/HyperClientTest.cfc | 50 +- 10 files changed, 434 insertions(+), 125 deletions(-) create mode 100644 models/BaseModel.cfc diff --git a/models/AliasBuilder.cfc b/models/AliasBuilder.cfc index 17903860..5a477ba5 100644 --- a/models/AliasBuilder.cfc +++ b/models/AliasBuilder.cfc @@ -1,4 +1,4 @@ -component accessors="true" { +component accessors="true" extends="BaseModel" { property name="action"; property name="indexName"; diff --git a/models/BaseModel.cfc b/models/BaseModel.cfc new file mode 100644 index 00000000..e9db25cf --- /dev/null +++ b/models/BaseModel.cfc @@ -0,0 +1,45 @@ +/** + * + * Elasticsearch Document Object + * + * @package cbElasticsearch.models + * @author Jon Clausen + * @license Apache v2.0 + * + */ +component accessors="true" { + property name="requestOverrides" type="struct"; + + public BaseModel function init(){ + variables.requestOverrides = {}; + return this; + } + + public BaseModel function withRequestOverrides( struct overrides ){ + setRequestOverrides( arguments.overrides ); + return this; + } + + public struct function getRequestOverrides(){ + return variables.requestOverrides ?: {}; + } + + /** + * Handles any missing methods that start with "with" and adds the value to the requestOverrides struct for request configuration + * Example: withTimeout( 1000 ) would set a default timeout of 1000ms on all requests created by this model + * + * @param methodName the name of the method being called + * @param arguments the arguments passed to the method, where arguments[1] is expected to be the value to set for the default + * @return returns the model instance for chaining + */ + public BaseModel function onMissingMethod( string methodName, struct arguments ){ + if( left( methodName, 4 ) == "with" ){ + var defaultKey = lCase( mid( methodName, 5 ) ); + if( !isNull( arguments[ 2 ][ 1 ] ) ) { + variables.requestOverrides[ defaultKey ] = arguments[ 2 ][ 1 ]; + } + return this; + } + return this; + } +} \ No newline at end of file diff --git a/models/Document.cfc b/models/Document.cfc index 5ae858b9..2b4ab3a0 100644 --- a/models/Document.cfc +++ b/models/Document.cfc @@ -7,7 +7,7 @@ * @license Apache v2.0 * */ -component accessors="true" { +component accessors="true" extends="BaseModel" { property name="config" inject="Config@cbelasticsearch"; diff --git a/models/ILMPolicyBuilder.cfc b/models/ILMPolicyBuilder.cfc index d49b9c47..9956a820 100644 --- a/models/ILMPolicyBuilder.cfc +++ b/models/ILMPolicyBuilder.cfc @@ -1,4 +1,4 @@ -component accessors="true" { +component accessors="true" extends="BaseModel" { property name="policyName"; diff --git a/models/IndexBuilder.cfc b/models/IndexBuilder.cfc index 86ecbfc3..44fc769f 100644 --- a/models/IndexBuilder.cfc +++ b/models/IndexBuilder.cfc @@ -7,7 +7,7 @@ * @license Apache v2.0 * */ -component accessors="true" { +component accessors="true" extends="BaseModel" { // The name of our index property name="indexName"; diff --git a/models/Pipeline.cfc b/models/Pipeline.cfc index 6d77eb61..f335c0b4 100644 --- a/models/Pipeline.cfc +++ b/models/Pipeline.cfc @@ -1,4 +1,4 @@ -component accessors="true" threadSafe { +component accessors="true" extends="BaseModel" threadSafe { property name="Util" inject="Util@cbelasticsearch"; diff --git a/models/SearchBuilder.cfc b/models/SearchBuilder.cfc index 7dbdf719..13c8e1ed 100644 --- a/models/SearchBuilder.cfc +++ b/models/SearchBuilder.cfc @@ -7,7 +7,7 @@ * @license Apache v2.0 * */ -component accessors="true" { +component accessors="true" extends="BaseModel" { property name="configObject" inject="Config@cbelasticsearch"; diff --git a/models/io/HyperClient.cfc b/models/io/HyperClient.cfc index a801038b..be991a5a 100644 --- a/models/io/HyperClient.cfc +++ b/models/io/HyperClient.cfc @@ -169,15 +169,18 @@ component accessors="true" threadSafe singleton { /** * Execute a client search request * @searchBuilder SearchBuilder An instance of the SearchBuilder object + * @requestOverrides struct A struct of request overrides to pass to the node pool * * @return SearchResult The search result object * @interfaced **/ cbElasticsearch.models.SearchResult function executeSearch( - required cbElasticsearch.models.searchBuilder searchBuilder + required cbElasticsearch.models.searchBuilder searchBuilder, + struct requestOverrides = {} ){ + var mergedOverrides = arguments.searchBuilder.getRequestOverrides().append( arguments.requestOverrides ); var requestBuilder = variables.nodePool - .newRequest( SearchBuilder.getIndex() & "/_search", "POST" ) + .newRequest( searchBuilder.getIndex() & "/_search", "POST", mergedOverrides ) .setBody( arguments.searchBuilder.getJSON() ) .asJSON(); @@ -199,13 +202,18 @@ component accessors="true" threadSafe singleton { /** * Retreives a count of documents matching the given query * @searchBuilder [SearchBuilder] An instance of the SearchBuilder object + * @requestOverrides struct A struct of request overrides to pass to the node pool * * @return numeric The returned count matching the search parameters * @interfaced */ - numeric function count( required cbElasticsearch.models.searchBuilder searchBuilder ){ + numeric function count( + required cbElasticsearch.models.searchBuilder searchBuilder, + struct requestOverrides = {} + ){ + var mergedOverrides = arguments.searchBuilder.getRequestOverrides().append( arguments.requestOverrides ); var requestBuilder = variables.nodePool - .newRequest( SearchBuilder.getIndex() & "/_count", "POST" ) + .newRequest( searchBuilder.getIndex() & "/_count", "POST", mergedOverrides ) .setBody( getUtil().toJSON( { "query" : arguments.searchBuilder.getQuery() } ) ) .asJSON(); @@ -222,12 +230,16 @@ component accessors="true" threadSafe singleton { * Verifies whether an index exists * * @indexName string the name of the index + * @requestOverrides struct A struct of request overrides to pass to the node pool * @interfaced **/ - boolean function indexExists( required string indexName ){ + boolean function indexExists( + required string indexName, + struct requestOverrides = {} + ){ return ( variables.nodePool - .newRequest( arguments.indexName, "HEAD" ) + .newRequest( arguments.indexName, "HEAD", arguments.requestOverrides ) .send() .getStatusCode() < 400 ); @@ -237,11 +249,15 @@ component accessors="true" threadSafe singleton { * Verifies whether an index mapping exists * * @indexName string the name of the index + * @requestOverrides struct A struct of request overrides to pass to the node pool * @interfaced **/ - boolean function indexMappingExists( required string indexName ){ + boolean function indexMappingExists( + required string indexName, + struct requestOverrides = {} + ){ try { - var mappings = getMappings( arguments.indexName ); + var mappings = getMappings( arguments.indexName, arguments.requestOverrides ); } catch ( any e ) { return false; } @@ -253,10 +269,14 @@ component accessors="true" threadSafe singleton { * Returns the settings for an index * * @indexName string the name of the index ( optional ) if null returns all settings for the server + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - struct function getSettings( string indexName ){ + struct function getSettings( + string indexName, + struct requestOverrides = {} + ){ var response = variables.nodePool - .newRequest( !isNull( arguments.indexName ) ? arguments.indexName & "/_settings" : "_settings", "GET" ) + .newRequest( !isNull( arguments.indexName ) ? arguments.indexName & "/_settings" : "_settings", "GET", arguments.requestOverrides ) .send(); if ( response.getStatusCode() != 200 ) { @@ -273,8 +293,13 @@ component accessors="true" threadSafe singleton { * * @indexName string the name of the index * @field an optional field name or field pattern + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - struct function getMappings( required string indexName, string field ){ + struct function getMappings( + required string indexName, + string field, + struct requestOverrides = {} + ){ var path = arguments.indexName & "/_mapping"; if ( !isNull( arguments.field ) ) { path &= "/field/" & arguments.field; @@ -283,7 +308,7 @@ component accessors="true" threadSafe singleton { arguments.field = "*"; path &= "/field/" & arguments.field; } - var response = variables.nodePool.newRequest( path, "GET" ).send(); + var response = variables.nodePool.newRequest( path, "GET", arguments.requestOverrides ).send(); if ( response.getStatusCode() != 200 ) { onResponseFailure( response ); @@ -311,11 +336,15 @@ component accessors="true" threadSafe singleton { /** * Applies an index item ( create/update ) * @indexBuilder IndexBuilder An instance of the IndexBuilder object + * @requestOverrides struct A struct of request overrides to pass to the node pool * * @return boolean True if the index was successfully applied * @interfaced **/ - boolean function applyIndex( required cbElasticsearch.models.IndexBuilder indexBuilder ){ + boolean function applyIndex( + required cbElasticsearch.models.IndexBuilder indexBuilder, + struct requestOverrides = {} + ){ var indexResult = {}; if ( isNull( arguments.indexBuilder.getIndexName() ) ) { @@ -358,9 +387,11 @@ component accessors="true" threadSafe singleton { structDelete( indexDSL.mappings, "_all" ); } - if ( !indexExists( indexName ) ) { + var mergedOverrides = arguments.indexBuilder.getRequestOverrides().append( arguments.requestOverrides ); + + if ( !indexExists( indexName, mergedOverrides ) ) { var requestBuilder = variables.nodePool - .newRequest( indexName, "PUT" ) + .newRequest( indexName, "PUT", mergedOverrides ) .setBody( getUtil().toJSON( indexDSL ) ) .asJSON(); @@ -378,20 +409,19 @@ component accessors="true" threadSafe singleton { } else { if ( structKeyExists( indexDSL, "mappings" ) ) { if ( !isMajorVersion( 6 ) ) { - indexResult[ "mappings" ] = applyMapping( indexName, "_doc", indexDSL.mappings ); + indexResult[ "mappings" ] = applyMapping( indexName, "_doc", indexDSL.mappings, {}, mergedOverrides ); } else { - indexResult[ "mappings" ] = applyMappings( indexName, indexDSL.mappings ); + indexResult[ "mappings" ] = applyMappings( indexName, indexDSL.mappings, mergedOverrides ); } } if ( structKeyExists( indexDSL, "settings" ) && !structIsEmpty( indexDSL.settings ) ) { var requestBuilder = variables.nodePool - .newRequest( indexName & "/_settings", "PUT" ) + .newRequest( indexName & "/_settings", "PUT", mergedOverrides ) .setBody( getUtil().toJSON( indexDSL.settings ) ) .asJSON(); var response = requestBuilder.send(); - if ( structKeyExists( response.json(), "error" ) ) { onResponseFailure( response ); } @@ -406,11 +436,15 @@ component accessors="true" threadSafe singleton { * Deletes an index * * @indexName string the name of the index to be deleted + * @requestOverrides struct A struct of request overrides to pass to the node pool * **/ - struct function deleteIndex( required string indexName ){ + struct function deleteIndex( + required string indexName, + struct requestOverrides = {} + ){ return variables.nodePool - .newRequest( arguments.indexName, "DELETE" ) + .newRequest( arguments.indexName, "DELETE", arguments.requestOverrides ) .send() .json(); } @@ -427,6 +461,7 @@ component accessors="true" threadSafe singleton { * @script any A script to run while reindexing. * @throwOnError boolean Whether to throw an exception if the reindexing fails. This flag is * only used if `waitForCompletion` is `true`. + * @requestOverrides struct A struct of request overrides to pass to the node pool * * @return any Struct result of the reindex action if waiting for completion or a Task object if dispatched asnyc **/ @@ -436,10 +471,11 @@ component accessors="true" threadSafe singleton { boolean waitForCompletion = true, any params, any script, - boolean throwOnError = true + boolean throwOnError = true, + struct requestOverrides = {} ){ var requestBuilder = variables.nodePool - .newRequest( "_reindex", "POST" ) + .newRequest( "_reindex", "POST", arguments.requestOverrides ) .setQueryParam( "wait_for_completion", arguments.waitForCompletion ); var body = { @@ -515,12 +551,17 @@ component accessors="true" threadSafe singleton { * * @indexName string|array Index name or alias. Can accept an array of index/alias names. * @params struct Struct of query parameters to influence the request. For example: `{ "ignore_unavailable" : true }` + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - struct function refreshIndex( required any indexName, struct params = {} ){ + struct function refreshIndex( + required any indexName, + struct params = {}, + struct requestOverrides = {} + ){ if ( isArray( arguments.indexName ) ) { arguments.indexName = arrayToList( arguments.indexName ); } - var refreshRequest = variables.nodePool.newRequest( "/#arguments.indexName#/_refresh", "post" ); + var refreshRequest = variables.nodePool.newRequest( "/#arguments.indexName#/_refresh", "post", arguments.requestOverrides ); return refreshRequest .withQueryParams( arguments.params ) @@ -534,11 +575,13 @@ component accessors="true" threadSafe singleton { * @indexName string|array Index name or alias. Can accept an array of index/alias names. * @metrics array Array of index metrics to retrieve. I.e. `[ "completion","refresh", "request_cache" ]`. * @params struct Struct of query parameters to influence the request. For example: `{ "expand_wildcards" : "none", "level" : "shards" } + * @requestOverrides struct A struct of request overrides to pass to the node pool */ struct function getIndexStats( any indexName, array metrics = [], - struct params = {} + struct params = {}, + struct requestOverrides = {} ){ if ( isArray( arguments.indexName ) ) { arguments.indexName = arrayToList( arguments.indexName ); @@ -549,7 +592,7 @@ component accessors="true" threadSafe singleton { endpoint.prepend( arguments.indexName ); } endpoint.append( arrayToList( metrics ) ); - var statsRequest = variables.nodePool.newRequest( arrayToList( endpoint, "/" ), "get" ); + var statsRequest = variables.nodePool.newRequest( arrayToList( endpoint, "/" ), "get", arguments.requestOverrides ); return statsRequest .withQueryParams( arguments.params ) @@ -564,10 +607,15 @@ component accessors="true" threadSafe singleton { * * @indexName string|array Index name or alias. Can accept an array of index/alias names, as well as wildcard expressions or `_all`. * @params struct Struct of query parameters to influence the request. For example: `{ "expand_wildcards" : "none" } + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - function openIndex( required any indexName, any params ){ + function openIndex( + required any indexName, + any params, + struct requestOverrides = {} + ){ var requestBuilder = getNodePool() - .newRequest( "#arrayToList( arguments.indexName )#/_open", "POST" ) + .newRequest( "#arrayToList( arguments.indexName )#/_open", "POST", arguments.requestOverrides ) .asJSON(); if ( structKeyExists( arguments, "params" ) ) { @@ -592,10 +640,15 @@ component accessors="true" threadSafe singleton { * * @indexName string|array Index name or alias. Can accept an array of index/alias names, as well as wildcard expressions or `_all`. * @params struct Struct of query parameters to influence the request. For example: `{ "expand_wildcards" : "none" } + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - function closeIndex( required any indexName, any params ){ + function closeIndex( + required any indexName, + any params, + struct requestOverrides = {} + ){ var requestBuilder = getNodePool() - .newRequest( "#arrayToList( arguments.indexName )#/_close", "POST" ) + .newRequest( "#arrayToList( arguments.indexName )#/_close", "POST", arguments.requestOverrides ) .asJSON(); if ( structKeyExists( arguments, "params" ) ) { @@ -620,12 +673,14 @@ component accessors="true" threadSafe singleton { * @id string Document ID to query term vectors on. * @params struct Struct of query parameters to influence the request. For example: `"offsets": false }` * @options struct Body payload to send. For example: `{ "filter": { "max_num_terms": 3 } }` + * @requestOverrides struct A struct of request overrides to pass to the node pool */ struct function getTermVectors( required string indexName, string id = "", any fields = [], - struct options = {} + struct options = {}, + struct requestOverrides = {} ){ arguments.options[ "fields" ] = arguments.fields; if ( !isArray( arguments.options[ "fields" ] ) ) { @@ -636,7 +691,7 @@ component accessors="true" threadSafe singleton { if ( arguments.id != "" ) { endpoint.append( arguments.id ); } - var vectorRequest = variables.nodePool.newRequest( arrayToList( endpoint, "/" ), "POST" ); + var vectorRequest = variables.nodePool.newRequest( arrayToList( endpoint, "/" ), "POST", arguments.requestOverrides ); return vectorRequest .setBody( getUtil().toJSON( arguments.options ) ) @@ -648,9 +703,13 @@ component accessors="true" threadSafe singleton { * Returns a struct containing all indices in the system, with statistics * * @verbose boolean whether to return the full stats output for the index + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - struct function getIndices( verbose = false ){ - var statsRequest = variables.nodePool.newRequest( "_stats", "get" ); + struct function getIndices( + verbose = false, + struct requestOverrides = {} + ){ + var statsRequest = variables.nodePool.newRequest( "_stats", "get", arguments.requestOverrides ); var statsResult = statsRequest.send().json(); @@ -677,10 +736,11 @@ component accessors="true" threadSafe singleton { * Returns a struct containing the mappings of all aliases in the cluster * * @aliases + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - struct function getAliases(){ + struct function getAliases( struct requestOverrides = {} ){ var aliasesResult = variables.nodePool - .newRequest( "_alias" ) + .newRequest( "_alias", "GET", arguments.requestOverrides ) .setThrowOnError( true ) .send() .json(); @@ -722,10 +782,14 @@ component accessors="true" threadSafe singleton { * Applies an alias (or array of aliases) * * @aliases AliasBuilder An AliasBuilder instance (or array of instances) + * @requestOverrides struct A struct of request overrides to pass to the node pool * * @return boolean Boolean result as to whether the operations were successful **/ - boolean function applyAliases( required any aliases ){ + boolean function applyAliases( + required any aliases, + struct requestOverrides = {} + ){ arguments.aliases = isArray( arguments.aliases ) ? arguments.aliases : [ arguments.aliases ]; var requestBody = { "actions" : [] }; for ( var alias in arguments.aliases ) { @@ -737,8 +801,10 @@ component accessors="true" threadSafe singleton { } ); } + var mergedOverrides = arguments.aliases[ 1 ].getRequestOverrides().append( arguments.requestOverrides ); + return variables.nodePool - .newRequest( "_aliases", "POST" ) + .newRequest( "_aliases", "POST", mergedOverrides ) .setBody( getUtil().toJSON( requestBody ) ) .asJSON() .setThrowOnError( true ) @@ -753,12 +819,14 @@ component accessors="true" threadSafe singleton { * @indexName string the name of the index * @mappingName string the name of the mapping * @mappingConfig struct the mapping configuration struct + * @requestOverrides struct A struct of request overrides to pass to the node pool * @interfaced **/ struct function applyMapping( required string indexName, string mappingName, - required struct mappingConfig + required struct mappingConfig, + struct requestOverrides = {} ){ if ( !isMajorVersion( 6 ) ) { // remove v7/v8 unsupported keys @@ -773,7 +841,7 @@ component accessors="true" threadSafe singleton { } var mappingResult = variables.nodePool - .newRequest( "#arguments.indexName#/_mapping", "PUT" ) + .newRequest( "#arguments.indexName#/_mapping", "PUT", arguments.requestOverrides ) .setBody( JSONMapping ) .asJSON() .send(); @@ -792,9 +860,14 @@ component accessors="true" threadSafe singleton { * Applies multiple mappings to an index * @indexName string The name of the index * @mappings struct a struct containing the mapping configuration + * @requestOverrides struct A struct of request overrides to pass to the node pool * @interfaced **/ - struct function applyMappings( required string indexName, required struct mappings ){ + struct function applyMappings( + required string indexName, + required struct mappings, + struct requestOverrides = {} + ){ var mappingResults = {}; if ( arguments.mappings.keyExists( "properties" ) ) { @@ -805,7 +878,8 @@ component accessors="true" threadSafe singleton { mappingResults[ mapKey ] = applyMapping( arguments.indexName, mapKey, - arguments.mappings[ mapKey ] + arguments.mappings[ mapKey ], + arguments.requestOverrides ); } @@ -819,6 +893,7 @@ component accessors="true" threadSafe singleton { * @index string The name of the index * @type type The name of the type * @params struct A struct of params to pass to the request + * @requestOverrides struct A struct of request overrides to pass to the node pool * @interfaced * * @return any Returns a Document object if found, otherwise returns null @@ -827,7 +902,8 @@ component accessors="true" threadSafe singleton { required any id, string index, string type, - struct params = {} + struct params = {}, + struct requestOverrides = {} ){ if ( isNull( arguments.index ) ) { arguments.index = variables.instanceConfig.get( "defaultIndex" ); @@ -838,7 +914,7 @@ component accessors="true" threadSafe singleton { } var getRequest = variables.nodePool - .newRequest( "#arguments.index#/#arguments.type#/#urlEncodedFormat( arguments.id )#" ) + .newRequest( "#arguments.index#/#arguments.type#/#urlEncodedFormat( arguments.id )#", "GET", arguments.requestOverrides ) .setThrowOnError( false ); arguments.params @@ -871,6 +947,7 @@ component accessors="true" threadSafe singleton { * @keys array An array of keys to retrieve * @index string The name of the index * @type type The name of the type + * @requestOverrides struct A struct of request overrides to pass to the node pool * @interfaced * * @return array An array of Document objects @@ -879,7 +956,8 @@ component accessors="true" threadSafe singleton { required array keys, string index, string type, - struct params = {} + struct params = {}, + struct requestOverrides = {} ){ if ( isNull( arguments.index ) ) { arguments.index = variables.instanceConfig.get( "defaultIndex" ); @@ -899,7 +977,7 @@ component accessors="true" threadSafe singleton { } ); var multiRequest = variables.nodePool - .newRequest( "_mget", "POST" ) + .newRequest( "_mget", "POST", arguments.requestOverrides ) .setBody( getUtil().toJSON( requestBody ) ) .asJSON(); @@ -935,11 +1013,16 @@ component accessors="true" threadSafe singleton { * * @taskId string The identifier of the task to retreive * @taskObj Task The task object used for population - defaults to a new task + * @requestOverrides struct A struct of request overrides to pass to the node pool * * @interfaced */ - any function getTask( required string taskId, cbElasticsearch.models.Task taskObj = newTask() ){ - var taskResult = variables.nodePool.newRequest( "_tasks/#arguments.taskId#" ).send(); + any function getTask( + required string taskId, + cbElasticsearch.models.Task taskObj = newTask(), + struct requestOverrides = {} + ){ + var taskResult = variables.nodePool.newRequest( "_tasks/#arguments.taskId#", "GET", arguments.requestOverrides ).send(); if ( taskResult.getStatusCode() != 200 ) { onResponseFailure( taskResult ); @@ -951,11 +1034,13 @@ component accessors="true" threadSafe singleton { /** * Retreives all tasks running on the cluster * + * @requestOverrides struct A struct of request overrides to pass to the node pool + * * @interfaced */ - any function getTasks(){ + any function getTasks( struct requestOverrides = {} ){ var tasksResult = variables.nodePool - .newRequest( "_tasks" ) + .newRequest( "_tasks", "GET", arguments.requestOverrides ) .setQueryParam( "detailed", true ) .send() .json(); @@ -978,18 +1063,25 @@ component accessors="true" threadSafe singleton { /** * @document Document@cbElasticSearch An instance of the elasticsearch Document object - * @refresh any if `true`, will return a newly populated instance of the document retreived from the index ( useful for pipelined saves ). if `"wait_for"`, will block until the next index refresh ingests the document update. + * @refresh any if `true`, will return a newly populated instance of the document retreived from the index ( useful for pipelined saves ). if `"wait_for"`, will block until the next index refresh ingests the document update. + * @requestOverrides struct A struct of request overrides to pass to the node pool * * @return Document The saved cbElasticsearch Document object * @interfaced **/ - cbElasticsearch.models.Document function save( required cbElasticsearch.models.Document document, any refresh ){ + cbElasticsearch.models.Document function save( + required cbElasticsearch.models.Document document, + any refresh, + struct requestOverrides = {} + ){ + var mergedOverrides = arguments.document.getRequestOverrides().append( arguments.requestOverrides ); if ( isNull( arguments.document.getId() ) ) { - var saveRequest = variables.nodePool.newRequest( "#arguments.document.getIndex()#/_doc", "POST" ); + var saveRequest = variables.nodePool.newRequest( "#arguments.document.getIndex()#/_doc", "POST", mergedOverrides ); } else { var saveRequest = variables.nodePool.newRequest( "#arguments.document.getIndex()#/_doc/#urlEncodedFormat( arguments.document.getId() )#", - "PUT" + "PUT", + mergedOverrides ); } @@ -1040,6 +1132,7 @@ component accessors="true" threadSafe singleton { * @identifier string The identifier of the elasticsearch document * @contents struct A struct of contents to update. May contain script/doc information, along with upsert parameters * @params struct A struct of params to provide to the deletion request + * @requestOverrides struct A struct of request overrides to pass to the node pool * * @return void * @@ -1049,7 +1142,8 @@ component accessors="true" threadSafe singleton { required string index, required string identifier, required struct contents, - struct params = {} + struct params = {}, + struct requestOverrides = {} ){ if ( !arguments.contents.keyExists( "doc" ) && !arguments.contents.keyExists( "script" ) ) { var directive = { "doc" : arguments.contents }; @@ -1058,7 +1152,7 @@ component accessors="true" threadSafe singleton { } var patchRequest = variables.nodePool - .newRequest( "#arguments.index#/_update/#urlEncodedFormat( arguments.identifier )#", "POST" ) + .newRequest( "#arguments.index#/_update/#urlEncodedFormat( arguments.identifier )#", "POST", arguments.requestOverrides ) .setBody( getUtil().toJSON( directive ) ) .asJSON(); @@ -1074,6 +1168,7 @@ component accessors="true" threadSafe singleton { * @document Document the Document object for the document to be deleted * @throwOnError boolean whether to throw an error if the document cannot be deleted ( default: false ) * @params struct a struct of params to provide to the deletion request + * @requestOverrides struct A struct of request overrides to pass to the node pool * * @return boolean (true|false) as to whether the doucument was deleted * @@ -1082,13 +1177,15 @@ component accessors="true" threadSafe singleton { boolean function delete( required cbElasticsearch.models.Document document, boolean throwOnError = true, - struct params = {} + struct params = {}, + struct requestOverrides = {} ){ return deleteById( document.getIndex(), document.getId(), arguments.throwOnError, - arguments.params + arguments.params, + arguments.requestOverrides ); } @@ -1100,6 +1197,7 @@ component accessors="true" threadSafe singleton { * @identifier string the identifier of the document * @throwOnError boolean whether to throw an error if the document cannot be deleted ( default: false ) * @params struct a struct of params to provide to the deletion request + * @requestOverrides struct A struct of request overrides to pass to the node pool * * @return boolean (true|false) as to whether the doucument was deleted * @@ -1109,10 +1207,11 @@ component accessors="true" threadSafe singleton { required string index, required string identifier, boolean throwOnError = true, - params = {} + params = {}, + struct requestOverrides = {} ){ var deleteRequest = variables.nodePool - .newRequest( "#arguments.index#/_doc/#urlEncodedFormat( arguments.identifier )#", "DELETE" ) + .newRequest( "#arguments.index#/_doc/#urlEncodedFormat( arguments.identifier )#", "DELETE", arguments.requestOverrides ) .asJSON(); parseParams( arguments.params ).each( function( param ){ @@ -1133,10 +1232,12 @@ component accessors="true" threadSafe singleton { * Deletes items in the index by query * @searchBuilder SearchBuilder The search builder object to use for the query * @waitForCompletion boolean Whether to block the request until completion or return a task which can be checked + * @requestOverrides struct A struct of request overrides to pass to the node pool **/ any function deleteByQuery( required cbElasticsearch.models.SearchBuilder searchBuilder, - boolean waitForCompletion = true + boolean waitForCompletion = true, + struct requestOverrides = {} ){ if ( isNull( arguments.searchBuilder.getIndex() ) ) { throw( @@ -1145,9 +1246,11 @@ component accessors="true" threadSafe singleton { ); } + var mergedOverrides = arguments.searchBuilder.getRequestOverrides().append( arguments.requestOverrides ); var deleteRequest = variables.nodePool.newRequest( "#arguments.searchBuilder.getIndex()#/_delete_by_query", - "POST" + "POST", + mergedOverrides ); @@ -1178,11 +1281,13 @@ component accessors="true" threadSafe singleton { * @searchBuilder SearchBuilder The search builder object to use for the query * @script struct script to process on the query * @waitForCompletion boolean Whether to block the request until completion or return a task which can be checked + * @requestOverrides struct A struct of request overrides to pass to the node pool **/ any function updateByQuery( required cbElasticsearch.models.SearchBuilder searchBuilder, required struct script, - boolean waitForCompletion = true + boolean waitForCompletion = true, + struct requestOverrides = {} ){ if ( isNull( arguments.searchBuilder.getIndex() ) ) { throw( @@ -1191,8 +1296,9 @@ component accessors="true" threadSafe singleton { ); } + var mergedOverrides = arguments.searchBuilder.getRequestOverrides().append( arguments.requestOverrides ); var updateRequest = variables.nodePool - .newRequest( "#arguments.searchBuilder.getIndex()#/_update_by_query", "POST" ) + .newRequest( "#arguments.searchBuilder.getIndex()#/_update_by_query", "POST", mergedOverrides ) .setBody( reReplace( getUtil().toJSON( { @@ -1230,6 +1336,9 @@ component accessors="true" threadSafe singleton { * Persists multiple items to the index * @documents array An array of elasticsearch Document objects to persist * @throwOnError boolean Whether to throw an exception on error on individual documents which were not persisted + * @params struct A struct of params to pass to the request + * @mode string The mode to use for the bulk operation. Defaults to "update". + * @requestOverrides struct A struct of request overrides to pass to the node pool * * @return array An array of results for the saved items * @interfaced @@ -1238,11 +1347,13 @@ component accessors="true" threadSafe singleton { required array documents, boolean throwOnError = false, struct params = {}, - string mode = "update" + string mode = "update", + struct requestOverrides = {} ){ var requests = []; - var saveRequest = variables.nodePool.newRequest( "_bulk", "POST" ); + var mergedOverrides = arguments.documents[ 1 ].getRequestOverrides().append( arguments.requestOverrides ); + var saveRequest = variables.nodePool.newRequest( "_bulk", "POST", mergedOverrides ); arguments.params .keyArray() @@ -1365,16 +1476,18 @@ component accessors="true" threadSafe singleton { * @operations array An array of operations to perform * @params struct Parameters to apply on the request * @throwOnError boolean Whether to throw an error if the result was unsuccessful + * @requestOverrides struct A struct of request overrides to pass to the node pool * * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html */ any function processBulkOperation( required array operations, struct params = {}, - boolean throwOnError = true + boolean throwOnError = true, + struct requestOverrides = {} ){ var bulkRequest = variables.nodePool - .newRequest( "_bulk", "POST" ) + .newRequest( "_bulk", "POST", arguments.requestOverrides ) .setBody( arguments.operations.reduce( function( acc, action ){ if ( action.keyExists( "operation" ) ) { @@ -1410,10 +1523,15 @@ component accessors="true" threadSafe singleton { * Create or update pipeline * * @pipeline The Pipeline object + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - boolean function applyPipeline( required cbElasticsearch.models.Pipeline pipeline ){ + boolean function applyPipeline( + required cbElasticsearch.models.Pipeline pipeline, + struct requestOverrides = {} + ){ + var mergedOverrides = arguments.pipeline.getRequestOverrides().append( arguments.requestOverrides ); var response = variables.nodePool - .newRequest( "_ingest/pipeline/#urlEncodedFormat( arguments.pipeline.getId() )#", "PUT" ) + .newRequest( "_ingest/pipeline/#urlEncodedFormat( arguments.pipeline.getId() )#", "PUT", mergedOverrides ) .setBody( arguments.pipeline.getJSON() ) .send(); @@ -1433,10 +1551,14 @@ component accessors="true" threadSafe singleton { * Retreives the definition of a pipeline * * @id The identifier of the pipeline to retreive + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - any function getPipeline( required string id ){ + any function getPipeline( + required string id, + struct requestOverrides = {} + ){ var definition = variables.nodePool - .newRequest( "_ingest/pipeline/#urlEncodedFormat( arguments.id )#" ) + .newRequest( "_ingest/pipeline/#urlEncodedFormat( arguments.id )#", "GET", arguments.requestOverrides ) .send() .json(); return definition.keyExists( arguments.id ) ? definition[ arguments.id ] : javacast( "null", 0 ); @@ -1444,10 +1566,11 @@ component accessors="true" threadSafe singleton { /** * Retreives all pipeline definitions + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - any function getPipelines(){ + any function getPipelines( struct requestOverrides = {} ){ return variables.nodePool - .newRequest( "_ingest/pipeline" ) + .newRequest( "_ingest/pipeline", "GET", arguments.requestOverrides ) .send() .json(); } @@ -1456,10 +1579,14 @@ component accessors="true" threadSafe singleton { * Deletes a pipeline * * @id The identifier of the pipeline to delete + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - boolean function deletePipeline( required string id ){ + boolean function deletePipeline( + required string id, + struct requestOverrides = {} + ){ var response = variables.nodePool - .newRequest( "_ingest/pipeline/#urlEncodedFormat( arguments.id )#", "DELETE" ) + .newRequest( "_ingest/pipeline/#urlEncodedFormat( arguments.id )#", "DELETE", arguments.requestOverrides ) .send(); var responseData = response.json(); @@ -1480,18 +1607,27 @@ component accessors="true" threadSafe singleton { * Determines whether a snapshot repository exists * * @name + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - function snapshotRepositoryExists( required string name ){ + function snapshotRepositoryExists( + required string name, + struct requestOverrides = {} + ){ return variables.nodePool - .newRequest( "_snapshot/#arguments.name#" ) + .newRequest( "_snapshot/#arguments.name#", "GET", arguments.requestOverrides ) .send() .getStatusCode() == "200"; } /** * Creates or Updates a Snapshot Repository + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - function applySnapshotRepository( required name, required definition ){ + function applySnapshotRepository( + required name, + required definition, + struct requestOverrides = {} + ){ if ( isSimpleValue( arguments.definition ) ) { arguments.definition = { "type" : "fs", @@ -1500,7 +1636,7 @@ component accessors="true" threadSafe singleton { } var response = variables.nodePool - .newRequest( "_snapshot/#arguments.name#", "PUT" ) + .newRequest( "_snapshot/#arguments.name#", "PUT", arguments.requestOverrides ) .setBody( getUtil().toJSON( arguments.definition ) ) .asJSON() .send(); @@ -1512,9 +1648,13 @@ component accessors="true" threadSafe singleton { /** * Deletes a Snapshot Repository + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - function deleteSnapshotRepository( required name ){ - var response = variables.nodePool.newRequest( "_snapshot/#arguments.name#", "DELETE" ).send(); + function deleteSnapshotRepository( + required name, + struct requestOverrides = {} + ){ + var response = variables.nodePool.newRequest( "_snapshot/#arguments.name#", "DELETE", arguments.requestOverrides ).send(); return response.getStatusCode() == 200 ? response.json() @@ -1529,10 +1669,14 @@ component accessors="true" threadSafe singleton { * Determines whether an index template exists * * @name + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - boolean function indexTemplateExists( required string name ){ + boolean function indexTemplateExists( + required string name, + struct requestOverrides = {} + ){ return variables.nodePool - .newRequest( "_index_template/#arguments.name#" ) + .newRequest( "_index_template/#arguments.name#", "GET", arguments.requestOverrides ) .send() .getStatusCode() == "200"; } @@ -1542,10 +1686,15 @@ component accessors="true" threadSafe singleton { * * @name string * @definition struct + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - any function applyIndexTemplate( required string name, required struct definition ){ + any function applyIndexTemplate( + required string name, + required struct definition, + struct requestOverrides = {} + ){ var response = variables.nodePool - .newRequest( "_index_template/#arguments.name#", "PUT" ) + .newRequest( "_index_template/#arguments.name#", "PUT", arguments.requestOverrides ) .setBody( getUtil().toJSON( arguments.definition ) ) .asJSON() .send(); @@ -1558,9 +1707,13 @@ component accessors="true" threadSafe singleton { /** * Deletes an index template * @name string + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - any function deleteIndexTemplate( required string name ){ - var response = variables.nodePool.newRequest( "_index_template/#arguments.name#", "DELETE" ).send(); + any function deleteIndexTemplate( + required string name, + struct requestOverrides = {} + ){ + var response = variables.nodePool.newRequest( "_index_template/#arguments.name#", "DELETE", arguments.requestOverrides ).send(); return response.getStatusCode() == 200 ? response.json() @@ -1575,10 +1728,14 @@ component accessors="true" threadSafe singleton { * Determines whether an component template exists * * @name + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - boolean function componentTemplateExists( required string name ){ + boolean function componentTemplateExists( + required string name, + struct requestOverrides = {} + ){ return variables.nodePool - .newRequest( "_component_template/#arguments.name#" ) + .newRequest( "_component_template/#arguments.name#", "GET", arguments.requestOverrides ) .send() .getStatusCode() == "200"; } @@ -1588,10 +1745,15 @@ component accessors="true" threadSafe singleton { * * @name string * @definition struct + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - any function applyComponentTemplate( required string name, required struct definition ){ + any function applyComponentTemplate( + required string name, + required struct definition, + struct requestOverrides = {} + ){ var response = variables.nodePool - .newRequest( "_component_template/#arguments.name#", "PUT" ) + .newRequest( "_component_template/#arguments.name#", "PUT", arguments.requestOverrides ) .setBody( getUtil().toJSON( !definition.keyExists( "template" ) @@ -1610,9 +1772,13 @@ component accessors="true" threadSafe singleton { /** * Deletes a component template * @name string + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - any function deleteComponentTemplate( required string name ){ - var response = variables.nodePool.newRequest( "_component_template/#arguments.name#", "DELETE" ).send(); + any function deleteComponentTemplate( + required string name, + struct requestOverrides = {} + ){ + var response = variables.nodePool.newRequest( "_component_template/#arguments.name#", "DELETE", arguments.requestOverrides ).send(); return response.getStatusCode() == 200 ? response.json() @@ -1627,10 +1793,14 @@ component accessors="true" threadSafe singleton { * Checks whether a named ILM policy exists * * @name + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - boolean function ILMPolicyExists( required string name ){ + boolean function ILMPolicyExists( + required string name, + struct requestOverrides = {} + ){ return variables.nodePool - .newRequest( "_ilm/policy/#arguments.name#" ) + .newRequest( "_ilm/policy/#arguments.name#", "GET", arguments.requestOverrides ) .send() .getStatusCode() == 200; } @@ -1639,9 +1809,13 @@ component accessors="true" threadSafe singleton { * Get an ILM policy by name * * @name string + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - any function getILMPolicy( required string name ){ - var response = variables.nodePool.newRequest( "_ilm/policy/#arguments.name#" ).send(); + any function getILMPolicy( + required string name, + struct requestOverrides = {} + ){ + var response = variables.nodePool.newRequest( "_ilm/policy/#arguments.name#", "GET", arguments.requestOverrides ).send(); return response.getStatusCode() == 200 ? response.json() @@ -1653,10 +1827,15 @@ component accessors="true" threadSafe singleton { * * @name string * @policy object Either a struct defining the policy or a policy object + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - any function applyILMPolicy( required string name, required any policy ){ + any function applyILMPolicy( + required string name, + required any policy, + struct requestOverrides = {} + ){ var response = variables.nodePool - .newRequest( "_ilm/policy/#arguments.name#", "PUT" ) + .newRequest( "_ilm/policy/#arguments.name#", "PUT", arguments.requestOverrides ) .setBody( getUtil().toJSON( { "policy" : arguments.policy } ) ) .asJSON() .send(); @@ -1670,9 +1849,13 @@ component accessors="true" threadSafe singleton { * Deletes an ILM policy * * @name + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - any function deleteILMPolicy( required string name ){ - var response = variables.nodePool.newRequest( "_ilm/policy/#arguments.name#", "DELETE" ).send(); + any function deleteILMPolicy( + required string name, + struct requestOverrides = {} + ){ + var response = variables.nodePool.newRequest( "_ilm/policy/#arguments.name#", "DELETE", arguments.requestOverrides ).send(); return response.getStatusCode() == 200 ? response.json() @@ -1688,9 +1871,13 @@ component accessors="true" threadSafe singleton { * Checks to see whether a data stream exists * * @name + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - boolean function dataStreamExists( required string name ){ - var check = variables.nodePool.newRequest( "_data_stream/#arguments.name#" ).send(); + boolean function dataStreamExists( + required string name, + struct requestOverrides = {} + ){ + var check = variables.nodePool.newRequest( "_data_stream/#arguments.name#", "GET", arguments.requestOverrides ).send(); return check.getStatusCode() == "200" && check.json().data_streams.len(); } @@ -1698,9 +1885,13 @@ component accessors="true" threadSafe singleton { * Ensures the existence of a data stream * * @name + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - any function ensureDataStream( required string name ){ - var response = variables.nodePool.newRequest( "_data_stream/#arguments.name#", "PUT" ).send(); + any function ensureDataStream( + required string name, + struct requestOverrides = {} + ){ + var response = variables.nodePool.newRequest( "_data_stream/#arguments.name#", "PUT", arguments.requestOverrides ).send(); return response.getStatusCode() == 200 ? response.json() @@ -1711,10 +1902,14 @@ component accessors="true" threadSafe singleton { * Migrates an existing index in to a data stream * * @indexName + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - any function migrateToDataStream( required string indexName ){ + any function migrateToDataStream( + required string indexName, + struct requestOverrides = {} + ){ var response = variables.nodePool - .newRequest( "_data_stream/_migrate/#arguments.indexName#", "POST" ) + .newRequest( "_data_stream/_migrate/#arguments.indexName#", "POST", arguments.requestOverrides ) .send(); return response.getStatusCode() == 200 @@ -1726,9 +1921,13 @@ component accessors="true" threadSafe singleton { * Gets a datastream definition * * @name + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - any function getDataStream( required string name ){ - var response = variables.nodePool.newRequest( "_data_stream/#arguments.name#" ).send(); + any function getDataStream( + required string name, + struct requestOverrides = {} + ){ + var response = variables.nodePool.newRequest( "_data_stream/#arguments.name#", "GET", arguments.requestOverrides ).send(); return response.getStatusCode() == 200 ? response.json() @@ -1740,9 +1939,13 @@ component accessors="true" threadSafe singleton { * * @name string the name of the stream * @template string the name of the index template to use for this data stream + * @requestOverrides struct A struct of request overrides to pass to the node pool */ - any function deleteDataStream( required string name ){ - var response = variables.nodePool.newRequest( "_data_stream/#arguments.name#", "DELETE" ).send(); + any function deleteDataStream( + required string name, + struct requestOverrides = {} + ){ + var response = variables.nodePool.newRequest( "_data_stream/#arguments.name#", "DELETE", arguments.requestOverrides ).send(); return response.getStatusCode() == 200 ? response.json() @@ -1803,6 +2006,7 @@ component accessors="true" threadSafe singleton { * * @indexName string|array Index name or array of index names to query on * @field string|struct If string, field name to query. Otherwise, a struct of query options where only "field" is required. + * @requestOverrides struct A struct of request overrides to pass to the node pool * * @see https://www.elastic.co/guide/en/elasticsearch/reference/8.7/search-terms-enum.html */ @@ -1811,12 +2015,13 @@ component accessors="true" threadSafe singleton { required any field, any match, numeric size = 10, - boolean caseInsensitive = true + boolean caseInsensitive = true, + struct requestOverrides = {} ){ if ( isArray( arguments.indexName ) ) { arguments.indexName = arrayToList( arguments.indexName ); } - var termsRequest = variables.nodePool.newRequest( "/#arguments.indexName#/_terms_enum", "post" ); + var termsRequest = variables.nodePool.newRequest( "/#arguments.indexName#/_terms_enum", "post", arguments.requestOverrides ); var opts = { "size" : arguments.size, diff --git a/models/io/HyperPool.cfc b/models/io/HyperPool.cfc index 8d5e3a6f..79f245d1 100644 --- a/models/io/HyperPool.cfc +++ b/models/io/HyperPool.cfc @@ -79,8 +79,9 @@ component { * * @route the relative URI to the route * @method the HTTP Request method + * @overrides any default values to set on the request ( timeout, referrer, bodyFormat, etc. ) */ - public Hyper.models.HyperRequest function newRequest( required string route, string method = "GET" ){ + public Hyper.models.HyperRequest function newRequest( required string route, string method = "GET", struct overrides = {} ){ var requestObj = hyper.new(); var nodeUsage = "write"; @@ -100,8 +101,8 @@ component { requestObj .setMethod( arguments.method ) - .setThrowOnError( false ) - .setTimeout( variables.instanceConfig.get( "readTimeout" ) ); + .setTimeout( variables.instanceConfig.get( "readTimeout" ) ) + .setThrowOnError( false ); var uriParts = listToArray( route, "/" ); uriParts.prepend( node.url ); @@ -133,6 +134,16 @@ component { message = "The authentication type #variables.authenticationScheme# is not currently supported" ); } + // allow overriding of any request key via the overrides struct argument + for ( var key in arguments.overrides ) { + if( structkeyExists( requestObj, "set#key#" ) ) { + invoke( + requestObj, + "set#key#", + [ arguments.overrides[ key ] ] + ); + } + } return requestObj; } diff --git a/test-harness/tests/specs/unit/HyperClientTest.cfc b/test-harness/tests/specs/unit/HyperClientTest.cfc index 2d86894f..06241dde 100644 --- a/test-harness/tests/specs/unit/HyperClientTest.cfc +++ b/test-harness/tests/specs/unit/HyperClientTest.cfc @@ -12,6 +12,8 @@ component extends="coldbox.system.testing.BaseTestCase" { variables.testIndexName = lCase( "ElasticsearchClientTests" ); variables.model.deleteIndex( variables.testIndexName ); + + addMatchers( "hyper.models.TestBoxMatchers" ); } function afterAll(){ @@ -1043,7 +1045,6 @@ component extends="coldbox.system.testing.BaseTestCase" { testDocument._id, "title" ); -debug( result ); expect( result.keyExists( "error" ) ).toBeFalse(); expect( result.keyExists( "term_vectors" ) ).toBeTrue(); expect( result.term_vectors ).toHaveKey( "title" ); @@ -1764,6 +1765,53 @@ debug( result ); .toHaveKey( "_shards" ); }); }); + + fdescribe( "connection overrides", function() { + beforeEach( function() { + getWirebox().getInstance( "HyperBuilder@hyper" ).fake({ + "*": function( newFakeResponse, req ) { + debug( req.getMemento() ); + return newFakeResponse() + .setStatusCode( 200 ) + .setData( serializeJSON( req.getMemento() ) ); + } + }); + if( !variables.keyExists( "hyper" ) ){ + variables.hyper = getWirebox().getInstance( "HyperBuilder@hyper" ); + } + addMatchers( "hyper.models.TestBoxMatchers" ); + } ); + afterEach( function() { + getWirebox().getInstance( "HyperBuilder@hyper" ).clearFakes(); + } ); + it( "indexExists() - can set custom connection timeout", function(){ + + var response = variables.model.indexExists( "bar", { timeout = 300 } ); + + expect( variables.hyper ) + .toHaveSentRequest( ( req ) => { + return req.getMethod() === "HEAD" && + req.getUrl() == "http://127.0.0.1:9200/bar" && + req.getTimeout() === 300; + } ); + } ); + + it( "Can set request overrides from IndexBuilder", function(){ + + var response = getWirebox() + .getInstance( "IndexBuilder@cbelasticsearch" ) + .withTimeout( 45 ) + .new( name = "foo", settings = { "refresh_interval" : "1s" } ) + .save(); + + expect( variables.hyper ) + .toHaveSentRequest( ( req ) => { + return req.getMethod() === "PUT" && + req.getUrl() == "http://127.0.0.1:9200/foo/_settings" && + req.getTimeout() === 45; + } ); + } ); + }) } ); } From f7509bbdf4c8b532f84cd3afe51184604a740d21 Mon Sep 17 00:00:00 2001 From: michaelborn Date: Fri, 13 Mar 2026 16:11:43 +0000 Subject: [PATCH 02/15] Apply cfformat changes --- models/BaseModel.cfc | 64 +++--- models/io/HyperClient.cfc | 440 ++++++++++++++++++++++++-------------- models/io/HyperPool.cfc | 8 +- 3 files changed, 321 insertions(+), 191 deletions(-) diff --git a/models/BaseModel.cfc b/models/BaseModel.cfc index e9db25cf..f1074ab9 100644 --- a/models/BaseModel.cfc +++ b/models/BaseModel.cfc @@ -8,38 +8,40 @@ * */ component accessors="true" { - property name="requestOverrides" type="struct"; - public BaseModel function init(){ - variables.requestOverrides = {}; - return this; - } + property name="requestOverrides" type="struct"; - public BaseModel function withRequestOverrides( struct overrides ){ - setRequestOverrides( arguments.overrides ); - return this; - } + public BaseModel function init(){ + variables.requestOverrides = {}; + return this; + } - public struct function getRequestOverrides(){ - return variables.requestOverrides ?: {}; - } + public BaseModel function withRequestOverrides( struct overrides ){ + setRequestOverrides( arguments.overrides ); + return this; + } - /** - * Handles any missing methods that start with "with" and adds the value to the requestOverrides struct for request configuration - * Example: withTimeout( 1000 ) would set a default timeout of 1000ms on all requests created by this model - * - * @param methodName the name of the method being called - * @param arguments the arguments passed to the method, where arguments[1] is expected to be the value to set for the default - * @return returns the model instance for chaining - */ - public BaseModel function onMissingMethod( string methodName, struct arguments ){ - if( left( methodName, 4 ) == "with" ){ - var defaultKey = lCase( mid( methodName, 5 ) ); - if( !isNull( arguments[ 2 ][ 1 ] ) ) { - variables.requestOverrides[ defaultKey ] = arguments[ 2 ][ 1 ]; - } - return this; - } - return this; - } -} \ No newline at end of file + public struct function getRequestOverrides(){ + return variables.requestOverrides ?: {}; + } + + /** + * Handles any missing methods that start with "with" and adds the value to the requestOverrides struct for request configuration + * Example: withTimeout( 1000 ) would set a default timeout of 1000ms on all requests created by this model + * + * @param methodName the name of the method being called + * @param arguments the arguments passed to the method, where arguments[1] is expected to be the value to set for the default + * @return returns the model instance for chaining + */ + public BaseModel function onMissingMethod( string methodName, struct arguments ){ + if ( left( methodName, 4 ) == "with" ) { + var defaultKey = lCase( mid( methodName, 5 ) ); + if ( !isNull( arguments[ 2 ][ 1 ] ) ) { + variables.requestOverrides[ defaultKey ] = arguments[ 2 ][ 1 ]; + } + return this; + } + return this; + } + +} diff --git a/models/io/HyperClient.cfc b/models/io/HyperClient.cfc index be991a5a..ef42af4d 100644 --- a/models/io/HyperClient.cfc +++ b/models/io/HyperClient.cfc @@ -179,8 +179,12 @@ component accessors="true" threadSafe singleton { struct requestOverrides = {} ){ var mergedOverrides = arguments.searchBuilder.getRequestOverrides().append( arguments.requestOverrides ); - var requestBuilder = variables.nodePool - .newRequest( searchBuilder.getIndex() & "/_search", "POST", mergedOverrides ) + var requestBuilder = variables.nodePool + .newRequest( + searchBuilder.getIndex() & "/_search", + "POST", + mergedOverrides + ) .setBody( arguments.searchBuilder.getJSON() ) .asJSON(); @@ -212,8 +216,12 @@ component accessors="true" threadSafe singleton { struct requestOverrides = {} ){ var mergedOverrides = arguments.searchBuilder.getRequestOverrides().append( arguments.requestOverrides ); - var requestBuilder = variables.nodePool - .newRequest( searchBuilder.getIndex() & "/_count", "POST", mergedOverrides ) + var requestBuilder = variables.nodePool + .newRequest( + searchBuilder.getIndex() & "/_count", + "POST", + mergedOverrides + ) .setBody( getUtil().toJSON( { "query" : arguments.searchBuilder.getQuery() } ) ) .asJSON(); @@ -233,13 +241,14 @@ component accessors="true" threadSafe singleton { * @requestOverrides struct A struct of request overrides to pass to the node pool * @interfaced **/ - boolean function indexExists( - required string indexName, - struct requestOverrides = {} - ){ + boolean function indexExists( required string indexName, struct requestOverrides = {} ){ return ( variables.nodePool - .newRequest( arguments.indexName, "HEAD", arguments.requestOverrides ) + .newRequest( + arguments.indexName, + "HEAD", + arguments.requestOverrides + ) .send() .getStatusCode() < 400 ); @@ -252,10 +261,7 @@ component accessors="true" threadSafe singleton { * @requestOverrides struct A struct of request overrides to pass to the node pool * @interfaced **/ - boolean function indexMappingExists( - required string indexName, - struct requestOverrides = {} - ){ + boolean function indexMappingExists( required string indexName, struct requestOverrides = {} ){ try { var mappings = getMappings( arguments.indexName, arguments.requestOverrides ); } catch ( any e ) { @@ -271,12 +277,13 @@ component accessors="true" threadSafe singleton { * @indexName string the name of the index ( optional ) if null returns all settings for the server * @requestOverrides struct A struct of request overrides to pass to the node pool */ - struct function getSettings( - string indexName, - struct requestOverrides = {} - ){ + struct function getSettings( string indexName, struct requestOverrides = {} ){ var response = variables.nodePool - .newRequest( !isNull( arguments.indexName ) ? arguments.indexName & "/_settings" : "_settings", "GET", arguments.requestOverrides ) + .newRequest( + !isNull( arguments.indexName ) ? arguments.indexName & "/_settings" : "_settings", + "GET", + arguments.requestOverrides + ) .send(); if ( response.getStatusCode() != 200 ) { @@ -409,14 +416,24 @@ component accessors="true" threadSafe singleton { } else { if ( structKeyExists( indexDSL, "mappings" ) ) { if ( !isMajorVersion( 6 ) ) { - indexResult[ "mappings" ] = applyMapping( indexName, "_doc", indexDSL.mappings, {}, mergedOverrides ); + indexResult[ "mappings" ] = applyMapping( + indexName, + "_doc", + indexDSL.mappings, + {}, + mergedOverrides + ); } else { indexResult[ "mappings" ] = applyMappings( indexName, indexDSL.mappings, mergedOverrides ); } } if ( structKeyExists( indexDSL, "settings" ) && !structIsEmpty( indexDSL.settings ) ) { var requestBuilder = variables.nodePool - .newRequest( indexName & "/_settings", "PUT", mergedOverrides ) + .newRequest( + indexName & "/_settings", + "PUT", + mergedOverrides + ) .setBody( getUtil().toJSON( indexDSL.settings ) ) .asJSON(); @@ -439,12 +456,13 @@ component accessors="true" threadSafe singleton { * @requestOverrides struct A struct of request overrides to pass to the node pool * **/ - struct function deleteIndex( - required string indexName, - struct requestOverrides = {} - ){ + struct function deleteIndex( required string indexName, struct requestOverrides = {} ){ return variables.nodePool - .newRequest( arguments.indexName, "DELETE", arguments.requestOverrides ) + .newRequest( + arguments.indexName, + "DELETE", + arguments.requestOverrides + ) .send() .json(); } @@ -471,7 +489,7 @@ component accessors="true" threadSafe singleton { boolean waitForCompletion = true, any params, any script, - boolean throwOnError = true, + boolean throwOnError = true, struct requestOverrides = {} ){ var requestBuilder = variables.nodePool @@ -555,13 +573,17 @@ component accessors="true" threadSafe singleton { */ struct function refreshIndex( required any indexName, - struct params = {}, + struct params = {}, struct requestOverrides = {} ){ if ( isArray( arguments.indexName ) ) { arguments.indexName = arrayToList( arguments.indexName ); } - var refreshRequest = variables.nodePool.newRequest( "/#arguments.indexName#/_refresh", "post", arguments.requestOverrides ); + var refreshRequest = variables.nodePool.newRequest( + "/#arguments.indexName#/_refresh", + "post", + arguments.requestOverrides + ); return refreshRequest .withQueryParams( arguments.params ) @@ -579,8 +601,8 @@ component accessors="true" threadSafe singleton { */ struct function getIndexStats( any indexName, - array metrics = [], - struct params = {}, + array metrics = [], + struct params = {}, struct requestOverrides = {} ){ if ( isArray( arguments.indexName ) ) { @@ -592,7 +614,11 @@ component accessors="true" threadSafe singleton { endpoint.prepend( arguments.indexName ); } endpoint.append( arrayToList( metrics ) ); - var statsRequest = variables.nodePool.newRequest( arrayToList( endpoint, "/" ), "get", arguments.requestOverrides ); + var statsRequest = variables.nodePool.newRequest( + arrayToList( endpoint, "/" ), + "get", + arguments.requestOverrides + ); return statsRequest .withQueryParams( arguments.params ) @@ -615,7 +641,11 @@ component accessors="true" threadSafe singleton { struct requestOverrides = {} ){ var requestBuilder = getNodePool() - .newRequest( "#arrayToList( arguments.indexName )#/_open", "POST", arguments.requestOverrides ) + .newRequest( + "#arrayToList( arguments.indexName )#/_open", + "POST", + arguments.requestOverrides + ) .asJSON(); if ( structKeyExists( arguments, "params" ) ) { @@ -648,7 +678,11 @@ component accessors="true" threadSafe singleton { struct requestOverrides = {} ){ var requestBuilder = getNodePool() - .newRequest( "#arrayToList( arguments.indexName )#/_close", "POST", arguments.requestOverrides ) + .newRequest( + "#arrayToList( arguments.indexName )#/_close", + "POST", + arguments.requestOverrides + ) .asJSON(); if ( structKeyExists( arguments, "params" ) ) { @@ -677,9 +711,9 @@ component accessors="true" threadSafe singleton { */ struct function getTermVectors( required string indexName, - string id = "", - any fields = [], - struct options = {}, + string id = "", + any fields = [], + struct options = {}, struct requestOverrides = {} ){ arguments.options[ "fields" ] = arguments.fields; @@ -691,7 +725,11 @@ component accessors="true" threadSafe singleton { if ( arguments.id != "" ) { endpoint.append( arguments.id ); } - var vectorRequest = variables.nodePool.newRequest( arrayToList( endpoint, "/" ), "POST", arguments.requestOverrides ); + var vectorRequest = variables.nodePool.newRequest( + arrayToList( endpoint, "/" ), + "POST", + arguments.requestOverrides + ); return vectorRequest .setBody( getUtil().toJSON( arguments.options ) ) @@ -705,10 +743,7 @@ component accessors="true" threadSafe singleton { * @verbose boolean whether to return the full stats output for the index * @requestOverrides struct A struct of request overrides to pass to the node pool */ - struct function getIndices( - verbose = false, - struct requestOverrides = {} - ){ + struct function getIndices( verbose = false, struct requestOverrides = {} ){ var statsRequest = variables.nodePool.newRequest( "_stats", "get", arguments.requestOverrides ); var statsResult = statsRequest.send().json(); @@ -786,10 +821,7 @@ component accessors="true" threadSafe singleton { * * @return boolean Boolean result as to whether the operations were successful **/ - boolean function applyAliases( - required any aliases, - struct requestOverrides = {} - ){ + boolean function applyAliases( required any aliases, struct requestOverrides = {} ){ arguments.aliases = isArray( arguments.aliases ) ? arguments.aliases : [ arguments.aliases ]; var requestBody = { "actions" : [] }; for ( var alias in arguments.aliases ) { @@ -841,7 +873,11 @@ component accessors="true" threadSafe singleton { } var mappingResult = variables.nodePool - .newRequest( "#arguments.indexName#/_mapping", "PUT", arguments.requestOverrides ) + .newRequest( + "#arguments.indexName#/_mapping", + "PUT", + arguments.requestOverrides + ) .setBody( JSONMapping ) .asJSON() .send(); @@ -902,7 +938,7 @@ component accessors="true" threadSafe singleton { required any id, string index, string type, - struct params = {}, + struct params = {}, struct requestOverrides = {} ){ if ( isNull( arguments.index ) ) { @@ -914,7 +950,11 @@ component accessors="true" threadSafe singleton { } var getRequest = variables.nodePool - .newRequest( "#arguments.index#/#arguments.type#/#urlEncodedFormat( arguments.id )#", "GET", arguments.requestOverrides ) + .newRequest( + "#arguments.index#/#arguments.type#/#urlEncodedFormat( arguments.id )#", + "GET", + arguments.requestOverrides + ) .setThrowOnError( false ); arguments.params @@ -956,7 +996,7 @@ component accessors="true" threadSafe singleton { required array keys, string index, string type, - struct params = {}, + struct params = {}, struct requestOverrides = {} ){ if ( isNull( arguments.index ) ) { @@ -1020,9 +1060,15 @@ component accessors="true" threadSafe singleton { any function getTask( required string taskId, cbElasticsearch.models.Task taskObj = newTask(), - struct requestOverrides = {} + struct requestOverrides = {} ){ - var taskResult = variables.nodePool.newRequest( "_tasks/#arguments.taskId#", "GET", arguments.requestOverrides ).send(); + var taskResult = variables.nodePool + .newRequest( + "_tasks/#arguments.taskId#", + "GET", + arguments.requestOverrides + ) + .send(); if ( taskResult.getStatusCode() != 200 ) { onResponseFailure( taskResult ); @@ -1076,7 +1122,11 @@ component accessors="true" threadSafe singleton { ){ var mergedOverrides = arguments.document.getRequestOverrides().append( arguments.requestOverrides ); if ( isNull( arguments.document.getId() ) ) { - var saveRequest = variables.nodePool.newRequest( "#arguments.document.getIndex()#/_doc", "POST", mergedOverrides ); + var saveRequest = variables.nodePool.newRequest( + "#arguments.document.getIndex()#/_doc", + "POST", + mergedOverrides + ); } else { var saveRequest = variables.nodePool.newRequest( "#arguments.document.getIndex()#/_doc/#urlEncodedFormat( arguments.document.getId() )#", @@ -1142,7 +1192,7 @@ component accessors="true" threadSafe singleton { required string index, required string identifier, required struct contents, - struct params = {}, + struct params = {}, struct requestOverrides = {} ){ if ( !arguments.contents.keyExists( "doc" ) && !arguments.contents.keyExists( "script" ) ) { @@ -1152,7 +1202,11 @@ component accessors="true" threadSafe singleton { } var patchRequest = variables.nodePool - .newRequest( "#arguments.index#/_update/#urlEncodedFormat( arguments.identifier )#", "POST", arguments.requestOverrides ) + .newRequest( + "#arguments.index#/_update/#urlEncodedFormat( arguments.identifier )#", + "POST", + arguments.requestOverrides + ) .setBody( getUtil().toJSON( directive ) ) .asJSON(); @@ -1176,8 +1230,8 @@ component accessors="true" threadSafe singleton { **/ boolean function delete( required cbElasticsearch.models.Document document, - boolean throwOnError = true, - struct params = {}, + boolean throwOnError = true, + struct params = {}, struct requestOverrides = {} ){ return deleteById( @@ -1206,12 +1260,16 @@ component accessors="true" threadSafe singleton { boolean function deleteById( required string index, required string identifier, - boolean throwOnError = true, - params = {}, + boolean throwOnError = true, + params = {}, struct requestOverrides = {} ){ var deleteRequest = variables.nodePool - .newRequest( "#arguments.index#/_doc/#urlEncodedFormat( arguments.identifier )#", "DELETE", arguments.requestOverrides ) + .newRequest( + "#arguments.index#/_doc/#urlEncodedFormat( arguments.identifier )#", + "DELETE", + arguments.requestOverrides + ) .asJSON(); parseParams( arguments.params ).each( function( param ){ @@ -1237,7 +1295,7 @@ component accessors="true" threadSafe singleton { any function deleteByQuery( required cbElasticsearch.models.SearchBuilder searchBuilder, boolean waitForCompletion = true, - struct requestOverrides = {} + struct requestOverrides = {} ){ if ( isNull( arguments.searchBuilder.getIndex() ) ) { throw( @@ -1247,7 +1305,7 @@ component accessors="true" threadSafe singleton { } var mergedOverrides = arguments.searchBuilder.getRequestOverrides().append( arguments.requestOverrides ); - var deleteRequest = variables.nodePool.newRequest( + var deleteRequest = variables.nodePool.newRequest( "#arguments.searchBuilder.getIndex()#/_delete_by_query", "POST", mergedOverrides @@ -1287,7 +1345,7 @@ component accessors="true" threadSafe singleton { required cbElasticsearch.models.SearchBuilder searchBuilder, required struct script, boolean waitForCompletion = true, - struct requestOverrides = {} + struct requestOverrides = {} ){ if ( isNull( arguments.searchBuilder.getIndex() ) ) { throw( @@ -1297,8 +1355,12 @@ component accessors="true" threadSafe singleton { } var mergedOverrides = arguments.searchBuilder.getRequestOverrides().append( arguments.requestOverrides ); - var updateRequest = variables.nodePool - .newRequest( "#arguments.searchBuilder.getIndex()#/_update_by_query", "POST", mergedOverrides ) + var updateRequest = variables.nodePool + .newRequest( + "#arguments.searchBuilder.getIndex()#/_update_by_query", + "POST", + mergedOverrides + ) .setBody( reReplace( getUtil().toJSON( { @@ -1345,15 +1407,15 @@ component accessors="true" threadSafe singleton { **/ array function saveAll( required array documents, - boolean throwOnError = false, - struct params = {}, - string mode = "update", + boolean throwOnError = false, + struct params = {}, + string mode = "update", struct requestOverrides = {} ){ var requests = []; var mergedOverrides = arguments.documents[ 1 ].getRequestOverrides().append( arguments.requestOverrides ); - var saveRequest = variables.nodePool.newRequest( "_bulk", "POST", mergedOverrides ); + var saveRequest = variables.nodePool.newRequest( "_bulk", "POST", mergedOverrides ); arguments.params .keyArray() @@ -1482,8 +1544,8 @@ component accessors="true" threadSafe singleton { */ any function processBulkOperation( required array operations, - struct params = {}, - boolean throwOnError = true, + struct params = {}, + boolean throwOnError = true, struct requestOverrides = {} ){ var bulkRequest = variables.nodePool @@ -1530,8 +1592,12 @@ component accessors="true" threadSafe singleton { struct requestOverrides = {} ){ var mergedOverrides = arguments.pipeline.getRequestOverrides().append( arguments.requestOverrides ); - var response = variables.nodePool - .newRequest( "_ingest/pipeline/#urlEncodedFormat( arguments.pipeline.getId() )#", "PUT", mergedOverrides ) + var response = variables.nodePool + .newRequest( + "_ingest/pipeline/#urlEncodedFormat( arguments.pipeline.getId() )#", + "PUT", + mergedOverrides + ) .setBody( arguments.pipeline.getJSON() ) .send(); @@ -1553,12 +1619,13 @@ component accessors="true" threadSafe singleton { * @id The identifier of the pipeline to retreive * @requestOverrides struct A struct of request overrides to pass to the node pool */ - any function getPipeline( - required string id, - struct requestOverrides = {} - ){ + any function getPipeline( required string id, struct requestOverrides = {} ){ var definition = variables.nodePool - .newRequest( "_ingest/pipeline/#urlEncodedFormat( arguments.id )#", "GET", arguments.requestOverrides ) + .newRequest( + "_ingest/pipeline/#urlEncodedFormat( arguments.id )#", + "GET", + arguments.requestOverrides + ) .send() .json(); return definition.keyExists( arguments.id ) ? definition[ arguments.id ] : javacast( "null", 0 ); @@ -1570,7 +1637,11 @@ component accessors="true" threadSafe singleton { */ any function getPipelines( struct requestOverrides = {} ){ return variables.nodePool - .newRequest( "_ingest/pipeline", "GET", arguments.requestOverrides ) + .newRequest( + "_ingest/pipeline", + "GET", + arguments.requestOverrides + ) .send() .json(); } @@ -1581,12 +1652,13 @@ component accessors="true" threadSafe singleton { * @id The identifier of the pipeline to delete * @requestOverrides struct A struct of request overrides to pass to the node pool */ - boolean function deletePipeline( - required string id, - struct requestOverrides = {} - ){ + boolean function deletePipeline( required string id, struct requestOverrides = {} ){ var response = variables.nodePool - .newRequest( "_ingest/pipeline/#urlEncodedFormat( arguments.id )#", "DELETE", arguments.requestOverrides ) + .newRequest( + "_ingest/pipeline/#urlEncodedFormat( arguments.id )#", + "DELETE", + arguments.requestOverrides + ) .send(); var responseData = response.json(); @@ -1609,12 +1681,13 @@ component accessors="true" threadSafe singleton { * @name * @requestOverrides struct A struct of request overrides to pass to the node pool */ - function snapshotRepositoryExists( - required string name, - struct requestOverrides = {} - ){ + function snapshotRepositoryExists( required string name, struct requestOverrides = {} ){ return variables.nodePool - .newRequest( "_snapshot/#arguments.name#", "GET", arguments.requestOverrides ) + .newRequest( + "_snapshot/#arguments.name#", + "GET", + arguments.requestOverrides + ) .send() .getStatusCode() == "200"; } @@ -1636,7 +1709,11 @@ component accessors="true" threadSafe singleton { } var response = variables.nodePool - .newRequest( "_snapshot/#arguments.name#", "PUT", arguments.requestOverrides ) + .newRequest( + "_snapshot/#arguments.name#", + "PUT", + arguments.requestOverrides + ) .setBody( getUtil().toJSON( arguments.definition ) ) .asJSON() .send(); @@ -1650,11 +1727,14 @@ component accessors="true" threadSafe singleton { * Deletes a Snapshot Repository * @requestOverrides struct A struct of request overrides to pass to the node pool */ - function deleteSnapshotRepository( - required name, - struct requestOverrides = {} - ){ - var response = variables.nodePool.newRequest( "_snapshot/#arguments.name#", "DELETE", arguments.requestOverrides ).send(); + function deleteSnapshotRepository( required name, struct requestOverrides = {} ){ + var response = variables.nodePool + .newRequest( + "_snapshot/#arguments.name#", + "DELETE", + arguments.requestOverrides + ) + .send(); return response.getStatusCode() == 200 ? response.json() @@ -1671,12 +1751,13 @@ component accessors="true" threadSafe singleton { * @name * @requestOverrides struct A struct of request overrides to pass to the node pool */ - boolean function indexTemplateExists( - required string name, - struct requestOverrides = {} - ){ + boolean function indexTemplateExists( required string name, struct requestOverrides = {} ){ return variables.nodePool - .newRequest( "_index_template/#arguments.name#", "GET", arguments.requestOverrides ) + .newRequest( + "_index_template/#arguments.name#", + "GET", + arguments.requestOverrides + ) .send() .getStatusCode() == "200"; } @@ -1694,7 +1775,11 @@ component accessors="true" threadSafe singleton { struct requestOverrides = {} ){ var response = variables.nodePool - .newRequest( "_index_template/#arguments.name#", "PUT", arguments.requestOverrides ) + .newRequest( + "_index_template/#arguments.name#", + "PUT", + arguments.requestOverrides + ) .setBody( getUtil().toJSON( arguments.definition ) ) .asJSON() .send(); @@ -1709,11 +1794,14 @@ component accessors="true" threadSafe singleton { * @name string * @requestOverrides struct A struct of request overrides to pass to the node pool */ - any function deleteIndexTemplate( - required string name, - struct requestOverrides = {} - ){ - var response = variables.nodePool.newRequest( "_index_template/#arguments.name#", "DELETE", arguments.requestOverrides ).send(); + any function deleteIndexTemplate( required string name, struct requestOverrides = {} ){ + var response = variables.nodePool + .newRequest( + "_index_template/#arguments.name#", + "DELETE", + arguments.requestOverrides + ) + .send(); return response.getStatusCode() == 200 ? response.json() @@ -1730,12 +1818,13 @@ component accessors="true" threadSafe singleton { * @name * @requestOverrides struct A struct of request overrides to pass to the node pool */ - boolean function componentTemplateExists( - required string name, - struct requestOverrides = {} - ){ + boolean function componentTemplateExists( required string name, struct requestOverrides = {} ){ return variables.nodePool - .newRequest( "_component_template/#arguments.name#", "GET", arguments.requestOverrides ) + .newRequest( + "_component_template/#arguments.name#", + "GET", + arguments.requestOverrides + ) .send() .getStatusCode() == "200"; } @@ -1753,7 +1842,11 @@ component accessors="true" threadSafe singleton { struct requestOverrides = {} ){ var response = variables.nodePool - .newRequest( "_component_template/#arguments.name#", "PUT", arguments.requestOverrides ) + .newRequest( + "_component_template/#arguments.name#", + "PUT", + arguments.requestOverrides + ) .setBody( getUtil().toJSON( !definition.keyExists( "template" ) @@ -1774,11 +1867,14 @@ component accessors="true" threadSafe singleton { * @name string * @requestOverrides struct A struct of request overrides to pass to the node pool */ - any function deleteComponentTemplate( - required string name, - struct requestOverrides = {} - ){ - var response = variables.nodePool.newRequest( "_component_template/#arguments.name#", "DELETE", arguments.requestOverrides ).send(); + any function deleteComponentTemplate( required string name, struct requestOverrides = {} ){ + var response = variables.nodePool + .newRequest( + "_component_template/#arguments.name#", + "DELETE", + arguments.requestOverrides + ) + .send(); return response.getStatusCode() == 200 ? response.json() @@ -1795,12 +1891,13 @@ component accessors="true" threadSafe singleton { * @name * @requestOverrides struct A struct of request overrides to pass to the node pool */ - boolean function ILMPolicyExists( - required string name, - struct requestOverrides = {} - ){ + boolean function ILMPolicyExists( required string name, struct requestOverrides = {} ){ return variables.nodePool - .newRequest( "_ilm/policy/#arguments.name#", "GET", arguments.requestOverrides ) + .newRequest( + "_ilm/policy/#arguments.name#", + "GET", + arguments.requestOverrides + ) .send() .getStatusCode() == 200; } @@ -1811,11 +1908,14 @@ component accessors="true" threadSafe singleton { * @name string * @requestOverrides struct A struct of request overrides to pass to the node pool */ - any function getILMPolicy( - required string name, - struct requestOverrides = {} - ){ - var response = variables.nodePool.newRequest( "_ilm/policy/#arguments.name#", "GET", arguments.requestOverrides ).send(); + any function getILMPolicy( required string name, struct requestOverrides = {} ){ + var response = variables.nodePool + .newRequest( + "_ilm/policy/#arguments.name#", + "GET", + arguments.requestOverrides + ) + .send(); return response.getStatusCode() == 200 ? response.json() @@ -1835,7 +1935,11 @@ component accessors="true" threadSafe singleton { struct requestOverrides = {} ){ var response = variables.nodePool - .newRequest( "_ilm/policy/#arguments.name#", "PUT", arguments.requestOverrides ) + .newRequest( + "_ilm/policy/#arguments.name#", + "PUT", + arguments.requestOverrides + ) .setBody( getUtil().toJSON( { "policy" : arguments.policy } ) ) .asJSON() .send(); @@ -1851,11 +1955,14 @@ component accessors="true" threadSafe singleton { * @name * @requestOverrides struct A struct of request overrides to pass to the node pool */ - any function deleteILMPolicy( - required string name, - struct requestOverrides = {} - ){ - var response = variables.nodePool.newRequest( "_ilm/policy/#arguments.name#", "DELETE", arguments.requestOverrides ).send(); + any function deleteILMPolicy( required string name, struct requestOverrides = {} ){ + var response = variables.nodePool + .newRequest( + "_ilm/policy/#arguments.name#", + "DELETE", + arguments.requestOverrides + ) + .send(); return response.getStatusCode() == 200 ? response.json() @@ -1873,11 +1980,14 @@ component accessors="true" threadSafe singleton { * @name * @requestOverrides struct A struct of request overrides to pass to the node pool */ - boolean function dataStreamExists( - required string name, - struct requestOverrides = {} - ){ - var check = variables.nodePool.newRequest( "_data_stream/#arguments.name#", "GET", arguments.requestOverrides ).send(); + boolean function dataStreamExists( required string name, struct requestOverrides = {} ){ + var check = variables.nodePool + .newRequest( + "_data_stream/#arguments.name#", + "GET", + arguments.requestOverrides + ) + .send(); return check.getStatusCode() == "200" && check.json().data_streams.len(); } @@ -1887,11 +1997,14 @@ component accessors="true" threadSafe singleton { * @name * @requestOverrides struct A struct of request overrides to pass to the node pool */ - any function ensureDataStream( - required string name, - struct requestOverrides = {} - ){ - var response = variables.nodePool.newRequest( "_data_stream/#arguments.name#", "PUT", arguments.requestOverrides ).send(); + any function ensureDataStream( required string name, struct requestOverrides = {} ){ + var response = variables.nodePool + .newRequest( + "_data_stream/#arguments.name#", + "PUT", + arguments.requestOverrides + ) + .send(); return response.getStatusCode() == 200 ? response.json() @@ -1904,12 +2017,13 @@ component accessors="true" threadSafe singleton { * @indexName * @requestOverrides struct A struct of request overrides to pass to the node pool */ - any function migrateToDataStream( - required string indexName, - struct requestOverrides = {} - ){ + any function migrateToDataStream( required string indexName, struct requestOverrides = {} ){ var response = variables.nodePool - .newRequest( "_data_stream/_migrate/#arguments.indexName#", "POST", arguments.requestOverrides ) + .newRequest( + "_data_stream/_migrate/#arguments.indexName#", + "POST", + arguments.requestOverrides + ) .send(); return response.getStatusCode() == 200 @@ -1923,11 +2037,14 @@ component accessors="true" threadSafe singleton { * @name * @requestOverrides struct A struct of request overrides to pass to the node pool */ - any function getDataStream( - required string name, - struct requestOverrides = {} - ){ - var response = variables.nodePool.newRequest( "_data_stream/#arguments.name#", "GET", arguments.requestOverrides ).send(); + any function getDataStream( required string name, struct requestOverrides = {} ){ + var response = variables.nodePool + .newRequest( + "_data_stream/#arguments.name#", + "GET", + arguments.requestOverrides + ) + .send(); return response.getStatusCode() == 200 ? response.json() @@ -1941,11 +2058,14 @@ component accessors="true" threadSafe singleton { * @template string the name of the index template to use for this data stream * @requestOverrides struct A struct of request overrides to pass to the node pool */ - any function deleteDataStream( - required string name, - struct requestOverrides = {} - ){ - var response = variables.nodePool.newRequest( "_data_stream/#arguments.name#", "DELETE", arguments.requestOverrides ).send(); + any function deleteDataStream( required string name, struct requestOverrides = {} ){ + var response = variables.nodePool + .newRequest( + "_data_stream/#arguments.name#", + "DELETE", + arguments.requestOverrides + ) + .send(); return response.getStatusCode() == 200 ? response.json() @@ -2021,7 +2141,11 @@ component accessors="true" threadSafe singleton { if ( isArray( arguments.indexName ) ) { arguments.indexName = arrayToList( arguments.indexName ); } - var termsRequest = variables.nodePool.newRequest( "/#arguments.indexName#/_terms_enum", "post", arguments.requestOverrides ); + var termsRequest = variables.nodePool.newRequest( + "/#arguments.indexName#/_terms_enum", + "post", + arguments.requestOverrides + ); var opts = { "size" : arguments.size, diff --git a/models/io/HyperPool.cfc b/models/io/HyperPool.cfc index 79f245d1..200b0706 100644 --- a/models/io/HyperPool.cfc +++ b/models/io/HyperPool.cfc @@ -81,7 +81,11 @@ component { * @method the HTTP Request method * @overrides any default values to set on the request ( timeout, referrer, bodyFormat, etc. ) */ - public Hyper.models.HyperRequest function newRequest( required string route, string method = "GET", struct overrides = {} ){ + public Hyper.models.HyperRequest function newRequest( + required string route, + string method = "GET", + struct overrides = {} + ){ var requestObj = hyper.new(); var nodeUsage = "write"; @@ -136,7 +140,7 @@ component { } // allow overriding of any request key via the overrides struct argument for ( var key in arguments.overrides ) { - if( structkeyExists( requestObj, "set#key#" ) ) { + if ( structKeyExists( requestObj, "set#key#" ) ) { invoke( requestObj, "set#key#", From b6caa226a8432a82faad22878a727ab7d4aba064 Mon Sep 17 00:00:00 2001 From: Michael Born Date: Fri, 13 Mar 2026 12:36:59 -0400 Subject: [PATCH 03/15] Tweaks to support withHeader() method --- models/BaseModel.cfc | 45 +++++++++---------- models/io/HyperPool.cfc | 8 ++-- .../tests/specs/unit/HyperClientTest.cfc | 30 ++++++++++++- 3 files changed, 56 insertions(+), 27 deletions(-) diff --git a/models/BaseModel.cfc b/models/BaseModel.cfc index f1074ab9..babad85a 100644 --- a/models/BaseModel.cfc +++ b/models/BaseModel.cfc @@ -21,27 +21,26 @@ component accessors="true" { return this; } - public struct function getRequestOverrides(){ - return variables.requestOverrides ?: {}; - } - - /** - * Handles any missing methods that start with "with" and adds the value to the requestOverrides struct for request configuration - * Example: withTimeout( 1000 ) would set a default timeout of 1000ms on all requests created by this model - * - * @param methodName the name of the method being called - * @param arguments the arguments passed to the method, where arguments[1] is expected to be the value to set for the default - * @return returns the model instance for chaining - */ - public BaseModel function onMissingMethod( string methodName, struct arguments ){ - if ( left( methodName, 4 ) == "with" ) { - var defaultKey = lCase( mid( methodName, 5 ) ); - if ( !isNull( arguments[ 2 ][ 1 ] ) ) { - variables.requestOverrides[ defaultKey ] = arguments[ 2 ][ 1 ]; - } - return this; - } - return this; - } - + /** + * Handles any missing methods that start with "with" and adds the value to the requestOverrides struct for request configuration + * Example: withTimeout( 1000 ) would set a default timeout of 1000ms on all requests created by this model + * + * @param methodName the name of the method being called + * @param arguments the arguments passed to the method, where arguments[1] is expected to be the value to set for the default + * @return returns the model instance for chaining + */ + public BaseModel function onMissingMethod( string methodName, struct arguments ){ + var args = []; + if( !isNull( arguments[ 2 ] ) ) { + args = arguments[ 2 ].reduce( function( acc, key, val ){ + acc.append( val ); + return acc; + }, [] ); + } + if( left( methodName, 4 ) == "with" ){ + variables.requestOverrides[ lCase( mid( methodName, 5 ) ) ] = args; + return this; + } + return this; + } } diff --git a/models/io/HyperPool.cfc b/models/io/HyperPool.cfc index 200b0706..43fdefaa 100644 --- a/models/io/HyperPool.cfc +++ b/models/io/HyperPool.cfc @@ -139,12 +139,14 @@ component { ); } // allow overriding of any request key via the overrides struct argument + var withMethods = [ "headers" ]; for ( var key in arguments.overrides ) { - if ( structKeyExists( requestObj, "set#key#" ) ) { + if( structkeyExists( requestObj, "set#key#" ) ) { + var methodName = withMethods.contains( key ) ? "with#key#" : "set#key#"; invoke( requestObj, - "set#key#", - [ arguments.overrides[ key ] ] + methodName, + isArray( arguments.overrides[ key ] ) ? arguments.overrides[ key ] : [ arguments.overrides[ key ] ] ); } } diff --git a/test-harness/tests/specs/unit/HyperClientTest.cfc b/test-harness/tests/specs/unit/HyperClientTest.cfc index 06241dde..60d395ec 100644 --- a/test-harness/tests/specs/unit/HyperClientTest.cfc +++ b/test-harness/tests/specs/unit/HyperClientTest.cfc @@ -1766,7 +1766,7 @@ component extends="coldbox.system.testing.BaseTestCase" { }); }); - fdescribe( "connection overrides", function() { + describe( "connection overrides", function() { beforeEach( function() { getWirebox().getInstance( "HyperBuilder@hyper" ).fake({ "*": function( newFakeResponse, req ) { @@ -1811,6 +1811,34 @@ component extends="coldbox.system.testing.BaseTestCase" { req.getTimeout() === 45; } ); } ); + + it( "Can set custom username/password", function(){ + + var response = variables.model.indexExists( "bar", { username: "admin", password: "admin123$" } ); + + expect( variables.hyper ) + .toHaveSentRequest( ( req ) => { + return req.getMethod() === "HEAD" && + req.getUrl() == "http://127.0.0.1:9200/bar" && + req.getUsername() === "admin" && + req.getPassword() === "admin123$"; + } ); + } ); + + it( "Can set custom request headers", function(){ + var response = getWirebox() + .getInstance( "IndexBuilder@cbelasticsearch" ) + .withHeader( "X-Custom-Header", "MyValue" ) + .new( name = "foo", settings = { "refresh_interval" : "1s" } ) + .save(); + + expect( variables.hyper ) + .toHaveSentRequest( ( req ) => { + return req.getMethod() == "PUT" && + req.getUrl() == "http://127.0.0.1:9200/foo/_settings" && + req.getHeader( "X-Custom-Header" ) == "MyValue"; + } ); + } ); }) } ); } From afe22fe0a3ee7495762663faf48d27aa7ec8abae Mon Sep 17 00:00:00 2001 From: Michael Born Date: Fri, 13 Mar 2026 12:37:33 -0400 Subject: [PATCH 04/15] Document request overrides --- docs/Getting-Started/Configuration.md | 77 ++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/docs/Getting-Started/Configuration.md b/docs/Getting-Started/Configuration.md index a5849b3b..378d8dd1 100644 --- a/docs/Getting-Started/Configuration.md +++ b/docs/Getting-Started/Configuration.md @@ -83,4 +83,79 @@ You will need to read these settings into the coldfusion server upon server star {% hint style="warning" %} For security reasons, make sure to add `.env` to your `.gitignore` file to avoid committing environment secrets to github/your git server. -{% endhint %} \ No newline at end of file +{% endhint %} + +## Request Parameter Overrides + +In addition to global configuration settings, you can override request-specific parameters on a per-call basis. This is useful when you need different timeouts or settings for specific operations. + +### Builder-Level Overrides + +Builder objects (like `IndexBuilder`, `SearchBuilder`, `Document`, etc.) extend `BaseModel` and support fluent configuration of request parameters using the `.with*()` method: + +```cfc +// Create an index with a custom 10-second timeout +get( "IndexBuilder@cbelasticsearch" ) + .new( name = "books" ) + .withTimeout( 10 ) + .save(); +``` + +Here's an example of setting a custom header on a search builder: + +```cfc +// Search with custom headers +get( "SearchBuilder@cbelasticsearch" ) + .new() + .withHeader( "X-Custom-Header", "MyValue" ) + .setQuery( ... ) + .execute(); +``` + +### Direct Client Method Overrides + +All public methods in `HyperClient` accept a `requestOverrides` struct as the final parameter. This allows you to pass request-specific configuration directly to the client: + +```cfc +// Check if index exists with a 45-second timeout +get( "HyperClient@cbelasticsearch" ) + .indexExists( "foo", { "timeout" : 45 } ); + +// Get index settings with custom timeout +get( "HyperClient@cbelasticsearch" ) + .getSettings( "myIndex", { "timeout" : 30 } ); + +// Search with request overrides +get( "HyperClient@cbelasticsearch" ) + .executeSearch( + searchBuilder, + { "timeout" : 60, "readTimeout" : 5000 } + ); +``` + +### Merging Overrides + +When both builder-level and client-level overrides are provided, they are merged together with client-level overrides taking precedence: + +```cfc +var builder = get( "IndexBuilder@cbelasticsearch" ) + .new( name = "books" ) + .withTimeout( 10 ); // Builder-level: 10 seconds + +// Client call with override - the 30-second timeout wins +get( "HyperClient@cbelasticsearch" ) + .applyIndex( builder, { "timeout" : 30 } ); +``` + +### Available Override Parameters + +Any HyperRequest parameters can be overridden on a per-request basis. These include: + +- `timeout` - Connection timeout in seconds +- `username` - Username for authentication +- `password` - Password for authentication +- `maximumRedirects` - Maximum number of redirects to follow +- `retries` - Number of times to retry a request in case of failure +- `proxyUser` - Username for proxy authentication +- `proxyPassword` - Password for proxy authentication +- `headers` - Struct of custom headers to include in the request - use `.withHeaders( { "X-Custom-Header" : "MyValue" } )` to fluently set headers on builders \ No newline at end of file From 800408c4d807c6dd5c3b665dfb85d9aef54d5619 Mon Sep 17 00:00:00 2001 From: michaelborn Date: Fri, 13 Mar 2026 17:09:09 +0000 Subject: [PATCH 05/15] Apply cfformat changes --- models/BaseModel.cfc | 45 +++++++++++++++++++++-------------------- models/io/HyperPool.cfc | 6 ++++-- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/models/BaseModel.cfc b/models/BaseModel.cfc index babad85a..18f55995 100644 --- a/models/BaseModel.cfc +++ b/models/BaseModel.cfc @@ -21,26 +21,27 @@ component accessors="true" { return this; } - /** - * Handles any missing methods that start with "with" and adds the value to the requestOverrides struct for request configuration - * Example: withTimeout( 1000 ) would set a default timeout of 1000ms on all requests created by this model - * - * @param methodName the name of the method being called - * @param arguments the arguments passed to the method, where arguments[1] is expected to be the value to set for the default - * @return returns the model instance for chaining - */ - public BaseModel function onMissingMethod( string methodName, struct arguments ){ - var args = []; - if( !isNull( arguments[ 2 ] ) ) { - args = arguments[ 2 ].reduce( function( acc, key, val ){ - acc.append( val ); - return acc; - }, [] ); - } - if( left( methodName, 4 ) == "with" ){ - variables.requestOverrides[ lCase( mid( methodName, 5 ) ) ] = args; - return this; - } - return this; - } + /** + * Handles any missing methods that start with "with" and adds the value to the requestOverrides struct for request configuration + * Example: withTimeout( 1000 ) would set a default timeout of 1000ms on all requests created by this model + * + * @param methodName the name of the method being called + * @param arguments the arguments passed to the method, where arguments[1] is expected to be the value to set for the default + * @return returns the model instance for chaining + */ + public BaseModel function onMissingMethod( string methodName, struct arguments ){ + var args = []; + if ( !isNull( arguments[ 2 ] ) ) { + args = arguments[ 2 ].reduce( function( acc, key, val ){ + acc.append( val ); + return acc; + }, [] ); + } + if ( left( methodName, 4 ) == "with" ) { + variables.requestOverrides[ lCase( mid( methodName, 5 ) ) ] = args; + return this; + } + return this; + } + } diff --git a/models/io/HyperPool.cfc b/models/io/HyperPool.cfc index 43fdefaa..88973a72 100644 --- a/models/io/HyperPool.cfc +++ b/models/io/HyperPool.cfc @@ -141,12 +141,14 @@ component { // allow overriding of any request key via the overrides struct argument var withMethods = [ "headers" ]; for ( var key in arguments.overrides ) { - if( structkeyExists( requestObj, "set#key#" ) ) { + if ( structKeyExists( requestObj, "set#key#" ) ) { var methodName = withMethods.contains( key ) ? "with#key#" : "set#key#"; invoke( requestObj, methodName, - isArray( arguments.overrides[ key ] ) ? arguments.overrides[ key ] : [ arguments.overrides[ key ] ] + isArray( arguments.overrides[ key ] ) ? arguments.overrides[ key ] : [ + arguments.overrides[ key ] + ] ); } } From f9bcc4924b941cd9708a418fa45d1127b7db4f29 Mon Sep 17 00:00:00 2001 From: Michael Born Date: Fri, 13 Mar 2026 14:40:47 -0400 Subject: [PATCH 06/15] Tweaks per Copilot review Thanks, Copilot. :) --- models/AliasBuilder.cfc | 5 +++++ models/BaseModel.cfc | 7 ++++++- models/Document.cfc | 5 +++++ models/ILMPolicyBuilder.cfc | 5 +++++ models/IndexBuilder.cfc | 5 +++++ models/Pipeline.cfc | 1 + models/SearchBuilder.cfc | 6 ++++++ test-harness/tests/specs/unit/HyperClientTest.cfc | 1 - 8 files changed, 33 insertions(+), 2 deletions(-) diff --git a/models/AliasBuilder.cfc b/models/AliasBuilder.cfc index 5a477ba5..509ef5f6 100644 --- a/models/AliasBuilder.cfc +++ b/models/AliasBuilder.cfc @@ -4,6 +4,11 @@ component accessors="true" extends="BaseModel" { property name="indexName"; property name="aliasName"; + function init(){ + super.init(); + return this; + } + function add( required string indexName, required string aliasName ){ arguments.action = "add"; return new ( argumentCollection = arguments ); diff --git a/models/BaseModel.cfc b/models/BaseModel.cfc index 18f55995..b9ce10b6 100644 --- a/models/BaseModel.cfc +++ b/models/BaseModel.cfc @@ -1,6 +1,11 @@ /** * - * Elasticsearch Document Object + * Base Model for Fluent Request Overrides + * + * Provides a foundation for builder and model objects to support per-request + * configuration overrides via fluent method chaining. All builder classes + * (IndexBuilder, SearchBuilder, AliasBuilder, Document, Pipeline, etc.) extend + * this component to inherit request override capabilities. * * @package cbElasticsearch.models * @author Jon Clausen diff --git a/models/Document.cfc b/models/Document.cfc index 2b4ab3a0..34dfcb28 100644 --- a/models/Document.cfc +++ b/models/Document.cfc @@ -51,6 +51,11 @@ component accessors="true" extends="BaseModel" { */ property name="fields" type="struct"; + function init(){ + super.init(); + return this; + } + function onDIComplete(){ reset(); } diff --git a/models/ILMPolicyBuilder.cfc b/models/ILMPolicyBuilder.cfc index 9956a820..bbdfe71e 100644 --- a/models/ILMPolicyBuilder.cfc +++ b/models/ILMPolicyBuilder.cfc @@ -20,6 +20,11 @@ component accessors="true" extends="BaseModel" { * @phases a struct of phases ( optional ) * @meta optional struct of meta */ + ILMPolicyBuilder function init(){ + super.init(); + return this; + } + ILMPolicyBuilder function new( required string policyName, struct phases, diff --git a/models/IndexBuilder.cfc b/models/IndexBuilder.cfc index 44fc769f..b9cc2fa1 100644 --- a/models/IndexBuilder.cfc +++ b/models/IndexBuilder.cfc @@ -25,6 +25,11 @@ component accessors="true" extends="BaseModel" { property name="aliases"; + function init(){ + super.init(); + return this; + } + function onDIComplete(){ reset(); } diff --git a/models/Pipeline.cfc b/models/Pipeline.cfc index f335c0b4..ec84bf5c 100644 --- a/models/Pipeline.cfc +++ b/models/Pipeline.cfc @@ -30,6 +30,7 @@ component accessors="true" extends="BaseModel" threadSafe { cbElasticsearch.models.Pipeline function init( struct definition ){ + super.init(); variables.description = ""; variables.processors = []; diff --git a/models/SearchBuilder.cfc b/models/SearchBuilder.cfc index 13c8e1ed..2a227314 100644 --- a/models/SearchBuilder.cfc +++ b/models/SearchBuilder.cfc @@ -15,6 +15,12 @@ component accessors="true" extends="BaseModel" { * Property containing the index name of the active builder search **/ property name="index"; + + function init(){ + super.init(); + return this; + } + /** * Property containing the object type within the index **/ diff --git a/test-harness/tests/specs/unit/HyperClientTest.cfc b/test-harness/tests/specs/unit/HyperClientTest.cfc index 60d395ec..b92d19c3 100644 --- a/test-harness/tests/specs/unit/HyperClientTest.cfc +++ b/test-harness/tests/specs/unit/HyperClientTest.cfc @@ -1770,7 +1770,6 @@ component extends="coldbox.system.testing.BaseTestCase" { beforeEach( function() { getWirebox().getInstance( "HyperBuilder@hyper" ).fake({ "*": function( newFakeResponse, req ) { - debug( req.getMemento() ); return newFakeResponse() .setStatusCode( 200 ) .setData( serializeJSON( req.getMemento() ) ); From 873b0df50dc9c55d0518fc5406068cd7e15acfcd Mon Sep 17 00:00:00 2001 From: Michael Born Date: Fri, 13 Mar 2026 14:50:14 -0400 Subject: [PATCH 07/15] More tweaks per copilot review --- docs/Getting-Started/Configuration.md | 2 +- models/BaseModel.cfc | 44 +++++++++++++-------------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/docs/Getting-Started/Configuration.md b/docs/Getting-Started/Configuration.md index 378d8dd1..79d94473 100644 --- a/docs/Getting-Started/Configuration.md +++ b/docs/Getting-Started/Configuration.md @@ -158,4 +158,4 @@ Any HyperRequest parameters can be overridden on a per-request basis. These incl - `retries` - Number of times to retry a request in case of failure - `proxyUser` - Username for proxy authentication - `proxyPassword` - Password for proxy authentication -- `headers` - Struct of custom headers to include in the request - use `.withHeaders( { "X-Custom-Header" : "MyValue" } )` to fluently set headers on builders \ No newline at end of file +- `headers` - Struct of custom headers to include in the request - use `.withHeader( "X-Custom-Header", "MyValue" )` to fluently set headers on builders \ No newline at end of file diff --git a/models/BaseModel.cfc b/models/BaseModel.cfc index b9ce10b6..d7a1e061 100644 --- a/models/BaseModel.cfc +++ b/models/BaseModel.cfc @@ -26,27 +26,25 @@ component accessors="true" { return this; } - /** - * Handles any missing methods that start with "with" and adds the value to the requestOverrides struct for request configuration - * Example: withTimeout( 1000 ) would set a default timeout of 1000ms on all requests created by this model - * - * @param methodName the name of the method being called - * @param arguments the arguments passed to the method, where arguments[1] is expected to be the value to set for the default - * @return returns the model instance for chaining - */ - public BaseModel function onMissingMethod( string methodName, struct arguments ){ - var args = []; - if ( !isNull( arguments[ 2 ] ) ) { - args = arguments[ 2 ].reduce( function( acc, key, val ){ - acc.append( val ); - return acc; - }, [] ); - } - if ( left( methodName, 4 ) == "with" ) { - variables.requestOverrides[ lCase( mid( methodName, 5 ) ) ] = args; - return this; - } - return this; - } - + /** + * Handles any missing methods that start with "with" and adds the value to the requestOverrides struct for request configuration + * Example: withTimeout( 1000 ) would set a default timeout of 1000ms on all requests created by this model + * + * @param methodName the name of the method being called + * @param arguments the arguments passed to the method, where arguments[1] is expected to be the value to set for the default + * @return returns the model instance for chaining + */ + public BaseModel function onMissingMethod( string methodName, struct arguments ){ + if( left( methodName, 4 ) == "with" ){ + var args = []; + if( !isNull( arguments[ 2 ] ) ) { + args = arguments[ 2 ].reduce( function( acc, key, val ){ + acc.append( val ); + return acc; + }, [] ); + } + variables.requestOverrides[ lCase( mid( methodName, 5 ) ) ] = args; + return this; + } + } } From b0dfbd843d3892edbc8d152134b457584ea5e4e1 Mon Sep 17 00:00:00 2001 From: Michael Born Date: Fri, 13 Mar 2026 15:22:39 -0400 Subject: [PATCH 08/15] More tweaks per Copilot --- models/BaseModel.cfc | 6 ++++++ models/io/HyperPool.cfc | 4 ++-- .../tests/specs/unit/HyperClientTest.cfc | 17 +++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/models/BaseModel.cfc b/models/BaseModel.cfc index d7a1e061..faa1f000 100644 --- a/models/BaseModel.cfc +++ b/models/BaseModel.cfc @@ -46,5 +46,11 @@ component accessors="true" { variables.requestOverrides[ lCase( mid( methodName, 5 ) ) ] = args; return this; } + // For all other missing methods, raise a proper missing-method exception + throw( + type = "MissingMethodException", + message = "No such method found for #methodName# on #getMetadata( this ).name#.", + detail = "Use with#methodName# to set default values for request overrides on this model." + ); } } diff --git a/models/io/HyperPool.cfc b/models/io/HyperPool.cfc index 88973a72..2de0e7ab 100644 --- a/models/io/HyperPool.cfc +++ b/models/io/HyperPool.cfc @@ -141,8 +141,8 @@ component { // allow overriding of any request key via the overrides struct argument var withMethods = [ "headers" ]; for ( var key in arguments.overrides ) { - if ( structKeyExists( requestObj, "set#key#" ) ) { - var methodName = withMethods.contains( key ) ? "with#key#" : "set#key#"; + var methodName = withMethods.contains( key ) ? "with#key#" : "set#key#"; + if ( structKeyExists( requestObj, methodName ) ) { invoke( requestObj, methodName, diff --git a/test-harness/tests/specs/unit/HyperClientTest.cfc b/test-harness/tests/specs/unit/HyperClientTest.cfc index b92d19c3..f8197ca5 100644 --- a/test-harness/tests/specs/unit/HyperClientTest.cfc +++ b/test-harness/tests/specs/unit/HyperClientTest.cfc @@ -1811,6 +1811,23 @@ component extends="coldbox.system.testing.BaseTestCase" { } ); } ); + it( "BaseModel will throw on missing method", function(){ + expect( () => { + var response = getWirebox() + .getInstance( "IndexBuilder@cbelasticsearch" ) + .Timeout( 45 ) + .new( name = "foo", settings = { "refresh_interval" : "1s" } ) + .save(); + + expect( variables.hyper ) + .toHaveSentRequest( ( req ) => { + return req.getMethod() === "PUT" && + req.getUrl() == "http://127.0.0.1:9200/foo/_settings" && + req.getTimeout() === 45; + } ); + } ).toThrow( "MissingMethodException" ); + } ); + it( "Can set custom username/password", function(){ var response = variables.model.indexExists( "bar", { username: "admin", password: "admin123$" } ); From 6a2f6837515485d7d28b225879bee667400ce410 Mon Sep 17 00:00:00 2001 From: michaelborn Date: Fri, 13 Mar 2026 19:23:17 +0000 Subject: [PATCH 09/15] Apply cfformat changes --- models/BaseModel.cfc | 55 ++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/models/BaseModel.cfc b/models/BaseModel.cfc index faa1f000..5ba63258 100644 --- a/models/BaseModel.cfc +++ b/models/BaseModel.cfc @@ -26,31 +26,32 @@ component accessors="true" { return this; } - /** - * Handles any missing methods that start with "with" and adds the value to the requestOverrides struct for request configuration - * Example: withTimeout( 1000 ) would set a default timeout of 1000ms on all requests created by this model - * - * @param methodName the name of the method being called - * @param arguments the arguments passed to the method, where arguments[1] is expected to be the value to set for the default - * @return returns the model instance for chaining - */ - public BaseModel function onMissingMethod( string methodName, struct arguments ){ - if( left( methodName, 4 ) == "with" ){ - var args = []; - if( !isNull( arguments[ 2 ] ) ) { - args = arguments[ 2 ].reduce( function( acc, key, val ){ - acc.append( val ); - return acc; - }, [] ); - } - variables.requestOverrides[ lCase( mid( methodName, 5 ) ) ] = args; - return this; - } - // For all other missing methods, raise a proper missing-method exception - throw( - type = "MissingMethodException", - message = "No such method found for #methodName# on #getMetadata( this ).name#.", - detail = "Use with#methodName# to set default values for request overrides on this model." - ); - } + /** + * Handles any missing methods that start with "with" and adds the value to the requestOverrides struct for request configuration + * Example: withTimeout( 1000 ) would set a default timeout of 1000ms on all requests created by this model + * + * @param methodName the name of the method being called + * @param arguments the arguments passed to the method, where arguments[1] is expected to be the value to set for the default + * @return returns the model instance for chaining + */ + public BaseModel function onMissingMethod( string methodName, struct arguments ){ + if ( left( methodName, 4 ) == "with" ) { + var args = []; + if ( !isNull( arguments[ 2 ] ) ) { + args = arguments[ 2 ].reduce( function( acc, key, val ){ + acc.append( val ); + return acc; + }, [] ); + } + variables.requestOverrides[ lCase( mid( methodName, 5 ) ) ] = args; + return this; + } + // For all other missing methods, raise a proper missing-method exception + throw( + type = "MissingMethodException", + message = "No such method found for #methodName# on #getMetadata( this ).name#.", + detail = "Use with#methodName# to set default values for request overrides on this model." + ); + } + } From e9ab26db80a944e62968a853372f380324643f35 Mon Sep 17 00:00:00 2001 From: Michael Born <8106227+michaelborn@users.noreply.github.com> Date: Fri, 13 Mar 2026 15:24:07 -0400 Subject: [PATCH 10/15] Fix getMappings() call when passing request overrides Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- models/io/HyperClient.cfc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/models/io/HyperClient.cfc b/models/io/HyperClient.cfc index ef42af4d..25587742 100644 --- a/models/io/HyperClient.cfc +++ b/models/io/HyperClient.cfc @@ -263,7 +263,10 @@ component accessors="true" threadSafe singleton { **/ boolean function indexMappingExists( required string indexName, struct requestOverrides = {} ){ try { - var mappings = getMappings( arguments.indexName, arguments.requestOverrides ); + var mappings = getMappings( + indexName = arguments.indexName, + requestOverrides = arguments.requestOverrides + ); } catch ( any e ) { return false; } From 834dfcc5b4639cb5d661ca33c85feb9eebe769c6 Mon Sep 17 00:00:00 2001 From: Michael Born <8106227+michaelborn@users.noreply.github.com> Date: Fri, 13 Mar 2026 15:36:24 -0400 Subject: [PATCH 11/15] Drop incorrect 5th argument in applyMapping() Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- models/io/HyperClient.cfc | 1 - 1 file changed, 1 deletion(-) diff --git a/models/io/HyperClient.cfc b/models/io/HyperClient.cfc index 25587742..3616face 100644 --- a/models/io/HyperClient.cfc +++ b/models/io/HyperClient.cfc @@ -423,7 +423,6 @@ component accessors="true" threadSafe singleton { indexName, "_doc", indexDSL.mappings, - {}, mergedOverrides ); } else { From e692ea630904602af7472b35d3e959420705c599 Mon Sep 17 00:00:00 2001 From: Michael Born Date: Mon, 18 May 2026 12:28:15 -0400 Subject: [PATCH 12/15] Fix parameter validation error in ACF Both start and count values are required for mid(). --- models/BaseModel.cfc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/BaseModel.cfc b/models/BaseModel.cfc index 5ba63258..61399d35 100644 --- a/models/BaseModel.cfc +++ b/models/BaseModel.cfc @@ -43,7 +43,7 @@ component accessors="true" { return acc; }, [] ); } - variables.requestOverrides[ lCase( mid( methodName, 5 ) ) ] = args; + variables.requestOverrides[ lCase( replace( methodName, "with", "" ) ) ] = args; return this; } // For all other missing methods, raise a proper missing-method exception From d0ee22c227f54518723cbe62056e52436314138e Mon Sep 17 00:00:00 2001 From: Michael Born Date: Mon, 18 May 2026 12:49:01 -0400 Subject: [PATCH 13/15] Move init method out of property declarations --- models/SearchBuilder.cfc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/models/SearchBuilder.cfc b/models/SearchBuilder.cfc index 2a227314..cb6f015e 100644 --- a/models/SearchBuilder.cfc +++ b/models/SearchBuilder.cfc @@ -16,11 +16,6 @@ component accessors="true" extends="BaseModel" { **/ property name="index"; - function init(){ - super.init(); - return this; - } - /** * Property containing the object type within the index **/ @@ -110,6 +105,11 @@ component accessors="true" extends="BaseModel" { property name="size"; property name="from"; + function init(){ + super.init(); + return this; + } + function onDIComplete(){ reset(); From 61c7ed02664d6148081f536f1bc3dfd423a07758 Mon Sep 17 00:00:00 2001 From: Michael Born Date: Mon, 18 May 2026 13:03:31 -0400 Subject: [PATCH 14/15] Fix method name argument for ACF --- models/BaseModel.cfc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/models/BaseModel.cfc b/models/BaseModel.cfc index 61399d35..39259049 100644 --- a/models/BaseModel.cfc +++ b/models/BaseModel.cfc @@ -34,8 +34,8 @@ component accessors="true" { * @param arguments the arguments passed to the method, where arguments[1] is expected to be the value to set for the default * @return returns the model instance for chaining */ - public BaseModel function onMissingMethod( string methodName, struct arguments ){ - if ( left( methodName, 4 ) == "with" ) { + public BaseModel function onMissingMethod( string missingMethodName, struct arguments ){ + if ( left( missingMethodName, 4 ) == "with" ) { var args = []; if ( !isNull( arguments[ 2 ] ) ) { args = arguments[ 2 ].reduce( function( acc, key, val ){ @@ -43,14 +43,14 @@ component accessors="true" { return acc; }, [] ); } - variables.requestOverrides[ lCase( replace( methodName, "with", "" ) ) ] = args; + variables.requestOverrides[ lCase( replace( missingMethodName, "with", "" ) ) ] = args; return this; } // For all other missing methods, raise a proper missing-method exception throw( type = "MissingMethodException", - message = "No such method found for #methodName# on #getMetadata( this ).name#.", - detail = "Use with#methodName# to set default values for request overrides on this model." + message = "No such method found for #missingMethodName# on #getMetadata( this ).name#.", + detail = "Use with#missingMethodName# to set default values for request overrides on this model." ); } From 87ce5b7ced81116116786cffc8b4eb110992c977 Mon Sep 17 00:00:00 2001 From: Michael Born Date: Mon, 18 May 2026 13:49:49 -0400 Subject: [PATCH 15/15] Fix dynamic arguments in onMissingMethod --- models/BaseModel.cfc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/models/BaseModel.cfc b/models/BaseModel.cfc index 39259049..e5c1138b 100644 --- a/models/BaseModel.cfc +++ b/models/BaseModel.cfc @@ -34,11 +34,11 @@ component accessors="true" { * @param arguments the arguments passed to the method, where arguments[1] is expected to be the value to set for the default * @return returns the model instance for chaining */ - public BaseModel function onMissingMethod( string missingMethodName, struct arguments ){ + public BaseModel function onMissingMethod( string missingMethodName, struct missingMethodArguments ){ if ( left( missingMethodName, 4 ) == "with" ) { var args = []; - if ( !isNull( arguments[ 2 ] ) ) { - args = arguments[ 2 ].reduce( function( acc, key, val ){ + if ( !isNull( missingMethodArguments ) ) { + args = missingMethodArguments.reduce( function( acc, key, val ){ acc.append( val ); return acc; }, [] );