From e21733c519ba84fa6539c903dc34fb611d9a612c Mon Sep 17 00:00:00 2001 From: aleynik0vav Date: Mon, 13 Apr 2026 10:44:59 +0300 Subject: [PATCH] [Java] [Spring] Missing declaration of `x-setter-extra-annotation` at codegen for unique arrays --- .../codegen/CodegenConstants.java | 1 + .../openapitools/codegen/CodegenProperty.java | 1 + .../openapitools/codegen/DefaultCodegen.java | 5 ++ .../openapitools/codegen/VendorExtension.java | 2 +- .../languages/AbstractJavaCodegen.java | 5 +- .../codegen/java/JavaModelTest.java | 67 +++++++++++++++++-- 6 files changed, 73 insertions(+), 8 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java index bd39579e1f69..0b15ae15a746 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java @@ -489,4 +489,5 @@ public static enum ENUM_PROPERTY_NAMING_TYPE {camelCase, PascalCase, snake_case, public static final String X_DISCRIMINATOR_VALUE = "x-discriminator-value"; public static final String X_ONE_OF_NAME = "x-one-of-name"; public static final String X_NULLABLE = "x-nullable"; + public static final String X_SETTER_EXTRA_ANNOTATION = "x-setter-extra-annotation"; } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenProperty.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenProperty.java index c854389be7b2..cb106636b258 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenProperty.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenProperty.java @@ -187,6 +187,7 @@ public class CodegenProperty implements Cloneable, IJsonSchemaValidationProperti public boolean isDiscriminator; public boolean isNew; // true when this property overrides an inherited property public Boolean isOverridden; // true if the property is a parent property (not defined in child/current schema) + public boolean isSetSetterExtensionDeclared; // true if a setter annotation is declared for `array` and `uniqueItems` @Getter @Setter public List _enum; @Getter @Setter 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 40482655ea51..db20a72db709 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 @@ -4055,6 +4055,11 @@ public CodegenProperty fromProperty(String name, Schema p, boolean required, boo property.required = required; ModelUtils.syncValidationProperties(p, property); property.setFormat(p.getFormat()); + property.isSetSetterExtensionDeclared = + p.getUniqueItems() != null + && p.getUniqueItems() + && p.getExtensions() != null + && p.getExtensions().containsKey(X_SETTER_EXTRA_ANNOTATION); property.name = toVarName(name); property.baseName = name; 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 bce0e2a691f4..6d3bc66716ec 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 @@ -16,7 +16,7 @@ public enum VendorExtension { 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"), + 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. Default @JsonDeserialize can be overridden by a custom one. In other cases has no value"), X_WEBCLIENT_BLOCKING("x-webclient-blocking", ExtensionLevel.OPERATION, "Specifies if method for specific operation should be blocking or non-blocking(ex: return `Mono/Flux` or `return T/List/Set` & execute `.block()` inside generated method)", "false"), X_WEBCLIENT_RETURN_EXCEPT_LIST_OF_STRING("x-webclient-return-except-list-of-string", ExtensionLevel.OPERATION, "Specifies if method for specific operation should return the type except List and Set(ex: return type expect the `Mono>/Flux>` and `Mono>/Flux>`)", "false"), X_TAGS("x-tags", ExtensionLevel.OPERATION, "Specify multiple swagger tags for operation", null), diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java index 0335a09d6962..2da152767037 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java @@ -76,6 +76,7 @@ import java.util.stream.StreamSupport; import static org.openapitools.codegen.CodegenConstants.X_IMPLEMENTS; +import static org.openapitools.codegen.CodegenConstants.X_SETTER_EXTRA_ANNOTATION; import static org.openapitools.codegen.utils.CamelizeOption.*; import static org.openapitools.codegen.utils.ModelUtils.getSchemaItems; import static org.openapitools.codegen.utils.OnceLogger.once; @@ -1953,9 +1954,9 @@ public void postProcessModelProperty(CodegenModel model, CodegenProperty propert } else if ("set".equals(property.containerType)) { model.imports.add("LinkedHashSet"); model.imports.add("Arrays"); - if ((!openApiNullable || !property.isNullable) && jackson) { // cannot be wrapped to nullable + if ((!openApiNullable || !property.isNullable) && jackson && !property.isSetSetterExtensionDeclared) { // cannot be wrapped to nullable model.imports.add("JsonDeserialize"); - property.vendorExtensions.put("x-setter-extra-annotation", "@JsonDeserialize(as = LinkedHashSet.class)"); + property.vendorExtensions.put(X_SETTER_EXTRA_ANNOTATION, "@JsonDeserialize(as = LinkedHashSet.class)"); } } else if ("map".equals(property.containerType)) { model.imports.add("HashMap"); diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaModelTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaModelTest.java index 2f3070275e35..4193f95d33e6 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaModelTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaModelTest.java @@ -17,18 +17,44 @@ package org.openapitools.codegen.java; +import java.io.File; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + import com.google.common.collect.Sets; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; -import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.oas.models.media.ArraySchema; +import io.swagger.v3.oas.models.media.BooleanSchema; +import io.swagger.v3.oas.models.media.ByteArraySchema; +import io.swagger.v3.oas.models.media.Content; +import io.swagger.v3.oas.models.media.DateTimeSchema; +import io.swagger.v3.oas.models.media.IntegerSchema; +import io.swagger.v3.oas.models.media.MapSchema; +import io.swagger.v3.oas.models.media.MediaType; +import io.swagger.v3.oas.models.media.ObjectSchema; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.media.StringSchema; +import io.swagger.v3.oas.models.media.XML; import io.swagger.v3.oas.models.parameters.Parameter; import io.swagger.v3.oas.models.parameters.QueryParameter; import io.swagger.v3.oas.models.parameters.RequestBody; import io.swagger.v3.oas.models.responses.ApiResponse; import io.swagger.v3.oas.models.responses.ApiResponses; import io.swagger.v3.parser.util.SchemaTypeUtil; -import org.openapitools.codegen.*; +import org.openapitools.codegen.ClientOptInput; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.CodegenModel; +import org.openapitools.codegen.CodegenOperation; +import org.openapitools.codegen.CodegenParameter; +import org.openapitools.codegen.CodegenProperty; +import org.openapitools.codegen.CodegenResponse; +import org.openapitools.codegen.DefaultCodegen; +import org.openapitools.codegen.DefaultGenerator; +import org.openapitools.codegen.TestUtils; import org.openapitools.codegen.config.CodegenConfigurator; import org.openapitools.codegen.languages.JavaClientCodegen; import org.openapitools.codegen.languages.features.DocumentationProviderFeatures.AnnotationLibrary; @@ -36,9 +62,7 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import java.io.File; -import java.nio.file.Files; -import java.util.List; +import static org.openapitools.codegen.CodegenConstants.X_SETTER_EXTRA_ANNOTATION; public class JavaModelTest { @@ -171,6 +195,39 @@ public void setPropertyTest() { Assert.assertTrue(property.isContainer); } + @Test(description = "convert a model with set property and declared setter extension") + public void setPropertyWithDeclaredSetterExtensionTest() { + var declaredSetterExtension = "@com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = org.openapitools.tools.TrimmingStringSetDeserializer.class)"; + final Schema schema = new Schema() + .description("a sample model") + .addProperties("urls", + new ArraySchema() + .items(new StringSchema()) + .uniqueItems(true) + .extensions(Map.of(X_SETTER_EXTRA_ANNOTATION, declaredSetterExtension)) + ) + .addRequiredItem("id"); + + final DefaultCodegen codegen = new DefaultCodegen(); + OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", schema); + codegen.setOpenAPI(openAPI); + + final CodegenModel cm = codegen.fromModel("sample", schema); + final CodegenProperty property = cm.vars.get(0); + + Assert.assertTrue( + property.isSetSetterExtensionDeclared, + "Expected to be discovered if a setter annotation is declared" + ); + Assert.assertListContains( + new ArrayList<>(property.getVendorExtensions().entrySet()), + extension -> + extension.getKey().equals(X_SETTER_EXTRA_ANNOTATION) + && ((String) extension.getValue()).contains(declaredSetterExtension), + "Expected to have a setter extension if its annotation is declared" + ); + } + @Test(description = "convert a model with a map property") public void mapPropertyTest() { final Schema schema = new Schema()