diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java index b14adec76fc..eb5886d5fb2 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java @@ -1036,6 +1036,7 @@ public class OpenAPINormalizer { as.setMaxItems(schema.getMaxItems()); as.setExtensions(schema.getExtensions()); as.setXml(schema.getXml()); + as.setUniqueItems(schema.getUniqueItems()); if (schema.getItems() != null) { // `items` is also a json schema if (StringUtils.isNotEmpty(schema.getItems().get$ref())) { 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 b5ff1b07306..34534065da1 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 @@ -1103,13 +1103,13 @@ public abstract class AbstractJavaCodegen extends DefaultCodegen implements Code */ public String toArrayDefaultValue(CodegenProperty cp, Schema schema) { if (schema.getDefault() != null) { // has default value - if (cp.isArray && !cp.getUniqueItems()) { // array + if (cp.isArray) { List _values = new ArrayList<>(); if (schema.getDefault() instanceof ArrayNode) { // array of default values ArrayNode _default = (ArrayNode) schema.getDefault(); if (_default.isEmpty()) { // e.g. default: [] - return "new ArrayList<>()"; + return getDefaultCollectionType(schema); } List final_values = _values; @@ -1155,14 +1155,12 @@ public abstract class AbstractJavaCodegen extends DefaultCodegen implements Code defaultValue = StringUtils.join(_values, ", "); } } else { - return "new ArrayList<>()"; + return getDefaultCollectionType(schema); } - return String.format(Locale.ROOT, "new ArrayList<>(Arrays.asList(%s))", defaultValue); - } else if (cp.isArray && cp.getUniqueItems()) { // set - // TODO - return null; - } else if (cp.isMap) { // map + return getDefaultCollectionType(schema, defaultValue); + } + if (cp.isMap) { // map // TODO return null; } else { @@ -1181,18 +1179,10 @@ public abstract class AbstractJavaCodegen extends DefaultCodegen implements Code // nullable or containerDefaultToNull set to true if (cp.isNullable || containerDefaultToNull) { return null; - } else { - if (ModelUtils.isSet(schema)) { - return String.format(Locale.ROOT, "new %s<>()", - instantiationTypes().getOrDefault("set", "LinkedHashSet")); - } else { - return String.format(Locale.ROOT, "new %s<>()", - instantiationTypes().getOrDefault("array", "ArrayList")); - } } - } else { // has default value - return toArrayDefaultValue(cp, schema); + return getDefaultCollectionType(schema); } + return toArrayDefaultValue(cp, schema); } else if (ModelUtils.isMapSchema(schema) && !(ModelUtils.isComposedSchema(schema))) { if (schema.getProperties() != null && schema.getProperties().size() > 0) { // object is complex object with free-form additional properties @@ -1202,7 +1192,8 @@ public abstract class AbstractJavaCodegen extends DefaultCodegen implements Code return null; } - if (cp.isNullable || containerDefaultToNull) { // nullable or containerDefaultToNull set to true + // nullable or containerDefaultToNull set to true + if (cp.isNullable || containerDefaultToNull) { return null; } @@ -1290,6 +1281,24 @@ public abstract class AbstractJavaCodegen extends DefaultCodegen implements Code return super.toDefaultValue(schema); } + private String getDefaultCollectionType(Schema schema) { + return getDefaultCollectionType(schema, null); + } + + private String getDefaultCollectionType(Schema schema, String defaultValues) { + String arrayFormat = "new %s<>(Arrays.asList(%s))"; + if(defaultValues == null || defaultValues.isEmpty()){ + defaultValues = ""; + arrayFormat = "new %s<>()"; + } + + if (ModelUtils.isSet(schema)) { + return String.format(Locale.ROOT, arrayFormat, + instantiationTypes().getOrDefault("set", "LinkedHashSet"),defaultValues); + } + return String.format(Locale.ROOT, arrayFormat, instantiationTypes().getOrDefault("array", "ArrayList"),defaultValues); + } + @Override public String toDefaultParameterValue(final Schema schema) { Object defaultValue = schema.get$ref() != null ? ModelUtils.getReferencedSchema(openAPI, schema).getDefault() : schema.getDefault(); diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/JavaFileAssert.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/JavaFileAssert.java index b2f3c40881c..007eb2c40e9 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/JavaFileAssert.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/JavaFileAssert.java @@ -161,6 +161,20 @@ public class JavaFileAssert extends AbstractAssert new IllegalStateException("Empty file")) + .toString(); + Assertions.assertThat(actualBody) + .withFailMessage( + "File should not contains lines\n====\n%s\n====\nbut actually was\n====\n%s\n====", + Arrays.stream(lines).collect(Collectors.joining(System.lineSeparator())), actualBody + ) + .doesNotContain(lines); + + return this; + } + public TypeAnnotationAssert assertTypeAnnotations() { return new TypeAnnotationAssert(this, actual.getType(0).getAnnotations()); } 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 a00a999b078..4d5bdef1d46 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 @@ -4548,4 +4548,46 @@ public class SpringCodegenTest { .fileContains("private List photoUrls = new ArrayList<>();"); } + + @Test + public void testCollectionTypesWithDefaults_issue_18102() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + + OpenAPI openAPI = new OpenAPIParser() + .readLocation("src/test/resources/3_1/java/issue_18102.yaml", null, new ParseOptions()).getOpenAPI(); + SpringCodegen codegen = new SpringCodegen(); + codegen.setLibrary(SPRING_CLOUD_LIBRARY); + codegen.setOutputDir(output.getAbsolutePath()); + codegen.additionalProperties().put(CodegenConstants.MODEL_PACKAGE, "xyz.model"); + codegen.additionalProperties().put(CodegenConstants.API_NAME_SUFFIX, "Controller"); + codegen.additionalProperties().put(CodegenConstants.API_PACKAGE, "xyz.controller"); + codegen.additionalProperties().put(CodegenConstants.MODEL_NAME_SUFFIX, "Dto"); + codegen.setContainerDefaultToNull(true); + + + ClientOptInput input = new ClientOptInput() + .openAPI(openAPI) + .config(codegen); + + DefaultGenerator generator = new DefaultGenerator(); + Map files = generator.opts(input).generate().stream() + .collect(Collectors.toMap(File::getName, Function.identity())); + + JavaFileAssert.assertThat(files.get("PetDto.java")) + .fileContains("private List<@Valid TagDto> tags") + .fileContains("private List<@Valid TagDto> tagsDefaultList = new ArrayList<>()") + .fileContains("private Set<@Valid TagDto> tagsUnique") + .fileContains("private Set<@Valid TagDto> tagsDefaultSet = new LinkedHashSet<>();") + .fileContains("private List stringList") + .fileContains("private List stringDefaultList = new ArrayList<>(Arrays.asList(\"A\", \"B\"));") + .fileContains("private List stringEmptyDefaultList = new ArrayList<>();") + .fileContains("Set stringSet") + .fileContains("private Set stringDefaultSet = new LinkedHashSet<>(Arrays.asList(\"A\", \"B\"));") + .fileContains("private Set stringEmptyDefaultSet = new LinkedHashSet<>();") + .fileDoesNotContains("private List<@Valid TagDto> tags = new ArrayList<>()") + .fileDoesNotContains("private Set<@Valid TagDto> tagsUnique = new LinkedHashSet<>()") + .fileDoesNotContains("private List stringList = new ArrayList<>()") + .fileDoesNotContains("private Set stringSet = new LinkedHashSet<>()"); + } } diff --git a/modules/openapi-generator/src/test/resources/3_1/java/issue_18102.yaml b/modules/openapi-generator/src/test/resources/3_1/java/issue_18102.yaml new file mode 100644 index 00000000000..2859af19dcd --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_1/java/issue_18102.yaml @@ -0,0 +1,112 @@ +openapi: 3.1.0 +servers: + - url: 'http://petstore.swagger.io/v2' +info: + description: >- + This is a sample server Petstore server. For this sample, you can use the api key + `special-key` to test the authorization filters. + version: 1.0.0 + title: OpenAPI Petstore + license: + name: Apache-2.0 + url: 'https://www.apache.org/licenses/LICENSE-2.0.html' +paths: + '/pet/{petId}': + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found +components: + schemas: + Tag: + title: Pet Tag + description: A tag for a pet + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + Pet: + title: a Pet + description: A pet for sale in the pet store + type: object + properties: + tags: + type: array + items: + $ref: '#/components/schemas/Tag' + tagsDefaultList: + type: array + default: [] + items: + $ref: '#/components/schemas/Tag' + tagsUnique: + type: array + uniqueItems: true + items: + $ref: '#/components/schemas/Tag' + tagsDefaultSet: + type: array + default: [ ] + uniqueItems: true + items: + $ref: '#/components/schemas/Tag' + stringList: + type: array + items: + type: string + stringDefaultList: + type: array + default: + - A + - B + items: + type: string + stringEmptyDefaultList: + type: array + default: [] + items: + type: string + stringSet: + type: array + uniqueItems: true + items: + type: string + stringDefaultSet: + type: array + uniqueItems: true + default: + - A + - B + items: + type: string + stringEmptyDefaultSet: + type: array + uniqueItems: true + default: [] + items: + type: string +