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 644cd68b9902..cb32ed5a0266 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 @@ -1758,6 +1758,15 @@ public Map postProcessAllModels(Map objs) && (cm.hasRequired || cm.vendorExtensions.containsKey("x-java-all-args-constructor"))) { cm.vendorExtensions.put("x-java-no-args-constructor", true); } + // the required args constructor only has lombok's signature when every own field is a plain required + // argument: requiredVars also holds inherited properties, a JsonNullable field takes a plain value + // and the additionalProperties map is a field too + if (lombokAnnotations != null && lombokAnnotations.containsKey("AllArgsConstructor") + && cm.additionalPropertiesType == null + && cm.requiredVars.size() == cm.vars.size() + && cm.vars.stream().allMatch(p -> p.required && !(openApiNullable && p.isNullable))) { + cm.vendorExtensions.put("x-java-lombok-duplicates-required-args-constructor", true); + } } if (substituteGenericPagedModel && !pagedModelRegistry.isEmpty()) { diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache index b0ac0565b6f9..1de972894947 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache @@ -106,6 +106,7 @@ public {{>sealed}}class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}} {{/vendorExtensions.x-java-no-args-constructor}} {{^lombok.Data}} {{^lombok.RequiredArgsConstructor}} + {{^vendorExtensions.x-java-lombok-duplicates-required-args-constructor}} {{#generatedConstructorWithRequiredArgs}} {{#hasRequired}} @@ -129,6 +130,7 @@ public {{>sealed}}class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}} } {{/hasRequired}} {{/generatedConstructorWithRequiredArgs}} + {{/vendorExtensions.x-java-lombok-duplicates-required-args-constructor}} {{/lombok.RequiredArgsConstructor}} {{#vendorExtensions.x-java-all-args-constructor}} diff --git a/modules/openapi-generator/src/main/resources/java-camel-server/pojo.mustache b/modules/openapi-generator/src/main/resources/java-camel-server/pojo.mustache index f8c8e1773e54..766dbe9b76e6 100644 --- a/modules/openapi-generator/src/main/resources/java-camel-server/pojo.mustache +++ b/modules/openapi-generator/src/main/resources/java-camel-server/pojo.mustache @@ -117,6 +117,7 @@ public class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}{{^parent}} {{/vendorExtensions.x-java-no-args-constructor}} {{^lombok.Data}} {{^lombok.RequiredArgsConstructor}} + {{^vendorExtensions.x-java-lombok-duplicates-required-args-constructor}} {{#generatedConstructorWithRequiredArgs}} {{#hasRequired}} @@ -140,6 +141,7 @@ public class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}{{^parent}} } {{/hasRequired}} {{/generatedConstructorWithRequiredArgs}} + {{/vendorExtensions.x-java-lombok-duplicates-required-args-constructor}} {{/lombok.RequiredArgsConstructor}} {{#vendorExtensions.x-java-all-args-constructor}} 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 c078874e3eaa..3189ffcb33b2 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 @@ -5827,6 +5827,78 @@ public void generateAllArgsConstructor() throws IOException { .hasParameter("count").toConstructor(); } + @Test + public void lombokAllArgsConstructorReplacesRequiredArgsConstructor_issue25395() throws IOException { + Map output = generateFromContract( + "src/test/resources/3_0/spring/issue_25395.yaml", + SPRING_BOOT, + Map.of(AbstractJavaCodegen.ADDITIONAL_MODEL_TYPE_ANNOTATIONS, "@lombok.AllArgsConstructor", + CodegenConstants.OPENAPI_NULLABLE, "false", + INTERFACE_ONLY, "true") + ); + + // lombok generates Pet(Long, String), a generated one would be a duplicate + JavaFileAssert.assertThat(output.get("Pet.java")) + .assertConstructor().toFileAssert() + .hasNoConstructor("Long", "String"); + JavaFileAssert.assertThat(output.get("NicknamedPet.java")) + .assertConstructor().toFileAssert() + .hasNoConstructor("Long", "String"); + JavaFileAssert.assertThat(output.get("Tag.java")) + .assertConstructor("Long"); + } + + @Test + public void lombokAllArgsConstructorKeepsRequiredArgsConstructorOfNullableProperty_issue25395() throws IOException { + Map output = generateFromContract( + "src/test/resources/3_0/spring/issue_25395.yaml", + SPRING_BOOT, + Map.of(AbstractJavaCodegen.ADDITIONAL_MODEL_TYPE_ANNOTATIONS, "@lombok.AllArgsConstructor", + CodegenConstants.OPENAPI_NULLABLE, "true", + INTERFACE_ONLY, "true") + ); + + // the field is JsonNullable but the generated parameter is String, so lombok's constructor differs + JavaFileAssert.assertThat(output.get("NicknamedPet.java")) + .assertConstructor("Long", "String"); + } + + @Test + public void lombokAllArgsConstructorWithInheritedProperties_issue25395() throws IOException { + Map output = generateFromContract( + "src/test/resources/3_0/spring/issue_25395.yaml", + SPRING_BOOT, + Map.of(AbstractJavaCodegen.ADDITIONAL_MODEL_TYPE_ANNOTATIONS, "@lombok.AllArgsConstructor", + CodegenConstants.OPENAPI_NULLABLE, "false", + INTERFACE_ONLY, "true") + ); + + // lombok only covers the own field, the generated constructor also takes the inherited one + JavaFileAssert.assertThat(output.get("Dog.java")) + .assertConstructor("String", "String") + .bodyContainsLines("super(kind)", "this.breed = breed"); + // the parent has no required property, so the signature is the same as lombok's + JavaFileAssert.assertThat(output.get("Hound.java")) + .assertConstructor().toFileAssert() + .hasNoConstructor("String"); + } + + @Test + public void lombokAllArgsConstructorKeepsRequiredArgsConstructorWithAdditionalProperties_issue25395() throws IOException { + Map output = generateFromContract( + "src/test/resources/3_0/spring/issue_25395.yaml", + SPRING_BOOT, + Map.of(AbstractJavaCodegen.ADDITIONAL_MODEL_TYPE_ANNOTATIONS, "@lombok.AllArgsConstructor", + CodegenConstants.OPENAPI_NULLABLE, "false", + INTERFACE_ONLY, "true") + ); + + // the additionalProperties map is a field too, so lombok's constructor takes it as a third parameter + JavaFileAssert.assertThat(output.get("Bag.java")) + .assertConstructor().toFileAssert() + .assertConstructor("Long", "String"); + } + @Test public void allOfDuplicatedProperties() throws IOException { Map output = generateFromContract( diff --git a/modules/openapi-generator/src/test/resources/3_0/spring/issue_25395.yaml b/modules/openapi-generator/src/test/resources/3_0/spring/issue_25395.yaml new file mode 100644 index 000000000000..af2cfa67ac39 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/spring/issue_25395.yaml @@ -0,0 +1,99 @@ +openapi: 3.0.3 +info: + title: Test Issue #25395 + version: 1.0.0 +paths: + /pets: + get: + operationId: listPets + responses: + '200': + description: default response + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + Tag: + type: object + required: + - id + properties: + id: + type: integer + format: int64 + label: + type: string + NicknamedPet: + type: object + required: + - id + - nickname + properties: + id: + type: integer + format: int64 + nickname: + type: string + nullable: true + Animal: + type: object + required: + - kind + properties: + kind: + type: string + discriminator: + propertyName: kind + Dog: + allOf: + - $ref: '#/components/schemas/Animal' + - type: object + required: + - breed + properties: + breed: + type: string + Base: + type: object + properties: + kind: + type: string + discriminator: + propertyName: kind + Hound: + allOf: + - $ref: '#/components/schemas/Base' + - type: object + required: + - scent + properties: + scent: + type: string + Bag: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + additionalProperties: + type: string