-
-
Notifications
You must be signed in to change notification settings - Fork 7.4k
[dart2] [client] Adds correct code generation for enums #17811
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
…s#17547) & (OpenAPITools#13459). * Generated Enums now have equality and hashcode methods checking against their in-memory id then against their underlying value * Classes with a generated Numeric Enum in their constructor enums now instantiate sytactically correctly, without causing compile errors * Classes with default-available generated Enum in their fromJson() method now use a syntactically correct default, without causing compile errors
|
Need to regenerate samples, seems you fixed the extra whitespace which I was going to ask about :) |
…ion for non-string enums
|
Here's the spec file I'm working with: spec fileopenapi: 3.0.3
info:
version: "1.1"
title: Dart Uint8list Demo
servers:
- url: "localhost"
variables:
host:
default: localhost
paths:
/item:
get:
operationId: GetItem
description: "Should return an Item"
responses:
"200":
description: items
content:
application/json:
schema:
$ref: "#components/schemas/item"
components:
schemas:
WithEnums:
type: object
properties:
stringEnum:
type: string
enum:
- strA
- strB
- strC
default: strC
intEnum:
type: integer
enum:
- 0
- 1
- 2
default: 2
boolEnum:
type: boolean
enum:
- true
- false
default: true
Some sample generated code: generated dart enum//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of lol;
class WithEnums {
/// Returns a new [WithEnums] instance.
WithEnums({
this.stringEnum = const WithEnumsStringEnumEnum._('strC'),
this.intEnum = WithEnumsIntEnumEnum.number2,
this.boolEnum = true,
});
WithEnumsStringEnumEnum stringEnum;
WithEnumsIntEnumEnum intEnum;
bool boolEnum;
@override
bool operator ==(Object other) => identical(this, other) || other is WithEnums && other.stringEnum == stringEnum && other.intEnum == intEnum && other.boolEnum == boolEnum;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(stringEnum.hashCode) + (intEnum.hashCode) + (boolEnum.hashCode);
@override
String toString() => 'WithEnums[stringEnum=$stringEnum, intEnum=$intEnum, boolEnum=$boolEnum]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'stringEnum'] = this.stringEnum;
json[r'intEnum'] = this.intEnum;
json[r'boolEnum'] = this.boolEnum;
return json;
}
/// Returns a new [WithEnums] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static WithEnums? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
// Ensure that the map contains the required keys.
// Note 1: the values aren't checked for validity beyond being non-null.
// Note 2: this code is stripped in release mode!
assert(() {
requiredKeys.forEach((key) {
assert(json.containsKey(key), 'Required key "WithEnums[$key]" is missing from JSON.');
assert(json[key] != null, 'Required key "WithEnums[$key]" has a null value in JSON.');
});
return true;
}());
return WithEnums(
stringEnum: WithEnumsStringEnumEnum.fromJson(json[r'stringEnum']) ?? const WithEnumsStringEnumEnum._('strC'),
intEnum: WithEnumsIntEnumEnum.fromJson(json[r'intEnum']) ?? WithEnumsIntEnumEnum.number2,
boolEnum: mapValueOfType<bool>(json, r'boolEnum') ?? true,
);
}
return null;
}
static List<WithEnums> listFromJson(
dynamic json, {
bool growable = false,
}) {
final result = <WithEnums>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = WithEnums.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, WithEnums> mapFromJson(dynamic json) {
final map = <String, WithEnums>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = WithEnums.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of WithEnums-objects as value to a dart map
static Map<String, List<WithEnums>> mapListFromJson(
dynamic json, {
bool growable = false,
}) {
final map = <String, List<WithEnums>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = WithEnums.listFromJson(
entry.value,
growable: growable,
);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{};
}
class WithEnumsStringEnumEnum {
/// Instantiate a new enum with the provided [value].
const WithEnumsStringEnumEnum._(this.value);
/// The underlying value of this enum member.
final String value;
@override
String toString() => value;
String toJson() => value;
static const strA = WithEnumsStringEnumEnum._(r'strA');
static const strB = WithEnumsStringEnumEnum._(r'strB');
static const strC = WithEnumsStringEnumEnum._(r'strC');
/// List of all possible values in this [enum][WithEnumsStringEnumEnum].
static const values = <WithEnumsStringEnumEnum>[
strA,
strB,
strC,
];
static WithEnumsStringEnumEnum? fromJson(dynamic value) => WithEnumsStringEnumEnumTypeTransformer().decode(value);
static List<WithEnumsStringEnumEnum> listFromJson(
dynamic json, {
bool growable = false,
}) {
final result = <WithEnumsStringEnumEnum>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = WithEnumsStringEnumEnum.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
@override
bool operator ==(Object other) => identical(this, other) || other is WithEnumsStringEnumEnum && other.value == value;
@override
int get hashCode => value.hashCode;
}
/// Transformation class that can [encode] an instance of [WithEnumsStringEnumEnum] to String,
/// and [decode] dynamic data back to [WithEnumsStringEnumEnum].
class WithEnumsStringEnumEnumTypeTransformer {
factory WithEnumsStringEnumEnumTypeTransformer() => _instance ??= const WithEnumsStringEnumEnumTypeTransformer._();
const WithEnumsStringEnumEnumTypeTransformer._();
String encode(WithEnumsStringEnumEnum data) => data.value;
/// Decodes a [dynamic value][data] to a WithEnumsStringEnumEnum.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
///
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
WithEnumsStringEnumEnum? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'strA':
return WithEnumsStringEnumEnum.strA;
case r'strB':
return WithEnumsStringEnumEnum.strB;
case r'strC':
return WithEnumsStringEnumEnum.strC;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [WithEnumsStringEnumEnumTypeTransformer] instance.
static WithEnumsStringEnumEnumTypeTransformer? _instance;
}
class WithEnumsIntEnumEnum {
/// Instantiate a new enum with the provided [value].
const WithEnumsIntEnumEnum._(this.value);
/// The underlying value of this enum member.
final int value;
@override
String toString() => value.toString();
int toJson() => value;
static const number0 = WithEnumsIntEnumEnum._(0);
static const number1 = WithEnumsIntEnumEnum._(1);
static const number2 = WithEnumsIntEnumEnum._(2);
/// List of all possible values in this [enum][WithEnumsIntEnumEnum].
static const values = <WithEnumsIntEnumEnum>[
number0,
number1,
number2,
];
static WithEnumsIntEnumEnum? fromJson(dynamic value) => WithEnumsIntEnumEnumTypeTransformer().decode(value);
static List<WithEnumsIntEnumEnum> listFromJson(
dynamic json, {
bool growable = false,
}) {
final result = <WithEnumsIntEnumEnum>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = WithEnumsIntEnumEnum.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
@override
bool operator ==(Object other) => identical(this, other) || other is WithEnumsIntEnumEnum && other.value == value;
@override
int get hashCode => value.hashCode;
}
/// Transformation class that can [encode] an instance of [WithEnumsIntEnumEnum] to int,
/// and [decode] dynamic data back to [WithEnumsIntEnumEnum].
class WithEnumsIntEnumEnumTypeTransformer {
factory WithEnumsIntEnumEnumTypeTransformer() => _instance ??= const WithEnumsIntEnumEnumTypeTransformer._();
const WithEnumsIntEnumEnumTypeTransformer._();
int encode(WithEnumsIntEnumEnum data) => data.value;
/// Decodes a [dynamic value][data] to a WithEnumsIntEnumEnum.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
///
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
WithEnumsIntEnumEnum? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case 0:
return WithEnumsIntEnumEnum.number0;
case 1:
return WithEnumsIntEnumEnum.number1;
case 2:
return WithEnumsIntEnumEnum.number2;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [WithEnumsIntEnumEnumTypeTransformer] instance.
static WithEnumsIntEnumEnumTypeTransformer? _instance;
}
The key bit is this portion: WithEnums({
this.stringEnum = const WithEnumsStringEnumEnum._('strC'),
this.intEnum = WithEnumsIntEnumEnum.number2,
this.boolEnum = true,
});This is now valid dart code. It's a bit syntactically wonky because the generator is producing the following mustache variable output for the defaultValue field: "defaultValue" : "WithEnumsIntEnumEnum.number2",vs "defaultValue" : "'strC'",But otherwise, the code is technically correct. |
|
Looks fine for me but someone using the dart generator should review this as well. |
…r numeric enums with default values in FromJson methods
fix #17547
fix #13459
This PR is part of the effort to split PR #17548
Numeric enums of the form:
will now generate correct code, instead of invalid, non compile-able dart code.
Enums with a default choice will now also generate valid dart code for instantiating that default:
Additionally, I added hashCode and == override to these enums which check against their inner values. Previously, equality checks would check against the object in memory, because these are kind of fake pseudo enums instead of actual dart supported keyword enums.
@jaumard (2018/09) @josh-burton (2019/12) @amondnet (2019/12) @sbu-WBT (2020/12) @kuhnroyal (2020/12) @agilob (2020/12) @ahmednfwela (2021/08)