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/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/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 0379c7d1..187a3836 100644 --- a/swagger_parser/example/swagger_parser.yaml +++ b/swagger_parser/example/swagger_parser.yaml @@ -80,10 +80,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/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..76797adc 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.jsonSerializable, 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..98d6142c 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.jsonSerializable, _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..df38cb8d --- /dev/null +++ b/swagger_parser/lib/src/generator/models/json_serializer.dart @@ -0,0 +1,18 @@ +import 'package:collection/collection.dart'; + +enum JsonSerializer { + jsonSerializable('json_serializable'), + + freezed('freezed'), + + dartMappable('dart_mappable'); + + const JsonSerializer(this.name); + + final String name; + + /// 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..5aabeb11 100644 --- a/swagger_parser/lib/src/generator/models/programming_language.dart +++ b/swagger_parser/lib/src/generator/models/programming_language.dart @@ -1,6 +1,8 @@ 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'; @@ -12,10 +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 { @@ -38,7 +38,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 +48,7 @@ enum ProgrammingLanguage { if (dataClass is UniversalEnumClass) { return dartEnumDtoTemplate( dataClass, - freezed: freezed, + jsonSerializer: jsonSerializer, enumsToJson: enumsToJson, unknownEnumValue: unknownEnumValue, markFileAsGenerated: markFilesAsGenerated, @@ -60,16 +60,24 @@ enum ProgrammingLanguage { markFileAsGenerated: markFilesAsGenerated, ); } - if (freezed) { - return dartFreezedDtoTemplate( - dataClass, - markFileAsGenerated: markFilesAsGenerated, - ); + + switch (jsonSerializer) { + case JsonSerializer.freezed: + return dartFreezedDtoTemplate( + dataClass, + markFileAsGenerated: markFilesAsGenerated, + ); + case JsonSerializer.jsonSerializable: + return dartJsonSerializableDtoTemplate( + dataClass, + markFileAsGenerated: markFilesAsGenerated, + ); + case JsonSerializer.dartMappable: + return dartDartMappableDtoTemplate( + dataClass, + markFileAsGenerated: markFilesAsGenerated, + ); } - return dartJsonSerializableDtoTemplate( - dataClass, - markFileAsGenerated: markFilesAsGenerated, - ); } case kotlin: if (dataClass is UniversalEnumClass) { 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..b4979939 --- /dev/null +++ b/swagger_parser/lib/src/generator/templates/dart_dart_mappable_dto_template.dart @@ -0,0 +1,85 @@ +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)} + +${indentation(2)}static $className fromJson(Map json) => ${className}Mapper.ensureInitialized().decodeMap<${className}>(json); +} +'''; +} + +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 41b45477..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 @@ -1,14 +1,61 @@ 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'; +import 'dart_import_dto_template.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, +}) { + 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, @@ -17,21 +64,18 @@ String dartEnumDtoTemplate( 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, - )}import '${freezed ? 'package:freezed_annotation/freezed_annotation.dart' : 'package:json_annotation/json_annotation.dart'}'; + )}${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 } '''; } @@ -65,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/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/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/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: diff --git a/swagger_parser/test/generator/data_classes_test.dart b/swagger_parser/test/generator/data_classes_test.dart index d8dd68da..6f2e077c 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'; @@ -317,6 +324,94 @@ 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; + + static ClassName fromJson(Map json) => ClassNameMapper.ensureInitialized().decodeMap(json); +} +'''; + expect( + filledContent.contents, + equalsIgnoringWhitespace(expectedContents), + ); + }); + test('kotlin + moshi', () async { const dataClass = UniversalComponentClass( name: 'ClassName', @@ -434,7 +529,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 +663,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 +841,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 +996,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 +1134,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 +1392,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 +1472,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 +1683,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 +1958,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 +2174,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'; 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" + } + } + } + } + } +}