From 98b31312664f4b5dc1e175f71aca6cb3b2e8afd5 Mon Sep 17 00:00:00 2001 From: Josh Burton Date: Tue, 21 Jan 2025 10:09:00 +1300 Subject: [PATCH 1/5] fix: #290 supports oneOf polymorphic types with dart_mappable --- .../dart_dart_mappable_dto_template.dart | 36 ++++++-- .../model/universal_component_class.dart | 14 +++- .../parser/model/universal_data_class.dart | 1 + .../src/parser/parser/open_api_parser.dart | 7 ++ swagger_parser/test/e2e/e2e_test.dart | 13 +++ .../expected_files/models/cat.dart | 1 + .../expected_files/models/dog.dart | 1 + .../models/family_members_union.dart | 3 + .../expected_files/models/human.dart | 1 + .../discriminated_one_of.3.0.json | 83 +++++++++++++++++++ .../expected_files/export.dart | 13 +++ .../expected_files/models/cat.dart | 24 ++++++ .../expected_files/models/cat_type.dart | 13 +++ .../expected_files/models/dog.dart | 24 ++++++ .../expected_files/models/dog_type.dart | 13 +++ .../expected_files/models/family.dart | 21 +++++ .../models/family_members_union.dart | 23 +++++ .../expected_files/models/human.dart | 24 ++++++ .../expected_files/models/human_type.dart | 13 +++ .../test/generator/data_classes_test.dart | 64 +++++++------- 20 files changed, 352 insertions(+), 40 deletions(-) create mode 100644 swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/discriminated_one_of.3.0.json create mode 100644 swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/export.dart create mode 100644 swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/cat.dart create mode 100644 swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/cat_type.dart create mode 100644 swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/dog.dart create mode 100644 swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/dog_type.dart create mode 100644 swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/family.dart create mode 100644 swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/family_members_union.dart create mode 100644 swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/human.dart create mode 100644 swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/human_type.dart diff --git a/swagger_parser/lib/src/generator/templates/dart_dart_mappable_dto_template.dart b/swagger_parser/lib/src/generator/templates/dart_dart_mappable_dto_template.dart index 2e85364e..2e47d6a0 100644 --- a/swagger_parser/lib/src/generator/templates/dart_dart_mappable_dto_template.dart +++ b/swagger_parser/lib/src/generator/templates/dart_dart_mappable_dto_template.dart @@ -13,14 +13,28 @@ String dartDartMappableDtoTemplate( required bool markFileAsGenerated, }) { final className = dataClass.name.toPascal; + + final parent = dataClass.discriminatorValue?.parentClass; + return ''' ${generatedFileComment(markFileAsGenerated: markFileAsGenerated)} ${dartImportDtoTemplate(JsonSerializer.dartMappable)} ${dartImports(imports: dataClass.imports)} part '${dataClass.name.toSnake}.mapper.dart'; -${descriptionComment(dataClass.description)}@MappableClass() -class $className with ${className}Mappable { +${descriptionComment(dataClass.description)}@MappableClass(${() { + if (dataClass.discriminator != null) { + return [ + "discriminatorKey: '${dataClass.discriminator!.propertyName}'", + "includeSubClasses: [${dataClass.discriminator!.discriminatorValueToRefMapping.keys.join(', ')}]", + ].join(", "); + } + if (dataClass.discriminatorValue != null) { + return "discriminatorValue: '${dataClass.discriminatorValue!.propertyValue}'"; + } + return ""; + }()}) +class $className ${parent != null ? "extends $parent " : ""}with ${className}Mappable { ${indentation(2)}const $className(${getParameters(dataClass)}); @@ -34,16 +48,26 @@ ${indentation( } String getParameters(UniversalComponentClass dataClass) { - if (dataClass.parameters.isNotEmpty) { - return '{\n${_parametersToString(dataClass.parameters)}\n${indentation(2)}}'; + // if this class has discriminated values, don't populate the discriminator field + // in the parent class + final parameters = dataClass.parameters + .where((it) => it.name != dataClass.discriminator?.propertyName) + .toList(); + if (parameters.isNotEmpty) { + return '{\n${_parametersToString(parameters)}\n${indentation(2)}}'; } else { return ''; } } String getFields(UniversalComponentClass dataClass) { - if (dataClass.parameters.isNotEmpty) { - return '${_fieldsToString(dataClass.parameters)}\n'; + // if this class has discriminated values, don't populate the discriminator field + // in the parent class + final parameters = dataClass.parameters + .where((it) => it.name != dataClass.discriminator?.propertyName) + .toList(); + if (parameters.isNotEmpty) { + return '${_fieldsToString(parameters)}\n'; } else { return ''; } diff --git a/swagger_parser/lib/src/parser/model/universal_component_class.dart b/swagger_parser/lib/src/parser/model/universal_component_class.dart index f716626d..a3ee125a 100644 --- a/swagger_parser/lib/src/parser/model/universal_component_class.dart +++ b/swagger_parser/lib/src/parser/model/universal_component_class.dart @@ -1,22 +1,25 @@ part of 'universal_data_class.dart'; /// Universal template for containing information about component -@immutable final class UniversalComponentClass extends UniversalDataClass { /// Constructor for [UniversalComponentClass] - const UniversalComponentClass({ + UniversalComponentClass({ required super.name, required this.imports, required this.parameters, this.allOf, this.typeDef = false, this.discriminator, + this.discriminatorValue, super.description, }); /// List of additional references to components final Set imports; + /// The import of this class + String get import => name.toPascal; + /// List of class fields final List parameters; @@ -35,6 +38,13 @@ final class UniversalComponentClass extends UniversalDataClass { Map> refProperties, })? discriminator; + /// When using a discriminated oneOf, where this class is one of the discriminated values, this field contains the information about the parent + ({ + // The name of the property that is used to discriminate the oneOf variants + String propertyValue, + String parentClass, + })? discriminatorValue; + /// Whether or not this schema is a basic type /// "Date": { /// "type": "string", diff --git a/swagger_parser/lib/src/parser/model/universal_data_class.dart b/swagger_parser/lib/src/parser/model/universal_data_class.dart index 0ad4ee87..7aa52a44 100644 --- a/swagger_parser/lib/src/parser/model/universal_data_class.dart +++ b/swagger_parser/lib/src/parser/model/universal_data_class.dart @@ -1,6 +1,7 @@ import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; +import '../utils/case_utils.dart'; import '../utils/type_utils.dart'; import 'universal_type.dart'; diff --git a/swagger_parser/lib/src/parser/parser/open_api_parser.dart b/swagger_parser/lib/src/parser/parser/open_api_parser.dart index cdd17eea..e0412e56 100644 --- a/swagger_parser/lib/src/parser/parser/open_api_parser.dart +++ b/swagger_parser/lib/src/parser/parser/open_api_parser.dart @@ -916,6 +916,13 @@ class OpenApiParser { } discriminator.refProperties[ref] = refedClass.parameters; discriminatedOneOfClass.imports.addAll(refedClass.imports); + discriminatedOneOfClass.imports.add(refedClass.import); + + refedClass.imports.add(discriminatedOneOfClass.import); + refedClass.discriminatorValue = ( + propertyValue: ref, + parentClass: discriminatedOneOfClass.name, + ); } } diff --git a/swagger_parser/test/e2e/e2e_test.dart b/swagger_parser/test/e2e/e2e_test.dart index 842d513c..7109ae76 100644 --- a/swagger_parser/test/e2e/e2e_test.dart +++ b/swagger_parser/test/e2e/e2e_test.dart @@ -198,6 +198,19 @@ void main() { ); }); + test('discriminated_one_of.3.0_mappable', () async { + await e2eTest( + 'basic/discriminated_one_of.3.0_mappable', + (outputDirectory, schemaPath) => SWPConfig( + outputDirectory: outputDirectory, + schemaPath: schemaPath, + jsonSerializer: JsonSerializer.dartMappable, + putClientsInFolder: true, + ), + schemaFileName: 'discriminated_one_of.3.0.json', + ); + }); + test('empty_class.2.0', () async { await e2eTest( 'basic/empty_class.2.0', diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0/expected_files/models/cat.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0/expected_files/models/cat.dart index d91624bb..7b65c1d3 100644 --- a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0/expected_files/models/cat.dart +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0/expected_files/models/cat.dart @@ -5,6 +5,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'cat_type.dart'; +import 'family_members_union.dart'; part 'cat.freezed.dart'; part 'cat.g.dart'; diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0/expected_files/models/dog.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0/expected_files/models/dog.dart index cf88cec3..cf720a78 100644 --- a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0/expected_files/models/dog.dart +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0/expected_files/models/dog.dart @@ -5,6 +5,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'dog_type.dart'; +import 'family_members_union.dart'; part 'dog.freezed.dart'; part 'dog.g.dart'; diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0/expected_files/models/family_members_union.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0/expected_files/models/family_members_union.dart index 8977e723..5e03aef6 100644 --- a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0/expected_files/models/family_members_union.dart +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0/expected_files/models/family_members_union.dart @@ -4,8 +4,11 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import 'cat.dart'; import 'cat_type.dart'; +import 'dog.dart'; import 'dog_type.dart'; +import 'human.dart'; import 'human_type.dart'; part 'family_members_union.freezed.dart'; diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0/expected_files/models/human.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0/expected_files/models/human.dart index 7a69f3c7..c12a9370 100644 --- a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0/expected_files/models/human.dart +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0/expected_files/models/human.dart @@ -4,6 +4,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import 'family_members_union.dart'; import 'human_type.dart'; part 'human.freezed.dart'; diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/discriminated_one_of.3.0.json b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/discriminated_one_of.3.0.json new file mode 100644 index 00000000..d636fcf2 --- /dev/null +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/discriminated_one_of.3.0.json @@ -0,0 +1,83 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Family API", + "version": "1.0.0" + }, + "paths": {}, + "components": { + "schemas": { + "Family": { + "type": "object", + "properties": { + "members": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/Cat" + }, + { + "$ref": "#/components/schemas/Dog" + }, + { + "$ref": "#/components/schemas/Human" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "Cat": "#/components/schemas/Cat", + "Dog": "#/components/schemas/Dog", + "Human": "#/components/schemas/Human" + } + } + } + } + } + }, + "Cat": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["Cat"] + }, + "mewCount": { + "type": "integer", + "description": "Number of times the cat meows." + } + }, + "required": ["type", "mewCount"] + }, + "Dog": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["Dog"] + }, + "barkSound": { + "type": "string", + "description": "The sound of the dog's bark." + } + }, + "required": ["type", "barkSound"] + }, + "Human": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["Human"] + }, + "job": { + "type": "string", + "description": "The job of the human." + } + }, + "required": ["type", "job"] + } + } + } +} \ No newline at end of file diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/export.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/export.dart new file mode 100644 index 00000000..9f4f6638 --- /dev/null +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/export.dart @@ -0,0 +1,13 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, unused_import + +// Data classes +export 'models/family.dart'; +export 'models/cat.dart'; +export 'models/dog.dart'; +export 'models/human.dart'; +export 'models/family_members_union.dart'; +export 'models/cat_type.dart'; +export 'models/dog_type.dart'; +export 'models/human_type.dart'; diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/cat.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/cat.dart new file mode 100644 index 00000000..9a3b90b9 --- /dev/null +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/cat.dart @@ -0,0 +1,24 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, unused_import + +import 'package:dart_mappable/dart_mappable.dart'; + +import 'cat_type.dart'; +import 'family_members_union.dart'; + +part 'cat.mapper.dart'; + +@MappableClass(discriminatorValue: 'Cat') +class Cat extends FamilyMembersUnion with CatMappable { + const Cat({ + required this.type, + required this.mewCount, + }); + + final CatType type; + final int mewCount; + + static Cat fromJson(Map json) => + CatMapper.ensureInitialized().decodeMap(json); +} diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/cat_type.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/cat_type.dart new file mode 100644 index 00000000..be2b5c85 --- /dev/null +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/cat_type.dart @@ -0,0 +1,13 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, unused_import + +import 'package:dart_mappable/dart_mappable.dart'; + +part 'cat_type.mapper.dart'; + +@MappableEnum() +enum CatType { + @MappableValue('Cat') + cat, +} diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/dog.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/dog.dart new file mode 100644 index 00000000..f80e5ab8 --- /dev/null +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/dog.dart @@ -0,0 +1,24 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, unused_import + +import 'package:dart_mappable/dart_mappable.dart'; + +import 'dog_type.dart'; +import 'family_members_union.dart'; + +part 'dog.mapper.dart'; + +@MappableClass(discriminatorValue: 'Dog') +class Dog extends FamilyMembersUnion with DogMappable { + const Dog({ + required this.type, + required this.barkSound, + }); + + final DogType type; + final String barkSound; + + static Dog fromJson(Map json) => + DogMapper.ensureInitialized().decodeMap(json); +} diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/dog_type.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/dog_type.dart new file mode 100644 index 00000000..04585dda --- /dev/null +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/dog_type.dart @@ -0,0 +1,13 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, unused_import + +import 'package:dart_mappable/dart_mappable.dart'; + +part 'dog_type.mapper.dart'; + +@MappableEnum() +enum DogType { + @MappableValue('Dog') + dog, +} diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/family.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/family.dart new file mode 100644 index 00000000..d9b3913a --- /dev/null +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/family.dart @@ -0,0 +1,21 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, unused_import + +import 'package:dart_mappable/dart_mappable.dart'; + +import 'family_members_union.dart'; + +part 'family.mapper.dart'; + +@MappableClass() +class Family with FamilyMappable { + const Family({ + required this.members, + }); + + final List members; + + static Family fromJson(Map json) => + FamilyMapper.ensureInitialized().decodeMap(json); +} diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/family_members_union.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/family_members_union.dart new file mode 100644 index 00000000..bdc5b015 --- /dev/null +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/family_members_union.dart @@ -0,0 +1,23 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, unused_import + +import 'package:dart_mappable/dart_mappable.dart'; + +import 'cat.dart'; +import 'cat_type.dart'; +import 'dog.dart'; +import 'dog_type.dart'; +import 'human.dart'; +import 'human_type.dart'; + +part 'family_members_union.mapper.dart'; + +@MappableClass(discriminatorKey: 'type', includeSubClasses: [Cat, Dog, Human]) +class FamilyMembersUnion with FamilyMembersUnionMappable { + const FamilyMembersUnion(); + + static FamilyMembersUnion fromJson(Map json) => + FamilyMembersUnionMapper.ensureInitialized() + .decodeMap(json); +} diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/human.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/human.dart new file mode 100644 index 00000000..ba14ee03 --- /dev/null +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/human.dart @@ -0,0 +1,24 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, unused_import + +import 'package:dart_mappable/dart_mappable.dart'; + +import 'family_members_union.dart'; +import 'human_type.dart'; + +part 'human.mapper.dart'; + +@MappableClass(discriminatorValue: 'Human') +class Human extends FamilyMembersUnion with HumanMappable { + const Human({ + required this.type, + required this.job, + }); + + final HumanType type; + final String job; + + static Human fromJson(Map json) => + HumanMapper.ensureInitialized().decodeMap(json); +} diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/human_type.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/human_type.dart new file mode 100644 index 00000000..80def142 --- /dev/null +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/human_type.dart @@ -0,0 +1,13 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, unused_import + +import 'package:dart_mappable/dart_mappable.dart'; + +part 'human_type.mapper.dart'; + +@MappableEnum() +enum HumanType { + @MappableValue('Human') + human, +} diff --git a/swagger_parser/test/generator/data_classes_test.dart b/swagger_parser/test/generator/data_classes_test.dart index 197d8c4a..b51fad10 100644 --- a/swagger_parser/test/generator/data_classes_test.dart +++ b/swagger_parser/test/generator/data_classes_test.dart @@ -4,7 +4,7 @@ import 'package:test/test.dart'; void main() { group('Empty data class', () { test('dart + json_serializable', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {}, parameters: [], @@ -31,7 +31,7 @@ class ClassName { }); test('dart + freezed', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {}, parameters: [], @@ -61,7 +61,7 @@ class ClassName with _$ClassName { }); test('kotlin + moshi', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {}, parameters: [], @@ -87,7 +87,7 @@ data class ClassName() group('Imports', () { test('dart + json_serializable', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: { 'camelClass', @@ -126,7 +126,7 @@ class ClassName { }); test('dart + freezed', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: { 'camelClass', @@ -174,7 +174,7 @@ class ClassName with _$ClassName { group('Parameters', () { test('dart + json_serializable', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {}, parameters: [ @@ -295,7 +295,7 @@ class ClassName { }); test('dart + freezed', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {}, parameters: [ @@ -406,7 +406,7 @@ class ClassName with _$ClassName { }); test('dart + dart_mappable', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {}, parameters: [ @@ -531,7 +531,7 @@ class ClassName with ClassNameMappable { }); test('dart + dart_mappable with custom json key', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {}, parameters: [ @@ -576,7 +576,7 @@ class ClassName with ClassNameMappable { }); test('kotlin + moshi', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {}, parameters: [ @@ -681,7 +681,7 @@ data class ClassName( group('Array type', () { test('dart + json_serializable', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {'Another'}, parameters: [ @@ -746,7 +746,7 @@ class ClassName { }); test('dart + freezed', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {'Another'}, parameters: [ @@ -810,7 +810,7 @@ class ClassName with _$ClassName { }); test('kotlin + moshi', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {'Another'}, parameters: [ @@ -868,7 +868,7 @@ data class ClassName( group('JsonKey name', () { test('dart + json_serializable', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {}, parameters: [ @@ -933,7 +933,7 @@ class ClassName { }); test('dart + freezed', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {}, parameters: [ @@ -996,7 +996,7 @@ class ClassName with _$ClassName { }); test('kotlin + moshi', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {}, parameters: [ @@ -1055,7 +1055,7 @@ data class ClassName( group('defaultValue', () { test('dart + json_serializable', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {'Haha'}, parameters: [ @@ -1129,7 +1129,7 @@ class ClassName { }); test('dart + freezed', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {'Haha'}, parameters: [ @@ -1205,7 +1205,7 @@ class ClassName with _$ClassName { }); test('kotlin + moshi', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {}, parameters: [ @@ -1262,7 +1262,7 @@ data class ClassName( group('Required parameters', () { test('dart + json_serializable', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {'Another'}, parameters: [ @@ -1327,7 +1327,7 @@ class ClassName { }); test('dart + freezed', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {'Another'}, parameters: [ @@ -1390,7 +1390,7 @@ class ClassName with _$ClassName { }); test('kotlin + moshi', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {'Another'}, parameters: [ @@ -1447,7 +1447,7 @@ data class ClassName( group('Put required parameters first', () { test('dart + json_serializable', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {'Another'}, parameters: [ @@ -1508,7 +1508,7 @@ class ClassName { }); test('dart + freezed', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {'Another'}, parameters: [ @@ -2152,7 +2152,7 @@ enum class EnumName { group('Typedef data class', () { test('dart', () async { - const dataClasses = [ + final dataClasses = [ UniversalComponentClass( name: 'Date', imports: {}, @@ -2213,7 +2213,7 @@ typedef AnotherValue = Another; }); test('kotlin', () async { - const dataClasses = [ + final dataClasses = [ UniversalComponentClass( name: 'Date', imports: {}, @@ -2278,7 +2278,7 @@ typealias AnotherValue = Another; group('nullable', () { test('dart + json_serializable', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {}, parameters: [ @@ -2352,7 +2352,7 @@ class ClassName { }); test('dart + freezed', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {}, parameters: [ @@ -2423,7 +2423,7 @@ class ClassName with _$ClassName { }); test('kotlin + moshi', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {}, parameters: [ @@ -2490,7 +2490,7 @@ data class ClassName( group('description', () { test('dart + json_serializable', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {}, description: 'Test class', @@ -2586,7 +2586,7 @@ class ClassName { }); test('dart + freezed', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {}, description: 'Test class', @@ -2679,7 +2679,7 @@ class ClassName with _$ClassName { }); test('kotlin + moshi ', () async { - const dataClass = UniversalComponentClass( + final dataClass = UniversalComponentClass( name: 'ClassName', imports: {}, description: 'Test class', From db656770a43d547149c57e621a74e2b408948698 Mon Sep 17 00:00:00 2001 From: Josh Burton Date: Tue, 21 Jan 2025 13:49:21 +1300 Subject: [PATCH 2/5] =?UTF-8?q?fix:=20parse=20discriminator=20for=20types?= =?UTF-8?q?=20without=20=E2=80=98properties=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dart_dart_mappable_dto_template.dart | 2 +- .../model/universal_component_class.dart | 34 +++++---- .../src/parser/parser/open_api_parser.dart | 49 +++++++----- .../discriminated_one_of.3.0.json | 74 +++++++++++++++++-- .../expected_files/export.dart | 5 ++ .../expected_files/models/android_device.dart | 22 ++++++ .../models/android_device_type.dart | 13 ++++ .../expected_files/models/ios_device.dart | 22 ++++++ .../models/ios_device_type.dart | 13 ++++ .../expected_files/models/mobile_device.dart | 21 ++++++ 10 files changed, 214 insertions(+), 41 deletions(-) create mode 100644 swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/android_device.dart create mode 100644 swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/android_device_type.dart create mode 100644 swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/ios_device.dart create mode 100644 swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/ios_device_type.dart create mode 100644 swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/mobile_device.dart diff --git a/swagger_parser/lib/src/generator/templates/dart_dart_mappable_dto_template.dart b/swagger_parser/lib/src/generator/templates/dart_dart_mappable_dto_template.dart index 2e47d6a0..df85d2df 100644 --- a/swagger_parser/lib/src/generator/templates/dart_dart_mappable_dto_template.dart +++ b/swagger_parser/lib/src/generator/templates/dart_dart_mappable_dto_template.dart @@ -26,7 +26,7 @@ ${descriptionComment(dataClass.description)}@MappableClass(${() { if (dataClass.discriminator != null) { return [ "discriminatorKey: '${dataClass.discriminator!.propertyName}'", - "includeSubClasses: [${dataClass.discriminator!.discriminatorValueToRefMapping.keys.join(', ')}]", + "includeSubClasses: [${dataClass.discriminator!.discriminatorValueToRefMapping.values.join(', ')}]", ].join(", "); } if (dataClass.discriminatorValue != null) { diff --git a/swagger_parser/lib/src/parser/model/universal_component_class.dart b/swagger_parser/lib/src/parser/model/universal_component_class.dart index a3ee125a..b7a34850 100644 --- a/swagger_parser/lib/src/parser/model/universal_component_class.dart +++ b/swagger_parser/lib/src/parser/model/universal_component_class.dart @@ -1,5 +1,22 @@ part of 'universal_data_class.dart'; +typedef Discriminator = ({ +// The name of the property that is used to discriminate the oneOf variants + String propertyName, + +// The mapping of the property value to the ref + Map discriminatorValueToRefMapping, + +// The list of properties stored for each ref + Map> refProperties, +}); + +typedef DiscriminatorValue = ({ +// The name of the property that is used to discriminate the oneOf variants + String propertyValue, + String parentClass, +}); + /// Universal template for containing information about component final class UniversalComponentClass extends UniversalDataClass { /// Constructor for [UniversalComponentClass] @@ -27,23 +44,10 @@ final class UniversalComponentClass extends UniversalDataClass { final ({List refs, List properties})? allOf; /// When using a discriminated oneOf, this contains the information about the property name, the mapping of the ref to the property name, and the properties of each of the oneOf variants - final ({ - // The name of the property that is used to discriminate the oneOf variants - String propertyName, - - // The mapping of the property value to the ref - Map discriminatorValueToRefMapping, - - // The list of properties stored for each ref - Map> refProperties, - })? discriminator; + final Discriminator? discriminator; /// When using a discriminated oneOf, where this class is one of the discriminated values, this field contains the information about the parent - ({ - // The name of the property that is used to discriminate the oneOf variants - String propertyValue, - String parentClass, - })? discriminatorValue; + DiscriminatorValue? discriminatorValue; /// Whether or not this schema is a basic type /// "Date": { diff --git a/swagger_parser/lib/src/parser/parser/open_api_parser.dart b/swagger_parser/lib/src/parser/parser/open_api_parser.dart index e0412e56..7ba74bc8 100644 --- a/swagger_parser/lib/src/parser/parser/open_api_parser.dart +++ b/swagger_parser/lib/src/parser/parser/open_api_parser.dart @@ -856,6 +856,7 @@ class OpenApiParser { final allOf = refs.isNotEmpty ? (refs: refs, properties: parameters) : null; + final discriminator = _parseDiscriminatorInfo(value); dataClasses.add( UniversalComponentClass( name: key, @@ -863,6 +864,7 @@ class OpenApiParser { parameters: allOf != null ? [] : parameters, allOf: allOf, description: value[_descriptionConst]?.toString(), + discriminator: discriminator, ), ); }); @@ -920,7 +922,10 @@ class OpenApiParser { refedClass.imports.add(discriminatedOneOfClass.import); refedClass.discriminatorValue = ( - propertyValue: ref, + propertyValue: discriminatedOneOfClass + .discriminator!.discriminatorValueToRefMapping.entries + .firstWhere((it) => it.value == ref) + .key, parentClass: discriminatedOneOfClass.name, ); } @@ -1160,10 +1165,6 @@ class OpenApiParser { .containsKey(_propertyNameConst) && (map[_discriminatorConst] as Map) .containsKey(_mappingConst)) { - final discriminator = map[_discriminatorConst] as Map; - final propertyName = discriminator[_propertyNameConst] as String; - final refMapping = discriminator[_mappingConst] as Map; - // Create a base union class for the discriminated types final baseClassName = '${additionalName ?? ''} ${name ?? ''} Union'.toPascal; @@ -1173,12 +1174,7 @@ class OpenApiParser { description: map[_descriptionConst]?.toString(), ); - // Cleanup the refMapping to contain only the class name - final cleanedRefMapping = {}; - for (final key in refMapping.keys) { - final refMap = {_refConst: refMapping[key]}; - cleanedRefMapping[key] = _formatRef(refMap); - } + final discriminator = _parseDiscriminatorInfo(map); // Create a sealed class to represent the discriminated union _objectClasses.add( @@ -1188,16 +1184,11 @@ class OpenApiParser { parameters: [ UniversalType( type: 'String', - name: propertyName, + name: discriminator?.propertyName, isRequired: true, ), ], - discriminator: ( - propertyName: propertyName, - discriminatorValueToRefMapping: cleanedRefMapping, - // This property is populated by the parser after all the data classes are created - refProperties: >{}, - ), + discriminator: discriminator, ), ); @@ -1372,6 +1363,28 @@ class OpenApiParser { ); } } + + Discriminator? _parseDiscriminatorInfo(Map map) { + if (!map.containsKey(_oneOfConst)) { + return null; + } + final discriminator = map[_discriminatorConst] as Map; + final propertyName = discriminator[_propertyNameConst] as String; + final refMapping = discriminator[_mappingConst] as Map; + + // Cleanup the refMapping to contain only the class name + final cleanedRefMapping = {}; + for (final key in refMapping.keys) { + final refMap = {_refConst: refMapping[key]}; + cleanedRefMapping[key] = _formatRef(refMap); + } + return ( + propertyName: propertyName, + discriminatorValueToRefMapping: cleanedRefMapping, + // This property is populated by the parser after all the data classes are created + refProperties: >{}, + ); + } } /// Extension used for [YamlMap] diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/discriminated_one_of.3.0.json b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/discriminated_one_of.3.0.json index d636fcf2..d1068508 100644 --- a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/discriminated_one_of.3.0.json +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/discriminated_one_of.3.0.json @@ -41,43 +41,103 @@ "properties": { "type": { "type": "string", - "enum": ["Cat"] + "enum": [ + "Cat" + ] }, "mewCount": { "type": "integer", "description": "Number of times the cat meows." } }, - "required": ["type", "mewCount"] + "required": [ + "type", + "mewCount" + ] }, "Dog": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["Dog"] + "enum": [ + "Dog" + ] }, "barkSound": { "type": "string", "description": "The sound of the dog's bark." } }, - "required": ["type", "barkSound"] + "required": [ + "type", + "barkSound" + ] }, "Human": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["Human"] + "enum": [ + "Human" + ] }, "job": { "type": "string", "description": "The job of the human." } }, - "required": ["type", "job"] + "required": [ + "type", + "job" + ] + }, + "MobileDevice": { + "discriminator": { + "propertyName": "type", + "mapping": { + "ios": "#/components/schemas/IOSDevice", + "android": "#/components/schemas/AndroidDevice" + } + }, + "oneOf": [ + { + "$ref": "#/components/schemas/IOSDevice" + }, + { + "$ref": "#/components/schemas/AndroidDevice" + } + ] + }, + "IOSDevice": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "ios" + ] + } + }, + "required": [ + "type" + ] + }, + "AndroidDevice": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "android" + ] + } + }, + "required": [ + "type" + ] } } } -} \ No newline at end of file +} diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/export.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/export.dart index 9f4f6638..76498436 100644 --- a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/export.dart +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/export.dart @@ -7,7 +7,12 @@ export 'models/family.dart'; export 'models/cat.dart'; export 'models/dog.dart'; export 'models/human.dart'; +export 'models/mobile_device.dart'; +export 'models/ios_device.dart'; +export 'models/android_device.dart'; export 'models/family_members_union.dart'; export 'models/cat_type.dart'; export 'models/dog_type.dart'; export 'models/human_type.dart'; +export 'models/ios_device_type.dart'; +export 'models/android_device_type.dart'; diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/android_device.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/android_device.dart new file mode 100644 index 00000000..05516c81 --- /dev/null +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/android_device.dart @@ -0,0 +1,22 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, unused_import + +import 'package:dart_mappable/dart_mappable.dart'; + +import 'android_device_type.dart'; +import 'mobile_device.dart'; + +part 'android_device.mapper.dart'; + +@MappableClass(discriminatorValue: 'android') +class AndroidDevice extends MobileDevice with AndroidDeviceMappable { + const AndroidDevice({ + required this.type, + }); + + final AndroidDeviceType type; + + static AndroidDevice fromJson(Map json) => + AndroidDeviceMapper.ensureInitialized().decodeMap(json); +} diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/android_device_type.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/android_device_type.dart new file mode 100644 index 00000000..57064577 --- /dev/null +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/android_device_type.dart @@ -0,0 +1,13 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, unused_import + +import 'package:dart_mappable/dart_mappable.dart'; + +part 'android_device_type.mapper.dart'; + +@MappableEnum() +enum AndroidDeviceType { + @MappableValue('android') + android, +} diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/ios_device.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/ios_device.dart new file mode 100644 index 00000000..569e23e3 --- /dev/null +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/ios_device.dart @@ -0,0 +1,22 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, unused_import + +import 'package:dart_mappable/dart_mappable.dart'; + +import 'ios_device_type.dart'; +import 'mobile_device.dart'; + +part 'ios_device.mapper.dart'; + +@MappableClass(discriminatorValue: 'ios') +class IosDevice extends MobileDevice with IosDeviceMappable { + const IosDevice({ + required this.type, + }); + + final IosDeviceType type; + + static IosDevice fromJson(Map json) => + IosDeviceMapper.ensureInitialized().decodeMap(json); +} diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/ios_device_type.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/ios_device_type.dart new file mode 100644 index 00000000..478fe691 --- /dev/null +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/ios_device_type.dart @@ -0,0 +1,13 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, unused_import + +import 'package:dart_mappable/dart_mappable.dart'; + +part 'ios_device_type.mapper.dart'; + +@MappableEnum() +enum IosDeviceType { + @MappableValue('ios') + ios, +} diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/mobile_device.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/mobile_device.dart new file mode 100644 index 00000000..a6bb54dd --- /dev/null +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/mobile_device.dart @@ -0,0 +1,21 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, unused_import + +import 'package:dart_mappable/dart_mappable.dart'; + +import 'android_device.dart'; +import 'android_device_type.dart'; +import 'ios_device.dart'; +import 'ios_device_type.dart'; + +part 'mobile_device.mapper.dart'; + +@MappableClass( + discriminatorKey: 'type', includeSubClasses: [IosDevice, AndroidDevice]) +class MobileDevice with MobileDeviceMappable { + const MobileDevice(); + + static MobileDevice fromJson(Map json) => + MobileDeviceMapper.ensureInitialized().decodeMap(json); +} From afc1497104c78a8c73cd2ba2c443b52117022a5a Mon Sep 17 00:00:00 2001 From: Josh Burton Date: Tue, 21 Jan 2025 14:08:04 +1300 Subject: [PATCH 3/5] generates convenience methods when/maybeWhen on discriminated union, for easy access of underlying type --- .../dart_dart_mappable_dto_template.dart | 26 +++++++++++++++++-- .../expected_files/models/android_device.dart | 1 - .../expected_files/models/cat.dart | 1 - .../expected_files/models/dog.dart | 1 - .../expected_files/models/family.dart | 1 - .../models/family_members_union.dart | 25 ++++++++++++++++++ .../expected_files/models/human.dart | 1 - .../expected_files/models/ios_device.dart | 1 - .../expected_files/models/mobile_device.dart | 21 +++++++++++++++ 9 files changed, 70 insertions(+), 8 deletions(-) diff --git a/swagger_parser/lib/src/generator/templates/dart_dart_mappable_dto_template.dart b/swagger_parser/lib/src/generator/templates/dart_dart_mappable_dto_template.dart index df85d2df..52cc5beb 100644 --- a/swagger_parser/lib/src/generator/templates/dart_dart_mappable_dto_template.dart +++ b/swagger_parser/lib/src/generator/templates/dart_dart_mappable_dto_template.dart @@ -37,9 +37,8 @@ ${descriptionComment(dataClass.description)}@MappableClass(${() { class $className ${parent != null ? "extends $parent " : ""}with ${className}Mappable { ${indentation(2)}const $className(${getParameters(dataClass)}); - ${getFields(dataClass)} - +${getDiscriminatorConvenienceMethods(dataClass)} ${indentation( 2, )}static $className fromJson(Map json) => ${className}Mapper.ensureInitialized().decodeMap<$className>(json); @@ -47,6 +46,29 @@ ${indentation( '''; } +String getDiscriminatorConvenienceMethods(UniversalComponentClass dataClass){ + if (dataClass.discriminator == null){ + return ''; + } + return ''' + T when({ + ${dataClass.discriminator!.discriminatorValueToRefMapping.entries.map((e) => 'required T Function(${e.value} ${e.key}) ${e.key.toCamel},').join('\n')} + }) { + return maybeWhen( + ${dataClass.discriminator!.discriminatorValueToRefMapping.entries.map((e) => '${e.key.toCamel}: ${e.key.toCamel},').join('\n')} + )!; + } + T? maybeWhen({ + ${dataClass.discriminator!.discriminatorValueToRefMapping.entries.map((e) => 'required T Function(${e.value} ${e.key}) ${e.key.toCamel},').join('\n')} + }) { + return switch (this) { + ${dataClass.discriminator!.discriminatorValueToRefMapping.entries.map((e) => '${e.value} _ => ${e.key.toCamel}(this as ${e.value}),').join('\n')} + _ => throw Exception("Unhandled type: \${this.runtimeType}"), + }; + } + '''; +} + String getParameters(UniversalComponentClass dataClass) { // if this class has discriminated values, don't populate the discriminator field // in the parent class diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/android_device.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/android_device.dart index 05516c81..9cb8cb7a 100644 --- a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/android_device.dart +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/android_device.dart @@ -14,7 +14,6 @@ class AndroidDevice extends MobileDevice with AndroidDeviceMappable { const AndroidDevice({ required this.type, }); - final AndroidDeviceType type; static AndroidDevice fromJson(Map json) => diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/cat.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/cat.dart index 9a3b90b9..7e3222d5 100644 --- a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/cat.dart +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/cat.dart @@ -15,7 +15,6 @@ class Cat extends FamilyMembersUnion with CatMappable { required this.type, required this.mewCount, }); - final CatType type; final int mewCount; diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/dog.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/dog.dart index f80e5ab8..f076aa6f 100644 --- a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/dog.dart +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/dog.dart @@ -15,7 +15,6 @@ class Dog extends FamilyMembersUnion with DogMappable { required this.type, required this.barkSound, }); - final DogType type; final String barkSound; diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/family.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/family.dart index d9b3913a..3cbf4ed9 100644 --- a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/family.dart +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/family.dart @@ -13,7 +13,6 @@ class Family with FamilyMappable { const Family({ required this.members, }); - final List members; static Family fromJson(Map json) => diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/family_members_union.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/family_members_union.dart index bdc5b015..e05ec2e1 100644 --- a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/family_members_union.dart +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/family_members_union.dart @@ -17,6 +17,31 @@ part 'family_members_union.mapper.dart'; class FamilyMembersUnion with FamilyMembersUnionMappable { const FamilyMembersUnion(); + T when({ + required T Function(Cat Cat) cat, + required T Function(Dog Dog) dog, + required T Function(Human Human) human, + }) { + return maybeWhen( + cat: cat, + dog: dog, + human: human, + )!; + } + + T? maybeWhen({ + required T Function(Cat Cat) cat, + required T Function(Dog Dog) dog, + required T Function(Human Human) human, + }) { + return switch (this) { + Cat _ => cat(this as Cat), + Dog _ => dog(this as Dog), + Human _ => human(this as Human), + _ => throw Exception("Unhandled type: ${this.runtimeType}"), + }; + } + static FamilyMembersUnion fromJson(Map json) => FamilyMembersUnionMapper.ensureInitialized() .decodeMap(json); diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/human.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/human.dart index ba14ee03..31005550 100644 --- a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/human.dart +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/human.dart @@ -15,7 +15,6 @@ class Human extends FamilyMembersUnion with HumanMappable { required this.type, required this.job, }); - final HumanType type; final String job; diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/ios_device.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/ios_device.dart index 569e23e3..74389a40 100644 --- a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/ios_device.dart +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/ios_device.dart @@ -14,7 +14,6 @@ class IosDevice extends MobileDevice with IosDeviceMappable { const IosDevice({ required this.type, }); - final IosDeviceType type; static IosDevice fromJson(Map json) => diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/mobile_device.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/mobile_device.dart index a6bb54dd..5712b4cb 100644 --- a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/mobile_device.dart +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/mobile_device.dart @@ -16,6 +16,27 @@ part 'mobile_device.mapper.dart'; class MobileDevice with MobileDeviceMappable { const MobileDevice(); + T when({ + required T Function(IosDevice ios) ios, + required T Function(AndroidDevice android) android, + }) { + return maybeWhen( + ios: ios, + android: android, + )!; + } + + T? maybeWhen({ + required T Function(IosDevice ios) ios, + required T Function(AndroidDevice android) android, + }) { + return switch (this) { + IosDevice _ => ios(this as IosDevice), + AndroidDevice _ => android(this as AndroidDevice), + _ => throw Exception("Unhandled type: ${this.runtimeType}"), + }; + } + static MobileDevice fromJson(Map json) => MobileDeviceMapper.ensureInitialized().decodeMap(json); } From ff63766ec02800530cf504338517b65c9240184c Mon Sep 17 00:00:00 2001 From: Josh Burton Date: Tue, 21 Jan 2025 14:10:35 +1300 Subject: [PATCH 4/5] fix sanitises strings --- .../templates/dart_dart_mappable_dto_template.dart | 4 ++-- .../expected_files/models/family_members_union.dart | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/swagger_parser/lib/src/generator/templates/dart_dart_mappable_dto_template.dart b/swagger_parser/lib/src/generator/templates/dart_dart_mappable_dto_template.dart index 52cc5beb..34e10390 100644 --- a/swagger_parser/lib/src/generator/templates/dart_dart_mappable_dto_template.dart +++ b/swagger_parser/lib/src/generator/templates/dart_dart_mappable_dto_template.dart @@ -52,14 +52,14 @@ String getDiscriminatorConvenienceMethods(UniversalComponentClass dataClass){ } return ''' T when({ - ${dataClass.discriminator!.discriminatorValueToRefMapping.entries.map((e) => 'required T Function(${e.value} ${e.key}) ${e.key.toCamel},').join('\n')} + ${dataClass.discriminator!.discriminatorValueToRefMapping.entries.map((e) => 'required T Function(${e.value} ${e.key.toCamel}) ${e.key.toCamel},').join('\n')} }) { return maybeWhen( ${dataClass.discriminator!.discriminatorValueToRefMapping.entries.map((e) => '${e.key.toCamel}: ${e.key.toCamel},').join('\n')} )!; } T? maybeWhen({ - ${dataClass.discriminator!.discriminatorValueToRefMapping.entries.map((e) => 'required T Function(${e.value} ${e.key}) ${e.key.toCamel},').join('\n')} + ${dataClass.discriminator!.discriminatorValueToRefMapping.entries.map((e) => 'required T Function(${e.value} ${e.key.toCamel}) ${e.key.toCamel},').join('\n')} }) { return switch (this) { ${dataClass.discriminator!.discriminatorValueToRefMapping.entries.map((e) => '${e.value} _ => ${e.key.toCamel}(this as ${e.value}),').join('\n')} diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/family_members_union.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/family_members_union.dart index e05ec2e1..cf9fca61 100644 --- a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/family_members_union.dart +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/family_members_union.dart @@ -18,9 +18,9 @@ class FamilyMembersUnion with FamilyMembersUnionMappable { const FamilyMembersUnion(); T when({ - required T Function(Cat Cat) cat, - required T Function(Dog Dog) dog, - required T Function(Human Human) human, + required T Function(Cat cat) cat, + required T Function(Dog dog) dog, + required T Function(Human human) human, }) { return maybeWhen( cat: cat, @@ -30,9 +30,9 @@ class FamilyMembersUnion with FamilyMembersUnionMappable { } T? maybeWhen({ - required T Function(Cat Cat) cat, - required T Function(Dog Dog) dog, - required T Function(Human Human) human, + required T Function(Cat cat) cat, + required T Function(Dog dog) dog, + required T Function(Human human) human, }) { return switch (this) { Cat _ => cat(this as Cat), From ea3c86f2573a1f356a37a390bf574a97e8f06b02 Mon Sep 17 00:00:00 2001 From: Josh Burton Date: Tue, 21 Jan 2025 14:28:44 +1300 Subject: [PATCH 5/5] fix: maybeWhen syntax --- .../templates/dart_dart_mappable_dto_template.dart | 4 ++-- .../expected_files/models/family_members_union.dart | 12 ++++++------ .../expected_files/models/mobile_device.dart | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/swagger_parser/lib/src/generator/templates/dart_dart_mappable_dto_template.dart b/swagger_parser/lib/src/generator/templates/dart_dart_mappable_dto_template.dart index 34e10390..7bda29a8 100644 --- a/swagger_parser/lib/src/generator/templates/dart_dart_mappable_dto_template.dart +++ b/swagger_parser/lib/src/generator/templates/dart_dart_mappable_dto_template.dart @@ -59,10 +59,10 @@ String getDiscriminatorConvenienceMethods(UniversalComponentClass dataClass){ )!; } T? maybeWhen({ - ${dataClass.discriminator!.discriminatorValueToRefMapping.entries.map((e) => 'required T Function(${e.value} ${e.key.toCamel}) ${e.key.toCamel},').join('\n')} + ${dataClass.discriminator!.discriminatorValueToRefMapping.entries.map((e) => 'T Function(${e.value} ${e.key.toCamel})? ${e.key.toCamel},').join('\n')} }) { return switch (this) { - ${dataClass.discriminator!.discriminatorValueToRefMapping.entries.map((e) => '${e.value} _ => ${e.key.toCamel}(this as ${e.value}),').join('\n')} + ${dataClass.discriminator!.discriminatorValueToRefMapping.entries.map((e) => '${e.value} _ => ${e.key.toCamel}?.call(this as ${e.value}),').join('\n')} _ => throw Exception("Unhandled type: \${this.runtimeType}"), }; } diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/family_members_union.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/family_members_union.dart index cf9fca61..cabd1d9b 100644 --- a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/family_members_union.dart +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/family_members_union.dart @@ -30,14 +30,14 @@ class FamilyMembersUnion with FamilyMembersUnionMappable { } T? maybeWhen({ - required T Function(Cat cat) cat, - required T Function(Dog dog) dog, - required T Function(Human human) human, + T Function(Cat cat)? cat, + T Function(Dog dog)? dog, + T Function(Human human)? human, }) { return switch (this) { - Cat _ => cat(this as Cat), - Dog _ => dog(this as Dog), - Human _ => human(this as Human), + Cat _ => cat?.call(this as Cat), + Dog _ => dog?.call(this as Dog), + Human _ => human?.call(this as Human), _ => throw Exception("Unhandled type: ${this.runtimeType}"), }; } diff --git a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/mobile_device.dart b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/mobile_device.dart index 5712b4cb..da5c6601 100644 --- a/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/mobile_device.dart +++ b/swagger_parser/test/e2e/tests/basic/discriminated_one_of.3.0_mappable/expected_files/models/mobile_device.dart @@ -27,12 +27,12 @@ class MobileDevice with MobileDeviceMappable { } T? maybeWhen({ - required T Function(IosDevice ios) ios, - required T Function(AndroidDevice android) android, + T Function(IosDevice ios)? ios, + T Function(AndroidDevice android)? android, }) { return switch (this) { - IosDevice _ => ios(this as IosDevice), - AndroidDevice _ => android(this as AndroidDevice), + IosDevice _ => ios?.call(this as IosDevice), + AndroidDevice _ => android?.call(this as AndroidDevice), _ => throw Exception("Unhandled type: ${this.runtimeType}"), }; }