From 77df77b9fcbf9c6d30d7e8a752900508c6c2c562 Mon Sep 17 00:00:00 2001 From: jpfinne Date: Wed, 24 Jul 2024 15:41:28 +0200 Subject: [PATCH 1/3] Allow specialized containerDefaultToNull --- .../codegen/ContainerDefaultEvaluator.java | 333 ++++++++++++++++++ .../languages/AbstractJavaCodegen.java | 27 +- .../ContainerDefaultEvaluatorTest.java | 173 +++++++++ 3 files changed, 526 insertions(+), 7 deletions(-) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/ContainerDefaultEvaluator.java create mode 100644 modules/openapi-generator/src/test/java/org/openapitools/codegen/ContainerDefaultEvaluatorTest.java diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/ContainerDefaultEvaluator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/ContainerDefaultEvaluator.java new file mode 100644 index 000000000000..342071cd3be6 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/ContainerDefaultEvaluator.java @@ -0,0 +1,333 @@ +package org.openapitools.codegen; + +import io.swagger.v3.oas.models.media.Schema; +import org.openapitools.codegen.utils.ModelUtils; + +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class ContainerDefaultEvaluator { + public static final boolean[] DEFAULT_7_5_0 = new boolean[]{false, true, false, true, false, false}; + public static final boolean[] DEFAULT_7_4_0 = new boolean[]{true, true, false, true, true, false}; + + public static final boolean[] ALL = new boolean[]{true, true, true, true, true, true}; + public static final boolean[] CONTAINER_TO_NULLABLE = new boolean[]{false, true, false, true, true, true}; + + public static final boolean[] NONE = new boolean[]{true, true, true, true, true, true}; + static Map SHORTCUTS = Map.of( + "7.5.0", DEFAULT_7_5_0, + "openapi3specification", DEFAULT_7_5_0, + "containerToNullable", CONTAINER_TO_NULLABLE, + "7.4.0", DEFAULT_7_4_0, + "all", ALL, + "none", NONE + ); + private final boolean legacy; + private boolean containerDefaultToNull; + + private final Map decisionTable; + + + public ContainerDefaultEvaluator(String containerDefaultToNull) { + if (containerDefaultToNull == null) { + // legacy + containerDefaultToNull = "false"; + } + + if (containerDefaultToNull.equalsIgnoreCase("true") || containerDefaultToNull.equalsIgnoreCase("false")) { + this.legacy = true; + this.containerDefaultToNull = Boolean.parseBoolean(containerDefaultToNull); + this.decisionTable = null; + } else { + this.legacy = false; + this.containerDefaultToNull = false; + this.decisionTable = evaluate(containerDefaultToNull); + } + } + + + public boolean isNullDefault(CodegenProperty cp, Schema schema) { + if (legacy) { + // nullable or containerDefaultToNull set to true + return (cp.isNullable || containerDefaultToNull); + } + + int index = evaluateIndex(cp, schema); + + ContainerType containerType = ContainerType.valueOf(cp, schema); + return decisionTable.get(containerType)[index]; + } + + + enum ContainerType { + array, map, set; + + public static ContainerType valueOf(CodegenProperty cp, Schema schema) { + if (ModelUtils.isSet(schema)) { + return set; + } + if (cp.isMap) { + return map; + } + return array; + } + } + + + public static Map evaluate(String expression) { + return new ContainerDefaultParser(expression).evaluate(); + } + + static class ContainerDefaultParser { + private final java.util.StringTokenizer tokenizer; + + private String token; + + private Boolean required; + private Boolean nullable; + private boolean nullableParsed; + + private boolean positive; + private boolean notDefined; + + private boolean booleans[]; + + private Set containerTypeSet; + + private Map map = new HashMap<>(); + private boolean rightExpressionParsing; + + + ContainerDefaultParser(String expression) { + this.tokenizer = new java.util.StringTokenizer(expression, "&!| ?:;", true); + reset(); + } + + private void reset() { + this.booleans = initAllToFalse(); + this.required = false; + this.nullable = null; + this.nullableParsed = false; + this.positive = false; + this.notDefined = false; + this.containerTypeSet = null; + this.rightExpressionParsing = false; + } + + private boolean[] initAllToFalse() { + return new boolean[6]; + } + + private boolean nextToken() { + if ("EOF".equals(token)) { + return false; + } + for (; ; ) { + if (tokenizer.hasMoreElements()) { + token = tokenizer.nextToken(); + if (" ".equals(token)) { + continue; + } + return true; + } else { + token = "EOF"; + return false; + } + } + } + + private void advance() { + nextToken(); + } + + Map evaluate() { + nextToken(); + while (!"EOF".equals(token)) { + switch (token) { + case ";": + reset(); + nextToken(); + break; + default: + evaluateToken(); + break; + } + } + + // add default to not defined values + for (ContainerType other : ContainerType.values()) { + if (!map.containsKey(other)) { + map.put(other, DEFAULT_7_5_0); + } + } + + return map; + } + + private void addToMap() { + if (containerTypeSet != null) { + containerTypeSet.forEach(c -> map.put(c, booleans)); + } else { + map = allContainerTypes(booleans); + } + reset(); + } + + private void assignBooleanExpression() { + rightExpressionParsing = true; + if (!nullableParsed) { + throw new IllegalArgumentException("nullable should be specified"); + } + if (required == null) { + throw new IllegalArgumentException("nullable should be specified"); + } + + if (required == Boolean.FALSE && nullable == Boolean.FALSE) { + booleans[0] = true; + } else if (required == Boolean.FALSE && nullable == Boolean.TRUE) { + booleans[1] = true; + } else if (required == Boolean.TRUE && nullable == Boolean.FALSE) { + booleans[2] = true; + } else if (required == Boolean.TRUE && nullable == Boolean.TRUE) { + booleans[3] = true; + } else if (required == Boolean.FALSE && nullable == null) { + booleans[4] = true; + } else if (required == Boolean.TRUE && nullable == null) { + booleans[5] = true; + } else { + // should not happen + throw new IllegalArgumentException("Unexpected combination"); + } + } + + private void evaluateToken() { + switch (token) { + case "array": + case "set": + case "map": + parseContainerTypes(); + break; + default: + evaluateAndExpression(); + break; + } + } + + void evaluateAndExpression() { + + evaluateExpression(); + for (; ; ) { + switch (token) { + case "EOF": + case ";": + return; + } + evaluateExpression(); + ; + } + } + + void evaluateExpression() { + boolean[] predefinedValues = SHORTCUTS.get(token); + if (predefinedValues != null) { + booleans = predefinedValues; + nextToken(); + if ("|".equals(token)) { + nextToken(); + } else { + addToMap(); + return; + } + } + for (; ; ) { + switch (token) { + + case "required": + rightExpressionParsing = true; + required = positive; + if (notDefined) { + throw new IllegalArgumentException("?required not allowed"); + } + positive = true; + break; + case "nullable": + nullableParsed = true; + nullable = notDefined ? null : positive; + positive = true; + break; + case "!": + positive = false; + break; + case "&": + if (!rightExpressionParsing) { + throw new IllegalArgumentException(token + " unexpected in right expression parsing"); + } + positive = true; + notDefined = false; + break; + case "?": + notDefined = true; + break; + case ";": + case "EOF": + assignBooleanExpression(); + addToMap(); + nextToken(); + reset(); + return; + case "|": + assignBooleanExpression(); + nextToken(); + return; + default: + throw new IllegalArgumentException("Unknow token " + token); + } + nextToken(); + + } + } + + private void parseContainerTypes() { + this.containerTypeSet = EnumSet.of(ContainerType.valueOf(token)); + for (; ; ) { + nextToken(); + switch (token) { + case ":": + nextToken(); + evaluateExpression(); + return; + case "|": + continue; + default: + this.containerTypeSet.add(ContainerType.valueOf(token)); + } + } + } + + protected static Map allContainerTypes(boolean[] booleans) { + EnumMap map = new EnumMap<>(ContainerType.class); + map.put(ContainerType.array, booleans); + map.put(ContainerType.set, booleans); + map.put(ContainerType.map, booleans); + return map; + } + } + + + private static int evaluateIndex(CodegenProperty cp, Schema schema) { + boolean nullableSpecified = schema.getNullable() != null; + if (nullableSpecified) { + if (cp.required) { + return cp.isNullable ? 3 : 2; + } else { + return cp.isNullable ? 1 : 0; + } + } else { + return cp.required ? 5 : 4; + } + } +} + 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 52e71fc22b79..d61143b7f1d1 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 @@ -175,7 +175,7 @@ public abstract class AbstractJavaCodegen extends DefaultCodegen implements Code @Setter protected String implicitHeadersRegex = null; @Setter protected boolean camelCaseDollarSign = false; @Setter protected boolean useJakartaEe = false; - @Setter protected boolean containerDefaultToNull = false; + @Setter protected String containerDefaultToNull = "false"; @Getter @Setter protected boolean generateConstructorWithAllArgs = false; @Getter @Setter @@ -189,6 +189,7 @@ public abstract class AbstractJavaCodegen extends DefaultCodegen implements Code @Getter @Setter protected boolean useBeanValidation = false; private Map schemaKeyToModelNameCache = new HashMap<>(); + private ContainerDefaultEvaluator containerDefaultEvaluator = new ContainerDefaultEvaluator("false"); public AbstractJavaCodegen() { super(); @@ -636,7 +637,7 @@ public void processOpts() { applyJavaxPackage(); } - convertPropertyToBooleanAndWriteBack(CONTAINER_DEFAULT_TO_NULL, this::setContainerDefaultToNull); + convertPropertyToStringAndWriteBack(CONTAINER_DEFAULT_TO_NULL, this::setContainerDefaultToNull); additionalProperties.put("sanitizeGeneric", (Mustache.Lambda) (fragment, writer) -> { String content = fragment.execute(); @@ -645,6 +646,9 @@ public void processOpts() { } writer.write(content); }); + + + this.containerDefaultEvaluator = new ContainerDefaultEvaluator(this.containerDefaultToNull); } /** @@ -1257,8 +1261,7 @@ public String toDefaultValue(CodegenProperty cp, Schema schema) { schema = ModelUtils.getReferencedSchema(this.openAPI, schema); if (ModelUtils.isArraySchema(schema)) { if (schema.getDefault() == null) { - // nullable or containerDefaultToNull set to true - if (cp.isNullable || containerDefaultToNull) { + if (containerDefaultEvaluator.isNullDefault(cp, schema)) { return null; } return getDefaultCollectionType(schema); @@ -1273,8 +1276,7 @@ public String toDefaultValue(CodegenProperty cp, Schema schema) { return null; } - // nullable or containerDefaultToNull set to true - if (cp.isNullable || containerDefaultToNull) { + if (containerDefaultEvaluator.isNullDefault(cp, schema)) { return null; } @@ -1283,7 +1285,7 @@ public String toDefaultValue(CodegenProperty cp, Schema schema) { } return String.format(Locale.ROOT, "new %s<>()", - instantiationTypes().getOrDefault("map", "HashMap")); + instantiationTypes.getOrDefault("map", "HashMap")); } else if (ModelUtils.isIntegerSchema(schema)) { if (schema.getDefault() != null) { if (SchemaTypeUtil.INTEGER64_FORMAT.equals(schema.getFormat())) { @@ -2350,4 +2352,15 @@ public static void addImports(List> imports, CodegenModel cm public boolean isTypeErasedGenerics() { return true; } + + public void setContainerDefaultToNull(String value) { + this.containerDefaultToNull=value; + } + + /** + * for legacy (before 7.8.0) a boolean can be set + */ + public void setContainerDefaultToNull(boolean value) { + this.containerDefaultToNull=Boolean.toString(value); + } } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/ContainerDefaultEvaluatorTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/ContainerDefaultEvaluatorTest.java new file mode 100644 index 000000000000..550fcc309a93 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/ContainerDefaultEvaluatorTest.java @@ -0,0 +1,173 @@ +package org.openapitools.codegen; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.media.ArraySchema; +import io.swagger.v3.oas.models.media.ByteArraySchema; +import io.swagger.v3.oas.models.media.MapSchema; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.media.StringSchema; +import org.openapitools.codegen.languages.JavaClientCodegen; +import org.testng.annotations.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ContainerDefaultEvaluatorTest { + + @Test + public void compatibility_7_5_0_shortcut() { + testAll("7.5.0", ContainerDefaultEvaluator.DEFAULT_7_5_0); + } + + @Test + public void compatibility_7_5_0_shortcut_and_override() { + testAll("7.5.0 | !required & !nullable", true, true, false, true, false, false); + } + + + @Test + public void compatibility_7_5_0() { + testAll("!required&nullable | required&nullable", ContainerDefaultEvaluator.DEFAULT_7_5_0); + } + + @Test + public void compatibility_7_4_0() { + testAll("!required&!nullable | !required&nullable | required&nullable | !required&?nullable", ContainerDefaultEvaluator.DEFAULT_7_4_0); + } + + @Test + public void compatibility_map_7_5_array_7_4_set_all() { + testAll("map: 7.5.0 ; array: 7.4.0 ; set: all", Map.of( + ContainerDefaultEvaluator.ContainerType.map, ContainerDefaultEvaluator.DEFAULT_7_5_0, + ContainerDefaultEvaluator.ContainerType.array, ContainerDefaultEvaluator.DEFAULT_7_4_0, + ContainerDefaultEvaluator.ContainerType.set, ContainerDefaultEvaluator.ALL)); + } + + @Test + public void compatibility_only_array() { + testAll("array: !required&!nullable | !required&nullable | required&nullable | !required&?nullable", Map.of( + ContainerDefaultEvaluator.ContainerType.array, new boolean[]{true, true, false, true, true, false}, + ContainerDefaultEvaluator.ContainerType.set, ContainerDefaultEvaluator.DEFAULT_7_5_0, + ContainerDefaultEvaluator.ContainerType.map, ContainerDefaultEvaluator.DEFAULT_7_5_0)); + } + + @Test + public void compatibility_only_arrayAndSet() { + testAll("array|set: !required&!nullable | !required&nullable | required&nullable | !required&?nullable", Map.of( + ContainerDefaultEvaluator.ContainerType.array, new boolean[]{true, true, false, true, true, false}, + ContainerDefaultEvaluator.ContainerType.set, new boolean[]{true, true, false, true, true, false}, + ContainerDefaultEvaluator.ContainerType.map, ContainerDefaultEvaluator.DEFAULT_7_5_0)); + } + + @Test + public void testMapDefault_DefaultSetting() { + MapSchema mapSchema = new MapSchema().type("string"); + mapSchema.setAdditionalProperties(new StringSchema()); + assertThat(getArrayDefaultValue(mapSchema, null,null, false, false)).isEqualTo("new HashMap<>()"); + assertThat(getArrayDefaultValue(mapSchema,null,null, false, true)).isNull(); + assertThat(getArrayDefaultValue(mapSchema,null,null, true, false)).isEqualTo("new HashMap<>()"); + assertThat(getArrayDefaultValue(mapSchema,null,null, true, true)).isNull(); + assertThat(getArrayDefaultValue(mapSchema,null,null, false, null)).isEqualTo("new HashMap<>()"); + assertThat(getArrayDefaultValue(mapSchema,null,null, true, null)).isEqualTo("new HashMap<>()"); + } + + + @Test + public void testArrayDefault_SetContainerToNullable() { + assertThat(getArrayDefaultValue(new ArraySchema(), null,"array|set|map", false, false)).isEqualTo("new ArrayList<>()"); + assertThat(getArrayDefaultValue(new ArraySchema(), null,"array|set|map", false, true)).isNull(); + assertThat(getArrayDefaultValue(new ArraySchema(), null,"array|set|map", true, false)).isEqualTo("new ArrayList<>()"); + assertThat(getArrayDefaultValue(new ArraySchema(), null,"array|set|map", true, true)).isNull(); + assertThat(getArrayDefaultValue(new ArraySchema(), null,"array|set|map", false, null)).isNull(); + assertThat(getArrayDefaultValue(new ArraySchema(), null,"array|set|map", true, null)).isNull(); + } + + @Test + public void testArrayDefault_ContainerDefaultToNullTrue() { + assertThat(getArrayDefaultValue(new ArraySchema(), "true",null, false, false)).isNull(); + assertThat(getArrayDefaultValue(new ArraySchema(), "true",null, false, true)).isNull(); + assertThat(getArrayDefaultValue(new ArraySchema(), "true",null, true, false)).isNull(); + assertThat(getArrayDefaultValue(new ArraySchema(), "true",null, true, true)).isNull(); + assertThat(getArrayDefaultValue(new ArraySchema(), "true",null, false, null)).isNull(); + assertThat(getArrayDefaultValue(new ArraySchema(), "true",null, true, null)).isNull(); + } + + @Test + public void testArrayDefault_7_4_0() { + assertThat(getArrayDefaultValue(new ArraySchema(), "7.4.0",null, false, false)).isNull(); + assertThat(getArrayDefaultValue(new ArraySchema(), "7.4.0",null, false, true)).isNull(); + assertThat(getArrayDefaultValue(new ArraySchema(), "7.4.0",null, true, false)).isEqualTo("new ArrayList<>()"); + assertThat(getArrayDefaultValue(new ArraySchema(), "7.4.0",null, true, true)).isNull(); + assertThat(getArrayDefaultValue(new ArraySchema(), "7.4.0",null, false, null)).isNull(); + assertThat(getArrayDefaultValue(new ArraySchema(), "7.4.0",null, true, null)).isEqualTo("new ArrayList<>()"); + } + + + @Test + public void testMapDefault_7_4_0() { + MapSchema mapSchema = new MapSchema().type("string"); + mapSchema.setAdditionalProperties(new StringSchema()); + assertThat(getArrayDefaultValue(mapSchema,"7.4.0",null, false, false)).isEqualTo(null); + assertThat(getArrayDefaultValue(mapSchema,"7.4.0",null, false, true)).isNull(); + assertThat(getArrayDefaultValue(mapSchema,"7.4.0",null, true, false)).isEqualTo("new HashMap<>()"); + assertThat(getArrayDefaultValue(mapSchema,"7.4.0", null, true, true)).isNull(); + assertThat(getArrayDefaultValue(mapSchema,"7.4.0", null, false, null)).isEqualTo(null); + assertThat(getArrayDefaultValue(mapSchema,"7.4.0", null, true, null)).isEqualTo("new HashMap<>()"); + } + + @Test + public void testArrayDefault_DefaultSetting() { + assertThat(getArrayDefaultValue(new ArraySchema(), null,null, false, false)).isEqualTo("new ArrayList<>()"); + assertThat(getArrayDefaultValue(new ArraySchema(), null,null, false, true)).isNull(); + assertThat(getArrayDefaultValue(new ArraySchema(), null,null, true, false)).isEqualTo("new ArrayList<>()"); + assertThat(getArrayDefaultValue(new ArraySchema(), null,null, true, true)).isNull(); + assertThat(getArrayDefaultValue(new ArraySchema(), null,null, false, null)).isEqualTo("new ArrayList<>()"); + assertThat(getArrayDefaultValue(new ArraySchema(), null,null, true, null)).isEqualTo("new ArrayList<>()"); + } + + String getArrayDefaultValue(Schema containerSchema, String containerDefaultToNull,String setContainerToNullable, boolean required, Boolean nullable) { + + final Schema schema = new Schema() + .addProperties("container", containerSchema); + + if (required) { + schema.setRequired(Arrays.asList("container")); + } + containerSchema.setNullable(nullable); + + OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", schema); + if (setContainerToNullable != null) { + Map options = new HashMap<>(); + options.put("SET_CONTAINER_TO_NULLABLE", setContainerToNullable); + OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options); + openAPINormalizer.normalize(); + } + final JavaClientCodegen codegen = new JavaClientCodegen(); + if (containerDefaultToNull != null) { + codegen.setContainerDefaultToNull(containerDefaultToNull); + } + codegen.processOpts(); + codegen.setOpenAPI(openAPI); + final CodegenModel cm = codegen.fromModel("sample", schema); + + final CodegenProperty property = cm.vars.get(0); + return codegen.toDefaultValue(property, containerSchema); + } + + private void testAll(String expression, Map expected) { + Map map = ContainerDefaultEvaluator.evaluate(expression); + expected.forEach((containerType, booleans) -> assertThat(map.get(containerType)).describedAs("containerType %s", containerType).isEqualTo(booleans)); + + } + + + private void testAll(String expression, boolean... booleans) { + Map map = ContainerDefaultEvaluator.evaluate(expression); + assertThat(map.get(ContainerDefaultEvaluator.ContainerType.array)).isEqualTo(booleans); + } + + +} From 2b7a1b87f7fca3f998e029606d23b6211840579e Mon Sep 17 00:00:00 2001 From: Martin <2026226+martin-mfg@users.noreply.github.com> Date: Wed, 21 Aug 2024 21:00:07 +0200 Subject: [PATCH 2/3] rework, don't differentiate collection types, fix for x-nullable --- .../codegen/ContainerDefaultEvaluator.java | 329 +----------------- .../codegen/ContainerDefaultParser.java | 101 ++++++ .../languages/AbstractJavaCodegen.java | 14 +- .../ContainerDefaultEvaluatorTest.java | 281 ++++++++------- .../java/spring/SpringCodegenTest.java | 2 +- .../3_0/nullable_required_combinations.yaml | 57 +++ 6 files changed, 336 insertions(+), 448 deletions(-) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/ContainerDefaultParser.java create mode 100644 modules/openapi-generator/src/test/resources/3_0/nullable_required_combinations.yaml diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/ContainerDefaultEvaluator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/ContainerDefaultEvaluator.java index 342071cd3be6..cd13b4657ce6 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/ContainerDefaultEvaluator.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/ContainerDefaultEvaluator.java @@ -1,333 +1,34 @@ package org.openapitools.codegen; import io.swagger.v3.oas.models.media.Schema; -import org.openapitools.codegen.utils.ModelUtils; -import java.util.EnumMap; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; +import java.util.List; public class ContainerDefaultEvaluator { - public static final boolean[] DEFAULT_7_5_0 = new boolean[]{false, true, false, true, false, false}; - public static final boolean[] DEFAULT_7_4_0 = new boolean[]{true, true, false, true, true, false}; - - public static final boolean[] ALL = new boolean[]{true, true, true, true, true, true}; - public static final boolean[] CONTAINER_TO_NULLABLE = new boolean[]{false, true, false, true, true, true}; - - public static final boolean[] NONE = new boolean[]{true, true, true, true, true, true}; - static Map SHORTCUTS = Map.of( - "7.5.0", DEFAULT_7_5_0, - "openapi3specification", DEFAULT_7_5_0, - "containerToNullable", CONTAINER_TO_NULLABLE, - "7.4.0", DEFAULT_7_4_0, - "all", ALL, - "none", NONE - ); - private final boolean legacy; - private boolean containerDefaultToNull; - - private final Map decisionTable; + final List conditions; public ContainerDefaultEvaluator(String containerDefaultToNull) { - if (containerDefaultToNull == null) { - // legacy - containerDefaultToNull = "false"; - } - - if (containerDefaultToNull.equalsIgnoreCase("true") || containerDefaultToNull.equalsIgnoreCase("false")) { - this.legacy = true; - this.containerDefaultToNull = Boolean.parseBoolean(containerDefaultToNull); - this.decisionTable = null; - } else { - this.legacy = false; - this.containerDefaultToNull = false; - this.decisionTable = evaluate(containerDefaultToNull); - } + conditions = ContainerDefaultParser.parseExpression(containerDefaultToNull); } - public boolean isNullDefault(CodegenProperty cp, Schema schema) { - if (legacy) { - // nullable or containerDefaultToNull set to true - return (cp.isNullable || containerDefaultToNull); - } - - int index = evaluateIndex(cp, schema); - - ContainerType containerType = ContainerType.valueOf(cp, schema); - return decisionTable.get(containerType)[index]; - } - - - enum ContainerType { - array, map, set; - - public static ContainerType valueOf(CodegenProperty cp, Schema schema) { - if (ModelUtils.isSet(schema)) { - return set; - } - if (cp.isMap) { - return map; - } - return array; - } - } - - - public static Map evaluate(String expression) { - return new ContainerDefaultParser(expression).evaluate(); - } - - static class ContainerDefaultParser { - private final java.util.StringTokenizer tokenizer; - - private String token; - - private Boolean required; - private Boolean nullable; - private boolean nullableParsed; - - private boolean positive; - private boolean notDefined; - - private boolean booleans[]; - - private Set containerTypeSet; - - private Map map = new HashMap<>(); - private boolean rightExpressionParsing; - - - ContainerDefaultParser(String expression) { - this.tokenizer = new java.util.StringTokenizer(expression, "&!| ?:;", true); - reset(); - } - - private void reset() { - this.booleans = initAllToFalse(); - this.required = false; - this.nullable = null; - this.nullableParsed = false; - this.positive = false; - this.notDefined = false; - this.containerTypeSet = null; - this.rightExpressionParsing = false; - } - - private boolean[] initAllToFalse() { - return new boolean[6]; - } - - private boolean nextToken() { - if ("EOF".equals(token)) { - return false; - } - for (; ; ) { - if (tokenizer.hasMoreElements()) { - token = tokenizer.nextToken(); - if (" ".equals(token)) { - continue; - } - return true; - } else { - token = "EOF"; - return false; - } - } - } - - private void advance() { - nextToken(); - } - - Map evaluate() { - nextToken(); - while (!"EOF".equals(token)) { - switch (token) { - case ";": - reset(); - nextToken(); - break; - default: - evaluateToken(); - break; - } - } - - // add default to not defined values - for (ContainerType other : ContainerType.values()) { - if (!map.containsKey(other)) { - map.put(other, DEFAULT_7_5_0); - } - } - - return map; - } - - private void addToMap() { - if (containerTypeSet != null) { - containerTypeSet.forEach(c -> map.put(c, booleans)); - } else { - map = allContainerTypes(booleans); - } - reset(); - } - - private void assignBooleanExpression() { - rightExpressionParsing = true; - if (!nullableParsed) { - throw new IllegalArgumentException("nullable should be specified"); - } - if (required == null) { - throw new IllegalArgumentException("nullable should be specified"); - } - - if (required == Boolean.FALSE && nullable == Boolean.FALSE) { - booleans[0] = true; - } else if (required == Boolean.FALSE && nullable == Boolean.TRUE) { - booleans[1] = true; - } else if (required == Boolean.TRUE && nullable == Boolean.FALSE) { - booleans[2] = true; - } else if (required == Boolean.TRUE && nullable == Boolean.TRUE) { - booleans[3] = true; - } else if (required == Boolean.FALSE && nullable == null) { - booleans[4] = true; - } else if (required == Boolean.TRUE && nullable == null) { - booleans[5] = true; + ContainerDefaultParser.NullableState nullable; + if (schema.getNullable() == null && (schema.getExtensions() == null || !schema.getExtensions().containsKey("x-nullable"))) { + nullable = ContainerDefaultParser.NullableState.UNSPECIFIED; + } else { + if (cp.isNullable) { + nullable = ContainerDefaultParser.NullableState.YES; } else { - // should not happen - throw new IllegalArgumentException("Unexpected combination"); - } - } - - private void evaluateToken() { - switch (token) { - case "array": - case "set": - case "map": - parseContainerTypes(); - break; - default: - evaluateAndExpression(); - break; - } - } - - void evaluateAndExpression() { - - evaluateExpression(); - for (; ; ) { - switch (token) { - case "EOF": - case ";": - return; - } - evaluateExpression(); - ; - } - } - - void evaluateExpression() { - boolean[] predefinedValues = SHORTCUTS.get(token); - if (predefinedValues != null) { - booleans = predefinedValues; - nextToken(); - if ("|".equals(token)) { - nextToken(); - } else { - addToMap(); - return; - } - } - for (; ; ) { - switch (token) { - - case "required": - rightExpressionParsing = true; - required = positive; - if (notDefined) { - throw new IllegalArgumentException("?required not allowed"); - } - positive = true; - break; - case "nullable": - nullableParsed = true; - nullable = notDefined ? null : positive; - positive = true; - break; - case "!": - positive = false; - break; - case "&": - if (!rightExpressionParsing) { - throw new IllegalArgumentException(token + " unexpected in right expression parsing"); - } - positive = true; - notDefined = false; - break; - case "?": - notDefined = true; - break; - case ";": - case "EOF": - assignBooleanExpression(); - addToMap(); - nextToken(); - reset(); - return; - case "|": - assignBooleanExpression(); - nextToken(); - return; - default: - throw new IllegalArgumentException("Unknow token " + token); - } - nextToken(); - - } - } - - private void parseContainerTypes() { - this.containerTypeSet = EnumSet.of(ContainerType.valueOf(token)); - for (; ; ) { - nextToken(); - switch (token) { - case ":": - nextToken(); - evaluateExpression(); - return; - case "|": - continue; - default: - this.containerTypeSet.add(ContainerType.valueOf(token)); - } + nullable = ContainerDefaultParser.NullableState.NO; } } - protected static Map allContainerTypes(boolean[] booleans) { - EnumMap map = new EnumMap<>(ContainerType.class); - map.put(ContainerType.array, booleans); - map.put(ContainerType.set, booleans); - map.put(ContainerType.map, booleans); - return map; - } + return conditions.stream().anyMatch( + myCondition -> + (myCondition.getRequired() == null || myCondition.getRequired().equals(cp.required)) + && (myCondition.getNullable() == null || myCondition.getNullable().equals(nullable)) + ); } - - private static int evaluateIndex(CodegenProperty cp, Schema schema) { - boolean nullableSpecified = schema.getNullable() != null; - if (nullableSpecified) { - if (cp.required) { - return cp.isNullable ? 3 : 2; - } else { - return cp.isNullable ? 1 : 0; - } - } else { - return cp.required ? 5 : 4; - } - } } - diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/ContainerDefaultParser.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/ContainerDefaultParser.java new file mode 100644 index 000000000000..239e5f770156 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/ContainerDefaultParser.java @@ -0,0 +1,101 @@ +package org.openapitools.codegen; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +class ContainerDefaultParser { + + protected static final String REQUIRED = "required"; + protected static final String NULLABLE = "nullable"; + protected static final String ERROR_UNEXPECTED = "unexpected containerDefaultToNull keyword: "; + + protected static List parseExpression(String input) { + if (input == null || "".equals(input.trim())) { + input = "7.5.0"; + } + + String[] orConditions = input.split("\\|"); + List conditions = new ArrayList<>(); + + for (String myOrCondition : orConditions) { + conditions.addAll(parseCondition(myOrCondition.trim())); + } + + return conditions; + } + + protected static List parseCondition(String conditionStr) { + String[] andConditions = conditionStr.split("&"); + Boolean required = null; + NullableState nullable = null; + + for (String myAndCondition : andConditions) { + String trimmed = myAndCondition.trim(); + + if (trimmed.startsWith("!")) { + String keyword = trimmed.substring(1).trim(); + if (keyword.equals(REQUIRED)) { + required = false; + } else if (keyword.equals(NULLABLE)) { + nullable = NullableState.NO; + } else { + throw new IllegalArgumentException(ERROR_UNEXPECTED + keyword); + } + } else if (trimmed.startsWith("?")) { + String keyword = trimmed.substring(1).trim(); + if (keyword.equals(NULLABLE)) { + nullable = NullableState.UNSPECIFIED; + } else { + throw new IllegalArgumentException(ERROR_UNEXPECTED + keyword); + } + } else { + switch (trimmed) { + case REQUIRED: + required = true; + break; + case NULLABLE: + nullable = NullableState.YES; + break; + case "7.5.0": + case "false": + return List.of( + new Condition(true, NullableState.YES), + new Condition(false, NullableState.YES)); + case "7.4.0": + return List.of( + new Condition(true, NullableState.YES), + new Condition(false, NullableState.YES), + new Condition(false, NullableState.NO), + new Condition(false, NullableState.UNSPECIFIED)); + case "true": + return List.of( + new Condition(true, NullableState.YES), + new Condition(true, NullableState.NO), + new Condition(true, NullableState.UNSPECIFIED), + new Condition(false, NullableState.YES), + new Condition(false, NullableState.NO), + new Condition(false, NullableState.UNSPECIFIED)); + case "none": + return new ArrayList<>(); + default: + throw new IllegalArgumentException(ERROR_UNEXPECTED + trimmed); + } + } + } + + return List.of(new Condition(required, nullable)); + } + + protected enum NullableState { + YES, NO, UNSPECIFIED + } + + @Data + protected static class Condition { + private final Boolean required; + private final NullableState nullable; + } + +} 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 d61143b7f1d1..0cc21d1207e5 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 @@ -647,7 +647,6 @@ public void processOpts() { writer.write(content); }); - this.containerDefaultEvaluator = new ContainerDefaultEvaluator(this.containerDefaultToNull); } @@ -1285,7 +1284,7 @@ public String toDefaultValue(CodegenProperty cp, Schema schema) { } return String.format(Locale.ROOT, "new %s<>()", - instantiationTypes.getOrDefault("map", "HashMap")); + instantiationTypes().getOrDefault("map", "HashMap")); } else if (ModelUtils.isIntegerSchema(schema)) { if (schema.getDefault() != null) { if (SchemaTypeUtil.INTEGER64_FORMAT.equals(schema.getFormat())) { @@ -2352,15 +2351,4 @@ public static void addImports(List> imports, CodegenModel cm public boolean isTypeErasedGenerics() { return true; } - - public void setContainerDefaultToNull(String value) { - this.containerDefaultToNull=value; - } - - /** - * for legacy (before 7.8.0) a boolean can be set - */ - public void setContainerDefaultToNull(boolean value) { - this.containerDefaultToNull=Boolean.toString(value); - } } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/ContainerDefaultEvaluatorTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/ContainerDefaultEvaluatorTest.java index 550fcc309a93..c85695492f35 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/ContainerDefaultEvaluatorTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/ContainerDefaultEvaluatorTest.java @@ -1,173 +1,214 @@ package org.openapitools.codegen; +import io.swagger.parser.OpenAPIParser; import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.media.ArraySchema; -import io.swagger.v3.oas.models.media.ByteArraySchema; -import io.swagger.v3.oas.models.media.MapSchema; -import io.swagger.v3.oas.models.media.Schema; -import io.swagger.v3.oas.models.media.StringSchema; +import io.swagger.v3.parser.core.models.ParseOptions; +import org.assertj.core.api.Assertions; +import org.openapitools.codegen.java.assertions.JavaFileAssert; import org.openapitools.codegen.languages.JavaClientCodegen; import org.testng.annotations.Test; -import java.util.Arrays; -import java.util.HashMap; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; +import java.util.function.Function; +import java.util.stream.Collectors; public class ContainerDefaultEvaluatorTest { @Test - public void compatibility_7_5_0_shortcut() { - testAll("7.5.0", ContainerDefaultEvaluator.DEFAULT_7_5_0); + public void testNull() throws IOException { + executeTest( + null, + "private List r0n0 = new ArrayList<>();", + "private List r0n1;", + "private List r1n0 = new ArrayList<>();", + "private List r1n1;", + "private List r0 = new ArrayList<>();", + "private List r1 = new ArrayList<>();", + "private Set mySet;", + "private Map myMap;" + ); } @Test - public void compatibility_7_5_0_shortcut_and_override() { - testAll("7.5.0 | !required & !nullable", true, true, false, true, false, false); + public void testEmptyString() throws IOException { + executeTest( + "", + "private List r0n0 = new ArrayList<>();", + "private List r0n1;", + "private List r1n0 = new ArrayList<>();", + "private List r1n1;", + "private List r0 = new ArrayList<>();", + "private List r1 = new ArrayList<>();", + "private Set mySet;", + "private Map myMap;" + ); } - @Test - public void compatibility_7_5_0() { - testAll("!required&nullable | required&nullable", ContainerDefaultEvaluator.DEFAULT_7_5_0); + public void testFalse() throws IOException { + executeTest( + "false", + "private List r0n0 = new ArrayList<>();", + "private List r0n1;", + "private List r1n0 = new ArrayList<>();", + "private List r1n1;", + "private List r0 = new ArrayList<>();", + "private List r1 = new ArrayList<>();", + "private Set mySet;", + "private Map myMap;" + ); } @Test - public void compatibility_7_4_0() { - testAll("!required&!nullable | !required&nullable | required&nullable | !required&?nullable", ContainerDefaultEvaluator.DEFAULT_7_4_0); + public void testTrue() throws IOException { + executeTest( + "true", + "private List r0n0;", + "private List r0n1;", + "private List r1n0;", + "private List r1n1;", + "private List r0;", + "private List r1;", + "private Set mySet;", + "private Map myMap;" + ); } @Test - public void compatibility_map_7_5_array_7_4_set_all() { - testAll("map: 7.5.0 ; array: 7.4.0 ; set: all", Map.of( - ContainerDefaultEvaluator.ContainerType.map, ContainerDefaultEvaluator.DEFAULT_7_5_0, - ContainerDefaultEvaluator.ContainerType.array, ContainerDefaultEvaluator.DEFAULT_7_4_0, - ContainerDefaultEvaluator.ContainerType.set, ContainerDefaultEvaluator.ALL)); + public void testNone() throws IOException { + executeTest( + "none", + "private List r0n0 = new ArrayList<>();", + "private List r0n1 = new ArrayList<>();", + "private List r1n0 = new ArrayList<>();", + "private List r1n1 = new ArrayList<>();", + "private List r0 = new ArrayList<>();", + "private List r1 = new ArrayList<>();", + "private Set mySet = new LinkedHashSet<>();", + "private Map myMap = new HashMap<>();" + ); } @Test - public void compatibility_only_array() { - testAll("array: !required&!nullable | !required&nullable | required&nullable | !required&?nullable", Map.of( - ContainerDefaultEvaluator.ContainerType.array, new boolean[]{true, true, false, true, true, false}, - ContainerDefaultEvaluator.ContainerType.set, ContainerDefaultEvaluator.DEFAULT_7_5_0, - ContainerDefaultEvaluator.ContainerType.map, ContainerDefaultEvaluator.DEFAULT_7_5_0)); + public void test_7_5_0() throws IOException { + executeTest( + "7.5.0", + "private List r0n0 = new ArrayList<>();", + "private List r0n1;", + "private List r1n0 = new ArrayList<>();", + "private List r1n1;", + "private List r0 = new ArrayList<>();", + "private List r1 = new ArrayList<>();", + "private Set mySet;", + "private Map myMap;" + ); } @Test - public void compatibility_only_arrayAndSet() { - testAll("array|set: !required&!nullable | !required&nullable | required&nullable | !required&?nullable", Map.of( - ContainerDefaultEvaluator.ContainerType.array, new boolean[]{true, true, false, true, true, false}, - ContainerDefaultEvaluator.ContainerType.set, new boolean[]{true, true, false, true, true, false}, - ContainerDefaultEvaluator.ContainerType.map, ContainerDefaultEvaluator.DEFAULT_7_5_0)); + public void test_7_4_0() throws IOException { + executeTest( + "7.4.0", + "private List r0n0;", + "private List r0n1;", + "private List r1n0 = new ArrayList<>();", + "private List r1n1;", + "private List r0;", + "private List r1 = new ArrayList<>();", + "private Set mySet;", + "private Map myMap;" + ); } @Test - public void testMapDefault_DefaultSetting() { - MapSchema mapSchema = new MapSchema().type("string"); - mapSchema.setAdditionalProperties(new StringSchema()); - assertThat(getArrayDefaultValue(mapSchema, null,null, false, false)).isEqualTo("new HashMap<>()"); - assertThat(getArrayDefaultValue(mapSchema,null,null, false, true)).isNull(); - assertThat(getArrayDefaultValue(mapSchema,null,null, true, false)).isEqualTo("new HashMap<>()"); - assertThat(getArrayDefaultValue(mapSchema,null,null, true, true)).isNull(); - assertThat(getArrayDefaultValue(mapSchema,null,null, false, null)).isEqualTo("new HashMap<>()"); - assertThat(getArrayDefaultValue(mapSchema,null,null, true, null)).isEqualTo("new HashMap<>()"); + public void testCustomExpression1() throws IOException { + executeTest( + "7.5.0 | !required & ?nullable", + "private List r0n0 = new ArrayList<>();", + "private List r0n1;", + "private List r1n0 = new ArrayList<>();", + "private List r1n1;", + "private List r0;", + "private List r1 = new ArrayList<>();", + "private Set mySet;", + "private Map myMap;" + ); } - @Test - public void testArrayDefault_SetContainerToNullable() { - assertThat(getArrayDefaultValue(new ArraySchema(), null,"array|set|map", false, false)).isEqualTo("new ArrayList<>()"); - assertThat(getArrayDefaultValue(new ArraySchema(), null,"array|set|map", false, true)).isNull(); - assertThat(getArrayDefaultValue(new ArraySchema(), null,"array|set|map", true, false)).isEqualTo("new ArrayList<>()"); - assertThat(getArrayDefaultValue(new ArraySchema(), null,"array|set|map", true, true)).isNull(); - assertThat(getArrayDefaultValue(new ArraySchema(), null,"array|set|map", false, null)).isNull(); - assertThat(getArrayDefaultValue(new ArraySchema(), null,"array|set|map", true, null)).isNull(); + public void testCustomExpression2() throws IOException { + executeTest( + " ! required&?nullable| 7.5.0", + "private List r0n0 = new ArrayList<>();", + "private List r0n1;", + "private List r1n0 = new ArrayList<>();", + "private List r1n1;", + "private List r0;", + "private List r1 = new ArrayList<>();", + "private Set mySet;", + "private Map myMap;" + ); } @Test - public void testArrayDefault_ContainerDefaultToNullTrue() { - assertThat(getArrayDefaultValue(new ArraySchema(), "true",null, false, false)).isNull(); - assertThat(getArrayDefaultValue(new ArraySchema(), "true",null, false, true)).isNull(); - assertThat(getArrayDefaultValue(new ArraySchema(), "true",null, true, false)).isNull(); - assertThat(getArrayDefaultValue(new ArraySchema(), "true",null, true, true)).isNull(); - assertThat(getArrayDefaultValue(new ArraySchema(), "true",null, false, null)).isNull(); - assertThat(getArrayDefaultValue(new ArraySchema(), "true",null, true, null)).isNull(); + public void testNullable() throws IOException { + executeTest( + "nullable", + "private List r0n0 = new ArrayList<>();", + "private List r0n1;", + "private List r1n0 = new ArrayList<>();", + "private List r1n1;", + "private List r0 = new ArrayList<>();", + "private List r1 = new ArrayList<>();", + "private Set mySet;", + "private Map myMap;" + ); } @Test - public void testArrayDefault_7_4_0() { - assertThat(getArrayDefaultValue(new ArraySchema(), "7.4.0",null, false, false)).isNull(); - assertThat(getArrayDefaultValue(new ArraySchema(), "7.4.0",null, false, true)).isNull(); - assertThat(getArrayDefaultValue(new ArraySchema(), "7.4.0",null, true, false)).isEqualTo("new ArrayList<>()"); - assertThat(getArrayDefaultValue(new ArraySchema(), "7.4.0",null, true, true)).isNull(); - assertThat(getArrayDefaultValue(new ArraySchema(), "7.4.0",null, false, null)).isNull(); - assertThat(getArrayDefaultValue(new ArraySchema(), "7.4.0",null, true, null)).isEqualTo("new ArrayList<>()"); + public void testNotRequired() throws IOException { + executeTest( + "!required", + "private List r0n0;", + "private List r0n1;", + "private List r1n0 = new ArrayList<>();", + "private List r1n1 = new ArrayList<>();", + "private List r0;", + "private List r1 = new ArrayList<>();", + "private Set mySet;", + "private Map myMap;" + ); } + private void executeTest(String containerDefaultToNull, String... expectedLines) throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); - @Test - public void testMapDefault_7_4_0() { - MapSchema mapSchema = new MapSchema().type("string"); - mapSchema.setAdditionalProperties(new StringSchema()); - assertThat(getArrayDefaultValue(mapSchema,"7.4.0",null, false, false)).isEqualTo(null); - assertThat(getArrayDefaultValue(mapSchema,"7.4.0",null, false, true)).isNull(); - assertThat(getArrayDefaultValue(mapSchema,"7.4.0",null, true, false)).isEqualTo("new HashMap<>()"); - assertThat(getArrayDefaultValue(mapSchema,"7.4.0", null, true, true)).isNull(); - assertThat(getArrayDefaultValue(mapSchema,"7.4.0", null, false, null)).isEqualTo(null); - assertThat(getArrayDefaultValue(mapSchema,"7.4.0", null, true, null)).isEqualTo("new HashMap<>()"); - } + OpenAPI openAPI = new OpenAPIParser() + .readLocation("src/test/resources/3_0/nullable_required_combinations.yaml", null, new ParseOptions()).getOpenAPI(); + JavaClientCodegen codegen = new JavaClientCodegen(); + codegen.setOutputDir(output.getAbsolutePath()); + codegen.setContainerDefaultToNull(containerDefaultToNull); - @Test - public void testArrayDefault_DefaultSetting() { - assertThat(getArrayDefaultValue(new ArraySchema(), null,null, false, false)).isEqualTo("new ArrayList<>()"); - assertThat(getArrayDefaultValue(new ArraySchema(), null,null, false, true)).isNull(); - assertThat(getArrayDefaultValue(new ArraySchema(), null,null, true, false)).isEqualTo("new ArrayList<>()"); - assertThat(getArrayDefaultValue(new ArraySchema(), null,null, true, true)).isNull(); - assertThat(getArrayDefaultValue(new ArraySchema(), null,null, false, null)).isEqualTo("new ArrayList<>()"); - assertThat(getArrayDefaultValue(new ArraySchema(), null,null, true, null)).isEqualTo("new ArrayList<>()"); - } + ClientOptInput input = new ClientOptInput() + .openAPI(openAPI) + .config(codegen); - String getArrayDefaultValue(Schema containerSchema, String containerDefaultToNull,String setContainerToNullable, boolean required, Boolean nullable) { - - final Schema schema = new Schema() - .addProperties("container", containerSchema); - - if (required) { - schema.setRequired(Arrays.asList("container")); - } - containerSchema.setNullable(nullable); - - OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", schema); - if (setContainerToNullable != null) { - Map options = new HashMap<>(); - options.put("SET_CONTAINER_TO_NULLABLE", setContainerToNullable); - OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options); - openAPINormalizer.normalize(); - } - final JavaClientCodegen codegen = new JavaClientCodegen(); - if (containerDefaultToNull != null) { - codegen.setContainerDefaultToNull(containerDefaultToNull); - } - codegen.processOpts(); - codegen.setOpenAPI(openAPI); - final CodegenModel cm = codegen.fromModel("sample", schema); - - final CodegenProperty property = cm.vars.get(0); - return codegen.toDefaultValue(property, containerSchema); - } + DefaultGenerator generator = new DefaultGenerator(); - private void testAll(String expression, Map expected) { - Map map = ContainerDefaultEvaluator.evaluate(expression); - expected.forEach((containerType, booleans) -> assertThat(map.get(containerType)).describedAs("containerType %s", containerType).isEqualTo(booleans)); + Map files = generator.opts(input).generate().stream() + .collect(Collectors.toMap(File::getName, Function.identity())); + JavaFileAssert.assertThat(files.get("Get200Response.java")).fileContains(expectedLines); } - - private void testAll(String expression, boolean... booleans) { - Map map = ContainerDefaultEvaluator.evaluate(expression); - assertThat(map.get(ContainerDefaultEvaluator.ContainerType.array)).isEqualTo(booleans); + @Test + public void testInvalidInput() { + Assertions.assertThatThrownBy( + () -> new ContainerDefaultEvaluator("NULLABLE") // we expect lowercase input + ).isExactlyInstanceOf(IllegalArgumentException.class); } - } 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 1a4c07ef12e8..a943de6607d7 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 @@ -4882,7 +4882,7 @@ public void testCollectionTypesWithDefaults_issue_18102() throws IOException { 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); + codegen.setContainerDefaultToNull("true"); ClientOptInput input = new ClientOptInput() diff --git a/modules/openapi-generator/src/test/resources/3_0/nullable_required_combinations.yaml b/modules/openapi-generator/src/test/resources/3_0/nullable_required_combinations.yaml new file mode 100644 index 000000000000..8389f8ca459f --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/nullable_required_combinations.yaml @@ -0,0 +1,57 @@ +openapi: 3.0.3 +info: + version: 1.0.0 + title: initialization demo +paths: + /: + get: + responses: + '200': + description: dummy + content: + application/json: + schema: + type: object + required: + - r1n0 + - r1n1 + - r1 + properties: + r0n0: + type: array + items: + type: string + nullable: false + r0n1: + type: array + items: + type: string + nullable: true + r1n0: + type: array + items: + type: string + nullable: false + r1n1: + type: array + items: + type: string + nullable: true + r0: + type: array + items: + type: string + r1: + type: array + items: + type: string + mySet: + uniqueItems: true + type: array + items: + type: string + nullable: true + myMap: + type: object + additionalProperties: true + nullable: true From 88cd4d8b0f102d562e37e24772e79bba5a45fe42 Mon Sep 17 00:00:00 2001 From: Martin <2026226+martin-mfg@users.noreply.github.com> Date: Fri, 23 Aug 2024 13:41:37 +0200 Subject: [PATCH 3/3] two setters --- .../codegen/languages/AbstractJavaCodegen.java | 13 ++++++++++++- .../codegen/java/spring/SpringCodegenTest.java | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) 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 0cc21d1207e5..4fdeb67189c2 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 @@ -175,7 +175,7 @@ public abstract class AbstractJavaCodegen extends DefaultCodegen implements Code @Setter protected String implicitHeadersRegex = null; @Setter protected boolean camelCaseDollarSign = false; @Setter protected boolean useJakartaEe = false; - @Setter protected String containerDefaultToNull = "false"; + protected String containerDefaultToNull = "false"; @Getter @Setter protected boolean generateConstructorWithAllArgs = false; @Getter @Setter @@ -2116,6 +2116,17 @@ public String escapeUnsafeCharacters(String input) { return input.replace("*/", "*_/").replace("/*", "/_*"); } + public void setContainerDefaultToNull(String value) { + this.containerDefaultToNull = value; + } + + /** + * for legacy (before 7.8.0) a boolean can be set + */ + public void setContainerDefaultToNull(boolean value) { + this.containerDefaultToNull = Boolean.toString(value); + } + /* * Derive invoker package name based on the input * e.g. foo.bar.model => foo.bar 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 a943de6607d7..1a4c07ef12e8 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 @@ -4882,7 +4882,7 @@ public void testCollectionTypesWithDefaults_issue_18102() throws IOException { 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"); + codegen.setContainerDefaultToNull(true); ClientOptInput input = new ClientOptInput()