Skip to content

support a per-function @noCbSwagger attribute #43

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions models/RoutesParser.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,21 @@ component accessors="true" threadsafe singleton {
for ( var methodList in actions ) {
// handle any delimited method lists
for ( var methodName in listToArray( methodList ) ) {
//
// If we have handler component metadata (when do we expect it to be null?),
// and we have function metadata (when do we expect it to be null?),
// and there is an appropriate annotation on the function from that function's metadata,
// then do not expose this function to swagger docs.
//
if ( !isNull( arguments.handlerMetadata ) ) {
var functionMetadata = getFunctionMetadata( handlerMetadata = arguments.handlerMetadata, functionName = lCase( actions[ methodName ] ) );
if ( !isNull( functionMetadata ) ) {
if ( !isDocumentationEnabled( functionMetadata = functionMetadata ) ) {
continue;
}
}
}

// method not in error methods
if ( !arrayFindNoCase( errorMethods, actions[ methodList ] ) ) {
// Create new path template
Expand Down Expand Up @@ -324,6 +339,11 @@ component accessors="true" threadsafe singleton {
}
}

if ( structIsEmpty( path ) ) {
// if we skipped over every possible verb, there's nothing to place into swagger docs for this route
return;
}

// Strip out any typing placeholders in routes
var pathSegments = listToArray( arguments.pathKey, "/" );
var typingParams = [ "numeric", "alpha", "regex:" ];
Expand Down Expand Up @@ -592,6 +612,23 @@ component accessors="true" threadsafe singleton {
}
}

/**
* return true if the supplied metadata represents a function that has NOT been explicitly marked as disabled
*/
private boolean function isDocumentationEnabled( required struct functionMetadata ) {
//
// function foo() {} <-- no attr, documentation is enabled (depending on module config)
// function foo() cbSwagger {} <-- has attr with no value, documentation is enabled (depending on module config)
// function foo() cbSwagger=true {} <-- has attr, with truthy value, documentation is enabled (depending on module config)
// function foo() cbSwagger=false {} <-- has attr, with falsy value, documentation is disabled (overrides module config)
// function foo() cbSwagger=xyz {} <-- has attr, with non-booleanish value, which we consider falsy, documentation is disabled (overrides module config)
//
return !structKeyExists( functionMetadata, "cbSwagger" )
? true // there is no `cbSwagger` attribute, so the default is to assume true.
// booleanish test first, to handle docbloc attrs that have no associated value being assigned `true` values
: (isValid("boolean", functionMetadata.cbSwagger) && functionMetadata.cbSwagger) || len( functionMetadata.cbSwagger ) == 0;
}

private void function appendConventionSamples(
required string type,
required any methodName,
Expand Down
18 changes: 18 additions & 0 deletions test-harness/config/Router.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,24 @@ component{
action=defaultAPIActions
);

// Would be included in docs, but function def has @noCbSwagger attribute
addRoute(
pattern='/api/v1/noCbSwagger',
handler='api.v1.Users',
action={
"GET": "sharedRouteDifferentHTTPMethods_get_shouldNotBeExposed",
"POST": "sharedRouteDifferentHTTPMethods_post_shouldBeExposed"
}
);

addRoute(
pattern='/api/v1/noCbSwagger2',
handler='api.v1.Users',
action = {
"GET": "loneRoute_get_shouldNotBeExposed"
}
);

// @app_routes@

// Conventions-Based Routing
Expand Down
11 changes: 11 additions & 0 deletions test-harness/handlers/api/v1/Users.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,15 @@ component displayname="API.v1.Users" {
};
}

/**
* @cbSwagger false
*/
function sharedRouteDifferentHTTPMethods_get_shouldNotBeExposed() {}
function sharedRouteDifferentHTTPMethods_post_shouldBeExposed() {}

/**
* @cbSwagger false
*/
function loneRoute_get_shouldNotBeExposed() {}

}
19 changes: 18 additions & 1 deletion test-harness/tests/specs/RoutesParserTest.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ component

expect( isJSON( APIDoc.asJSON() ) ).toBeTrue();

expect( NormalizedDoc.paths["/api/v1/noCbSwagger"] ).toHaveKey( "post", "post function marked cbSwagger=false" );
expect( NormalizedDoc.paths["/api/v1/noCbSwagger"] ).notToHaveKey( "get", "get function was not marked cbSwagger=false" );
expect( NormalizedDoc.paths ).notToHaveKey( "/api/v1/noCbSwagger2", "the lone function handling this was marked cbSwagger=false" );

variables.APIDoc = APIDoc;
} );

Expand Down Expand Up @@ -128,17 +132,30 @@ component

expect( arrayLen( CBRoutes ) ).toBeGT( 0 );

// in cases where we are testing that some route does not end up in the docs,
// we want to ensure we DO see that the route exists, and consequently that its absence from the docs
// is due to an intentional exclusion of a route that positively exists.
var requireTheseRoutesExistWithoutDocs = {"/api/v1/noCbSwagger2": 1}

// Tests that all of our configured paths exist
for ( var routePrefix in apiPrefixes ) {
for ( var route in cbRoutes ) {
if ( left( route.pattern, len( routePrefix ) ) == routePrefix ) {
var translatedPath = swaggerUtil.translatePath( route.pattern );
if ( !len( route.moduleRouting ) ) {
expect( normalizedDoc[ "paths" ] ).toHaveKey( translatedPath );
if ( structKeyExists(requireTheseRoutesExistWithoutDocs, translatedPath) ) {
expect( normalizedDoc[ "paths" ] ).notToHaveKey( translatedPath, "expected to have discarded this route from the docs" );
structDelete( requireTheseRoutesExistWithoutDocs, translatedPath )
}
else {
expect( normalizedDoc[ "paths" ] ).toHaveKey( translatedPath );
}
}
}
}
}

expect( requireTheseRoutesExistWithoutDocs ).toBeEmpty( "all routes expected to not have docs were seen" );
} );

it( "Tests the API Document for module introspection", function(){
Expand Down