From 6f4107bcdab7fc9b871594575af522b910d23f59 Mon Sep 17 00:00:00 2001 From: dagyu Date: Wed, 22 Nov 2023 17:16:43 +0100 Subject: [PATCH 1/6] Make jsonSerializer as enum to support multiples serializer --- swagger_parser/example/swagger_parser.yaml | 2 +- .../lib/src/config/yaml_config.dart | 19 +++++-- .../lib/src/generator/fill_controller.dart | 9 +-- .../lib/src/generator/generator.dart | 11 ++-- .../src/generator/models/json_serializer.dart | 12 ++++ .../models/programming_language.dart | 27 +++++---- .../templates/dart_enum_dto_template.dart | 15 ++++- .../test/generator/data_classes_test.dart | 56 ++++++++++++++----- 8 files changed, 108 insertions(+), 43 deletions(-) create mode 100644 swagger_parser/lib/src/generator/models/json_serializer.dart diff --git a/swagger_parser/example/swagger_parser.yaml b/swagger_parser/example/swagger_parser.yaml index 0379c7d1..ab2a25bc 100644 --- a/swagger_parser/example/swagger_parser.yaml +++ b/swagger_parser/example/swagger_parser.yaml @@ -80,7 +80,7 @@ swagger_parser: schemas: - schema_path: schemas/openapi.json root_client_name: ApiMicroservice - freezed: true + jsonSerializer: "freezed" put_in_folder: true replacement_rules: [] diff --git a/swagger_parser/lib/src/config/yaml_config.dart b/swagger_parser/lib/src/config/yaml_config.dart index 727d38bf..4c3dbd8f 100644 --- a/swagger_parser/lib/src/config/yaml_config.dart +++ b/swagger_parser/lib/src/config/yaml_config.dart @@ -2,6 +2,7 @@ import 'package:args/args.dart'; import 'package:collection/collection.dart'; import 'package:yaml/yaml.dart'; +import '../generator/models/json_serializer.dart'; import '../generator/models/prefer_schema_source.dart'; import '../generator/models/programming_language.dart'; import '../generator/models/replacement_rule.dart'; @@ -26,7 +27,7 @@ final class YamlConfig { this.schemaFromUrlToFile, this.preferSchemaSource, this.language, - this.freezed, + this.jsonSerializer, this.rootClient, this.rootClientName, this.exportFile, @@ -138,9 +139,15 @@ final class YamlConfig { } } - final freezed = yamlConfig['freezed']; - if (freezed is! bool?) { - throw const ConfigException("Config parameter 'freezed' must be bool."); + JsonSerializer? jsonSerializer; + final rawJsonSerializer = yamlConfig['jsonSerializer']?.toString(); + if (rawJsonSerializer != null) { + jsonSerializer = JsonSerializer.fromString(rawJsonSerializer); + if (jsonSerializer == null) { + throw ConfigException( + "'jsonSerializer' field must be contained in ${JsonSerializer.values.map((e) => e.name)}.", + ); + } } final rootClient = @@ -278,7 +285,7 @@ final class YamlConfig { schemaFromUrlToFile ?? rootConfig?.schemaFromUrlToFile, preferSchemaSource: preferSchemaSource ?? rootConfig?.preferSchemaSource, language: language ?? rootConfig?.language, - freezed: freezed ?? rootConfig?.freezed, + jsonSerializer: jsonSerializer ?? rootConfig?.jsonSerializer, rootClient: rootClient ?? rootConfig?.rootClient, rootClientName: rootClientName ?? rootConfig?.rootClientName, exportFile: exportFile ?? rootConfig?.exportFile, @@ -378,7 +385,7 @@ final class YamlConfig { final bool? schemaFromUrlToFile; final PreferSchemaSource? preferSchemaSource; final ProgrammingLanguage? language; - final bool? freezed; + final JsonSerializer? jsonSerializer; final String? clientPostfix; final bool? rootClient; final String? rootClientName; diff --git a/swagger_parser/lib/src/generator/fill_controller.dart b/swagger_parser/lib/src/generator/fill_controller.dart index 0801c72d..a193f36e 100644 --- a/swagger_parser/lib/src/generator/fill_controller.dart +++ b/swagger_parser/lib/src/generator/fill_controller.dart @@ -1,5 +1,6 @@ import '../utils/case_utils.dart'; import 'models/generated_file.dart'; +import 'models/json_serializer.dart'; import 'models/open_api_info.dart'; import 'models/programming_language.dart'; import 'models/universal_data_class.dart'; @@ -15,7 +16,7 @@ final class FillController { String rootClientName = 'RestClient', String exportFileName = 'export', bool putClientsInFolder = false, - bool freezed = false, + JsonSerializer jsonSerializer = JsonSerializer.json_serializable, bool enumsToJson = false, bool unknownEnumValue = true, bool markFilesAsGenerated = false, @@ -26,7 +27,7 @@ final class FillController { _rootClientName = rootClientName, _exportFileName = exportFileName, _putClientsInFolder = putClientsInFolder, - _freezed = freezed, + _jsonSerializer = jsonSerializer, _enumsToJson = enumsToJson, _unknownEnumValue = unknownEnumValue, _markFilesAsGenerated = markFilesAsGenerated, @@ -37,7 +38,7 @@ final class FillController { final String _clientPostfix; final String _rootClientName; final String _exportFileName; - final bool _freezed; + final JsonSerializer _jsonSerializer; final bool _putClientsInFolder; final bool _enumsToJson; final bool _unknownEnumValue; @@ -51,7 +52,7 @@ final class FillController { '.${_programmingLanguage.fileExtension}', contents: _programmingLanguage.dtoFileContent( dataClass, - freezed: _freezed, + jsonSerializer: _jsonSerializer, enumsToJson: _enumsToJson, unknownEnumValue: _unknownEnumValue, markFilesAsGenerated: _markFilesAsGenerated, diff --git a/swagger_parser/lib/src/generator/generator.dart b/swagger_parser/lib/src/generator/generator.dart index 2e02e428..374ad80a 100644 --- a/swagger_parser/lib/src/generator/generator.dart +++ b/swagger_parser/lib/src/generator/generator.dart @@ -11,6 +11,7 @@ import 'fill_controller.dart'; import 'generator_exception.dart'; import 'models/generated_file.dart'; import 'models/generation_statistics.dart'; +import 'models/json_serializer.dart'; import 'models/open_api_info.dart'; import 'models/prefer_schema_source.dart'; import 'models/programming_language.dart'; @@ -34,7 +35,7 @@ final class Generator { PreferSchemaSource? preferSchemeSource, ProgrammingLanguage? language, String? name, - bool? freezed, + JsonSerializer? jsonSerializer, bool? rootClient, String? clientPostfix, bool? exportFile, @@ -59,7 +60,7 @@ final class Generator { _outputDirectory = outputDirectory, _name = name, _programmingLanguage = language ?? ProgrammingLanguage.dart, - _freezed = freezed ?? false, + _jsonSerializer = jsonSerializer ?? JsonSerializer.json_serializable, _rootClient = rootClient ?? true, _rootClientName = rootClientName ?? 'RestClient', _exportFile = exportFile ?? true, @@ -86,7 +87,7 @@ final class Generator { preferSchemeSource: yamlConfig.preferSchemaSource, language: yamlConfig.language, name: yamlConfig.name, - freezed: yamlConfig.freezed, + jsonSerializer: yamlConfig.jsonSerializer, rootClient: yamlConfig.rootClient, rootClientName: yamlConfig.rootClientName, exportFile: yamlConfig.exportFile, @@ -132,7 +133,7 @@ final class Generator { final ProgrammingLanguage _programmingLanguage; /// Use freezed to generate DTOs - final bool _freezed; + final JsonSerializer _jsonSerializer; /// Generate root client for all Clients final bool _rootClient; @@ -309,7 +310,7 @@ final class Generator { rootClientName: _rootClientName, clientPostfix: _clientPostfix, exportFileName: _name ?? 'export', - freezed: _freezed, + jsonSerializer: _jsonSerializer, putClientsInFolder: _putClientsInFolder, enumsToJson: _enumsToJson, unknownEnumValue: _unknownEnumValue, diff --git a/swagger_parser/lib/src/generator/models/json_serializer.dart b/swagger_parser/lib/src/generator/models/json_serializer.dart new file mode 100644 index 00000000..6eb2e966 --- /dev/null +++ b/swagger_parser/lib/src/generator/models/json_serializer.dart @@ -0,0 +1,12 @@ +import 'package:collection/collection.dart'; + +enum JsonSerializer { + json_serializable, + + freezed; + + /// Returns [JsonSerializer] from string + static JsonSerializer? fromString(String string) => values.firstWhereOrNull( + (e) => e.name == string, + ); +} diff --git a/swagger_parser/lib/src/generator/models/programming_language.dart b/swagger_parser/lib/src/generator/models/programming_language.dart index 181c9532..3538c38d 100644 --- a/swagger_parser/lib/src/generator/models/programming_language.dart +++ b/swagger_parser/lib/src/generator/models/programming_language.dart @@ -1,5 +1,6 @@ import 'package:collection/collection.dart'; +import '../../../swagger_parser.dart'; import '../generator_exception.dart'; import '../templates/dart_enum_dto_template.dart'; import '../templates/dart_export_file_template.dart'; @@ -13,6 +14,7 @@ import '../templates/kotlin_moshi_dto_template.dart'; import '../templates/kotlin_retrofit_client_template.dart'; import '../templates/kotlin_typedef_template.dart'; import 'generated_file.dart'; +import 'json_serializer.dart'; import 'open_api_info.dart'; import 'universal_data_class.dart'; import 'universal_rest_client.dart'; @@ -38,7 +40,7 @@ enum ProgrammingLanguage { /// Determines template for generating DTOs by language String dtoFileContent( UniversalDataClass dataClass, { - required bool freezed, + required JsonSerializer jsonSerializer, required bool enumsToJson, required bool unknownEnumValue, required bool markFilesAsGenerated, @@ -48,7 +50,7 @@ enum ProgrammingLanguage { if (dataClass is UniversalEnumClass) { return dartEnumDtoTemplate( dataClass, - freezed: freezed, + jsonSerializer: jsonSerializer, enumsToJson: enumsToJson, unknownEnumValue: unknownEnumValue, markFileAsGenerated: markFilesAsGenerated, @@ -60,16 +62,19 @@ enum ProgrammingLanguage { markFileAsGenerated: markFilesAsGenerated, ); } - if (freezed) { - return dartFreezedDtoTemplate( - dataClass, - markFileAsGenerated: markFilesAsGenerated, - ); + + switch (jsonSerializer) { + case JsonSerializer.freezed: + return dartFreezedDtoTemplate( + dataClass, + markFileAsGenerated: markFilesAsGenerated, + ); + case JsonSerializer.json_serializable: + return dartJsonSerializableDtoTemplate( + dataClass, + markFileAsGenerated: markFilesAsGenerated, + ); } - return dartJsonSerializableDtoTemplate( - dataClass, - markFileAsGenerated: markFilesAsGenerated, - ); } case kotlin: if (dataClass is UniversalEnumClass) { diff --git a/swagger_parser/lib/src/generator/templates/dart_enum_dto_template.dart b/swagger_parser/lib/src/generator/templates/dart_enum_dto_template.dart index 41b45477..82f6df8a 100644 --- a/swagger_parser/lib/src/generator/templates/dart_enum_dto_template.dart +++ b/swagger_parser/lib/src/generator/templates/dart_enum_dto_template.dart @@ -1,14 +1,25 @@ import 'package:collection/collection.dart'; +import '../../../swagger_parser.dart'; import '../../utils/case_utils.dart'; import '../../utils/type_utils.dart'; import '../../utils/utils.dart'; +import '../models/json_serializer.dart'; import '../models/universal_data_class.dart'; +String dartImportDtoTemplate(JsonSerializer jsonSerializer) { + switch (jsonSerializer) { + case JsonSerializer.freezed: + return "import 'package:freezed_annotation/freezed_annotation.dart';"; + case JsonSerializer.json_serializable: + return "import 'package:json_annotation/json_annotation.dart';"; + } +} + /// Provides template for generating dart enum DTO String dartEnumDtoTemplate( UniversalEnumClass enumClass, { - required bool freezed, + required JsonSerializer jsonSerializer, required bool enumsToJson, required bool unknownEnumValue, required bool markFileAsGenerated, @@ -27,7 +38,7 @@ String dartEnumDtoTemplate( return ''' ${generatedFileComment( markFileAsGenerated: markFileAsGenerated, - )}import '${freezed ? 'package:freezed_annotation/freezed_annotation.dart' : 'package:json_annotation/json_annotation.dart'}'; + )}${dartImportDtoTemplate(jsonSerializer)} ${descriptionComment(enumClass.description)}@JsonEnum() enum $className { diff --git a/swagger_parser/test/generator/data_classes_test.dart b/swagger_parser/test/generator/data_classes_test.dart index d8dd68da..c3829594 100644 --- a/swagger_parser/test/generator/data_classes_test.dart +++ b/swagger_parser/test/generator/data_classes_test.dart @@ -2,6 +2,7 @@ import 'package:swagger_parser/src/generator/fill_controller.dart'; import 'package:swagger_parser/src/generator/models/generated_file.dart'; +import 'package:swagger_parser/src/generator/models/json_serializer.dart'; import 'package:swagger_parser/src/generator/models/programming_language.dart'; import 'package:swagger_parser/src/generator/models/universal_data_class.dart'; import 'package:swagger_parser/src/generator/models/universal_type.dart'; @@ -40,7 +41,9 @@ class ClassName { imports: {}, parameters: [], ); - const fillController = FillController(freezed: true); + const fillController = FillController( + jsonSerializer: JsonSerializer.freezed, + ); final filledContent = fillController.fillDtoContent(dataClass); const expectedContents = r''' import 'package:freezed_annotation/freezed_annotation.dart'; @@ -129,7 +132,9 @@ class ClassName { }, parameters: [], ); - const fillController = FillController(freezed: true); + const fillController = FillController( + jsonSerializer: JsonSerializer.freezed, + ); final filledContent = fillController.fillDtoContent(dataClass); const expectedContents = r''' import 'package:freezed_annotation/freezed_annotation.dart'; @@ -284,7 +289,9 @@ class ClassName { UniversalType(type: 'Another', name: 'anotherType'), ], ); - const fillController = FillController(freezed: true); + const fillController = FillController( + jsonSerializer: JsonSerializer.freezed, + ); final filledContent = fillController.fillDtoContent(dataClass); const expectedContents = r''' import 'dart:io'; @@ -434,7 +441,9 @@ class ClassName { UniversalType(type: 'Another', name: 'list5', arrayDepth: 5), ], ); - const fillController = FillController(freezed: true); + const fillController = FillController( + jsonSerializer: JsonSerializer.freezed, + ); final filledContent = fillController.fillDtoContent(dataClass); const expectedContents = r''' import 'package:freezed_annotation/freezed_annotation.dart'; @@ -566,7 +575,9 @@ class ClassName { ), ], ); - const fillController = FillController(freezed: true); + const fillController = FillController( + jsonSerializer: JsonSerializer.freezed, + ); final filledContent = fillController.fillDtoContent(dataClass); const expectedContents = r''' import 'package:freezed_annotation/freezed_annotation.dart'; @@ -742,7 +753,9 @@ class ClassName { ), ], ); - const fillController = FillController(freezed: true); + const fillController = FillController( + jsonSerializer: JsonSerializer.freezed, + ); final filledContent = fillController.fillDtoContent(dataClass); const expectedContents = r''' import 'package:freezed_annotation/freezed_annotation.dart'; @@ -895,7 +908,9 @@ class ClassName { ), ], ); - const fillController = FillController(freezed: true); + const fillController = FillController( + jsonSerializer: JsonSerializer.freezed, + ); final filledContent = fillController.fillDtoContent(dataClass); const expectedContents = r''' import 'package:freezed_annotation/freezed_annotation.dart'; @@ -1031,7 +1046,9 @@ class ClassName { UniversalType(type: 'Another', name: 'list', arrayDepth: 1), ], ); - const fillController = FillController(freezed: true); + const fillController = FillController( + jsonSerializer: JsonSerializer.freezed, + ); final filledContent = fillController.fillDtoContent(dataClass); const expectedContents = r''' import 'package:freezed_annotation/freezed_annotation.dart'; @@ -1287,8 +1304,10 @@ enum EnumNameString { items: UniversalEnumItem.listFromNames({'FALSE', 'for', 'do'}), ), ]; - const fillController = - FillController(freezed: true, unknownEnumValue: false); + const fillController = FillController( + jsonSerializer: JsonSerializer.freezed, + unknownEnumValue: false, + ); final files = []; for (final enumClass in dataClasses) { files.add(fillController.fillDtoContent(enumClass)); @@ -1365,7 +1384,10 @@ enum KeywordsName { ), ), ]; - const fillController = FillController(freezed: true, enumsToJson: true); + const fillController = FillController( + jsonSerializer: JsonSerializer.freezed, + enumsToJson: true, + ); final files = []; for (final enumClass in dataClasses) { files.add(fillController.fillDtoContent(enumClass)); @@ -1573,7 +1595,9 @@ enum EnumName { type: 'int', items: UniversalEnumItem.listFromNames({'-2', '-1', '0', '1'}), ); - const fillController = FillController(freezed: true); + const fillController = FillController( + jsonSerializer: JsonSerializer.freezed, + ); final file = fillController.fillDtoContent(dataClass); const expectedContent = r''' @@ -1846,7 +1870,9 @@ class ClassName { ), ], ); - const fillController = FillController(freezed: true); + const fillController = FillController( + jsonSerializer: JsonSerializer.freezed, + ); final filledContent = fillController.fillDtoContent(dataClass); const expectedContents = r''' import 'package:freezed_annotation/freezed_annotation.dart'; @@ -2060,7 +2086,9 @@ class ClassName { ), ], ); - const fillController = FillController(freezed: true); + const fillController = FillController( + jsonSerializer: JsonSerializer.freezed, + ); final filledContent = fillController.fillDtoContent(dataClass); const expectedContents = r''' import 'package:freezed_annotation/freezed_annotation.dart'; From 0d1c0fd69b78f29e9920210e0745000b3b9a9d87 Mon Sep 17 00:00:00 2001 From: dagyu Date: Thu, 23 Nov 2023 10:14:08 +0100 Subject: [PATCH 2/6] Add support for "dart_mappable" --- swagger_parser/example/build.yaml | 7 ++ swagger_parser/example/pubspec.yaml | 2 + swagger_parser/example/swagger_parser.yaml | 11 +++ .../lib/src/generator/fill_controller.dart | 2 +- .../lib/src/generator/generator.dart | 2 +- .../src/generator/models/json_serializer.dart | 10 ++- .../models/programming_language.dart | 11 ++- .../dart_dart_mappable_dto_template.dart | 83 ++++++++++++++++++ .../templates/dart_enum_dto_template.dart | 79 +++++++++++++---- .../templates/dart_import_dto_template.dart | 12 +++ swagger_parser/lib/src/utils/utils.dart | 2 + .../test/generator/data_classes_test.dart | 87 +++++++++++++++++++ 12 files changed, 282 insertions(+), 26 deletions(-) create mode 100644 swagger_parser/lib/src/generator/templates/dart_dart_mappable_dto_template.dart create mode 100644 swagger_parser/lib/src/generator/templates/dart_import_dto_template.dart diff --git a/swagger_parser/example/build.yaml b/swagger_parser/example/build.yaml index fadcd1af..30220def 100644 --- a/swagger_parser/example/build.yaml +++ b/swagger_parser/example/build.yaml @@ -5,3 +5,10 @@ global_options: json_serializable: runs_before: - retrofit_generator + dart_mappable_builder: + runs_before: + - retrofit_generator + options: + renameMethods: + toJson: toJsonString + toMap: toJson diff --git a/swagger_parser/example/pubspec.yaml b/swagger_parser/example/pubspec.yaml index 210f80a8..dc0ece58 100644 --- a/swagger_parser/example/pubspec.yaml +++ b/swagger_parser/example/pubspec.yaml @@ -5,6 +5,7 @@ environment: sdk: ^3.0.0 dependencies: + dart_mappable: ^4.0.1 dio: ^5.3.3 freezed_annotation: ^2.4.1 json_annotation: ^4.8.1 @@ -13,6 +14,7 @@ dependencies: dev_dependencies: build_runner: ^2.4.6 carapacik_lints: ^1.4.2 + dart_mappable_builder: ^4.0.1 freezed: ^2.4.5 json_serializable: ^6.7.1 retrofit_generator: ^8.0.1 diff --git a/swagger_parser/example/swagger_parser.yaml b/swagger_parser/example/swagger_parser.yaml index ab2a25bc..ba636f2a 100644 --- a/swagger_parser/example/swagger_parser.yaml +++ b/swagger_parser/example/swagger_parser.yaml @@ -72,6 +72,10 @@ swagger_parser: # Example of rule - pattern: "[0-9]+" replacement: "" + # This rule is useful only for dart_mappable because otherwise the generated code will not compile. + # If you use freezed, you can remove this rule. + - pattern: "enum" + replacement: "apiEnum" # Optional. You can pass a list of schemes. # Each schema inherits the parameters described in swagger_parser, @@ -84,6 +88,13 @@ swagger_parser: put_in_folder: true replacement_rules: [] + - schema_url: https://petstore.swagger.io/v2/swagger.json + name: pet_service_dart_mappable + jsonSerializer: "dart_mappable" + client_postfix: Service + put_clients_in_folder: true + put_in_folder: true + - schema_url: https://petstore.swagger.io/v2/swagger.json name: pet_service client_postfix: Service diff --git a/swagger_parser/lib/src/generator/fill_controller.dart b/swagger_parser/lib/src/generator/fill_controller.dart index a193f36e..76797adc 100644 --- a/swagger_parser/lib/src/generator/fill_controller.dart +++ b/swagger_parser/lib/src/generator/fill_controller.dart @@ -16,7 +16,7 @@ final class FillController { String rootClientName = 'RestClient', String exportFileName = 'export', bool putClientsInFolder = false, - JsonSerializer jsonSerializer = JsonSerializer.json_serializable, + JsonSerializer jsonSerializer = JsonSerializer.jsonSerializable, bool enumsToJson = false, bool unknownEnumValue = true, bool markFilesAsGenerated = false, diff --git a/swagger_parser/lib/src/generator/generator.dart b/swagger_parser/lib/src/generator/generator.dart index 374ad80a..98d6142c 100644 --- a/swagger_parser/lib/src/generator/generator.dart +++ b/swagger_parser/lib/src/generator/generator.dart @@ -60,7 +60,7 @@ final class Generator { _outputDirectory = outputDirectory, _name = name, _programmingLanguage = language ?? ProgrammingLanguage.dart, - _jsonSerializer = jsonSerializer ?? JsonSerializer.json_serializable, + _jsonSerializer = jsonSerializer ?? JsonSerializer.jsonSerializable, _rootClient = rootClient ?? true, _rootClientName = rootClientName ?? 'RestClient', _exportFile = exportFile ?? true, diff --git a/swagger_parser/lib/src/generator/models/json_serializer.dart b/swagger_parser/lib/src/generator/models/json_serializer.dart index 6eb2e966..df38cb8d 100644 --- a/swagger_parser/lib/src/generator/models/json_serializer.dart +++ b/swagger_parser/lib/src/generator/models/json_serializer.dart @@ -1,9 +1,15 @@ import 'package:collection/collection.dart'; enum JsonSerializer { - json_serializable, + jsonSerializable('json_serializable'), - freezed; + freezed('freezed'), + + dartMappable('dart_mappable'); + + const JsonSerializer(this.name); + + final String name; /// Returns [JsonSerializer] from string static JsonSerializer? fromString(String string) => values.firstWhereOrNull( diff --git a/swagger_parser/lib/src/generator/models/programming_language.dart b/swagger_parser/lib/src/generator/models/programming_language.dart index 3538c38d..5aabeb11 100644 --- a/swagger_parser/lib/src/generator/models/programming_language.dart +++ b/swagger_parser/lib/src/generator/models/programming_language.dart @@ -2,6 +2,7 @@ import 'package:collection/collection.dart'; import '../../../swagger_parser.dart'; import '../generator_exception.dart'; +import '../templates/dart_dart_mappable_dto_template.dart'; import '../templates/dart_enum_dto_template.dart'; import '../templates/dart_export_file_template.dart'; import '../templates/dart_freezed_dto_template.dart'; @@ -13,11 +14,8 @@ import '../templates/kotlin_enum_dto_template.dart'; import '../templates/kotlin_moshi_dto_template.dart'; import '../templates/kotlin_retrofit_client_template.dart'; import '../templates/kotlin_typedef_template.dart'; -import 'generated_file.dart'; import 'json_serializer.dart'; import 'open_api_info.dart'; -import 'universal_data_class.dart'; -import 'universal_rest_client.dart'; /// Enumerates supported programming languages to determine templates enum ProgrammingLanguage { @@ -69,11 +67,16 @@ enum ProgrammingLanguage { dataClass, markFileAsGenerated: markFilesAsGenerated, ); - case JsonSerializer.json_serializable: + case JsonSerializer.jsonSerializable: return dartJsonSerializableDtoTemplate( dataClass, markFileAsGenerated: markFilesAsGenerated, ); + case JsonSerializer.dartMappable: + return dartDartMappableDtoTemplate( + dataClass, + markFileAsGenerated: markFilesAsGenerated, + ); } } case kotlin: 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 new file mode 100644 index 00000000..4bbd471f --- /dev/null +++ b/swagger_parser/lib/src/generator/templates/dart_dart_mappable_dto_template.dart @@ -0,0 +1,83 @@ +import 'package:collection/collection.dart'; + +import '../../../swagger_parser.dart'; +import '../../utils/case_utils.dart'; +import '../../utils/type_utils.dart'; +import '../../utils/utils.dart'; +import '../models/json_serializer.dart'; +import '../models/universal_type.dart'; +import 'dart_import_dto_template.dart'; + +String dartDartMappableDtoTemplate( + UniversalComponentClass dataClass, { + required bool markFileAsGenerated, +}) { + final className = dataClass.name.toPascal; + 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 { + +${indentation(2)}const $className(${getParameters(dataClass)}); + +${getFields(dataClass)} +} +'''; +} + +String getParameters(UniversalComponentClass dataClass) { + if (dataClass.parameters.isNotEmpty) { + return '{\n${_parametersToString(dataClass.parameters)}\n${indentation(2)}}'; + } else { + return ''; + } +} + +String getFields(UniversalComponentClass dataClass) { + if (dataClass.parameters.isNotEmpty) { + return '${_fieldsToString(dataClass.parameters)}\n'; + } else { + return ''; + } +} + +String _fieldsToString(List parameters) { + final sortedByRequired = + List.from(parameters.sorted((a, b) => a.compareTo(b))); + return sortedByRequired + .mapIndexed( + (i, e) => + '${indentation(2)}final ${e.toSuitableType(ProgrammingLanguage.dart)} ${e.name};', + ) + .join('\n'); +} + +String _parametersToString(List parameters) { + final sortedByRequired = + List.from(parameters.sorted((a, b) => a.compareTo(b))); + return sortedByRequired + .mapIndexed( + (i, e) => + '${indentation(4)}${_required(e)}this.${e.name}${getDefaultValue(e)},', + ) + .join('\n'); +} + +String getDefaultValue(UniversalType t) { + if (t.defaultValue == null) { + return ''; + } + return ' = ${_defaultValue(t)}'; +} + +/// return required if isRequired +String _required(UniversalType t) => + t.isRequired && t.defaultValue == null ? 'required ' : ''; + +/// return defaultValue if have +String _defaultValue(UniversalType t) => + '${t.enumType != null ? '${t.type}.${protectDefaultEnum(t.defaultValue)?.toCamel}' : protectDefaultValue(t.defaultValue, type: t.type)}'; diff --git a/swagger_parser/lib/src/generator/templates/dart_enum_dto_template.dart b/swagger_parser/lib/src/generator/templates/dart_enum_dto_template.dart index 82f6df8a..ea8d73be 100644 --- a/swagger_parser/lib/src/generator/templates/dart_enum_dto_template.dart +++ b/swagger_parser/lib/src/generator/templates/dart_enum_dto_template.dart @@ -6,15 +6,7 @@ import '../../utils/type_utils.dart'; import '../../utils/utils.dart'; import '../models/json_serializer.dart'; import '../models/universal_data_class.dart'; - -String dartImportDtoTemplate(JsonSerializer jsonSerializer) { - switch (jsonSerializer) { - case JsonSerializer.freezed: - return "import 'package:freezed_annotation/freezed_annotation.dart';"; - case JsonSerializer.json_serializable: - return "import 'package:json_annotation/json_annotation.dart';"; - } -} +import 'dart_import_dto_template.dart'; /// Provides template for generating dart enum DTO String dartEnumDtoTemplate( @@ -23,26 +15,67 @@ String dartEnumDtoTemplate( required bool enumsToJson, required bool unknownEnumValue, required bool markFileAsGenerated, +}) { + if (jsonSerializer == JsonSerializer.dartMappable) { + return _dartEnumDartMappableTemplate( + enumClass, + enumsToJson: enumsToJson, + unknownEnumValue: unknownEnumValue, + markFileAsGenerated: markFileAsGenerated, + ); + } else { + final className = enumClass.name.toPascal; + final jsonParam = unknownEnumValue || enumsToJson; + + final values = '${enumClass.items.mapIndexed( + (i, e) => _enumValue( + i, + enumClass.type, + e, + jsonParam: jsonParam, + ), + ).join(',\n')}${unknownEnumValue ? ',' : ';'}'; + final unkownEnumValueStr = unknownEnumValue ? _unkownEnumValue() : ''; + final constructorStr = jsonParam ? _constructor(className) : ''; + final fromJsonStr = unknownEnumValue ? _fromJson(className, enumClass) : ''; + final jsonFieldStr = jsonParam ? _jsonField(enumClass) : ''; + final toJsonStr = enumsToJson ? _toJson(enumClass, className) : ''; + + return ''' +${generatedFileComment( + markFileAsGenerated: markFileAsGenerated, + )}${dartImportDtoTemplate(jsonSerializer)} + +${descriptionComment(enumClass.description)}@JsonEnum() +enum $className { +$values$unkownEnumValueStr$constructorStr$fromJsonStr$jsonFieldStr$toJsonStr +} +'''; + } +} + +String _dartEnumDartMappableTemplate( + UniversalEnumClass enumClass, { + required bool enumsToJson, + required bool unknownEnumValue, + required bool markFileAsGenerated, }) { final className = enumClass.name.toPascal; final jsonParam = unknownEnumValue || enumsToJson; final values = - '${enumClass.items.mapIndexed((i, e) => _enumValue(i, enumClass.type, e, jsonParam: jsonParam)).join(',\n')}${unknownEnumValue ? ',' : ';'}'; - final unkownEnumValueStr = unknownEnumValue ? _unkownEnumValue() : ''; - final constructorStr = jsonParam ? _constructor(className) : ''; - final fromJsonStr = unknownEnumValue ? _fromJson(className, enumClass) : ''; - final jsonFieldStr = jsonParam ? _jsonField(enumClass) : ''; - final toJsonStr = enumsToJson ? _toJson(enumClass, className) : ''; + '${enumClass.items.mapIndexed((i, e) => _enumValueDartMappable(i, enumClass.type, e, jsonParam: jsonParam)).join(',\n')}${unknownEnumValue ? ',' : ';'}'; return ''' ${generatedFileComment( markFileAsGenerated: markFileAsGenerated, - )}${dartImportDtoTemplate(jsonSerializer)} + )}${dartImportDtoTemplate(JsonSerializer.dartMappable)} -${descriptionComment(enumClass.description)}@JsonEnum() +part '${enumClass.name.toSnake}.mapper.dart'; + +${descriptionComment(enumClass.description)}@MappableEnum() enum $className { -$values$unkownEnumValueStr$constructorStr$fromJsonStr$jsonFieldStr$toJsonStr +$values } '''; } @@ -76,5 +109,15 @@ String _enumValue( ${index != 0 ? '\n' : ''}${descriptionComment(item.description, tab: ' ')} @JsonValue(${type == 'string' ? "'${item.jsonKey}'" : item.jsonKey}) ${item.name.toCamel}${jsonParam ? '(${type == 'string' ? "'${item.jsonKey}'" : item.jsonKey})' : ''}'''; +String _enumValueDartMappable( + int index, + String type, + UniversalEnumItem item, { + required bool jsonParam, +}) => + ''' +${index != 0 ? '\n' : ''}${descriptionComment(item.description, tab: ' ')}${indentation(2)}@MappableValue(${type == 'string' ? "'${item.jsonKey}'" : item.jsonKey}) +${indentation(2)}${item.name.toCamel}'''; + String _toJson(UniversalEnumClass enumClass, String className) => '\n\n ${enumClass.type.toDartType()}? toJson() => json;'; diff --git a/swagger_parser/lib/src/generator/templates/dart_import_dto_template.dart b/swagger_parser/lib/src/generator/templates/dart_import_dto_template.dart new file mode 100644 index 00000000..5fc27c72 --- /dev/null +++ b/swagger_parser/lib/src/generator/templates/dart_import_dto_template.dart @@ -0,0 +1,12 @@ +import '../models/json_serializer.dart'; + +String dartImportDtoTemplate(JsonSerializer jsonSerializer) { + switch (jsonSerializer) { + case JsonSerializer.freezed: + return "import 'package:freezed_annotation/freezed_annotation.dart';"; + case JsonSerializer.jsonSerializable: + return "import 'package:json_annotation/json_annotation.dart';"; + case JsonSerializer.dartMappable: + return "import 'package:dart_mappable/dart_mappable.dart';"; + } +} diff --git a/swagger_parser/lib/src/utils/utils.dart b/swagger_parser/lib/src/utils/utils.dart index a8a90ec1..8af637f5 100644 --- a/swagger_parser/lib/src/utils/utils.dart +++ b/swagger_parser/lib/src/utils/utils.dart @@ -24,6 +24,8 @@ String dartImports({required Set imports, String? pathPrefix}) { return '\n${imports.map((import) => "import '${pathPrefix ?? ''}${import.toSnake}.dart';").join('\n')}\n'; } +String indentation(int length) => ' ' * length; + /// Provides description String descriptionComment( String? description, { diff --git a/swagger_parser/test/generator/data_classes_test.dart b/swagger_parser/test/generator/data_classes_test.dart index c3829594..598a8530 100644 --- a/swagger_parser/test/generator/data_classes_test.dart +++ b/swagger_parser/test/generator/data_classes_test.dart @@ -324,6 +324,93 @@ class ClassName with _$ClassName { expect(filledContent.contents, expectedContents); }); + test('dart + dart_mappable', () async { + const dataClass = UniversalComponentClass( + name: 'ClassName', + imports: {}, + parameters: [ + UniversalType(type: 'integer', name: 'intType'), + UniversalType(type: 'number', name: 'numberType'), + UniversalType( + type: 'number', + format: 'double', + name: 'doubleNumberType', + ), + UniversalType( + type: 'number', + format: 'float', + name: 'floatNumberType', + ), + UniversalType(type: 'string', name: 'stringType'), + UniversalType( + type: 'string', + format: 'binary', + name: 'binaryStringType', + ), + UniversalType( + type: 'string', + format: 'date', + name: 'dateStringType', + ), + UniversalType( + type: 'string', + format: 'date-time', + name: 'dateTimeStringType', + ), + UniversalType(type: 'file', name: 'fileType'), + UniversalType(type: 'boolean', name: 'boolType'), + UniversalType(type: 'object', name: 'objectType'), + UniversalType(type: 'Another', name: 'anotherType'), + ], + ); + const fillController = FillController( + jsonSerializer: JsonSerializer.dartMappable, + ); + final filledContent = fillController.fillDtoContent(dataClass); + const expectedContents = ''' +import 'package:dart_mappable/dart_mappable.dart'; + +part 'class_name.mapper.dart'; + +@MappableClass() +class ClassName with ClassNameMappable { + + const ClassName({ + required this.intType, + required this.numberType, + required this.doubleNumberType, + required this.floatNumberType, + required this.stringType, + required this.binaryStringType, + required this.dateStringType, + required this.dateTimeStringType, + required this.fileType, + required this.boolType, + required this.objectType, + required this.anotherType, + }); + + final int intType; + final num numberType; + final double doubleNumberType; + final double floatNumberType; + final String stringType; + final File binaryStringType; + final DateTime dateStringType; + final DateTime dateTimeStringType; + final File fileType; + final bool boolType; + final Object objectType; + final Another anotherType; + +} +'''; + expect( + filledContent.contents, + equalsIgnoringWhitespace(expectedContents), + ); + }); + test('kotlin + moshi', () async { const dataClass = UniversalComponentClass( name: 'ClassName', From 43a02fbc035e715e1afbf8258ea446faee66abdc Mon Sep 17 00:00:00 2001 From: dagyu Date: Fri, 1 Dec 2023 16:24:42 +0100 Subject: [PATCH 3/6] Add generated files to .gitignore and fixed enum name in arrays --- swagger_parser/example/.gitignore | 2 ++ swagger_parser/example/swagger_parser.yaml | 4 ---- swagger_parser/lib/src/parser/parser.dart | 1 + .../test/parser/data_classses_test.dart | 11 +++++++++ .../test/parser/schemas/enum_class.json | 24 +++++++++++++++++++ 5 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 swagger_parser/example/.gitignore create mode 100644 swagger_parser/test/parser/schemas/enum_class.json diff --git a/swagger_parser/example/.gitignore b/swagger_parser/example/.gitignore new file mode 100644 index 00000000..9d7e13b7 --- /dev/null +++ b/swagger_parser/example/.gitignore @@ -0,0 +1,2 @@ +schemas/** +lib/** \ No newline at end of file diff --git a/swagger_parser/example/swagger_parser.yaml b/swagger_parser/example/swagger_parser.yaml index ba636f2a..187a3836 100644 --- a/swagger_parser/example/swagger_parser.yaml +++ b/swagger_parser/example/swagger_parser.yaml @@ -72,10 +72,6 @@ swagger_parser: # Example of rule - pattern: "[0-9]+" replacement: "" - # This rule is useful only for dart_mappable because otherwise the generated code will not compile. - # If you use freezed, you can remove this rule. - - pattern: "enum" - replacement: "apiEnum" # Optional. You can pass a list of schemes. # Each schema inherits the parameters described in swagger_parser, diff --git a/swagger_parser/lib/src/parser/parser.dart b/swagger_parser/lib/src/parser/parser.dart index ed8fd75e..de44d757 100644 --- a/swagger_parser/lib/src/parser/parser.dart +++ b/swagger_parser/lib/src/parser/parser.dart @@ -774,6 +774,7 @@ class OpenApiParser { final arrayItems = map[_itemsConst] as Map; final arrayType = _findType( arrayItems, + name: name, additionalName: name, root: false, ); diff --git a/swagger_parser/test/parser/data_classses_test.dart b/swagger_parser/test/parser/data_classses_test.dart index 800f95e7..a3b21450 100644 --- a/swagger_parser/test/parser/data_classses_test.dart +++ b/swagger_parser/test/parser/data_classses_test.dart @@ -506,5 +506,16 @@ void main() { } expect(item2, expectedItem2); }); + + test('Enum name test', () async { + final schemaPath = p.join('test', 'parser', 'schemas', 'enum_class.json'); + final configFile = schemaFile(schemaPath); + final schemaContent = configFile!.readAsStringSync(); + final parser = OpenApiParser(schemaContent); + final dataClasses = parser.parseDataClasses(); + expect(dataClasses, hasLength(2)); + final enumClass = dataClasses.whereType().first; + expect(enumClass.name, 'Status'); + }); }); } diff --git a/swagger_parser/test/parser/schemas/enum_class.json b/swagger_parser/test/parser/schemas/enum_class.json new file mode 100644 index 00000000..23a04bee --- /dev/null +++ b/swagger_parser/test/parser/schemas/enum_class.json @@ -0,0 +1,24 @@ +{ + "openapi": "3.0.0", + "paths": {}, + "components": { + "schemas": { + "ClassName": { + "type": "object", + "properties": { + "status": { + "description": "Status values that need to be considered for filter", + "required": true, + "type": "array", + "items": { + "type": "string", + "enum": ["available", "pending", "sold"], + "default": "available" + }, + "collectionFormat": "multi" + } + } + } + } + } +} From 17224ba200055fdb521f70fafa5c6b93f45adee8 Mon Sep 17 00:00:00 2001 From: dagyu Date: Fri, 1 Dec 2023 16:29:58 +0100 Subject: [PATCH 4/6] Updated README.md and CHANGELOG.md --- README.md | 5 ++++- swagger_parser/CHANGELOG.md | 5 +++++ swagger_parser/README.md | 14 ++++++++++++-- swagger_parser/pubspec.yaml | 2 +- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2ca8031e..9f21cf9d 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,9 @@ - Supports OpenApi v2, v3.0 and v3.1 - Support JSON and YAML format - Generate REST client files based on Retrofit -- Generate data classes (also on [freezed](https://pub.dev/packages/freezed)) +- Generate data classes, using one of the following serializer: + - [json_serializable](https://pub.dev/packages/json_serializable) + - [freezed](https://pub.dev/packages/freezed) + - [dart_mappable](https://pub.dev/packages/dart_mappable) - Support for multiple languages (Dart, Kotlin) - Web interface at https://carapacik.github.io/swagger_parser diff --git a/swagger_parser/CHANGELOG.md b/swagger_parser/CHANGELOG.md index 3fd04105..254a39b7 100644 --- a/swagger_parser/CHANGELOG.md +++ b/swagger_parser/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.13.0 +- Added support for (`dart_mappable`)[https://github.com/schultek/dart_mappable] serializer +- Changed `freezed` schema property to `jsonSerializer`, which can be set to `freezed`, `dart_mappable` or `json_serializable` (default). +- Fixed enum generation name that are defined inside an array + ## 1.12.2 - Fixes enum duplicate names (#140)[https://github.com/Carapacik/swagger_parser/issues/140] diff --git a/swagger_parser/README.md b/swagger_parser/README.md index b65ee7d2..2680286d 100644 --- a/swagger_parser/README.md +++ b/swagger_parser/README.md @@ -16,7 +16,10 @@ - Support for generation by link - Support for multiple schemes - Generate REST client files based on Retrofit -- Generate data classes (also on [freezed](https://pub.dev/packages/freezed)) +- Generate data classes, using one of the following serializer: + - [json_serializable](https://pub.dev/packages/json_serializable) + - [freezed](https://pub.dev/packages/freezed) + - [dart_mappable](https://pub.dev/packages/dart_mappable) - Support for multiple languages (Dart, Kotlin) - Web interface at https://carapacik.github.io/swagger_parser @@ -144,10 +147,17 @@ swagger_parser: schemas: - schema_path: schemas/openapi.json root_client_name: ApiMicroservice - freezed: true + jsonSerializer: "freezed" put_in_folder: true replacement_rules: [] + - schema_url: https://petstore.swagger.io/v2/swagger.json + name: pet_service_dart_mappable + jsonSerializer: "dart_mappable" + client_postfix: Service + put_clients_in_folder: true + put_in_folder: true + - schema_url: https://petstore.swagger.io/v2/swagger.json name: pet_service client_postfix: Service diff --git a/swagger_parser/pubspec.yaml b/swagger_parser/pubspec.yaml index e88e2f2c..3c226221 100644 --- a/swagger_parser/pubspec.yaml +++ b/swagger_parser/pubspec.yaml @@ -1,6 +1,6 @@ name: swagger_parser description: Package that generates REST clients and data classes from OpenApi definition file -version: 1.12.2 +version: 1.13.0 repository: https://github.com/Carapacik/swagger_parser/tree/main/swagger_parser homepage: https://omega-r.com topics: From 48afcc5dcdfff8bd13e55814612219262340618a Mon Sep 17 00:00:00 2001 From: dagyu Date: Fri, 1 Dec 2023 18:25:32 +0100 Subject: [PATCH 5/6] Add fromJson method --- .../generator/templates/dart_dart_mappable_dto_template.dart | 2 ++ 1 file changed, 2 insertions(+) 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 4bbd471f..f0392dd8 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 @@ -25,6 +25,8 @@ class $className with ${className}Mappable { ${indentation(2)}const $className(${getParameters(dataClass)}); ${getFields(dataClass)} + +static $className fromJson(Map json) => ${className}Mapper.ensureInitialized().decodeMap<${className}>(json); } '''; } From c605d30d8fd283fa668814c7f6441515d8804464 Mon Sep 17 00:00:00 2001 From: dagyu Date: Sat, 2 Dec 2023 11:13:56 +0100 Subject: [PATCH 6/6] Fix test after adding static method fromJson --- .../generator/templates/dart_dart_mappable_dto_template.dart | 2 +- swagger_parser/test/generator/data_classes_test.dart | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) 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 f0392dd8..b4979939 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 @@ ${indentation(2)}const $className(${getParameters(dataClass)}); ${getFields(dataClass)} -static $className fromJson(Map json) => ${className}Mapper.ensureInitialized().decodeMap<${className}>(json); +${indentation(2)}static $className fromJson(Map json) => ${className}Mapper.ensureInitialized().decodeMap<${className}>(json); } '''; } diff --git a/swagger_parser/test/generator/data_classes_test.dart b/swagger_parser/test/generator/data_classes_test.dart index 598a8530..6f2e077c 100644 --- a/swagger_parser/test/generator/data_classes_test.dart +++ b/swagger_parser/test/generator/data_classes_test.dart @@ -403,6 +403,7 @@ class ClassName with ClassNameMappable { final Object objectType; final Another anotherType; + static ClassName fromJson(Map json) => ClassNameMapper.ensureInitialized().decodeMap(json); } '''; expect(