diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f67eec3..da7532f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,38 +10,63 @@ on: jobs: tests: name: Tests - runs-on: ubuntu-24.04 + runs-on: ubuntu-latest defaults: run: working-directory: ${{ github.workspace }}/cbswagger env: DB_USER: root DB_PASSWORD: root + continue-on-error: ${{ matrix.experimental }} strategy: fail-fast: false matrix: - cfengine: [ "lucee@5", "lucee@6" ,"adobe@2018", "adobe@2021" ] - coldboxVersion: [ "^6.0.0", "^7.0.0" ] + cfengine: [ "lucee@5", "lucee@6" ,"adobe@2018", "adobe@2021", "adobe@2023", "adobe@2025", "boxlang-cfml@1" ] + coldboxVersion: [ "^6.0.0", "^7.0.0", "^8.0.0" ] experimental: [ false ] include: - - cfengine: "adobe@2023" - coldboxVersion: "^6.0.0" + - coldboxVersion: "^8.0.0" + cfengine: "boxlang@1" experimental: true - coldboxVersion: "be" cfengine: "lucee@5" experimental: true + - coldboxVersion: "be" + cfengine: "lucee@6" + experimental: true + - coldboxVersion: "be" + cfengine: "lucee@be" + experimental: true - coldboxVersion: "be" cfengine: "adobe@2018" experimental: true + - coldboxVersion: "be" + cfengine: "adobe@2021" + experimental: true - coldboxVersion: "be" cfengine: "adobe@2023" experimental: true + - coldboxVersion: "be" + cfengine: "adobe@2025" + experimental: true + - coldboxVersion: "be" + cfengine: "adobe@be" + experimental: true - coldboxVersion: "be" cfengine: "boxlang@1" experimental: true + - coldboxVersion: "be" + cfengine: "boxlang@be" + experimental: true + - coldboxVersion: "be" + cfengine: "boxlang-cfml@1" + experimental: true + - coldboxVersion: "be" + cfengine: "boxlang-cfml@be" + experimental: true steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@v5 with: path: cbswagger @@ -52,29 +77,14 @@ jobs: # mysql -u${{ env.DB_USER }} -p${{ env.DB_PASSWORD }} < test-harness/tests/resources/coolblog.sql - name: Setup Java - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: distribution: "temurin" - java-version: "11" + java-version: 21 - name: Setup CommandBox CLI uses: Ortus-Solutions/setup-commandbox@v2.0.1 - # Not Needed in this module - #- name: Setup Environment For Testing Process - # run: | - # # Setup .env - # touch .env - # # ENV - # printf "DB_HOST=localhost\n" >> .env - # printf "DB_DATABASE=mydatabase\n" >> .env - # printf "DB_DRIVER=MySQL\n" >> .env - # printf "DB_USER=${{ env.DB_USER }}\n" >> .env - # printf "DB_PASSWORD=${{ env.DB_PASSWORD }}\n" >> .env - # printf "DB_CLASS=com.mysql.cj.jdbc.Driver\n" >> .env - # printf "DB_BUNDLEVERSION=8.0.19\n" >> .env - # printf "DB_BUNDLENAME=com.mysql.cj\n" >> .env - - name: Install Test Harness with ColdBox ${{ matrix.coldboxVersion }} run: | box install commandbox-boxlang --force diff --git a/models/RoutesParser.cfc b/models/RoutesParser.cfc index d78267c..12cc2c4 100644 --- a/models/RoutesParser.cfc +++ b/models/RoutesParser.cfc @@ -501,96 +501,114 @@ component accessors="true" threadsafe singleton { appendFunctionParams( argumentCollection = arguments ); appendFunctionResponses( argumentCollection = arguments ); - functionMetadata - .keyArray() - .each( function( infoKey ){ - // is !simple, continue to next key - if ( !isSimpleValue( functionMetaData[ infoKey ] ) ) continue; - - // parse values from each key - var infoMetadata = parseMetadataValue( functionMetaData[ infoKey ] ); + var parseGenericMetadata = function( md ){ + arguments.md + .keyArray() + .each( function( infoKey ){ + // is !simple, continue to next key + if ( !isSimpleValue( md[ infoKey ] ) ) continue; + + // parse values from each key + var infoMetadata = parseMetadataValue( md[ infoKey ] ); + + // hint/description + if ( infoKey == "hint" ) { + if ( !structKeyExists( method, "description" ) || method[ "description" ] == "" ) { + method[ "description" ] = infoMetadata; + } + if ( !structKeyExists( md, "summary" ) ) { + method[ "summary" ] = infoMetadata; + } + continue; + } - // hint/description - if ( infoKey == "hint" ) { - if ( !structKeyExists( method, "description" ) || method[ "description" ] == "" ) { + if ( infoKey == "description" && infoMetadata != "" ) { method[ "description" ] = infoMetadata; + continue; } - if ( !structKeyExists( functionMetadata, "summary" ) ) { + + if ( infoKey == "summary" ) { method[ "summary" ] = infoMetadata; + continue; } - continue; - } - - if ( infoKey == "description" && infoMetadata != "" ) { - method[ "description" ] = infoMetadata; - continue; - } - - if ( infoKey == "summary" ) { - method[ "summary" ] = infoMetadata; - continue; - } - - // Operation Tags - if ( infoKey == "tags" ) { - method[ "tags" ] = isSimpleValue( infoMetadata ) ? listToArray( infoMetadata ) : infoMetadata; - continue; - } - // Request body: { description, required, content : {} } if simple, we just add it as required, with listed as content - if ( left( infoKey, 12 ) == "requestBody" ) { - method[ "requestBody" ] = structNew( "ordered" ); + // Operation Tags + if ( infoKey == "tags" ) { + method[ "tags" ] = isSimpleValue( infoMetadata ) ? listToArray( infoMetadata ) : infoMetadata; + continue; + } - if ( isSimpleValue( infoMetadata ) ) { - method[ "requestBody" ][ "description" ] = infoMetadata; - method[ "requestBody" ][ "required" ] = true; - method[ "requestBody" ][ "content" ] = { "#infoMetadata#" : {} }; - } else { - structAppend( method[ "requestBody" ], infoMetadata, true ); + // Request body: { description, required, content : {} } if simple, we just add it as required, with listed as content + if ( left( infoKey, 12 ) == "requestBody" ) { + method[ "requestBody" ] = structNew( "ordered" ); + + if ( isSimpleValue( infoMetadata ) ) { + method[ "requestBody" ][ "description" ] = infoMetadata; + method[ "requestBody" ][ "required" ] = true; + method[ "requestBody" ][ "content" ] = { "#infoMetadata#" : {} }; + } else { + structAppend( method[ "requestBody" ], infoMetadata, true ); + } + continue; } - continue; - } - // security - if ( infoKey == "security" ) { - if ( isSimpleValue( infoMetadata ) ) { - // expect a list of pre-defined securitySchemes - method[ "security" ] = listToArray( infoMetadata ) - .filter( function( security ){ - return structKeyList( moduleSettings.components.securitySchemes ).find( - security - ); - } ) - .map( function( item ){ - return { "#item#" : [] }; - } ); - } else { - method[ "security" ] = infoMetadata; + // security + if ( infoKey == "security" ) { + if ( isSimpleValue( infoMetadata ) ) { + // expect a list of pre-defined securitySchemes + method[ "security" ] = listToArray( infoMetadata ) + .filter( function( security ){ + return structKeyList( moduleSettings.components.securitySchemes ).find( + security + ); + } ) + .map( function( item ){ + return { "#item#" : [] }; + } ); + } else { + method[ "security" ] = infoMetadata; + } + continue; } - continue; - } - // Spec Extensions x-{name}, must be in lowercase - if ( left( infoKey, 2 ) == "x-" ) { - var normalizedKey = replaceNoCase( infoKey, "x-", "" ).lcase(); - // evaluate whether we have an x- replacement or a standard x-attribute - if ( arrayContainsNoCase( defaultKeys, normalizedKey ) ) { - method[ normalizedKey ] = infoMetadata; - } else { - method[ infoKey.lcase() ] = infoMetadata; + // Spec Extensions x-{name}, must be in lowercase + if ( left( infoKey, 2 ) == "x-" ) { + var normalizedKey = replaceNoCase( infoKey, "x-", "" ).lcase(); + // evaluate whether we have an x- replacement or a standard x-attribute + if ( arrayContainsNoCase( defaultKeys, normalizedKey ) ) { + method[ normalizedKey ] = infoMetadata; + } else { + method[ infoKey.lcase() ] = infoMetadata; + } + continue; } - continue; - } - if ( arrayContainsNoCase( defaultKeys, infoKey ) ) { - // don't override any previously set convention assignments - if ( isSimpleValue( infoMetadata ) && len( infoMetadata ) ) { - method[ infoKey ] = infoMetadata; - } else if ( !isSimpleValue( infoMetadata ) ) { - method[ infoKey ] = infoMetadata; + if ( arrayContainsNoCase( defaultKeys, infoKey ) ) { + // don't override any previously set convention assignments + if ( isSimpleValue( infoMetadata ) && len( infoMetadata ) ) { + method[ infoKey ] = infoMetadata; + } else if ( !isSimpleValue( infoMetadata ) ) { + method[ infoKey ] = infoMetadata; + } } - } - } ); + } ); + }; + + parseGenericMetadata( arguments.functionMetadata ); + if ( + functionMetadata.keyExists( "annotations" ) && !isNull( functionMetadata.annotations ) && isStruct( + functionMetadata.annotations + ) + ) { + parseGenericMetadata( functionMetadata.annotations ); + } + if ( + functionMetadata.keyExists( "documentation" ) && !isNull( functionMetadata.documentation ) && isStruct( + functionMetadata.documentation + ) + ) { + parseGenericMetadata( functionMetadata.documentation ); + } // check for a request body convention file if ( !method.keyExists( "requestBody" ) || structIsEmpty( method[ "requestBody" ] ) ) { @@ -688,48 +706,59 @@ component accessors="true" threadsafe singleton { required any functionMetadata, moduleName ){ - functionMetadata - .keyArray() - .filter( function( key ){ - return left( key, 6 ) == "param-"; - } ) - .each( function( infoKey ){ - // parse values from each key - var infoMetadata = parseMetadataValue( functionMetaData[ infoKey ] ); - // Get the param name - var paramName = right( infoKey, len( infoKey ) - 6 ); - - // See if our parameter was already provided through URL parsing - var paramSearch = arrayFilter( method[ "parameters" ], function( item ){ - return item.name == paramName; - } ); + var parseParamsFromStruct = function( md ){ + arguments.md + .keyArray() + .filter( function( key ){ + return left( key, 6 ) == "param-"; + } ) + .each( function( infoKey ){ + // parse values from each key + var infoMetadata = parseMetadataValue( md[ infoKey ] ); + // Get the param name + var paramName = right( infoKey, len( infoKey ) - 6 ); - if ( arrayLen( paramSearch ) ) { - var parameter = paramSearch[ 1 ]; - if ( isSimpleValue( infoMetadata ) ) { - parameter[ "description" ] = infoMetadata; - } else { - structAppend( parameter, infoMetadata ); - } - } else { - // Default Params - var parameter = { - "name" : paramName, - "description" : "", - "in" : "query", - "required" : false, - "schema" : { "type" : "string", "default" : "" } - }; + // See if our parameter was already provided through URL parsing + var paramSearch = arrayFilter( method[ "parameters" ], function( item ){ + return item.name == paramName; + } ); - if ( isSimpleValue( infoMetadata ) ) { - parameter[ "description" ] = infoMetadata; + if ( arrayLen( paramSearch ) ) { + var parameter = paramSearch[ 1 ]; + if ( isSimpleValue( infoMetadata ) ) { + parameter[ "description" ] = infoMetadata; + } else { + structAppend( parameter, infoMetadata ); + } } else { - structAppend( parameter, infoMetadata ); + // Default Params + var parameter = { + "name" : paramName, + "description" : "", + "in" : "query", + "required" : false, + "schema" : { "type" : "string", "default" : "" } + }; + + if ( isSimpleValue( infoMetadata ) ) { + parameter[ "description" ] = infoMetadata; + } else { + structAppend( parameter, infoMetadata ); + } + + arrayAppend( method[ "parameters" ], parameter ); } + } ); + }; - arrayAppend( method[ "parameters" ], parameter ); - } - } ); + parseParamsFromStruct( functionMetadata ); + if ( + functionMetadata.keyExists( "annotations" ) && !isNull( functionMetadata.annotations ) && isStruct( + functionMetadata.annotations + ) + ) { + parseParamsFromStruct( functionMetadata.annotations ); + } sampleArgs = { "type" : "parameters" }; sampleArgs.append( arguments ); @@ -744,31 +773,42 @@ component accessors="true" threadsafe singleton { required any functionMetadata, moduleName ){ - functionMetadata - .keyArray() - .filter( function( key ){ - return left( key, 9 ) == "response-"; - } ) - .each( function( infoKey ){ - // parse values from each key - var infoMetadata = parseMetadataValue( functionMetaData[ infoKey ] ); - // get reponse name - var responseName = right( infoKey, len( infoKey ) - 9 ); - - method[ "responses" ][ responseName ] = structNew( "ordered" ); - - // Use simple value for description and content type - if ( isSimpleValue( infoMetadata ) ) { - method[ "responses" ][ responseName ][ "description" ] = infoMetadata; - method[ "responses" ][ responseName ][ "content" ] = { "#infoMetadata#" : {} }; - } else { - structAppend( - method[ "responses" ][ responseName ], - infoMetadata, - true - ); - } - } ); + var parseResponsesFromStruct = function( md ){ + arguments.md + .keyArray() + .filter( function( key ){ + return left( key, 9 ) == "response-"; + } ) + .each( function( infoKey ){ + // parse values from each key + var infoMetadata = parseMetadataValue( md[ infoKey ] ); + // get response name + var responseName = right( infoKey, len( infoKey ) - 9 ); + + method[ "responses" ][ responseName ] = structNew( "ordered" ); + + // Use simple value for description and content type + if ( isSimpleValue( infoMetadata ) ) { + method[ "responses" ][ responseName ][ "description" ] = infoMetadata; + method[ "responses" ][ responseName ][ "content" ] = { "#infoMetadata#" : {} }; + } else { + structAppend( + method[ "responses" ][ responseName ], + infoMetadata, + true + ); + } + } ); + }; + + parseResponsesFromStruct( functionMetadata ); + if ( + functionMetadata.keyExists( "annotations" ) && !isNull( functionMetadata.annotations ) && isStruct( + functionMetadata.annotations + ) + ) { + parseResponsesFromStruct( functionMetadata.annotations ); + } // Remove our empty default response if other responses were provided if ( diff --git a/server-adobe@2018.json b/server-adobe@2018.json index 3d1d728..12b1f83 100644 --- a/server-adobe@2018.json +++ b/server-adobe@2018.json @@ -15,6 +15,10 @@ "aliases":{ "/moduleroot/cbswagger":"../" } + }, + "jvm":{ + "heapSize":"1024", + "javaVersion":"openjdk8_jre" }, "openBrowser":"false", "cfconfig":{ diff --git a/server-adobe@2021.json b/server-adobe@2021.json index 1e11647..9fcca8f 100644 --- a/server-adobe@2021.json +++ b/server-adobe@2021.json @@ -15,6 +15,10 @@ "aliases":{ "/moduleroot/cbswagger":"../" } + }, + "jvm":{ + "heapSize":"1024", + "javaVersion":"openjdk11_jre" }, "openBrowser":"false", "cfconfig": { diff --git a/server-adobe@2023.json b/server-adobe@2023.json index e889825..e074f44 100644 --- a/server-adobe@2023.json +++ b/server-adobe@2023.json @@ -2,7 +2,7 @@ "name":"swagger-sdk-adobe@2023", "app":{ "serverHomeDirectory":".engine/adobe2023", - "cfengine":"adobe@2023.0.0-beta.1" + "cfengine":"adobe@2023" }, "web":{ "http":{ diff --git a/server-adobe@2025.json b/server-adobe@2025.json new file mode 100644 index 0000000..fbbcea7 --- /dev/null +++ b/server-adobe@2025.json @@ -0,0 +1,30 @@ +{ + "name":"swagger-sdk-adobe@2025", + "app":{ + "serverHomeDirectory":".engine/adobe2025", + "cfengine":"adobe@2025" + }, + "web":{ + "http":{ + "port":"60299" + }, + "rewrites":{ + "enable":true + }, + "webroot":"test-harness", + "aliases":{ + "/moduleroot/cbswagger":"../" + } + }, + "jvm":{ + "heapSize":"1024", + "javaVersion":"openjdk21_jre" + }, + "openBrowser":"false", + "cfconfig":{ + "file":".cfconfig.json" + }, + "scripts":{ + "onServerInstall":"cfpm install zip" + } +} diff --git a/server-adobe@be.json b/server-adobe@be.json new file mode 100644 index 0000000..95a2ced --- /dev/null +++ b/server-adobe@be.json @@ -0,0 +1,26 @@ +{ + "name":"swagger-sdk-adobe@be", + "app":{ + "serverHomeDirectory":".engine/adobeBE", + "cfengine":"adobe@be" + }, + "web":{ + "http":{ + "port":"60299" + }, + "rewrites":{ + "enable":true + }, + "webroot":"test-harness", + "aliases":{ + "/moduleroot/cbswagger":"../" + } + }, + "openBrowser":"false", + "cfconfig":{ + "file":".cfconfig.json" + }, + "scripts":{ + "onServerInstall":"cfpm install zip" + } +} diff --git a/server-boxlang-cfml@1.json b/server-boxlang-cfml@1.json new file mode 100644 index 0000000..7515c3f --- /dev/null +++ b/server-boxlang-cfml@1.json @@ -0,0 +1,35 @@ +{ + "app":{ + "cfengine":"boxlang@^1", + "serverHomeDirectory":".engine/boxlangCFML" + }, + "name":"cbSwagger-boxlang-cfml@1", + "force":true, + "openBrowser":false, + "web":{ + "directoryBrowsing":true, + "http":{ + "port":"60299" + }, + "rewrites":{ + "enable":"true" + }, + "webroot":"test-harness", + "aliases":{ + "/moduleroot/cbSwagger":"./" + } + }, + "JVM":{ + "heapSize":"1024", + "javaVersion":"openjdk21_jdk" + }, + "cfconfig":{ + "file":".cfconfig.json" + }, + "env":{ + "BOXLANG_DEBUG":true + }, + "scripts":{ + "onServerInitialInstall":"install bx-mail,bx-mysql,bx-derby,bx-compat-cfml@be,bx-unsafe-evaluate,bx-esapi --noSave" + } +} diff --git a/server-boxlang-cfml@be.json b/server-boxlang-cfml@be.json new file mode 100644 index 0000000..9cee3e9 --- /dev/null +++ b/server-boxlang-cfml@be.json @@ -0,0 +1,35 @@ +{ + "app":{ + "cfengine":"boxlang@be", + "serverHomeDirectory":".engine/boxlangCFMLBE" + }, + "name":"cbSwagger-boxlang-cfml@be", + "force":true, + "openBrowser":false, + "web":{ + "directoryBrowsing":true, + "http":{ + "port":"60299" + }, + "rewrites":{ + "enable":"true" + }, + "webroot":"test-harness", + "aliases":{ + "/moduleroot/cbSwagger":"./" + } + }, + "JVM":{ + "heapSize":"1024", + "javaVersion":"openjdk21_jdk" + }, + "cfconfig":{ + "file":".cfconfig.json" + }, + "env":{ + "BOXLANG_DEBUG":true + }, + "scripts":{ + "onServerInitialInstall":"install bx-mail,bx-mysql,bx-derby,bx-compat-cfml@be,bx-unsafe-evaluate,bx-esapi --noSave" + } +} diff --git a/server-boxlang@1.json b/server-boxlang@1.json index 923c647..fb91865 100644 --- a/server-boxlang@1.json +++ b/server-boxlang@1.json @@ -1,6 +1,6 @@ { "app":{ - "cfengine":"boxlang@be", + "cfengine":"boxlang@^1", "serverHomeDirectory":".engine/boxlang" }, "name":"cbSwagger-boxlang@1", @@ -21,8 +21,7 @@ }, "JVM":{ "heapSize":"1024", - "javaVersion":"openjdk21_jdk", - "args":"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=9999" + "javaVersion":"openjdk21_jdk" }, "cfconfig":{ "file":".cfconfig.json" @@ -31,6 +30,6 @@ "BOXLANG_DEBUG":true }, "scripts":{ - "onServerInitialInstall":"install bx-mail,bx-mysql,bx-derby,bx-compat-cfml@be,bx-unsafe-evaluate,bx-esapi --noSave" + "onServerInitialInstall":"install bx-mail,bx-mysql,bx-derby,bx-unsafe-evaluate,bx-esapi --noSave" } } diff --git a/server-boxlang@be.json b/server-boxlang@be.json new file mode 100644 index 0000000..253a0fd --- /dev/null +++ b/server-boxlang@be.json @@ -0,0 +1,35 @@ +{ + "app":{ + "cfengine":"boxlang@be", + "serverHomeDirectory":".engine/boxlangBE" + }, + "name":"cbSwagger-boxlang@be", + "force":true, + "openBrowser":false, + "web":{ + "directoryBrowsing":true, + "http":{ + "port":"60299" + }, + "rewrites":{ + "enable":"true" + }, + "webroot":"test-harness", + "aliases":{ + "/moduleroot/cbSwagger":"./" + } + }, + "JVM":{ + "heapSize":"1024", + "javaVersion":"openjdk21_jdk" + }, + "cfconfig":{ + "file":".cfconfig.json" + }, + "env":{ + "BOXLANG_DEBUG":true + }, + "scripts":{ + "onServerInitialInstall":"install bx-mail,bx-mysql,bx-derby,bx-unsafe-evaluate,bx-esapi --noSave" + } +} diff --git a/server-lucee@be.json b/server-lucee@be.json new file mode 100644 index 0000000..e380a63 --- /dev/null +++ b/server-lucee@be.json @@ -0,0 +1,23 @@ +{ + "name":"cbswagger-lucee@be", + "app":{ + "serverHomeDirectory":".engine/luceeBE", + "cfengine":"lucee@be" + }, + "web":{ + "http":{ + "port":"60299" + }, + "rewrites":{ + "enable":"true" + }, + "webroot":"test-harness", + "aliases":{ + "/moduleroot/cbswagger":"../" + } + }, + "openBrowser":"false", + "cfconfig":{ + "file":".cfconfig.json" + } +} diff --git a/test-harness/tests/specs/RoutesParserTest.cfc b/test-harness/tests/specs/RoutesParserTest.cfc index b3a9f02..dbce73f 100644 --- a/test-harness/tests/specs/RoutesParserTest.cfc +++ b/test-harness/tests/specs/RoutesParserTest.cfc @@ -22,7 +22,7 @@ component variables.samplesPath = controller.getAppRootPath() & variables.model.getModuleSettings().samplesPath; // make all of our private model methods public - var privateMethods = getMetadata( variables.model ).functions + var privateMethods = arraySlice( getMetadata( variables.model ).functions, 1 ) .filter( function( fn ){ return fn.keyExists( "access" ) && fn.access == "private"; } )