Skip to content

Commit 17a28eb

Browse files
authored
[dart] Fix enum defaults, Object arrays, and nullable required assertions in native serialization (#23027)
* fix(dart): fix enum defaults, Object arrays, and nullable required assertions in native serialization * update docs
1 parent 86a42dc commit 17a28eb

File tree

62 files changed

+167
-237
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+167
-237
lines changed

docs/generators/dart-dio.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
5858
## LANGUAGE PRIMITIVES
5959

6060
<ul class="column-ul">
61+
<li>Object</li>
6162
<li>String</li>
6263
<li>bool</li>
6364
<li>double</li>

docs/generators/dart.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
5454
## LANGUAGE PRIMITIVES
5555

5656
<ul class="column-ul">
57+
<li>Object</li>
5758
<li>String</li>
5859
<li>bool</li>
5960
<li>double</li>

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ public AbstractDartCodegen() {
128128
"bool",
129129
"int",
130130
"num",
131-
"double"
131+
"double",
132+
"Object"
132133
);
133134

134135
typeMapping = new HashMap<>();

modules/openapi-generator/src/main/resources/dart2/serialization/native/native_class.mustache

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,14 @@ class {{{classname}}} {
119119
// Note 1: the values aren't checked for validity beyond being non-null.
120120
// Note 2: this code is stripped in release mode!
121121
assert(() {
122-
requiredKeys.forEach((key) {
123-
assert(json.containsKey(key), 'Required key "{{{classname}}}[$key]" is missing from JSON.');
124-
assert(json[key] != null, 'Required key "{{{classname}}}[$key]" has a null value in JSON.');
125-
});
122+
{{#vars}}
123+
{{#required}}
124+
assert(json.containsKey(r'{{{baseName}}}'), 'Required key "{{{classname}}}[{{{baseName}}}]" is missing from JSON.');
125+
{{^isNullable}}
126+
assert(json[r'{{{baseName}}}'] != null, 'Required key "{{{classname}}}[{{{baseName}}}]" has a null value in JSON.');
127+
{{/isNullable}}
128+
{{/required}}
129+
{{/vars}}
126130
return true;
127131
}());
128132

@@ -221,7 +225,7 @@ class {{{classname}}} {
221225
{{{name}}}: mapValueOfType<{{{datatypeWithEnum}}}>(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}},
222226
{{/isEnum}}
223227
{{#isEnum}}
224-
{{{name}}}: {{{enumName}}}.fromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}},
228+
{{{name}}}: {{{enumName}}}.fromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? const {{{enumName}}}._({{{.}}}){{/defaultValue}}{{/required}},
225229
{{/isEnum}}
226230
{{/isNumber}}
227231
{{/isMap}}

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

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

1818
package org.openapitools.codegen.dart;
1919

20-
import org.openapitools.codegen.CodegenConstants;
20+
import org.openapitools.codegen.*;
21+
import org.openapitools.codegen.config.CodegenConfigurator;
2122
import org.openapitools.codegen.languages.DartClientCodegen;
2223
import org.testng.Assert;
2324
import org.testng.annotations.Test;
2425

2526
import java.io.BufferedReader;
27+
import java.io.File;
2628
import java.io.FileInputStream;
2729
import java.io.InputStreamReader;
2830
import java.nio.charset.StandardCharsets;
31+
import java.nio.file.Files;
2932
import java.util.ArrayList;
3033
import java.util.List;
3134
import java.util.Locale;
@@ -96,4 +99,74 @@ public void testEnumPropertyWithQuotes() {
9699
Assert.assertEquals(codegen.toEnumValue("1.0", "number"), "1.0");
97100
Assert.assertEquals(codegen.toEnumValue("1", "int"), "1");
98101
}
102+
103+
private List<File> generateDartNativeFromSpec(String specPath) throws Exception {
104+
File output = Files.createTempDirectory("dart-native-test").toFile();
105+
output.deleteOnExit();
106+
107+
final CodegenConfigurator configurator = new CodegenConfigurator()
108+
.setGeneratorName("dart")
109+
.setInputSpec(specPath)
110+
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));
111+
112+
ClientOptInput opts = configurator.toClientOptInput();
113+
DefaultGenerator generator = new DefaultGenerator();
114+
List<File> files = generator.opts(opts).generate();
115+
files.forEach(File::deleteOnExit);
116+
return files;
117+
}
118+
119+
@Test(description = "Object-typed arrays should not produce Object.listFromJson()")
120+
public void testObjectArrayDoesNotUseListFromJson() throws Exception {
121+
List<File> files = generateDartNativeFromSpec(
122+
"src/test/resources/3_0/dart/dart-native-deserialization-bugs.yaml");
123+
124+
File modelFile = files.stream()
125+
.filter(f -> f.getName().equals("object_array_model.dart"))
126+
.findFirst()
127+
.orElseThrow(() -> new AssertionError("object_array_model.dart not found in generated files"));
128+
129+
// Object is a primitive type and has no listFromJson — should use cast<Object>() instead
130+
TestUtils.assertFileNotContains(modelFile.toPath(), "Object.listFromJson");
131+
TestUtils.assertFileContains(modelFile.toPath(), "cast<Object>");
132+
}
133+
134+
@Test(description = "Enum properties with defaults should emit enum constructor, not string literal")
135+
public void testEnumDefaultUsesEnumConstructor() throws Exception {
136+
List<File> files = generateDartNativeFromSpec(
137+
"src/test/resources/3_0/dart/dart-native-deserialization-bugs.yaml");
138+
139+
File modelFile = files.stream()
140+
.filter(f -> f.getName().equals("enum_default_model.dart"))
141+
.findFirst()
142+
.orElseThrow(() -> new AssertionError("enum_default_model.dart not found in generated files"));
143+
144+
// Default should use const EnumType._('value'), not a bare string literal
145+
TestUtils.assertFileNotContains(modelFile.toPath(), "?? 'active'");
146+
TestUtils.assertFileContains(modelFile.toPath(),
147+
"?? const EnumDefaultModelStatusEnum._('active')");
148+
}
149+
150+
@Test(description = "Required+nullable fields should not assert non-null")
151+
public void testRequiredNullableFieldsDoNotAssertNonNull() throws Exception {
152+
List<File> files = generateDartNativeFromSpec(
153+
"src/test/resources/3_0/dart/dart-native-deserialization-bugs.yaml");
154+
155+
File modelFile = files.stream()
156+
.filter(f -> f.getName().equals("nullable_required_model.dart"))
157+
.findFirst()
158+
.orElseThrow(() -> new AssertionError("nullable_required_model.dart not found in generated files"));
159+
160+
// Required key 'name' (non-nullable) should assert both presence and non-null
161+
TestUtils.assertFileContains(modelFile.toPath(),
162+
"json.containsKey(r'name')");
163+
TestUtils.assertFileContains(modelFile.toPath(),
164+
"json[r'name'] != null");
165+
166+
// Required key 'nickname' (nullable) should assert presence but NOT non-null
167+
TestUtils.assertFileContains(modelFile.toPath(),
168+
"json.containsKey(r'nickname')");
169+
TestUtils.assertFileNotContains(modelFile.toPath(),
170+
"json[r'nickname'] != null");
171+
}
99172
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
openapi: 3.0.0
2+
info:
3+
title: Dart Native Deserialization Bugs
4+
description: Test spec for verifying fixes to Dart native serialization codegen bugs.
5+
version: 1.0.0
6+
paths: {}
7+
components:
8+
schemas:
9+
ObjectArrayModel:
10+
type: object
11+
properties:
12+
items:
13+
type: array
14+
items: {}
15+
EnumDefaultModel:
16+
type: object
17+
properties:
18+
status:
19+
type: string
20+
enum:
21+
- active
22+
- inactive
23+
default: active
24+
NullableRequiredModel:
25+
type: object
26+
required:
27+
- name
28+
- nickname
29+
properties:
30+
name:
31+
type: string
32+
nickname:
33+
type: string
34+
nullable: true

samples/openapi3/client/petstore/dart2/petstore_client_lib/lib/model/api_response.dart

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,6 @@ class ApiResponse {
8989
// Note 1: the values aren't checked for validity beyond being non-null.
9090
// Note 2: this code is stripped in release mode!
9191
assert(() {
92-
requiredKeys.forEach((key) {
93-
assert(json.containsKey(key), 'Required key "ApiResponse[$key]" is missing from JSON.');
94-
assert(json[key] != null, 'Required key "ApiResponse[$key]" has a null value in JSON.');
95-
});
9692
return true;
9793
}());
9894

samples/openapi3/client/petstore/dart2/petstore_client_lib/lib/model/category.dart

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,6 @@ class Category {
7373
// Note 1: the values aren't checked for validity beyond being non-null.
7474
// Note 2: this code is stripped in release mode!
7575
assert(() {
76-
requiredKeys.forEach((key) {
77-
assert(json.containsKey(key), 'Required key "Category[$key]" is missing from JSON.');
78-
assert(json[key] != null, 'Required key "Category[$key]" has a null value in JSON.');
79-
});
8076
return true;
8177
}());
8278

samples/openapi3/client/petstore/dart2/petstore_client_lib/lib/model/order.dart

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,6 @@ class Order {
122122
// Note 1: the values aren't checked for validity beyond being non-null.
123123
// Note 2: this code is stripped in release mode!
124124
assert(() {
125-
requiredKeys.forEach((key) {
126-
assert(json.containsKey(key), 'Required key "Order[$key]" is missing from JSON.');
127-
assert(json[key] != null, 'Required key "Order[$key]" has a null value in JSON.');
128-
});
129125
return true;
130126
}());
131127

samples/openapi3/client/petstore/dart2/petstore_client_lib/lib/model/pet.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,10 @@ class Pet {
102102
// Note 1: the values aren't checked for validity beyond being non-null.
103103
// Note 2: this code is stripped in release mode!
104104
assert(() {
105-
requiredKeys.forEach((key) {
106-
assert(json.containsKey(key), 'Required key "Pet[$key]" is missing from JSON.');
107-
assert(json[key] != null, 'Required key "Pet[$key]" has a null value in JSON.');
108-
});
105+
assert(json.containsKey(r'name'), 'Required key "Pet[name]" is missing from JSON.');
106+
assert(json[r'name'] != null, 'Required key "Pet[name]" has a null value in JSON.');
107+
assert(json.containsKey(r'photoUrls'), 'Required key "Pet[photoUrls]" is missing from JSON.');
108+
assert(json[r'photoUrls'] != null, 'Required key "Pet[photoUrls]" has a null value in JSON.');
109109
return true;
110110
}());
111111

0 commit comments

Comments
 (0)