From 20b5fdfd00a76ddf7c9a97459a10150cf59f482c Mon Sep 17 00:00:00 2001 From: jpfinne Date: Thu, 25 Sep 2025 15:00:19 +0200 Subject: [PATCH 1/5] Add spring api versioning support --- docs/generators/java-camel.md | 3 ++ docs/generators/spring.md | 3 ++ .../openapitools/codegen/VendorExtension.java | 1 + .../codegen/languages/SpringCodegen.java | 11 +++++++ .../main/resources/JavaSpring/api.mustache | 3 +- .../assertions/AbstractAnnotationsAssert.java | 31 ++++++++++++++----- .../java/spring/SpringCodegenTest.java | 21 +++++++++++++ .../test/resources/3_0/spring/apiVersion.yaml | 23 ++++++++++++++ 8 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 modules/openapi-generator/src/test/resources/3_0/spring/apiVersion.yaml diff --git a/docs/generators/java-camel.md b/docs/generators/java-camel.md index 24ba78294eb3..bff61a4b89a6 100644 --- a/docs/generators/java-camel.md +++ b/docs/generators/java-camel.md @@ -108,6 +108,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |useOptional|Use Optional container for optional parameters| |false| |useResponseEntity|Use the `ResponseEntity` type to wrap return values of generated API methods. If disabled, method are annotated using a `@ResponseStatus` annotation, which has the status of the first response declared in the Api definition| |true| |useSealed|Whether to generate sealed model interfaces and classes| |false| +|useSpringApiVersion|Generate version attribute in @RequestMapping for Spring 7.| |null| |useSpringBoot3|Generate code and provide dependencies for use with Spring Boot 3.x. (Use jakarta instead of javax in imports). Enabling this option will also enable `useJakartaEe`.| |false| |useSpringBuiltInValidation|Disable `@Validated` at the class level when using built-in validation.| |false| |useSpringController|Annotate the generated API as a Spring Controller| |false| @@ -132,6 +133,8 @@ These options may be applied as additional-properties (cli) or configOptions (pl |x-spring-paginated|Add `org.springframework.data.domain.Pageable` to controller method. Can be used to handle `page`, `size` and `sort` query parameters. If these query parameters are also specified in the operation spec, they will be removed from the controller method as their values can be obtained from the `Pageable` object.|OPERATION|false |x-version-param|Marker property that tells that this parameter would be used for endpoint versioning. Applicable for headers & query params. true/false|OPERATION_PARAMETER|null |x-pattern-message|Add this property whenever you need to customize the invalidation error message for the regex pattern of a variable|FIELD, OPERATION_PARAMETER|null +|x-pattern-message|Add this property whenever you need to customize the invalidation error message for the regex pattern of a variable|FIELD, OPERATION_PARAMETER|null +|x-spring-api-version|Generate version attribute in @RequestMapping for Spring 7.|OPERATION|null ## IMPORT MAPPING diff --git a/docs/generators/spring.md b/docs/generators/spring.md index 19c889f90916..7cc45d8c1105 100644 --- a/docs/generators/spring.md +++ b/docs/generators/spring.md @@ -101,6 +101,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |useOptional|Use Optional container for optional parameters| |false| |useResponseEntity|Use the `ResponseEntity` type to wrap return values of generated API methods. If disabled, method are annotated using a `@ResponseStatus` annotation, which has the status of the first response declared in the Api definition| |true| |useSealed|Whether to generate sealed model interfaces and classes| |false| +|useSpringApiVersion|Generate version attribute in @RequestMapping for Spring 7.| |null| |useSpringBoot3|Generate code and provide dependencies for use with Spring Boot 3.x. (Use jakarta instead of javax in imports). Enabling this option will also enable `useJakartaEe`.| |false| |useSpringBuiltInValidation|Disable `@Validated` at the class level when using built-in validation.| |false| |useSpringController|Annotate the generated API as a Spring Controller| |false| @@ -125,6 +126,8 @@ These options may be applied as additional-properties (cli) or configOptions (pl |x-spring-paginated|Add `org.springframework.data.domain.Pageable` to controller method. Can be used to handle `page`, `size` and `sort` query parameters. If these query parameters are also specified in the operation spec, they will be removed from the controller method as their values can be obtained from the `Pageable` object.|OPERATION|false |x-version-param|Marker property that tells that this parameter would be used for endpoint versioning. Applicable for headers & query params. true/false|OPERATION_PARAMETER|null |x-pattern-message|Add this property whenever you need to customize the invalidation error message for the regex pattern of a variable|FIELD, OPERATION_PARAMETER|null +|x-pattern-message|Add this property whenever you need to customize the invalidation error message for the regex pattern of a variable|FIELD, OPERATION_PARAMETER|null +|x-spring-api-version|Generate version attribute in @RequestMapping for Spring 7.|OPERATION|null ## IMPORT MAPPING diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/VendorExtension.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/VendorExtension.java index 70aa9ce222b1..14f42948cee7 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/VendorExtension.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/VendorExtension.java @@ -13,6 +13,7 @@ public enum VendorExtension { X_KOTLIN_IMPLEMENTS("x-kotlin-implements", ExtensionLevel.MODEL, "Ability to specify interfaces that model must implement", "empty array"), X_KOTLIN_IMPLEMENTS_FIELDS("x-kotlin-implements-fields", ExtensionLevel.MODEL, "Specify attributes that are implemented by the interface(s) added via `x-kotlin-implements`", "empty array"), X_SPRING_PAGINATED("x-spring-paginated", ExtensionLevel.OPERATION, "Add `org.springframework.data.domain.Pageable` to controller method. Can be used to handle `page`, `size` and `sort` query parameters. If these query parameters are also specified in the operation spec, they will be removed from the controller method as their values can be obtained from the `Pageable` object.", "false"), + X_SPRING_API_VERSION("x-spring-api-version", ExtensionLevel.OPERATION, "Generate version attribute in @RequestMapping for Spring 7.", null), X_SPRING_PROVIDE_ARGS("x-spring-provide-args", ExtensionLevel.OPERATION, "Allows adding additional hidden parameters in the API specification to allow access to content such as header values or properties", "empty array"), X_DISCRIMINATOR_VALUE("x-discriminator-value", ExtensionLevel.MODEL, "Used with model inheritance to specify value for discriminator that identifies current model", ""), X_SETTER_EXTRA_ANNOTATION("x-setter-extra-annotation", ExtensionLevel.FIELD, "Custom annotation that can be specified over java setter for specific field", "When field is array & uniqueItems, then this extension is used to add `@JsonDeserialize(as = LinkedHashSet.class)` over setter, otherwise no value"), diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java index 43712e054eab..33efee9855f9 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java @@ -100,6 +100,7 @@ public class SpringCodegen extends AbstractJavaCodegen public static final String OPTIONAL_ACCEPT_NULLABLE = "optionalAcceptNullable"; public static final String USE_SPRING_BUILT_IN_VALIDATION = "useSpringBuiltInValidation"; public static final String USE_DEDUCTION_FOR_ONE_OF_INTERFACES = "useDeductionForOneOfInterfaces"; + public static final String USE_SPRING_API_VERSION = "useSpringApiVersion"; @Getter public enum RequestMappingMode { @@ -286,6 +287,7 @@ public SpringCodegen() { optionalAcceptNullable)); cliOptions.add(CliOption.newBoolean(USE_DEDUCTION_FOR_ONE_OF_INTERFACES, "whether to use deduction for generated oneOf interfaces", useDeductionForOneOfInterfaces)); + cliOptions.add(CliOption.newString(USE_SPRING_API_VERSION, "Generate version attribute in @RequestMapping for Spring 7.")); supportedLibraries.put(SPRING_BOOT, "Spring-boot Server application."); supportedLibraries.put(SPRING_CLOUD_LIBRARY, "Spring-Cloud-Feign client with Spring-Boot auto-configured settings."); @@ -855,6 +857,8 @@ private void doDataTypeAssignment(String returnType, DataTypeAssigner dataTypeAs } private void prepareVersioningParameters(List operations) { + String apiVersion = String.valueOf(additionalProperties.get(USE_SPRING_API_VERSION)); + boolean hasApiVersion = isNotEmpty(apiVersion); for (CodegenOperation operation : operations) { if (operation.getHasHeaderParams()) { List versionParams = operation.headerParams.stream() @@ -877,6 +881,11 @@ private void prepareVersioningParameters(List operations) { operation.hasVersionQueryParams = !versionParams.isEmpty(); operation.vendorExtensions.put("versionQueryParamsList", versionParams); } + if (hasApiVersion) { + if (!operation.vendorExtensions.remove(VendorExtension.X_SPRING_API_VERSION.getName(), "")) { + operation.vendorExtensions.putIfAbsent(VendorExtension.X_SPRING_API_VERSION.getName(), apiVersion); + } + } } } @@ -1205,6 +1214,8 @@ public List getSupportedVendorExtensions() { extensions.add(VendorExtension.X_SPRING_PAGINATED); extensions.add(VendorExtension.X_VERSION_PARAM); extensions.add(VendorExtension.X_PATTERN_MESSAGE); + extensions.add(VendorExtension.X_PATTERN_MESSAGE); + extensions.add(VendorExtension.X_SPRING_API_VERSION); return extensions; } } diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/api.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/api.mustache index ef8c4f25840b..f82265346bed 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/api.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/api.mustache @@ -252,7 +252,8 @@ public interface {{classname}} { produces = { {{#produces}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/produces}} }{{/hasProduces}}{{#hasConsumes}}, consumes = { {{#consumes}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/consumes}} }{{/hasConsumes}}{{/singleContentTypes}}{{#hasVersionHeaders}}, headers = { {{#vendorExtensions.versionHeaderParamsList}}"{{baseName}}{{#defaultValue}}={{{.}}}{{/defaultValue}}"{{^-last}}, {{/-last}}{{/vendorExtensions.versionHeaderParamsList}} } {{/hasVersionHeaders}}{{#hasVersionQueryParams}}, - params = { {{#vendorExtensions.versionQueryParamsList}}"{{baseName}}{{#defaultValue}}={{{.}}}{{/defaultValue}}"{{^-last}}, {{/-last}}{{/vendorExtensions.versionQueryParamsList}} } {{/hasVersionQueryParams}} + params = { {{#vendorExtensions.versionQueryParamsList}}"{{baseName}}{{#defaultValue}}={{{.}}}{{/defaultValue}}"{{^-last}}, {{/-last}}{{/vendorExtensions.versionQueryParamsList}} } {{/hasVersionQueryParams}}{{#vendorExtensions.x-spring-api-version}}, + version = "{{{vendorExtensions.x-spring-api-version}}}"{{/vendorExtensions.x-spring-api-version}} ) {{^useResponseEntity}} @ResponseStatus({{#springHttpStatus}}{{#responses.0}}{{{code}}}{{/responses.0}}{{/springHttpStatus}}) diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/AbstractAnnotationsAssert.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/AbstractAnnotationsAssert.java index fad840acea97..e9ea536b232c 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/AbstractAnnotationsAssert.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/AbstractAnnotationsAssert.java @@ -50,23 +50,40 @@ public ACTUAL containsWithNameAndAttributes(final String name, final Map attributes) { + super + .withFailMessage("Should have annotation with name: " + name + " and no attributes: " + attributes + ", but was: " + actual) + .anyMatch(annotation -> annotation.getNameAsString().equals(name) && hasNotAttributes(annotation, attributes)); + return myself(); + } + + private static boolean hasNotAttributes(final AnnotationExpr annotation, final List attributes) { + final Map actualAttributes = getAttributes(annotation); + + return actualAttributes.entrySet().stream() + .noneMatch(attribute -> attributes.contains(attribute)); + } + private static boolean hasAttributes(final AnnotationExpr annotation, final Map expectedAttributesToContains) { - final Map actualAttributes; + final Map actualAttributes = getAttributes(annotation); + + return expectedAttributesToContains.entrySet().stream() + .allMatch(expected -> Objects.equals(actualAttributes.get(expected.getKey()), expected.getValue())); + } + + private static Map getAttributes(final AnnotationExpr annotation) { if (annotation instanceof SingleMemberAnnotationExpr) { - actualAttributes = ImmutableMap.of( + return ImmutableMap.of( "value", ((SingleMemberAnnotationExpr) annotation).getMemberValue().toString() ); } else if (annotation instanceof NormalAnnotationExpr) { - actualAttributes = ((NormalAnnotationExpr) annotation).getPairs().stream() + return ((NormalAnnotationExpr) annotation).getPairs().stream() .collect(Collectors.toMap(NodeWithSimpleName::getNameAsString, pair -> pair.getValue().toString())); } else if (annotation instanceof MarkerAnnotationExpr) { - actualAttributes = new HashMap<>(); + return new HashMap<>(); } else { throw new IllegalArgumentException("Unexpected annotation expression type for: " + annotation); } - - return expectedAttributesToContains.entrySet().stream() - .allMatch(expected -> Objects.equals(actualAttributes.get(expected.getKey()), expected.getValue())); } @SuppressWarnings("unchecked") diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java index e85833a6caf0..fba80d4a62e2 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java @@ -5738,4 +5738,25 @@ public void testOneOfInterfaceWithAnnotation() throws IOException { .isInterface() .assertTypeAnnotations().containsWithName("SuppressWarnings"); } + + @Test + public void testApiVersion() throws IOException { + final Map files = generateFromContract("src/test/resources/3_0/spring/apiVersion.yaml", SPRING_BOOT, + Map.of(SpringCodegen.USE_SPRING_API_VERSION, "v1", + USE_TAGS, true)); + JavaFileAssert.assertThat(files.get("TestApi.java")) + .assertMethod("getVersions") + .assertMethodAnnotations() + .containsWithNameAndAttributes("RequestMapping", Map.of("version", "\"v1\"")) + .toMethod().toFileAssert() + + .assertMethod("getOverrides") + .assertMethodAnnotations() + .containsWithNameAndAttributes("RequestMapping", Map.of("version", "\"2+\"")) + .toMethod().toFileAssert() + + .assertMethod("getNones") + .assertMethodAnnotations() + .containsWithNameAndDoesContainAttributes("RequestMapping", List.of("version")); + } } diff --git a/modules/openapi-generator/src/test/resources/3_0/spring/apiVersion.yaml b/modules/openapi-generator/src/test/resources/3_0/spring/apiVersion.yaml new file mode 100644 index 000000000000..1d8e9aa1c7fb --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/spring/apiVersion.yaml @@ -0,0 +1,23 @@ +openapi: 3.0.0 +info: + title: x-spring-api-version test + version: 1.0.0 +paths: + /versions: + get: + tags: + - Test + operationId: getVersions + /overrides: + get: + tags: + - Test + operationId: getOverrides + x-spring-api-version: '2+' + /nones: + get: + tags: + - Test + operationId: getNones + x-spring-api-version: '' + From d39ab35500775ec717e72a550a7127839cc3730b Mon Sep 17 00:00:00 2001 From: jpfinne Date: Thu, 25 Sep 2025 15:36:53 +0200 Subject: [PATCH 2/5] Do not add version if not requested --- .../org/openapitools/codegen/languages/SpringCodegen.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java index 33efee9855f9..992d0412cc57 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java @@ -857,8 +857,8 @@ private void doDataTypeAssignment(String returnType, DataTypeAssigner dataTypeAs } private void prepareVersioningParameters(List operations) { - String apiVersion = String.valueOf(additionalProperties.get(USE_SPRING_API_VERSION)); - boolean hasApiVersion = isNotEmpty(apiVersion); + Object apiVersion = additionalProperties.get(USE_SPRING_API_VERSION); + boolean hasApiVersion = apiVersion != null; for (CodegenOperation operation : operations) { if (operation.getHasHeaderParams()) { List versionParams = operation.headerParams.stream() From f59f7b9bc2de83f3985f3ed0ce289f3a9ff8232b Mon Sep 17 00:00:00 2001 From: jpfinne Date: Thu, 25 Sep 2025 15:48:25 +0200 Subject: [PATCH 3/5] Fix duplicate VendorExtensions --- .../java/org/openapitools/codegen/languages/SpringCodegen.java | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java index 992d0412cc57..39bce1ac8777 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java @@ -1214,7 +1214,6 @@ public List getSupportedVendorExtensions() { extensions.add(VendorExtension.X_SPRING_PAGINATED); extensions.add(VendorExtension.X_VERSION_PARAM); extensions.add(VendorExtension.X_PATTERN_MESSAGE); - extensions.add(VendorExtension.X_PATTERN_MESSAGE); extensions.add(VendorExtension.X_SPRING_API_VERSION); return extensions; } From c280f9d8a4702f0d4487208a64356658a263aad5 Mon Sep 17 00:00:00 2001 From: jpfinne Date: Thu, 25 Sep 2025 16:27:32 +0200 Subject: [PATCH 4/5] Improve description --- docs/generators/java-camel.md | 5 ++--- docs/generators/spring.md | 5 ++--- .../main/java/org/openapitools/codegen/VendorExtension.java | 2 +- .../org/openapitools/codegen/languages/SpringCodegen.java | 6 +++--- .../openapitools/codegen/java/spring/SpringCodegenTest.java | 2 +- 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/docs/generators/java-camel.md b/docs/generators/java-camel.md index bff61a4b89a6..613823e32500 100644 --- a/docs/generators/java-camel.md +++ b/docs/generators/java-camel.md @@ -95,6 +95,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true| |sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true| |sourceFolder|source folder for generated code| |src/main/java| +|springApiVersion|Value for 'version' attribute in @RequestMapping (for Spring 7 and above).| |null| |testOutput|Set output folder for models and APIs tests| |${project.build.directory}/generated-test-sources/openapi| |title|server title name or client service name| |OpenAPI Spring| |unhandledException|Declare operation methods to throw a generic exception and allow unhandled exceptions (useful for Spring `@ControllerAdvice` directives).| |false| @@ -108,7 +109,6 @@ These options may be applied as additional-properties (cli) or configOptions (pl |useOptional|Use Optional container for optional parameters| |false| |useResponseEntity|Use the `ResponseEntity` type to wrap return values of generated API methods. If disabled, method are annotated using a `@ResponseStatus` annotation, which has the status of the first response declared in the Api definition| |true| |useSealed|Whether to generate sealed model interfaces and classes| |false| -|useSpringApiVersion|Generate version attribute in @RequestMapping for Spring 7.| |null| |useSpringBoot3|Generate code and provide dependencies for use with Spring Boot 3.x. (Use jakarta instead of javax in imports). Enabling this option will also enable `useJakartaEe`.| |false| |useSpringBuiltInValidation|Disable `@Validated` at the class level when using built-in validation.| |false| |useSpringController|Annotate the generated API as a Spring Controller| |false| @@ -133,8 +133,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |x-spring-paginated|Add `org.springframework.data.domain.Pageable` to controller method. Can be used to handle `page`, `size` and `sort` query parameters. If these query parameters are also specified in the operation spec, they will be removed from the controller method as their values can be obtained from the `Pageable` object.|OPERATION|false |x-version-param|Marker property that tells that this parameter would be used for endpoint versioning. Applicable for headers & query params. true/false|OPERATION_PARAMETER|null |x-pattern-message|Add this property whenever you need to customize the invalidation error message for the regex pattern of a variable|FIELD, OPERATION_PARAMETER|null -|x-pattern-message|Add this property whenever you need to customize the invalidation error message for the regex pattern of a variable|FIELD, OPERATION_PARAMETER|null -|x-spring-api-version|Generate version attribute in @RequestMapping for Spring 7.|OPERATION|null +|x-spring-api-version|Value for 'version' attribute in @RequestMapping (for Spring 7 and above).|OPERATION|null ## IMPORT MAPPING diff --git a/docs/generators/spring.md b/docs/generators/spring.md index 7cc45d8c1105..050a7814b9ee 100644 --- a/docs/generators/spring.md +++ b/docs/generators/spring.md @@ -88,6 +88,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true| |sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true| |sourceFolder|source folder for generated code| |src/main/java| +|springApiVersion|Value for 'version' attribute in @RequestMapping (for Spring 7 and above).| |null| |testOutput|Set output folder for models and APIs tests| |${project.build.directory}/generated-test-sources/openapi| |title|server title name or client service name| |OpenAPI Spring| |unhandledException|Declare operation methods to throw a generic exception and allow unhandled exceptions (useful for Spring `@ControllerAdvice` directives).| |false| @@ -101,7 +102,6 @@ These options may be applied as additional-properties (cli) or configOptions (pl |useOptional|Use Optional container for optional parameters| |false| |useResponseEntity|Use the `ResponseEntity` type to wrap return values of generated API methods. If disabled, method are annotated using a `@ResponseStatus` annotation, which has the status of the first response declared in the Api definition| |true| |useSealed|Whether to generate sealed model interfaces and classes| |false| -|useSpringApiVersion|Generate version attribute in @RequestMapping for Spring 7.| |null| |useSpringBoot3|Generate code and provide dependencies for use with Spring Boot 3.x. (Use jakarta instead of javax in imports). Enabling this option will also enable `useJakartaEe`.| |false| |useSpringBuiltInValidation|Disable `@Validated` at the class level when using built-in validation.| |false| |useSpringController|Annotate the generated API as a Spring Controller| |false| @@ -126,8 +126,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |x-spring-paginated|Add `org.springframework.data.domain.Pageable` to controller method. Can be used to handle `page`, `size` and `sort` query parameters. If these query parameters are also specified in the operation spec, they will be removed from the controller method as their values can be obtained from the `Pageable` object.|OPERATION|false |x-version-param|Marker property that tells that this parameter would be used for endpoint versioning. Applicable for headers & query params. true/false|OPERATION_PARAMETER|null |x-pattern-message|Add this property whenever you need to customize the invalidation error message for the regex pattern of a variable|FIELD, OPERATION_PARAMETER|null -|x-pattern-message|Add this property whenever you need to customize the invalidation error message for the regex pattern of a variable|FIELD, OPERATION_PARAMETER|null -|x-spring-api-version|Generate version attribute in @RequestMapping for Spring 7.|OPERATION|null +|x-spring-api-version|Value for 'version' attribute in @RequestMapping (for Spring 7 and above).|OPERATION|null ## IMPORT MAPPING diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/VendorExtension.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/VendorExtension.java index 14f42948cee7..eed312856344 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/VendorExtension.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/VendorExtension.java @@ -13,7 +13,7 @@ public enum VendorExtension { X_KOTLIN_IMPLEMENTS("x-kotlin-implements", ExtensionLevel.MODEL, "Ability to specify interfaces that model must implement", "empty array"), X_KOTLIN_IMPLEMENTS_FIELDS("x-kotlin-implements-fields", ExtensionLevel.MODEL, "Specify attributes that are implemented by the interface(s) added via `x-kotlin-implements`", "empty array"), X_SPRING_PAGINATED("x-spring-paginated", ExtensionLevel.OPERATION, "Add `org.springframework.data.domain.Pageable` to controller method. Can be used to handle `page`, `size` and `sort` query parameters. If these query parameters are also specified in the operation spec, they will be removed from the controller method as their values can be obtained from the `Pageable` object.", "false"), - X_SPRING_API_VERSION("x-spring-api-version", ExtensionLevel.OPERATION, "Generate version attribute in @RequestMapping for Spring 7.", null), + X_SPRING_API_VERSION("x-spring-api-version", ExtensionLevel.OPERATION, "Value for 'version' attribute in @RequestMapping (for Spring 7 and above).", null), X_SPRING_PROVIDE_ARGS("x-spring-provide-args", ExtensionLevel.OPERATION, "Allows adding additional hidden parameters in the API specification to allow access to content such as header values or properties", "empty array"), X_DISCRIMINATOR_VALUE("x-discriminator-value", ExtensionLevel.MODEL, "Used with model inheritance to specify value for discriminator that identifies current model", ""), X_SETTER_EXTRA_ANNOTATION("x-setter-extra-annotation", ExtensionLevel.FIELD, "Custom annotation that can be specified over java setter for specific field", "When field is array & uniqueItems, then this extension is used to add `@JsonDeserialize(as = LinkedHashSet.class)` over setter, otherwise no value"), diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java index 39bce1ac8777..589b5daea982 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java @@ -100,7 +100,7 @@ public class SpringCodegen extends AbstractJavaCodegen public static final String OPTIONAL_ACCEPT_NULLABLE = "optionalAcceptNullable"; public static final String USE_SPRING_BUILT_IN_VALIDATION = "useSpringBuiltInValidation"; public static final String USE_DEDUCTION_FOR_ONE_OF_INTERFACES = "useDeductionForOneOfInterfaces"; - public static final String USE_SPRING_API_VERSION = "useSpringApiVersion"; + public static final String SPRING_API_VERSION = "springApiVersion"; @Getter public enum RequestMappingMode { @@ -287,7 +287,7 @@ public SpringCodegen() { optionalAcceptNullable)); cliOptions.add(CliOption.newBoolean(USE_DEDUCTION_FOR_ONE_OF_INTERFACES, "whether to use deduction for generated oneOf interfaces", useDeductionForOneOfInterfaces)); - cliOptions.add(CliOption.newString(USE_SPRING_API_VERSION, "Generate version attribute in @RequestMapping for Spring 7.")); + cliOptions.add(CliOption.newString(SPRING_API_VERSION, "Value for 'version' attribute in @RequestMapping (for Spring 7 and above).")); supportedLibraries.put(SPRING_BOOT, "Spring-boot Server application."); supportedLibraries.put(SPRING_CLOUD_LIBRARY, "Spring-Cloud-Feign client with Spring-Boot auto-configured settings."); @@ -857,7 +857,7 @@ private void doDataTypeAssignment(String returnType, DataTypeAssigner dataTypeAs } private void prepareVersioningParameters(List operations) { - Object apiVersion = additionalProperties.get(USE_SPRING_API_VERSION); + Object apiVersion = additionalProperties.get(SPRING_API_VERSION); boolean hasApiVersion = apiVersion != null; for (CodegenOperation operation : operations) { if (operation.getHasHeaderParams()) { diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java index fba80d4a62e2..a45c7a8e97e3 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java @@ -5742,7 +5742,7 @@ public void testOneOfInterfaceWithAnnotation() throws IOException { @Test public void testApiVersion() throws IOException { final Map files = generateFromContract("src/test/resources/3_0/spring/apiVersion.yaml", SPRING_BOOT, - Map.of(SpringCodegen.USE_SPRING_API_VERSION, "v1", + Map.of(SpringCodegen.SPRING_API_VERSION, "v1", USE_TAGS, true)); JavaFileAssert.assertThat(files.get("TestApi.java")) .assertMethod("getVersions") From efe558240cd0e33c75c17da0462b031a6410fe38 Mon Sep 17 00:00:00 2001 From: jpfinne Date: Mon, 29 Sep 2025 11:50:48 +0200 Subject: [PATCH 5/5] Fix removal of RequestMapping version attribute if x-spring-api-version: '' is set at the operation level --- .../org/openapitools/codegen/languages/SpringCodegen.java | 4 +--- .../src/main/resources/JavaSpring/api.mustache | 4 ++-- .../codegen/java/assertions/AbstractAnnotationsAssert.java | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java index 589b5daea982..5c6053474fcf 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java @@ -882,9 +882,7 @@ private void prepareVersioningParameters(List operations) { operation.vendorExtensions.put("versionQueryParamsList", versionParams); } if (hasApiVersion) { - if (!operation.vendorExtensions.remove(VendorExtension.X_SPRING_API_VERSION.getName(), "")) { - operation.vendorExtensions.putIfAbsent(VendorExtension.X_SPRING_API_VERSION.getName(), apiVersion); - } + operation.vendorExtensions.putIfAbsent(VendorExtension.X_SPRING_API_VERSION.getName(), apiVersion); } } } diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/api.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/api.mustache index f82265346bed..c3beb18bbde5 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/api.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/api.mustache @@ -252,8 +252,8 @@ public interface {{classname}} { produces = { {{#produces}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/produces}} }{{/hasProduces}}{{#hasConsumes}}, consumes = { {{#consumes}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/consumes}} }{{/hasConsumes}}{{/singleContentTypes}}{{#hasVersionHeaders}}, headers = { {{#vendorExtensions.versionHeaderParamsList}}"{{baseName}}{{#defaultValue}}={{{.}}}{{/defaultValue}}"{{^-last}}, {{/-last}}{{/vendorExtensions.versionHeaderParamsList}} } {{/hasVersionHeaders}}{{#hasVersionQueryParams}}, - params = { {{#vendorExtensions.versionQueryParamsList}}"{{baseName}}{{#defaultValue}}={{{.}}}{{/defaultValue}}"{{^-last}}, {{/-last}}{{/vendorExtensions.versionQueryParamsList}} } {{/hasVersionQueryParams}}{{#vendorExtensions.x-spring-api-version}}, - version = "{{{vendorExtensions.x-spring-api-version}}}"{{/vendorExtensions.x-spring-api-version}} + params = { {{#vendorExtensions.versionQueryParamsList}}"{{baseName}}{{#defaultValue}}={{{.}}}{{/defaultValue}}"{{^-last}}, {{/-last}}{{/vendorExtensions.versionQueryParamsList}} } {{/hasVersionQueryParams}}{{#vendorExtensions.x-spring-api-version}}{{^empty}}, + version = "{{{vendorExtensions.x-spring-api-version}}}"{{/empty}}{{/vendorExtensions.x-spring-api-version}} ) {{^useResponseEntity}} @ResponseStatus({{#springHttpStatus}}{{#responses.0}}{{{code}}}{{/responses.0}}{{/springHttpStatus}}) diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/AbstractAnnotationsAssert.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/AbstractAnnotationsAssert.java index e9ea536b232c..fef36947d8d1 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/AbstractAnnotationsAssert.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/AbstractAnnotationsAssert.java @@ -60,7 +60,7 @@ public ACTUAL containsWithNameAndDoesContainAttributes(final String name, final private static boolean hasNotAttributes(final AnnotationExpr annotation, final List attributes) { final Map actualAttributes = getAttributes(annotation); - return actualAttributes.entrySet().stream() + return actualAttributes.keySet().stream() .noneMatch(attribute -> attributes.contains(attribute)); }