Skip to content

Commit b33ba0c

Browse files
committed
[BUG][DART] Fix anyOf undeclared enum, empty class syntax, and nested map cast
Three bugs in the dart generator surfaced from the same end-user spec. The repros are added to the dart fake-petstore yaml as PetReactionStatus, PetReactionResponse, PetEmptyMetadata, PetReactionsResponse so each can be reproduced and red/green tested against generated output. 1. anyOf of [$ref enum, inline literal enum] emitted a property typed as `StatusEnum?` plus `StatusEnum.fromJson(...)`, but never declared the enum class anywhere in lib/model/. The dart generator can't produce a single enum class that covers all branches and the inline-enum template is suppressed for composed schemas. Fix: in AbstractDartCodegen.fromProperty, when the composed schema has 2+ branches and default codegen guessed at isEnum=true, drop the enum projection (clear isEnum, enumName, _enum, allowableValues; reset datatypeWithEnum=dataType). Single-element composed schemas are unchanged. 2. Empty-property classes emitted invalid Dart syntax: `Foo({ })` (Dart rejects empty named-args), a dangling `&&` on `==`, and a missing rhs/`;` on `hashCode`. Fix: `{{#hasVars}}` guards in dart_constructor.mustache and native_class.mustache. Empty classes now emit `Foo()`, `==> identical(this, other) || other is Foo;`, and `hashCode => 0;`. 3. `Map<String, Map<String, T>>` properties were decoded with `mapCastOfType<String, dynamic>` (returns `Map<String, dynamic>`, doesn't cast inner generics) which can't be assigned to the declared field type. Fix: native_class.mustache's nested-map branch now emits `(json[r'X'] as Map).map((k, v) => MapEntry(k as String, (v as Map).cast<String, T>()))`. Also fixes the same broken pattern in the pre-existing additional_properties_class.dart and map_test.dart samples. Verified red/green: pre-fix `dart analyze` reported the exact symptoms (undefined StatusEnum, empty-class syntax errors, Map<String, dynamic> not assignable). Post-fix all three model files are clean; remaining errors in the fake sample are pre-existing and unrelated. Plain petstore_client_lib sample analyzes clean. All 88 org.openapitools .codegen.dart.* tests pass. Closes #23665, closes #16715 Refs #9272, #12914, #15670
1 parent 2917ce8 commit b33ba0c

76 files changed

Lines changed: 1047 additions & 234 deletions

File tree

Some content is hidden

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

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,19 @@ public CodegenProperty fromProperty(String name, Schema p, boolean required) {
719719
property.isModel = true;
720720
});
721721

