diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 496ee4c..a2813e4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ jobs: fail-fast: false matrix: cfengine: ["lucee@5", "adobe@2018", "adobe@2021"] - coldbox: ["coldbox@6", "coldbox@7", "coldbox@be"] + coldbox: ["coldbox@6", "coldbox@7"] experimental: [false] include: - cfengine: "adobe@2023.0.0-beta.1" diff --git a/README.md b/README.md index 6acad23..d56dceb 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,6 @@ Available on ForgeBox Tested With TestBox

-

- Compatible with ColdFusion 11 - Compatible with ColdFusion 2016 - Compatible with Lucee 5 -

-

- Master Branch Build Status -

## A CFML HTTP Builder @@ -33,10 +25,19 @@ Hyper exists to provide a fluent builder experience for HTTP requests and respon ### Requirements -Hyper runs on Adobe ColdFusion 11+ and Lucee 5+. +Hyper runs on Adobe ColdFusion 2018+ and Lucee 5+. ColdBox is not required, but mappings are provided for ColdBox users automatically. +### Upgrade from v5 +Two breaking changes: +1. There is no default `Content-Type`. Make sure to either set it or use one of the helper methods, like `asJson()`. +2. `hyper@^6` dropped support for `adobe@2016`. + +### Upgrade from v4 +This is only a breaking change if you have a custom `HttpClient`. +The `HyperHttpClientInterface` now requires a `debug` method. + ### Upgrade from v3 The only changes between v3 and v4 is with the `getQueryParams` and `setQueryParams` methods. diff --git a/models/CfhttpHttpClient.cfc b/models/CfhttpHttpClient.cfc index cc306dc..1676352 100644 --- a/models/CfhttpHttpClient.cfc +++ b/models/CfhttpHttpClient.cfc @@ -124,21 +124,44 @@ component implements="HyperHttpClientInterface" { var cfhttpBody = []; if ( req.hasBody() ) { - if ( req.getBodyFormat() == "json" ) { - cfhttpBody.append( req.prepareBody() ); - } else if ( req.getBodyFormat() == "formFields" ) { - var body = req.getBody(); - for ( var fieldName in body ) { - for ( var value in arrayWrap( body[ fieldName ] ) ) { - cfhttpBody.append( { - "type" : "formfield", - "name" : fieldName, - "value" : value + switch ( req.getBodyFormat() ) { + case "json": + // this is here for backwards compatibility + if ( !headers.keyExists( "Content-Type" ) ) { + cfhttpHeaders.append( { + "name" : "Content-Type", + "value" : "application/json" } ); } - } - } else { - cfhttpBody.append( req.prepareBody() ); + + cfhttpBody.append( { + "type" : "body", + "value" : req.prepareBody() + } ); + break; + case "formFields": + var body = req.getBody(); + for ( var fieldName in body ) { + for ( var value in arrayWrap( body[ fieldName ] ) ) { + cfhttpBody.append( { + "type" : "formfield", + "name" : fieldName, + "value" : value + } ); + } + } + break; + case "xml": + cfhttpBody.append( { + "type" : "xml", + "value" : req.prepareBody() + } ); + break; + default: + cfhttpBody.append( { + "type" : "body", + "value" : req.prepareBody() + } ); } } @@ -242,21 +265,36 @@ component implements="HyperHttpClientInterface" { } if ( req.hasBody() ) { - if ( req.getBodyFormat() == "json" ) { - cfhttpparam( type = "body", value = req.prepareBody() ); - } else if ( req.getBodyFormat() == "formFields" ) { - var body = req.getBody(); - for ( var fieldName in body ) { - for ( var value in arrayWrap( body[ fieldName ] ) ) { + switch ( req.getBodyFormat() ) { + case "json": + // this is here for backwards compatibility + if ( !headers.keyExists( "Content-Type" ) ) { cfhttpparam( - type = "formfield", - name = fieldName, - value = value + type = "header", + name = "Content-Type", + value = "application/json" ); } - } - } else { - cfhttpparam( type = "body", value = req.prepareBody() ); + + cfhttpparam( type = "body", value = req.prepareBody() ); + break; + case "formFields": + var body = req.getBody(); + for ( var fieldName in body ) { + for ( var value in arrayWrap( body[ fieldName ] ) ) { + cfhttpparam( + type = "formfield", + name = fieldName, + value = value + ); + } + } + break; + case "xml": + cfhttpparam( type = "xml", value = req.prepareBody() ); + break; + default: + cfhttpparam( type = "body", value = req.prepareBody() ); } } } diff --git a/models/HyperRequest.cfc b/models/HyperRequest.cfc index b06e7c5..311285d 100644 --- a/models/HyperRequest.cfc +++ b/models/HyperRequest.cfc @@ -155,11 +155,10 @@ component accessors="true" { * @returns The HyperRequest instance. */ function init( httpClient = new CfhttpHttpClient() ) { - variables.requestID = createUUID(); - variables.httpClient = arguments.httpClient; - variables.queryParams = []; - variables.headers = createObject( "java", "java.util.LinkedHashMap" ).init(); - variables.headers.put( "Content-Type", "application/json" ); + variables.requestID = createUUID(); + variables.httpClient = arguments.httpClient; + variables.queryParams = []; + variables.headers = createObject( "java", "java.util.LinkedHashMap" ).init(); variables.files = []; variables.requestCallbacks = []; variables.responseCallbacks = []; @@ -737,6 +736,17 @@ component accessors="true" { return this; } + /** + * A convenience method to set the body format and Content-Type to xml. + * + * @returns The HyperRequest instance. + */ + function asXML() { + setBodyFormat( "xml" ); + setContentType( "application/xml" ); + return this; + } + /** * Attaches a file to the Hyper request. * Also sets the Content-Type as `multipart/form-data`. @@ -899,9 +909,8 @@ component accessors="true" { setBody( "" ); setBodyFormat( "json" ); setReferrer( javacast( "null", "" ) ); - variables.queryParams = []; - variables.headers = createObject( "java", "java.util.LinkedHashMap" ).init(); - variables.headers.put( "Content-Type", "application/json" ); + variables.queryParams = []; + variables.headers = createObject( "java", "java.util.LinkedHashMap" ).init(); variables.files = []; variables.requestCallbacks = []; variables.responseCallbacks = []; diff --git a/tests/specs/integration/DebugSpec.cfc b/tests/specs/integration/DebugSpec.cfc index 365829f..5656d12 100644 --- a/tests/specs/integration/DebugSpec.cfc +++ b/tests/specs/integration/DebugSpec.cfc @@ -21,8 +21,6 @@ component extends="tests.resources.ModuleIntegrationSpec" appMapping="/app" { .setUrl( "https://example.com" ) .debug(); - debug( debugReq ); - expect( debugReq ).toBeStruct(); expect( debugReq ).toHaveKey( "attributes" ); @@ -35,7 +33,10 @@ component extends="tests.resources.ModuleIntegrationSpec" appMapping="/app" { expect( debugReq.body ).toHaveKey( "body" ); expect( debugReq.body.body ).toBeArray(); expect( debugReq.body.body ).toHaveLength( 1 ); - expect( debugReq.body.body[ 1 ] ).toBe( binaryBody ); + expect( debugReq.body.body[ 1 ] ).toBe( { + "type" : "body", + "value" : binaryBody + } ); expect( debugReq.body ).toHaveKey( "files" ); expect( debugReq.body.files ).toBeEmpty(); diff --git a/tests/specs/unit/HyperRequestSpec.cfc b/tests/specs/unit/HyperRequestSpec.cfc index c920ff9..aa23ffa 100644 --- a/tests/specs/unit/HyperRequestSpec.cfc +++ b/tests/specs/unit/HyperRequestSpec.cfc @@ -162,6 +162,7 @@ component extends="testbox.system.BaseSpec" { } ); req.withHeaders( { "Accept" : "application/xml" } ) + .asJSON() .patch( "https://jsonplaceholder.typicode.com/posts/1" ); expect( method ).toBe( "PATCH" ); expect( headers ).toBe( { @@ -215,11 +216,16 @@ component extends="testbox.system.BaseSpec" { req.setBody( '{"query":{},"size":0,"from":0}' ); expect( req.prepareBody() ).toBe( '{"query":{},"size":0,"from":0}' ); } ); + it( "can handle a JSON body format with a body as an struct", function() { req.setBodyFormat( "json" ); req.setBody( { "query" : {}, "size" : 0, "from" : 0 } ); expect( req.prepareBody() ).toBe( '{"query":{},"size":0,"from":0}' ); } ); + + it( "defaults to no Content-Type or Headers", function() { + expect( req.getHeaders() ).toBeEmpty(); + } ); } ); }