Skip to content

Commit c6a88ea

Browse files
authored
[JAVA] Correct generation of schema default values of type object (issue #21051) (#21278)
* Fix Issue #21051 * Renamed Test to avoid being overriden by JUnit import * Test fix
1 parent c4dad53 commit c6a88ea

File tree

3 files changed

+258
-2
lines changed

3 files changed

+258
-2
lines changed

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
package org.openapitools.codegen.languages;
1919

2020
import com.fasterxml.jackson.databind.JsonNode;
21+
import com.fasterxml.jackson.databind.ObjectMapper;
2122
import com.fasterxml.jackson.databind.node.ArrayNode;
23+
import com.fasterxml.jackson.databind.node.ObjectNode;
2224
import com.google.common.base.Strings;
2325
import com.google.common.collect.Sets;
2426
import com.samskivert.mustache.Mustache;
@@ -1399,7 +1401,74 @@ public String toDefaultValue(CodegenProperty cp, Schema schema) {
13991401
return null;
14001402
} else if (ModelUtils.isObjectSchema(schema)) {
14011403
if (schema.getDefault() != null) {
1402-
return super.toDefaultValue(schema);
1404+
try {
1405+
StringBuilder stringBuilder = new StringBuilder();
1406+
stringBuilder.append("new " + cp.datatypeWithEnum + "()");
1407+
Map<String, Schema> propertySchemas = schema.getProperties();
1408+
if(propertySchemas != null) {
1409+
// With `parseOptions.setResolve(true)`, objects with 1 key-value pair are LinkedHashMap and objects with more than 1 are ObjectNode
1410+
// When not set, objects of any size are ObjectNode
1411+
ObjectMapper objectMapper = new ObjectMapper();
1412+
ObjectNode objectNode;
1413+
if(!(schema.getDefault() instanceof ObjectNode)) {
1414+
objectNode = objectMapper.valueToTree(schema.getDefault());
1415+
} else {
1416+
objectNode = (ObjectNode) schema.getDefault();
1417+
1418+
}
1419+
Set<Map.Entry<String, JsonNode>> defaultProperties = objectNode.properties();
1420+
for (Map.Entry<String, JsonNode> defaultProperty : defaultProperties) {
1421+
String key = defaultProperty.getKey();
1422+
JsonNode value = defaultProperty.getValue();
1423+
Schema propertySchema = propertySchemas.get(key);
1424+
if (!value.isValueNode() || propertySchema == null) { //Skip complex objects for now
1425+
continue;
1426+
}
1427+
1428+
String defaultPropertyExpression = null;
1429+
if(ModelUtils.isLongSchema(propertySchema)) {
1430+
defaultPropertyExpression = value.asText()+"l";
1431+
} else if(ModelUtils.isIntegerSchema(propertySchema)) {
1432+
defaultPropertyExpression = value.asText();
1433+
} else if(ModelUtils.isDoubleSchema(propertySchema)) {
1434+
defaultPropertyExpression = value.asText()+"d";
1435+
} else if(ModelUtils.isFloatSchema(propertySchema)) {
1436+
defaultPropertyExpression = value.asText()+"f";
1437+
} else if(ModelUtils.isNumberSchema(propertySchema)) {
1438+
defaultPropertyExpression = "new java.math.BigDecimal(\"" + value.asText() + "\")";
1439+
} else if(ModelUtils.isURISchema(propertySchema)) {
1440+
defaultPropertyExpression = "java.net.URI.create(\"" + escapeText(value.asText()) + "\")";
1441+
} else if(ModelUtils.isDateSchema(propertySchema)) {
1442+
if("java8".equals(getDateLibrary())) {
1443+
defaultPropertyExpression = String.format(Locale.ROOT, "java.time.LocalDate.parse(\"%s\")", value.asText());
1444+
}
1445+
} else if(ModelUtils.isDateTimeSchema(propertySchema)) {
1446+
if("java8".equals(getDateLibrary())) {
1447+
defaultPropertyExpression = String.format(Locale.ROOT, "java.time.OffsetDateTime.parse(\"%s\", %s)",
1448+
value.asText(),
1449+
"java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME.withZone(java.time.ZoneId.systemDefault())");
1450+
}
1451+
} else if(ModelUtils.isUUIDSchema(propertySchema)) {
1452+
defaultPropertyExpression = "java.util.UUID.fromString(\"" + value.asText() + "\")";
1453+
} else if(ModelUtils.isStringSchema(propertySchema)) {
1454+
defaultPropertyExpression = "\"" + value.asText() + "\"";
1455+
} else if(ModelUtils.isBooleanSchema(propertySchema)) {
1456+
defaultPropertyExpression = value.asText();
1457+
}
1458+
if(defaultPropertyExpression != null) {
1459+
stringBuilder
1460+
// .append(System.lineSeparator())
1461+
.append(".")
1462+
.append(toVarName(key))
1463+
.append("(").append(defaultPropertyExpression).append(")");
1464+
}
1465+
}
1466+
}
1467+
return stringBuilder.toString();
1468+
} catch (ClassCastException e) {
1469+
LOGGER.error("Can't resolve default value: "+schema.getDefault(), e);
1470+
return null;
1471+
}
14031472
}
14041473
return null;
14051474
} else if (ModelUtils.isComposedSchema(schema)) {

modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@
1717

1818
package org.openapitools.codegen.java;
1919

20+
import com.github.javaparser.StaticJavaParser;
21+
import com.github.javaparser.ast.CompilationUnit;
22+
import com.github.javaparser.ast.body.FieldDeclaration;
23+
import com.github.javaparser.ast.expr.Expression;
24+
import com.github.javaparser.ast.expr.MethodCallExpr;
25+
import com.github.javaparser.ast.visitor.*;
2026
import com.google.common.collect.ImmutableMap;
2127
import io.swagger.parser.OpenAPIParser;
2228
import io.swagger.v3.oas.models.OpenAPI;
@@ -60,7 +66,7 @@
6066
import static org.assertj.core.api.Assertions.assertThat;
6167
import static org.assertj.core.api.Assertions.entry;
6268
import static org.assertj.core.api.InstanceOfAssertFactories.FILE;
63-
import static org.openapitools.codegen.CodegenConstants.SERIALIZATION_LIBRARY;
69+
import static org.openapitools.codegen.CodegenConstants.*;
6470
import static org.openapitools.codegen.TestUtils.newTempFolder;
6571
import static org.openapitools.codegen.TestUtils.validateJavaSourceFiles;
6672
import static org.openapitools.codegen.languages.JavaClientCodegen.*;
@@ -3592,4 +3598,71 @@ public void testClassesAreValidJavaOkHttpGson() {
35923598
"public some.pkg.B getsomepkgB() throws ClassCastException {"
35933599
);
35943600
}
3601+
3602+
@Test(description = "Issue #21051")
3603+
public void givenComplexObjectHasDefaultValueWhenGenerateThenDefaultAssignmentsAreValid() throws Exception {
3604+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
3605+
output.deleteOnExit();
3606+
3607+
Map<String, Object> properties = new HashMap<>();
3608+
properties.put(APIS, false);
3609+
properties.put(API_DOCS, false);
3610+
properties.put(API_TESTS, false);
3611+
properties.put(MODEL_DOCS, false);
3612+
properties.put(MODEL_TESTS, false);
3613+
3614+
Generator generator = new DefaultGenerator();
3615+
CodegenConfigurator configurator = new CodegenConfigurator()
3616+
.setInputSpec("src/test/resources/3_1/issue_21051.yaml")
3617+
.setGeneratorName("java")
3618+
.setAdditionalProperties(properties)
3619+
.setOutputDir(output.getAbsolutePath());
3620+
ClientOptInput clientOptInput = configurator.toClientOptInput();
3621+
generator.opts(clientOptInput)
3622+
.generate();
3623+
System.out.println("Generator Settings: " + clientOptInput.getGeneratorSettings());
3624+
String outputPath = output.getAbsolutePath() + "/src/main/java/org/openapitools";
3625+
File testModel = new File(outputPath, "/client/model/TestCase.java");
3626+
String fileContent = Files.readString(testModel.toPath());
3627+
3628+
System.out.println(fileContent);
3629+
TestUtils.assertValidJavaSourceCode(fileContent);
3630+
CompilationUnit compilationUnit = StaticJavaParser.parse(testModel);
3631+
Map<String, FieldDeclaration> defaultFields = compilationUnit.getType(0).getFields().stream()
3632+
.collect(Collectors.toMap((f) -> f.getVariable(0).getName().asString(), (f) -> f));
3633+
//chain method calls for object initialization
3634+
class MethodCallVisitor extends VoidVisitorAdapter<Void> {
3635+
Map<String, Expression> expressionMap = new HashMap<>();
3636+
@Override
3637+
public void visit(MethodCallExpr n, Void arg) {
3638+
expressionMap.put(n.getNameAsString(), n.getArgument(0));
3639+
if(n.getScope().isPresent()) {
3640+
n.getScope().get().accept(this, arg);
3641+
}
3642+
}
3643+
3644+
}
3645+
MethodCallVisitor visitor = new MethodCallVisitor();
3646+
defaultFields.get("testComplexInlineObject").getVariable(0).getInitializer().get().asMethodCallExpr()
3647+
.accept(visitor, null);
3648+
Map<String, Expression> expressionMap = visitor.expressionMap;
3649+
assertTrue(expressionMap.get("foo").isStringLiteralExpr());
3650+
assertTrue(expressionMap.get("fooInt").isIntegerLiteralExpr());
3651+
assertTrue(expressionMap.get("fooLong").isLongLiteralExpr());
3652+
assertTrue(expressionMap.get("fooBool").isBooleanLiteralExpr());
3653+
assertTrue(expressionMap.get("fooFloat").isDoubleLiteralExpr());
3654+
assertTrue(expressionMap.get("fooDouble").isDoubleLiteralExpr());
3655+
assertTrue(expressionMap.containsKey("_void"));
3656+
3657+
assertFalse(expressionMap.containsKey("nonExistentDefault"));
3658+
assertFalse(expressionMap.containsKey("nonDefaultedProperty"));
3659+
3660+
assertTrue(defaultFields.get("testEmptyInlineObject").getVariable(0).getInitializer().get().isObjectCreationExpr());
3661+
assertTrue(defaultFields.get("testNullableEmptyInlineObject").getVariable(0).getInitializer().get().isObjectCreationExpr());
3662+
assertTrue(defaultFields.get("testNullableComplexInlineObject").getVariable(0).getInitializer().get().isMethodCallExpr());
3663+
assertTrue(defaultFields.get("testEmptyReference").getVariable(0).getInitializer().get().isObjectCreationExpr());
3664+
assertTrue(defaultFields.get("testComplexReference").getVariable(0).getInitializer().get().isMethodCallExpr());
3665+
assertTrue(defaultFields.get("testNullableEmptyReference").getVariable(0).getInitializer().get().isObjectCreationExpr());
3666+
assertTrue(defaultFields.get("testNullableComplexReference").getVariable(0).getInitializer().get().isMethodCallExpr());
3667+
}
35953668
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Modified from the original
2+
openapi: 3.1.0
3+
info:
4+
title: Petstore API
5+
description: Petstore API
6+
version: 1.0.0
7+
servers:
8+
- url: 'http://localhost'
9+
components:
10+
schemas:
11+
# Not generated - free-form object
12+
EmptyReferenceObject:
13+
type: object
14+
default: {}
15+
ComplexReferenceObject:
16+
type: object
17+
default:
18+
foo: bar
19+
properties:
20+
foo:
21+
type: string
22+
nullable: true
23+
NullableEmptyReferenceObject:
24+
type: object
25+
default: {}
26+
nullable: true
27+
NullableComplexReferenceObject:
28+
type: object
29+
default:
30+
foo: bar
31+
properties:
32+
foo:
33+
type: string
34+
nullable: true
35+
nullable: true
36+
TestCase:
37+
type: object
38+
properties:
39+
testEmptyInlineObject:
40+
type: object
41+
default: {}
42+
testComplexInlineObject:
43+
type: object
44+
default:
45+
foo: bar
46+
fooInt: 28
47+
fooLong: 5000000000
48+
fooBool: true
49+
fooFloat: 32.5
50+
fooDouble: 3332.555
51+
fooNumber: 120.6
52+
fooDateTime: "2000-01-01T20:20:20+00:00"
53+
fooUUID: "a0ed70ec-5fe5-415a-be97-a7bf13db9fb6"
54+
void: empty
55+
nonExistentDefault: 27
56+
properties:
57+
foo:
58+
type: string
59+
nullable: true
60+
fooInt:
61+
type: integer
62+
nullable: true
63+
fooLong:
64+
type: integer
65+
format: int64
66+
nullable: true
67+
fooBool:
68+
type: boolean
69+
fooFloat:
70+
type: number
71+
format: float
72+
nullable: true
73+
fooDouble:
74+
type: number
75+
format: double
76+
nullable: true
77+
fooNumber:
78+
type: number
79+
nullable: true
80+
fooDateTime:
81+
type: string
82+
format: date-time
83+
nullable: true
84+
fooUUID:
85+
type: string
86+
format: uuid
87+
nullable: true
88+
void: # Java keyword
89+
type: string
90+
nullable: true
91+
nonDefaultedProperty:
92+
type: string
93+
nullable: true
94+
testNullableEmptyInlineObject:
95+
type: object
96+
default: {}
97+
nullable: true
98+
testNullableComplexInlineObject:
99+
type: object
100+
default:
101+
foo: bar
102+
properties:
103+
foo:
104+
type: string
105+
nullable: true
106+
nullable: true
107+
testEmptyReference:
108+
$ref: '#/components/schemas/EmptyReferenceObject'
109+
testComplexReference:
110+
$ref: '#/components/schemas/ComplexReferenceObject'
111+
testNullableEmptyReference:
112+
$ref: '#/components/schemas/NullableEmptyReferenceObject'
113+
testNullableComplexReference:
114+
$ref: '#/components/schemas/NullableComplexReferenceObject'

0 commit comments

Comments
 (0)