722+
} else if (property.isEnum) {
723+
// Multi-element anyOf/oneOf where the default codegen guessed at an
724+
// inline enum (e.g. anyOf of [$ref enum, inline literal enum]). The
725+
// dart generator can't produce a single enum class that covers all
726+
// branches, and the inline-enum template is suppressed for composed
727+
// schemas, so we'd emit references to an enum class that is never
728+
// declared anywhere in lib/model/. Collapse the property to its
729+
// underlying primitive type instead.
730+
property.isEnum = false;
731+
property.datatypeWithEnum = property.dataType;
732+
property.enumName = null;
733+
property.allowableValues = null;
734+
property._enum = null;
722735
}
723736
}
724737
return property;
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
/// Returns a new [{{{classname}}}] instance.
2-
{{{classname}}}({
2+
{{{classname}}}({{#hasVars}}{
33
{{#vars}}
44
{{!
55
A field is required in Dart when it is
66
required && !defaultValue in OAS
77
}}
88
{{^required}}{{#vendorExtensions.x-is-optional}}this.{{{name}}}{{#defaultValue}} = const Optional.present({{#isEnum}}{{^isContainer}}const {{{enumName}}}._({{/isContainer}}{{/isEnum}}{{{.}}}{{#isEnum}}{{^isContainer}}){{/isContainer}}{{/isEnum}}){{/defaultValue}}{{^defaultValue}} = const Optional.absent(){{/defaultValue}},{{/vendorExtensions.x-is-optional}}{{/required}}{{^required}}{{^vendorExtensions.x-is-optional}}this.{{{name}}}{{#defaultValue}} = {{#isEnum}}{{^isContainer}}const {{{enumName}}}._({{/isContainer}}{{/isEnum}}{{{.}}}{{#isEnum}}{{^isContainer}}){{/isContainer}}{{/isEnum}}{{/defaultValue}},{{/vendorExtensions.x-is-optional}}{{/required}}{{#required}}{{^defaultValue}}required {{/defaultValue}}{{/required}}{{#required}}this.{{{name}}}{{#defaultValue}} = {{#isEnum}}{{^isContainer}}const {{{enumName}}}._({{/isContainer}}{{/isEnum}}{{{.}}}{{#isEnum}}{{^isContainer}}){{/isContainer}}{{/isEnum}}{{/defaultValue}},{{/required}}
99
{{/vars}}
10-
});
10+
}{{/hasVars}});

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,17 @@ class {{{classname}}} {
4747

4848
{{/vars}}
4949
@override
50-
bool operator ==(Object other) => identical(this, other) || other is {{{classname}}} &&
50+
bool operator ==(Object other) => identical(this, other) || other is {{{classname}}}{{#hasVars}} &&
5151
{{#vars}}
5252
{{#isMap}}_deepEquality.equals(other.{{{name}}}, {{{name}}}){{/isMap}}{{^isMap}}{{#isArray}}_deepEquality.equals(other.{{{name}}}, {{{name}}}){{/isArray}}{{^isArray}}other.{{{name}}} == {{{name}}}{{/isArray}}{{/isMap}}{{^-last}} &&{{/-last}}{{#-last}};{{/-last}}
53-
{{/vars}}
53+
{{/vars}}{{/hasVars}}{{^hasVars}};{{/hasVars}}
5454

5555
@override
5656
int get hashCode =>
5757
// ignore: unnecessary_parenthesis
5858
{{#vars}}
5959
({{#isNullable}}{{{name}}} == null ? 0 : {{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}{{{name}}} == null ? 0 : {{/defaultValue}}{{/required}}{{/isNullable}}{{{name}}}{{#isNullable}}!{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}!{{/defaultValue}}{{/required}}{{/isNullable}}.hashCode){{^-last}} +{{/-last}}{{#-last}};{{/-last}}
60-
{{/vars}}
60+
{{/vars}}{{^hasVars}} 0;{{/hasVars}}
6161

6262
@override
6363
String toString() => '{{{classname}}}[{{#vars}}{{{name}}}=${{{name}}}{{^-last}}, {{/-last}}{{/vars}}]';
@@ -228,7 +228,9 @@ class {{{classname}}} {
228228
{{{name}}}: {{items.complexType}}.mapFromJson(json[r'{{{baseName}}}']),
229229
{{/items.complexType}}
230230
{{^items.complexType}}
231-
{{{name}}}: mapCastOfType<String, dynamic>(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}},
231+
{{{name}}}: json[r'{{{baseName}}}'] == null
232+
? {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}}
233+
: (json[r'{{{baseName}}}'] as Map).map((k, v) => MapEntry(k as String, (v as Map).cast<String, {{items.items.dataType}}>())){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}},
232234
{{/items.complexType}}
233235
{{/items.isMap}}
234236
{{^items.isMap}}

modules/openapi-generator/src/test/resources/3_0/dart/petstore-with-fake-endpoints-models-for-testing.yaml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2141,3 +2141,54 @@ components:
21412141
$ref: '#/components/schemas/ObjectWithInlineEnum'
21422142
object_two:
21432143
$ref: '#/components/schemas/ObjectWithDuplicateInlineEnum'
2144+
PetReactionStatus:
2145+
type: string
2146+
title: PetReactionStatus
2147+
enum:
2148+
- liked
2149+
- disliked
2150+
- barked
2151+
PetReactionResponse:
2152+
type: object
2153+
description: |
2154+
Reproduces a bug where anyOf between a named-enum $ref and an inline
2155+
literal-enum produced a property typed as an undeclared enum class.
2156+
Generator must either inline a proper enum or fall back to String.
2157+
properties:
2158+
petId:
2159+
type: integer
2160+
format: int64
2161+
status:
2162+
anyOf:
2163+
- $ref: '#/components/schemas/PetReactionStatus'
2164+
- type: string
2165+
enum:
2166+
- pending-verification
2167+
PetEmptyMetadata:
2168+
description: |
2169+
Reproduces a bug where a schema that resolves to a class with zero
2170+
properties (e.g. an empty oneOf wrapper) emitted invalid Dart syntax
2171+
in `==`, `hashCode`, and the named-args constructor.
2172+
oneOf:
2173+
- type: string
2174+
- type: integer
2175+
PetReactionsResponse:
2176+
type: object
2177+
description: |
2178+
Reproduces a bug where nested `Map<String, Map<String, T>>` properties
2179+
were decoded with a single-level `mapCastOfType<String, dynamic>` cast
2180+
that can't be assigned to the declared field type.
2181+
properties:
2182+
myReacts:
2183+
type: object
2184+
additionalProperties:
2185+
type: object
2186+
additionalProperties:
2187+
type: boolean
2188+
reactionCounts:
2189+
type: object
2190+
additionalProperties:
2191+
type: object
2192+
additionalProperties:
2193+
type: integer
2194+
format: int32

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,17 @@ class ApiResponse {
4545
@override
4646
bool operator ==(Object other) => identical(this, other) || other is ApiResponse &&
4747
other.code == code &&
48-
other.type == type &&
49-
other.message == message;
48+
other.type == type &&
49+
other.message == message;
50+
5051

5152
@override
5253
int get hashCode =>
5354
// ignore: unnecessary_parenthesis
5455
(code == null ? 0 : code!.hashCode) +
55-
(type == null ? 0 : type!.hashCode) +
56-
(message == null ? 0 : message!.hashCode);
56+
(type == null ? 0 : type!.hashCode) +
57+
(message == null ? 0 : message!.hashCode);
58+
5759

5860
@override
5961
String toString() => 'ApiResponse[code=$code, type=$type, message=$message]';

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,15 @@ class Category {
3636
@override
3737
bool operator ==(Object other) => identical(this, other) || other is Category &&
3838
other.id == id &&
39-
other.name == name;
39+
other.name == name;
40+
4041

4142
@override
4243
int get hashCode =>
4344
// ignore: unnecessary_parenthesis
4445
(id == null ? 0 : id!.hashCode) +
45-
(name == null ? 0 : name!.hashCode);
46+
(name == null ? 0 : name!.hashCode);
47+
4648

4749
@override
4850
String toString() => 'Category[id=$id, name=$name]';

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

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -61,21 +61,23 @@ class Order {
6161
@override
6262
bool operator ==(Object other) => identical(this, other) || other is Order &&
6363
other.id == id &&
64-
other.petId == petId &&
65-
other.quantity == quantity &&
66-
other.shipDate == shipDate &&
67-
other.status == status &&
68-
other.complete == complete;
64+
other.petId == petId &&
65+
other.quantity == quantity &&
66+
other.shipDate == shipDate &&
67+
other.status == status &&
68+
other.complete == complete;
69+
6970

7071
@override
7172
int get hashCode =>
7273
// ignore: unnecessary_parenthesis
7374
(id == null ? 0 : id!.hashCode) +
74-
(petId == null ? 0 : petId!.hashCode) +
75-
(quantity == null ? 0 : quantity!.hashCode) +
76-
(shipDate == null ? 0 : shipDate!.hashCode) +
77-
(status == null ? 0 : status!.hashCode) +
78-
(complete.hashCode);
75+
(petId == null ? 0 : petId!.hashCode) +
76+
(quantity == null ? 0 : quantity!.hashCode) +
77+
(shipDate == null ? 0 : shipDate!.hashCode) +
78+
(status == null ? 0 : status!.hashCode) +
79+
(complete.hashCode);
80+
7981

8082
@override
8183
String toString() => 'Order[id=$id, petId=$petId, quantity=$quantity, shipDate=$shipDate, status=$status, complete=$complete]';

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

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,23 @@ class Pet {
4949
@override
5050
bool operator ==(Object other) => identical(this, other) || other is Pet &&
5151
other.id == id &&
52-
other.category == category &&
53-
other.name == name &&
54-
_deepEquality.equals(other.photoUrls, photoUrls) &&
55-
_deepEquality.equals(other.tags, tags) &&
56-
other.status == status;
52+
other.category == category &&
53+
other.name == name &&
54+
_deepEquality.equals(other.photoUrls, photoUrls) &&
55+
_deepEquality.equals(other.tags, tags) &&
56+
other.status == status;
57+
5758

5859
@override
5960
int get hashCode =>
6061
// ignore: unnecessary_parenthesis
6162
(id == null ? 0 : id!.hashCode) +
62-
(category == null ? 0 : category!.hashCode) +
63-
(name.hashCode) +
64-
(photoUrls.hashCode) +
65-
(tags.hashCode) +
66-
(status == null ? 0 : status!.hashCode);
63+
(category == null ? 0 : category!.hashCode) +
64+
(name.hashCode) +
65+
(photoUrls.hashCode) +
66+
(tags.hashCode) +
67+
(status == null ? 0 : status!.hashCode);
68+
6769

6870
@override
6971
String toString() => 'Pet[id=$id, category=$category, name=$name, photoUrls=$photoUrls, tags=$tags, status=$status]';

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,15 @@ class Tag {
3636
@override
3737
bool operator ==(Object other) => identical(this, other) || other is Tag &&
3838
other.id == id &&
39-
other.name == name;
39+
other.name == name;
40+
4041

4142
@override
4243
int get hashCode =>
4344
// ignore: unnecessary_parenthesis
4445
(id == null ? 0 : id!.hashCode) +
45-
(name == null ? 0 : name!.hashCode);
46+
(name == null ? 0 : name!.hashCode);
47+
4648

4749
@override
4850
String toString() => 'Tag[id=$id, name=$name]';

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

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -91,25 +91,27 @@ class User {
9191
@override
9292
bool operator ==(Object other) => identical(this, other) || other is User &&
9393
other.id == id &&
94-
other.username == username &&
95-
other.firstName == firstName &&
96-
other.lastName == lastName &&
97-
other.email == email &&
98-
other.password == password &&
99-
other.phone == phone &&
100-
other.userStatus == userStatus;
94+
other.username == username &&
95+
other.firstName == firstName &&
96+
other.lastName == lastName &&
97+
other.email == email &&
98+
other.password == password &&
99+
other.phone == phone &&
100+
other.userStatus == userStatus;
101+
101102

102103
@override
103104
int get hashCode =>
104105
// ignore: unnecessary_parenthesis
105106
(id == null ? 0 : id!.hashCode) +
106-
(username == null ? 0 : username!.hashCode) +
107-
(firstName == null ? 0 : firstName!.hashCode) +
108-
(lastName == null ? 0 : lastName!.hashCode) +
109-
(email == null ? 0 : email!.hashCode) +
110-
(password == null ? 0 : password!.hashCode) +
111-
(phone == null ? 0 : phone!.hashCode) +
112-
(userStatus == null ? 0 : userStatus!.hashCode);
107+
(username == null ? 0 : username!.hashCode) +
108+
(firstName == null ? 0 : firstName!.hashCode) +
109+
(lastName == null ? 0 : lastName!.hashCode) +
110+
(email == null ? 0 : email!.hashCode) +
111+
(password == null ? 0 : password!.hashCode) +
112+
(phone == null ? 0 : phone!.hashCode) +
113+
(userStatus == null ? 0 : userStatus!.hashCode);
114+
113115

114116
@override
115117
String toString() => 'User[id=$id, username=$username, firstName=$firstName, lastName=$lastName, email=$email, password=$password, phone=$phone, userStatus=$userStatus]';

0 commit comments

Comments
 (0)