From b77981733669f9d5941c0af9d39f051ba32552c0 Mon Sep 17 00:00:00 2001 From: Matheus Andrade Date: Tue, 22 Apr 2025 06:20:26 -0300 Subject: [PATCH 1/3] Added support for composed annotations. --- .../openapitools/codegen/DefaultCodegen.java | 1 + .../codegen/languages/SpringCodegen.java | 8 ++ ...omposedRequestMappingAnnotationLambda.java | 47 +++++++++ .../main/resources/JavaSpring/api.mustache | 7 ++ .../java/spring/SpringCodegenTest.java | 98 +++++++++++++++++++ 5 files changed, 161 insertions(+) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/mustache/ComposedRequestMappingAnnotationLambda.java diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index 3bb9d43238d5..f78e2fa12b84 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -419,6 +419,7 @@ protected ImmutableMap.Builder addMustacheLambdas() { .put("forwardslash", new ForwardSlashLambda()) .put("backslash", new BackSlashLambda()) .put("doublequote", new DoubleQuoteLambda()) + .put("composedannotationlambda", new ComposedRequestMappingAnnotationLambda()) .put("indented", new IndentedLambda()) .put("indented_8", new IndentedLambda(8, " ", false, false)) .put("indented_12", new IndentedLambda(12, " ", false, false)) 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 5237520ce9d9..05475eb99d3c 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 @@ -98,6 +98,8 @@ public class SpringCodegen extends AbstractJavaCodegen public static final String USE_SEALED = "useSealed"; public static final String OPTIONAL_ACCEPT_NULLABLE = "optionalAcceptNullable"; public static final String USE_SPRING_BUILT_IN_VALIDATION = "useSpringBuiltInValidation"; + public static final String USE_API_COMPOSED_ANNOTATION = "useApiComposedAnnotation"; + public static final String API_COMPOSED_ANNOTATION_LAMBDA = "apiComposedAnnotationLambda"; @Getter public enum RequestMappingMode { @@ -157,6 +159,8 @@ public enum RequestMappingMode { protected boolean optionalAcceptNullable = true; @Getter @Setter protected boolean useSpringBuiltInValidation = false; + @Getter @Setter + protected boolean useApiComposedAnnotation = false; public SpringCodegen() { super(); @@ -224,6 +228,9 @@ public SpringCodegen() { cliOptions.add(CliOption.newBoolean(USE_SPRING_BUILT_IN_VALIDATION, "Disable `@Validated` at the class level when using built-in validation.", useSpringBuiltInValidation)); + cliOptions.add(CliOption.newBoolean(USE_API_COMPOSED_ANNOTATION, + "Use @Mapping instead of @RequestMapping(method = RequestMethod.) when generating the API interfaces.", + useApiComposedAnnotation)); cliOptions.add(CliOption.newBoolean(PERFORM_BEANVALIDATION, "Use Bean Validation Impl. to perform BeanValidation", performBeanValidation)); cliOptions.add(CliOption.newBoolean(USE_SEALED, @@ -435,6 +442,7 @@ public void processOpts() { convertPropertyToBooleanAndWriteBack(USE_RESPONSE_ENTITY, this::setUseResponseEntity); convertPropertyToBooleanAndWriteBack(OPTIONAL_ACCEPT_NULLABLE, this::setOptionalAcceptNullable); convertPropertyToBooleanAndWriteBack(USE_SPRING_BUILT_IN_VALIDATION, this::setUseSpringBuiltInValidation); + convertPropertyToBooleanAndWriteBack(USE_API_COMPOSED_ANNOTATION, this::setUseApiComposedAnnotation); additionalProperties.put("springHttpStatus", new SpringHttpStatusLambda()); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/mustache/ComposedRequestMappingAnnotationLambda.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/mustache/ComposedRequestMappingAnnotationLambda.java new file mode 100644 index 000000000000..ba3737181b27 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/mustache/ComposedRequestMappingAnnotationLambda.java @@ -0,0 +1,47 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openapitools.codegen.templating.mustache; + +import com.samskivert.mustache.Mustache; +import com.samskivert.mustache.Template; + +import java.io.IOException; +import java.io.Writer; + +/** + * Replaces @RequestMapping annotation with the appropriate composed annotation when using Spring generator. e.g: + *

@RequestMapping(method = RequestMethod.GET + * will be replaced with:@GetMapping( as it is defined on the file JavaSpring/api.mustache under + * the {{#useApiComposedAnnotation}} section

+ * + * Register: + *
+ * additionalProperties.put("composedannotationlambda", new ComposedRequestMappingAnnotationLambda());
+ * 
+ * + * Use: + *
+ * {{#lambda.composedannotationlambda}}{{httpMethod}}{{/lambda.composedannotationlambda}}
+ * 
+ */ +public class ComposedRequestMappingAnnotationLambda implements Mustache.Lambda { + @Override + public void execute(Template.Fragment fragment, Writer writer) throws IOException { + String text = fragment.execute(); + writer.write(String.format("@%sMapping(",text.substring(0,1).toUpperCase()+text.substring(1).toLowerCase())); + } +} diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/api.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/api.mustache index c64945b74fc9..1d8930fc141d 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/api.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/api.mustache @@ -226,8 +226,15 @@ public interface {{classname}} { }) {{/swagger1AnnotationLibrary}} {{/implicitHeadersParams.0}} + {{#useApiComposedAnnotation}} + {{#lambda.composedannotationlambda}}{{httpMethod}}{{/lambda.composedannotationlambda}} + {{/useApiComposedAnnotation}} + {{^useApiComposedAnnotation}} @RequestMapping( + {{/useApiComposedAnnotation}} + {{^useApiComposedAnnotation}} method = RequestMethod.{{httpMethod}}, + {{/useApiComposedAnnotation}} value = "{{{path}}}"{{#singleContentTypes}}{{#hasProduces}}, produces = { {{#vendorExtensions.x-accepts}}"{{{.}}}"{{^-last}}, {{/-last}}{{/vendorExtensions.x-accepts}} }{{/hasProduces}}{{#hasConsumes}}, consumes = "{{{vendorExtensions.x-content-type}}}"{{/hasConsumes}}{{/singleContentTypes}}{{^singleContentTypes}}{{#hasProduces}}, 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 4a80f94d2afe..9bc01a2b1ee1 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 @@ -5433,6 +5433,104 @@ public void shouldDisableBuiltInValidationOptionByDefault() throws IOException { .containsWithName("Validated"); } + @Test + public void shouldUseApiComposedAnnotationWhenSetToTrue() throws IOException { + final SpringCodegen codegen = new SpringCodegen(); + codegen.additionalProperties().put(USE_API_COMPOSED_ANNOTATION, true); + + Map files = generateFiles(codegen, "src/test/resources/3_0/petstore.yaml"); + var file = files.get("UserApi.java"); + + JavaFileAssert.assertThat(file) + .assertMethod("getUserByName") + .assertMethodAnnotations() + .containsWithName("GetMapping") + .doesNotContainWithName("RequestMapping"); + + JavaFileAssert.assertThat(file) + .assertMethod("createUser") + .assertMethodAnnotations() + .containsWithName("PostMapping") + .doesNotContainWithName("RequestMapping"); + + JavaFileAssert.assertThat(file) + .assertMethod("updateUser") + .assertMethodAnnotations() + .containsWithName("PutMapping") + .doesNotContainWithName("RequestMapping"); + + JavaFileAssert.assertThat(file) + .assertMethod("deleteUser") + .assertMethodAnnotations() + .containsWithName("DeleteMapping") + .doesNotContainWithName("RequestMapping"); + } + + @Test + public void shouldNotUseApiComposedAnnotationWhenSetToFalse() throws IOException { + final SpringCodegen codegen = new SpringCodegen(); + codegen.additionalProperties().put(USE_API_COMPOSED_ANNOTATION, false); + + Map files = generateFiles(codegen, "src/test/resources/3_0/petstore.yaml"); + var file = files.get("UserApi.java"); + + JavaFileAssert.assertThat(file) + .assertMethod("getUserByName") + .assertMethodAnnotations() + .containsWithName("RequestMapping") + .doesNotContainWithName("GetMapping"); + + JavaFileAssert.assertThat(file) + .assertMethod("createUser") + .assertMethodAnnotations() + .containsWithName("RequestMapping") + .doesNotContainWithName("PostMapping"); + + JavaFileAssert.assertThat(file) + .assertMethod("updateUser") + .assertMethodAnnotations() + .containsWithName("RequestMapping") + .doesNotContainWithName("PutMapping"); + + JavaFileAssert.assertThat(file) + .assertMethod("deleteUser") + .assertMethodAnnotations() + .containsWithName("RequestMapping") + .doesNotContainWithName("DeleteMapping"); + } + + @Test + public void shouldNotUseApiComposedAnnotationByDefault() throws IOException { + final SpringCodegen codegen = new SpringCodegen(); + + Map files = generateFiles(codegen, "src/test/resources/3_0/petstore.yaml"); + var file = files.get("UserApi.java"); + + JavaFileAssert.assertThat(file) + .assertMethod("getUserByName") + .assertMethodAnnotations() + .containsWithName("RequestMapping") + .doesNotContainWithName("GetMapping"); + + JavaFileAssert.assertThat(file) + .assertMethod("createUser") + .assertMethodAnnotations() + .containsWithName("RequestMapping") + .doesNotContainWithName("PostMapping"); + + JavaFileAssert.assertThat(file) + .assertMethod("updateUser") + .assertMethodAnnotations() + .containsWithName("RequestMapping") + .doesNotContainWithName("PutMapping"); + + JavaFileAssert.assertThat(file) + .assertMethod("deleteUser") + .assertMethodAnnotations() + .containsWithName("RequestMapping") + .doesNotContainWithName("DeleteMapping"); + } + @Test public void testEnumFieldShouldBeFinal_issue21018() throws IOException { SpringCodegen codegen = new SpringCodegen(); From 81dbea8663a73762c3f081abf1fe38d9138ff296 Mon Sep 17 00:00:00 2001 From: Matheus Andrade Date: Fri, 16 May 2025 14:31:43 -0300 Subject: [PATCH 2/3] Requested changes --- .../openapitools/codegen/DefaultCodegen.java | 2 +- .../codegen/languages/SpringCodegen.java | 5 ++-- ...omposedRequestMappingAnnotationLambda.java | 5 ++-- .../main/resources/JavaSpring/api.mustache | 4 +-- .../java/spring/SpringCodegenTest.java | 26 +++++++++---------- 5 files changed, 19 insertions(+), 23 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index 936f3abf590a..2ac76aa2a76b 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -422,7 +422,7 @@ protected ImmutableMap.Builder addMustacheLambdas() { .put("forwardslash", new ForwardSlashLambda()) .put("backslash", new BackSlashLambda()) .put("doublequote", new DoubleQuoteLambda()) - .put("composedannotationlambda", new ComposedRequestMappingAnnotationLambda()) + .put("composedannotation", new ComposedRequestMappingAnnotationLambda()) .put("indented", new IndentedLambda()) .put("indented_8", new IndentedLambda(8, " ", false, false)) .put("indented_12", new IndentedLambda(12, " ", false, false)) 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 b64f6dfaa530..c9efece4f3a1 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 @@ -99,7 +99,6 @@ 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_API_COMPOSED_ANNOTATION = "useApiComposedAnnotation"; - public static final String API_COMPOSED_ANNOTATION_LAMBDA = "apiComposedAnnotationLambda"; @Getter public enum RequestMappingMode { @@ -160,7 +159,7 @@ public enum RequestMappingMode { @Getter @Setter protected boolean useSpringBuiltInValidation = false; @Getter @Setter - protected boolean useApiComposedAnnotation = false; + protected boolean useApiComposedAnnotation = true; public SpringCodegen() { super(); @@ -229,7 +228,7 @@ public SpringCodegen() { "Disable `@Validated` at the class level when using built-in validation.", useSpringBuiltInValidation)); cliOptions.add(CliOption.newBoolean(USE_API_COMPOSED_ANNOTATION, - "Use @Mapping instead of @RequestMapping(method = RequestMethod.) when generating the API interfaces.", + "Use @Mapping instead of @RequestMapping(method = RequestMethod.) when generating the API interfaces. Defaults to true", useApiComposedAnnotation)); cliOptions.add(CliOption.newBoolean(PERFORM_BEANVALIDATION, "Use Bean Validation Impl. to perform BeanValidation", performBeanValidation)); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/mustache/ComposedRequestMappingAnnotationLambda.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/mustache/ComposedRequestMappingAnnotationLambda.java index ba3737181b27..d82adf6890d7 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/mustache/ComposedRequestMappingAnnotationLambda.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/mustache/ComposedRequestMappingAnnotationLambda.java @@ -30,18 +30,19 @@ * * Register: *
- * additionalProperties.put("composedannotationlambda", new ComposedRequestMappingAnnotationLambda());
+ * additionalProperties.put("composedannotation", new ComposedRequestMappingAnnotationLambda());
  * 
* * Use: *
- * {{#lambda.composedannotationlambda}}{{httpMethod}}{{/lambda.composedannotationlambda}}
+ * {{#lambda.composedannotation}}{{httpMethod}}{{/lambda.composedannotation}}
  * 
*/ public class ComposedRequestMappingAnnotationLambda implements Mustache.Lambda { @Override public void execute(Template.Fragment fragment, Writer writer) throws IOException { String text = fragment.execute(); + if(text == null || text.isEmpty()) return; writer.write(String.format("@%sMapping(",text.substring(0,1).toUpperCase()+text.substring(1).toLowerCase())); } } diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/api.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/api.mustache index 2400a5935836..c8782c98e134 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/api.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/api.mustache @@ -243,12 +243,10 @@ public interface {{classname}} { {{/swagger1AnnotationLibrary}} {{/implicitHeadersParams.0}} {{#useApiComposedAnnotation}} - {{#lambda.composedannotationlambda}}{{httpMethod}}{{/lambda.composedannotationlambda}} + {{#lambda.composedannotation}}{{httpMethod}}{{/lambda.composedannotation}} {{/useApiComposedAnnotation}} {{^useApiComposedAnnotation}} @RequestMapping( - {{/useApiComposedAnnotation}} - {{^useApiComposedAnnotation}} method = RequestMethod.{{httpMethod}}, {{/useApiComposedAnnotation}} value = "{{{path}}}"{{#singleContentTypes}}{{#hasProduces}}, 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 917cdc56066f..f7340ad72bea 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 @@ -121,8 +121,7 @@ public void doAnnotateDatesOnModelParameters() throws IOException { .assertMethodAnnotations() .hasSize(2) .containsWithNameAndAttributes("Operation", ImmutableMap.of("operationId", "\"getZebras\"")) - .containsWithNameAndAttributes("RequestMapping", ImmutableMap.of( - "method", "RequestMethod.GET", + .containsWithNameAndAttributes("GetMapping", ImmutableMap.of( "value", "\"/zebras\"" )) .toMethod() @@ -199,8 +198,7 @@ public void doAnnotateDatesOnModelParametersWithOptionalAndJsonNullable() throws .assertMethodAnnotations() .hasSize(2) .containsWithNameAndAttributes("Operation", ImmutableMap.of("operationId", "\"getZebras\"")) - .containsWithNameAndAttributes("RequestMapping", ImmutableMap.of( - "method", "RequestMethod.GET", + .containsWithNameAndAttributes("GetMapping", ImmutableMap.of( "value", "\"/zebras\"" )) .toMethod() @@ -2393,7 +2391,7 @@ public void shouldHandleContentTypeWithSecondWildcardSubtype_issue12457() throws JavaFileAssert.assertThat(files.get("UsersApi.java")) .assertMethod("wildcardSubTypeForContentType") .assertMethodAnnotations() - .containsWithNameAndAttributes("RequestMapping", ImmutableMap.of( + .containsWithNameAndAttributes("GetMapping", ImmutableMap.of( "produces", "{ \"application/json\", \"application/*\" }", "consumes", "{ \"application/octet-stream\", \"application/*\" }" )); @@ -5556,7 +5554,7 @@ public void shouldNotUseApiComposedAnnotationWhenSetToFalse() throws IOException } @Test - public void shouldNotUseApiComposedAnnotationByDefault() throws IOException { + public void shouldUseApiComposedAnnotationByDefault() throws IOException { final SpringCodegen codegen = new SpringCodegen(); Map files = generateFiles(codegen, "src/test/resources/3_0/petstore.yaml"); @@ -5565,26 +5563,26 @@ public void shouldNotUseApiComposedAnnotationByDefault() throws IOException { JavaFileAssert.assertThat(file) .assertMethod("getUserByName") .assertMethodAnnotations() - .containsWithName("RequestMapping") - .doesNotContainWithName("GetMapping"); + .containsWithName("GetMapping") + .doesNotContainWithName("RequestMapping"); JavaFileAssert.assertThat(file) .assertMethod("createUser") .assertMethodAnnotations() - .containsWithName("RequestMapping") - .doesNotContainWithName("PostMapping"); + .containsWithName("PostMapping") + .doesNotContainWithName("RequestMapping"); JavaFileAssert.assertThat(file) .assertMethod("updateUser") .assertMethodAnnotations() - .containsWithName("RequestMapping") - .doesNotContainWithName("PutMapping"); + .containsWithName("PutMapping") + .doesNotContainWithName("RequestMapping"); JavaFileAssert.assertThat(file) .assertMethod("deleteUser") .assertMethodAnnotations() - .containsWithName("RequestMapping") - .doesNotContainWithName("DeleteMapping"); + .containsWithName("DeleteMapping") + .doesNotContainWithName("RequestMapping"); } @Test From 1964bd44e48c80367ccb1bb5404c5f6ebc8a7081 Mon Sep 17 00:00:00 2001 From: Matheus Andrade Date: Fri, 16 May 2025 14:41:33 -0300 Subject: [PATCH 3/3] Fixed broken test --- .../org/openapitools/codegen/config/MergedSpecBuilderTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/config/MergedSpecBuilderTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/config/MergedSpecBuilderTest.java index 5a19c6f78d89..0b97622a0f40 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/config/MergedSpecBuilderTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/config/MergedSpecBuilderTest.java @@ -73,7 +73,7 @@ private void assertFilesFromMergedSpec(String mergedSpec) throws IOException { .assertMethod("spec1OperationComplex") .hasReturnType("ResponseEntity") .assertMethodAnnotations() - .containsWithNameAndAttributes("RequestMapping", ImmutableMap.of("value", "\"/spec1/complex/{param1}/path\"")) + .containsWithNameAndAttributes("GetMapping", ImmutableMap.of("value", "\"/spec1/complex/{param1}/path\"")) .toMethod() .assertParameter("param1") .hasType("String")