[dart2] [client] Adds correct code generation for enums#17811
Open
0xNF wants to merge 4 commits intoOpenAPITools:masterfrom
Open
[dart2] [client] Adds correct code generation for enums#178110xNF wants to merge 4 commits intoOpenAPITools:masterfrom
0xNF wants to merge 4 commits intoOpenAPITools:masterfrom
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
5 tasks
Contributor
|
Need to regenerate samples, seems you fixed the extra whitespace which I was going to ask about :) |
…ion for non-string enums
Contributor
Author
|
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. |
Contributor
|
Looks fine for me but someone using the dart generator should review this as well. |
…r numeric enums with default values in FromJson methods
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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)