From 89ac3c4b0de116bc18fd2380c37f50042e804f1b Mon Sep 17 00:00:00 2001 From: "dean.maunder@flywire.com" Date: Tue, 9 Jun 2026 09:44:07 +1000 Subject: [PATCH] Support operation-level deprecated annotation Parse a `deprecated` annotation on handler actions and emit a standard OpenAPI operation-level `deprecated: true`. Function attributes arrive as strings (deprecated="true"), so the value is coerced to a real JSON boolean rather than serialised as the string "true". Pairs with swagger-sdk's `newMethod()` deprecated default, which seeds the operation object with `deprecated: false` for non-annotated actions. Generated with AI Co-Authored-By: Claude --- changelog.md | 7 ++++ models/RoutesParser.cfc | 9 +++++ test-harness/handlers/api/v1/Users.cfc | 1 + test-harness/tests/specs/RoutesParserTest.cfc | 34 +++++++++++++++++++ 4 files changed, 51 insertions(+) diff --git a/changelog.md b/changelog.md index a6a2094..c58aacc 100644 --- a/changelog.md +++ b/changelog.md @@ -9,6 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Support for an operation-level `deprecated` annotation on handler actions. A + `deprecated="true"` function attribute (or `@deprecated true` docblock tag) now emits a + standard OpenAPI `deprecated: true` on the operation object, coerced to a real JSON + boolean. Requires swagger-sdk's `newMethod()` `deprecated` default. + ## [3.1.3] - 2025-10-29 ## [3.1.2] - 2024-09-24 diff --git a/models/RoutesParser.cfc b/models/RoutesParser.cfc index 12cc2c4..1ce0dae 100644 --- a/models/RoutesParser.cfc +++ b/models/RoutesParser.cfc @@ -538,6 +538,15 @@ component accessors="true" threadsafe singleton { continue; } + // Operation deprecation flag — coerce the annotation value to a real + // JSON boolean. Function attributes arrive as strings ( deprecated="true" ), + // which would otherwise serialise as the string "true" rather than the + // boolean the OpenAPI spec requires. + if ( infoKey == "deprecated" ) { + method[ "deprecated" ] = isBoolean( 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" ); diff --git a/test-harness/handlers/api/v1/Users.cfc b/test-harness/handlers/api/v1/Users.cfc index 48cfa79..bfecf8e 100755 --- a/test-harness/handlers/api/v1/Users.cfc +++ b/test-harness/handlers/api/v1/Users.cfc @@ -52,6 +52,7 @@ component displayname="API.v1.Users" { /** * @tags [ "json", "list" ] + * @deprecated true * @param-firstname { "schema" : { "type": "string" }, "required" : "false", "in" : "query" } * @param-lastname { "schema" : { "type": "string" }, "required" : "false", "in" : "query" } * @param-email { "schema" : { "type": "string" }, "required" : "false", "in" : "query" } diff --git a/test-harness/tests/specs/RoutesParserTest.cfc b/test-harness/tests/specs/RoutesParserTest.cfc index dbce73f..74b05f1 100644 --- a/test-harness/tests/specs/RoutesParserTest.cfc +++ b/test-harness/tests/specs/RoutesParserTest.cfc @@ -255,6 +255,40 @@ component expect( path[ "put" ][ "responses" ][ "default" ][ "content" ] ).toBeStruct(); } ); + it( "Tests that the deprecated annotation is parsed as an operation-level boolean", function(){ + expect( variables ).toHaveKey( "APIDoc", "No APIDoc was found to test. Could not continue." ); + + var normalizedDoc = variables.APIDoc.getNormalizedDocument(); + + expect( normalizedDoc ).toHaveKey( "paths" ); + expect( normalizedDoc[ "paths" ] ).toHaveKey( "/api/v1/users/{id}" ); + + var path = normalizedDoc[ "paths" ][ "/api/v1/users/{id}" ]; + + // update() is annotated with @deprecated true + expect( path ).toHaveKey( "put" ); + expect( path[ "put" ] ).toHaveKey( "deprecated" ); + expect( path[ "put" ][ "deprecated" ] ).toBeBoolean(); + expect( path[ "put" ][ "deprecated" ] ).toBeTrue(); + } ); + + it( "Tests that operations without a deprecated annotation default to false", function(){ + expect( variables ).toHaveKey( "APIDoc", "No APIDoc was found to test. Could not continue." ); + + var normalizedDoc = variables.APIDoc.getNormalizedDocument(); + + expect( normalizedDoc ).toHaveKey( "paths" ); + expect( normalizedDoc[ "paths" ] ).toHaveKey( "/api/v1/users" ); + + var path = normalizedDoc[ "paths" ][ "/api/v1/users" ]; + + // add() carries no deprecated annotation — newMethod() default applies + expect( path ).toHaveKey( "post" ); + expect( path[ "post" ] ).toHaveKey( "deprecated" ); + expect( path[ "post" ][ "deprecated" ] ).toBeBoolean(); + expect( path[ "post" ][ "deprecated" ] ).toBeFalse(); + } ); + it( "Tests that an empty default response will be removed if status code responses are present", function(){ expect( variables ).toHaveKey( "APIDoc", "No APIDoc was found to test. Could not continue." );