diff --git a/swagger_parser/CHANGELOG.md b/swagger_parser/CHANGELOG.md index 65d4a471..38ecb4d9 100644 --- a/swagger_parser/CHANGELOG.md +++ b/swagger_parser/CHANGELOG.md @@ -1,3 +1,6 @@ +## 1.41.0 +- Add `field_parsers` option for setting field parsers for JSON serializable + ## 1.40.0 - Add `include_paths` option for filtering endpoints by paths diff --git a/swagger_parser/README.md b/swagger_parser/README.md index aec73916..2b424fc3 100644 --- a/swagger_parser/README.md +++ b/swagger_parser/README.md @@ -169,6 +169,22 @@ swagger_parser: - "/some/wildcard/*/path" - "/another/wildcard/**" + # Optional (dart & json_serializable only). Set field parsers for JSON serializable. + # + # Field parsers are used to parse specific fields of a DTO. + # + # Parser must implements JsonConverter interface from json_annotation package. + field_parsers: + - apply_to_type: "int" + parser_name: "CustomIntParser" + parser_absolute_path: "package:your_package/lib/utils/parsers/custom_int_parser.dart" + - apply_to_type: "int?" + parser_name: "CustomNullableIntParser" + parser_absolute_path: "package:your_package/lib/utils/parsers/custom_nullable_int_parser.dart" + - apply_to_type: "bool" + parser_name: "CustomBoolParser" + parser_absolute_path: "package:your_package/lib/utils/parsers/custom_bool_parser.dart" + # Optional. Skip parameters with names. skipped_parameters: - "X-Some-Token" diff --git a/swagger_parser/lib/src/config/swp_config.dart b/swagger_parser/lib/src/config/swp_config.dart index 842cba9c..f3bc934e 100644 --- a/swagger_parser/lib/src/config/swp_config.dart +++ b/swagger_parser/lib/src/config/swp_config.dart @@ -1,6 +1,7 @@ import 'package:args/args.dart'; import 'package:swagger_parser/src/config/config_exception.dart'; import 'package:swagger_parser/src/generator/config/generator_config.dart'; +import 'package:swagger_parser/src/generator/model/field_parser.dart'; import 'package:swagger_parser/src/generator/model/json_serializer.dart'; import 'package:swagger_parser/src/generator/model/programming_language.dart'; import 'package:swagger_parser/src/parser/swagger_parser_core.dart'; @@ -50,6 +51,7 @@ class SWPConfig { this.inferRequiredFromNullable = false, this.useFlutterCompute = false, this.generateUrlsConstants = false, + this.fieldParsers = const [], }); /// Internal constructor of [SWPConfig] @@ -93,6 +95,7 @@ class SWPConfig { required this.inferRequiredFromNullable, required this.useFlutterCompute, required this.generateUrlsConstants, + required this.fieldParsers, this.fallbackUnion, }); @@ -348,12 +351,38 @@ class SWPConfig { final generateUrlsConstants = yamlMap['generate_urls_constants'] as bool? ?? rootConfig?.generateUrlsConstants; + final rawFieldParsers = yamlMap['field_parsers'] as YamlList?; + List? fieldParsers; + if (rawFieldParsers != null) { + fieldParsers = []; + for (final p in rawFieldParsers) { + if (p is! YamlMap || + p['apply_to_type'] is! String || + p['parser_name'] is! String || + p['parser_absolute_path'] is! String) { + throw const ConfigException( + "Config parameter 'field_parsers' values must be List of maps with 'apply_to_type', 'parser_name', and 'parser_absolute_path'.", + ); + } + fieldParsers.add( + FieldParser( + applyToType: p['apply_to_type'].toString(), + parserName: p['parser_name'].toString(), + parserAbsolutePath: p['parser_absolute_path'].toString(), + ), + ); + } + } else if (rootConfig?.fieldParsers != null) { + fieldParsers = List.from(rootConfig!.fieldParsers); + } + // Default config final dc = SWPConfig(name: name, outputDirectory: outputDirectory); return SWPConfig._( schemaPath: schemaPath, schemaUrl: schemaUrl, + fieldParsers: fieldParsers ?? dc.fieldParsers, outputDirectory: outputDirectory, name: name, pathMethodName: pathMethodName ?? dc.pathMethodName, @@ -617,6 +646,15 @@ class SWPConfig { /// Optional. Set `true` to generate URL constants for all endpoints. final bool generateUrlsConstants; + /// {@template field_parsers} + /// DART ONLY + /// Optional. Set field parsers. + /// + /// Field parsers are used to parse specific fields of a DTO. + /// + /// {@endtemplate} + final List fieldParsers; + /// Convert [SWPConfig] to [GeneratorConfig] GeneratorConfig toGeneratorConfig() { return GeneratorConfig( @@ -647,6 +685,7 @@ class SWPConfig { includeIfNull: includeIfNull, useFlutterCompute: useFlutterCompute, generateUrlsConstants: generateUrlsConstants, + fieldParsers: fieldParsers, ); } diff --git a/swagger_parser/lib/src/generator/config/generator_config.dart b/swagger_parser/lib/src/generator/config/generator_config.dart index 2e539b7e..9dc19e2d 100644 --- a/swagger_parser/lib/src/generator/config/generator_config.dart +++ b/swagger_parser/lib/src/generator/config/generator_config.dart @@ -1,3 +1,4 @@ +import 'package:swagger_parser/src/generator/model/field_parser.dart'; import 'package:swagger_parser/src/generator/model/json_serializer.dart'; import 'package:swagger_parser/src/generator/model/programming_language.dart'; import 'package:swagger_parser/src/parser/model/replacement_rule.dart'; @@ -33,6 +34,7 @@ class GeneratorConfig { this.includeIfNull = false, this.useFlutterCompute = false, this.generateUrlsConstants = false, + this.fieldParsers = const [], }); /// Optional. Set API name for folder and export file or merged output file @@ -159,4 +161,7 @@ class GeneratorConfig { /// DART/FLUTTER ONLY /// Optional. Set `true` to generate URL constants for all endpoints. final bool generateUrlsConstants; + + /// {@macro field_parsers} + final List fieldParsers; } diff --git a/swagger_parser/lib/src/generator/generator/fill_controller.dart b/swagger_parser/lib/src/generator/generator/fill_controller.dart index 823085e5..edd8e1f4 100644 --- a/swagger_parser/lib/src/generator/generator/fill_controller.dart +++ b/swagger_parser/lib/src/generator/generator/fill_controller.dart @@ -28,6 +28,7 @@ final class FillController { dataClass, jsonSerializer: config.jsonSerializer, enumsToJson: config.enumsToJson, + fieldParsers: config.fieldParsers, unknownEnumValue: config.unknownEnumValue, markFilesAsGenerated: config.markFilesAsGenerated, generateValidator: config.generateValidator, diff --git a/swagger_parser/lib/src/generator/model/field_parser.dart b/swagger_parser/lib/src/generator/model/field_parser.dart new file mode 100644 index 00000000..1e503945 --- /dev/null +++ b/swagger_parser/lib/src/generator/model/field_parser.dart @@ -0,0 +1,25 @@ +/// {@template field_parser} +/// Configuration for a field parser +/// {@endtemplate} +class FieldParser { + /// {@macro field_parser} + const FieldParser({ + required this.applyToType, + required this.parserName, + required this.parserAbsolutePath, + }); + + /// The type of the field to apply the parser to + final String applyToType; + + /// The name of the parser + final String parserName; + + /// The absolute path to the parser + final String parserAbsolutePath; + + @override + String toString() { + return 'FieldParser(applyToType: $applyToType, parserName: $parserName, parserAbsolutePath: $parserAbsolutePath)'; + } +} diff --git a/swagger_parser/lib/src/generator/model/programming_language.dart b/swagger_parser/lib/src/generator/model/programming_language.dart index da0f0feb..fbe3ff20 100644 --- a/swagger_parser/lib/src/generator/model/programming_language.dart +++ b/swagger_parser/lib/src/generator/model/programming_language.dart @@ -1,3 +1,4 @@ +import 'package:swagger_parser/src/generator/model/field_parser.dart'; import 'package:swagger_parser/src/generator/model/generated_file.dart'; import 'package:swagger_parser/src/generator/model/json_serializer.dart'; import 'package:swagger_parser/src/generator/templates/dart_dart_mappable_dto_template.dart'; @@ -51,6 +52,7 @@ enum ProgrammingLanguage { required bool useMultipartFile, required bool dartMappableConvenientWhen, required bool includeIfNull, + required List fieldParsers, bool useFlutterCompute = false, String? fallbackUnion, }) { @@ -84,6 +86,7 @@ enum ProgrammingLanguage { dataClass, markFileAsGenerated: markFilesAsGenerated, useMultipartFile: useMultipartFile, + fieldParsers: fieldParsers, includeIfNull: includeIfNull, useFlutterCompute: useFlutterCompute, fallbackUnion: fallbackUnion, diff --git a/swagger_parser/lib/src/generator/templates/dart_json_serializable_dto_template.dart b/swagger_parser/lib/src/generator/templates/dart_json_serializable_dto_template.dart index 9c0f073a..d6508de0 100644 --- a/swagger_parser/lib/src/generator/templates/dart_json_serializable_dto_template.dart +++ b/swagger_parser/lib/src/generator/templates/dart_json_serializable_dto_template.dart @@ -1,4 +1,5 @@ import 'package:collection/collection.dart'; +import 'package:swagger_parser/src/generator/model/field_parser.dart'; import 'package:swagger_parser/src/generator/model/programming_language.dart'; import 'package:swagger_parser/src/parser/model/normalized_identifier.dart'; import 'package:swagger_parser/src/parser/swagger_parser_core.dart'; @@ -11,6 +12,7 @@ String dartJsonSerializableDtoTemplate( required bool markFileAsGenerated, required bool useMultipartFile, required bool includeIfNull, + required List fieldParsers, bool useFlutterCompute = false, String? fallbackUnion, }) { @@ -33,9 +35,24 @@ String dartJsonSerializableDtoTemplate( useFlutterCompute ? _generateFlutterComputeSerializer(className) : ''; final asyncImport = useFlutterCompute ? "import 'dart:async';\n\n" : ''; + final actualFieldParsers = fieldParsers + .where( + (e) => dataClass.parameters.any( + (p) => + _renameUnionTypes( + p.toSuitableType( + ProgrammingLanguage.dart, + useMultipartFile: useMultipartFile, + ), + ) == + e.applyToType, + ), + ) + .toList(); + return ''' $asyncImport${ioImport(dataClass.parameters, useMultipartFile: useMultipartFile)}import 'package:json_annotation/json_annotation.dart'; -${dartImports(imports: _filterUnionImportsForNonUnion(dataClass))} +${dartImports(imports: _filterUnionImportsForNonUnion(dataClass))}${actualFieldParsers.isEmpty ? '' : '\n${actualFieldParsers.map((e) => "import '${e.parserAbsolutePath}';").toSet().join('\n')}'} part '$classNameSnake.g.dart'; ${descriptionComment(dataClass.description)}@JsonSerializable() @@ -43,7 +60,7 @@ class $className { const $className(${dataClass.parameters.isNotEmpty ? '{' : ''}${_parametersInConstructor(dataClass.parameters, includeIfNull)}${dataClass.parameters.isNotEmpty ? '\n }' : ''}); factory $className.fromJson(Map json) => _\$${className}FromJson(json); - ${_parametersInClass(dataClass.parameters, useMultipartFile, includeIfNull)}${dataClass.parameters.isNotEmpty ? '\n' : ''} + ${_parametersInClass(dataClass.parameters, useMultipartFile, includeIfNull, actualFieldParsers)}${dataClass.parameters.isNotEmpty ? '\n' : ''} Map toJson() => _\$${className}ToJson(this); } $serializerClass'''; @@ -395,14 +412,17 @@ String _parametersInClass( Set parameters, bool useMultipartFile, bool includeIfNull, + List fieldParsers, ) => - parameters - .mapIndexed( - (i, e) => - '\n${i != 0 && (e.description?.isNotEmpty ?? false) ? '\n' : ''}${descriptionComment(e.description, tab: ' ')}' - '${_jsonKey(e, includeIfNull)} final ${_renameUnionTypes(e.toSuitableType(ProgrammingLanguage.dart, useMultipartFile: useMultipartFile))} ${e.name};', - ) - .join(); + parameters.mapIndexed((i, e) { + final dartType = _renameUnionTypes(e.toSuitableType( + ProgrammingLanguage.dart, + useMultipartFile: useMultipartFile)); + final fieldParser = + fieldParsers.firstWhereOrNull((f) => f.applyToType == dartType); + return '\n${i != 0 && (e.description?.isNotEmpty ?? false) ? '\n' : ''}${descriptionComment(e.description, tab: ' ')}' + '${fieldParser != null ? '\t@${fieldParser.parserName}()\n' : ''}${_jsonKey(e, includeIfNull)} final ${_renameUnionTypes(e.toSuitableType(ProgrammingLanguage.dart, useMultipartFile: useMultipartFile))} ${e.name};'; + }).join(); String _parametersInConstructor( Set parameters, bool includeIfNull) { diff --git a/swagger_parser/lib/swagger_parser.dart b/swagger_parser/lib/swagger_parser.dart index 43697b40..b5ea603d 100644 --- a/swagger_parser/lib/swagger_parser.dart +++ b/swagger_parser/lib/swagger_parser.dart @@ -11,6 +11,7 @@ export 'src/generator/exception/generator_exception.dart'; export 'src/generator/generator/fill_controller.dart'; export 'src/generator/generator/generator.dart'; export 'src/generator/generator/generator_processor.dart'; +export 'src/generator/model/field_parser.dart'; export 'src/generator/model/generated_file.dart'; export 'src/generator/model/json_serializer.dart'; export 'src/generator/model/programming_language.dart'; diff --git a/swagger_parser/test/config/swp_config_test.dart b/swagger_parser/test/config/swp_config_test.dart index f1f682ea..b3875841 100644 --- a/swagger_parser/test/config/swp_config_test.dart +++ b/swagger_parser/test/config/swp_config_test.dart @@ -835,4 +835,363 @@ void main() { expect(generatorConfig.includeIfNull, isTrue); }); }); + + group('Field Parsers Configuration', () { + test('should default fieldParsers to empty list', () { + const config = SWPConfig(outputDirectory: 'lib/api'); + expect(config.fieldParsers, isEmpty); + }); + + test('should parse single field parser from YAML', () { + final yamlMap = YamlMap.wrap({ + 'schema_path': 'api/openapi.yaml', + 'output_directory': 'lib/api', + 'field_parsers': [ + { + 'apply_to_type': 'int', + 'parser_name': 'CustomIntParser', + 'parser_absolute_path': + 'package:your_package/lib/utils/parsers/custom_int_parser.dart', + }, + ], + }); + + final config = SWPConfig.fromYaml(yamlMap); + expect(config.fieldParsers, hasLength(1)); + expect(config.fieldParsers[0].applyToType, equals('int')); + expect(config.fieldParsers[0].parserName, equals('CustomIntParser')); + expect( + config.fieldParsers[0].parserAbsolutePath, + equals('package:your_package/lib/utils/parsers/custom_int_parser.dart'), + ); + }); + + test('should parse multiple field parsers from YAML', () { + final yamlMap = YamlMap.wrap({ + 'schema_path': 'api/openapi.yaml', + 'output_directory': 'lib/api', + 'field_parsers': [ + { + 'apply_to_type': 'int', + 'parser_name': 'CustomIntParser', + 'parser_absolute_path': + 'package:your_package/lib/utils/parsers/custom_int_parser.dart', + }, + { + 'apply_to_type': 'int?', + 'parser_name': 'CustomNullableIntParser', + 'parser_absolute_path': + 'package:your_package/lib/utils/parsers/custom_nullable_int_parser.dart', + }, + { + 'apply_to_type': 'bool', + 'parser_name': 'CustomBoolParser', + 'parser_absolute_path': + 'package:your_package/lib/utils/parsers/custom_bool_parser.dart', + }, + ], + }); + + final config = SWPConfig.fromYaml(yamlMap); + expect(config.fieldParsers, hasLength(3)); + + expect(config.fieldParsers[0].applyToType, equals('int')); + expect(config.fieldParsers[0].parserName, equals('CustomIntParser')); + expect( + config.fieldParsers[0].parserAbsolutePath, + equals('package:your_package/lib/utils/parsers/custom_int_parser.dart'), + ); + + expect(config.fieldParsers[1].applyToType, equals('int?')); + expect( + config.fieldParsers[1].parserName, equals('CustomNullableIntParser')); + expect( + config.fieldParsers[1].parserAbsolutePath, + equals( + 'package:your_package/lib/utils/parsers/custom_nullable_int_parser.dart'), + ); + + expect(config.fieldParsers[2].applyToType, equals('bool')); + expect(config.fieldParsers[2].parserName, equals('CustomBoolParser')); + expect( + config.fieldParsers[2].parserAbsolutePath, + equals( + 'package:your_package/lib/utils/parsers/custom_bool_parser.dart'), + ); + }); + + test('should inherit field parsers from root config', () { + const rootConfig = SWPConfig( + outputDirectory: 'lib/shared', + fieldParsers: [ + FieldParser( + applyToType: 'int', + parserName: 'RootIntParser', + parserAbsolutePath: + 'package:root/lib/utils/parsers/root_int_parser.dart', + ), + ], + ); + + final yamlMap = YamlMap.wrap({ + 'schema_path': 'api/user.yaml', + 'name': 'user_api', + }); + + final config = SWPConfig.fromYaml(yamlMap, rootConfig: rootConfig); + expect(config.fieldParsers, hasLength(1)); + expect(config.fieldParsers[0].applyToType, equals('int')); + expect(config.fieldParsers[0].parserName, equals('RootIntParser')); + expect( + config.fieldParsers[0].parserAbsolutePath, + equals('package:root/lib/utils/parsers/root_int_parser.dart'), + ); + }); + + test('should override root config field parsers with local values', () { + const rootConfig = SWPConfig( + outputDirectory: 'lib/shared', + fieldParsers: [ + FieldParser( + applyToType: 'int', + parserName: 'RootIntParser', + parserAbsolutePath: + 'package:root/lib/utils/parsers/root_int_parser.dart', + ), + ], + ); + + final yamlMap = YamlMap.wrap({ + 'schema_path': 'api/user.yaml', + 'field_parsers': [ + { + 'apply_to_type': 'String', + 'parser_name': 'LocalStringParser', + 'parser_absolute_path': + 'package:local/lib/utils/parsers/local_string_parser.dart', + }, + ], + }); + + final config = SWPConfig.fromYaml(yamlMap, rootConfig: rootConfig); + expect(config.fieldParsers, hasLength(1)); + expect(config.fieldParsers[0].applyToType, equals('String')); + expect(config.fieldParsers[0].parserName, equals('LocalStringParser')); + expect( + config.fieldParsers[0].parserAbsolutePath, + equals('package:local/lib/utils/parsers/local_string_parser.dart'), + ); + }); + + test('should pass field parsers to GeneratorConfig', () { + const swpConfig = SWPConfig( + outputDirectory: 'lib/api', + fieldParsers: [ + FieldParser( + applyToType: 'int', + parserName: 'CustomIntParser', + parserAbsolutePath: + 'package:your_package/lib/utils/parsers/custom_int_parser.dart', + ), + ], + ); + + final generatorConfig = swpConfig.toGeneratorConfig(); + expect(generatorConfig.fieldParsers, hasLength(1)); + expect(generatorConfig.fieldParsers[0].applyToType, equals('int')); + expect(generatorConfig.fieldParsers[0].parserName, + equals('CustomIntParser')); + expect( + generatorConfig.fieldParsers[0].parserAbsolutePath, + equals('package:your_package/lib/utils/parsers/custom_int_parser.dart'), + ); + }); + + test('should handle empty field parsers list from YAML', () { + final yamlMap = YamlMap.wrap({ + 'schema_path': 'api/openapi.yaml', + 'output_directory': 'lib/api', + 'field_parsers': >[], + }); + + final config = SWPConfig.fromYaml(yamlMap); + expect(config.fieldParsers, isEmpty); + }); + + test( + 'should throw ConfigException for field parsers with missing apply_to_type', + () { + final yamlMap = YamlMap.wrap({ + 'schema_path': 'api/openapi.yaml', + 'output_directory': 'lib/api', + 'field_parsers': [ + { + 'parser_name': 'CustomIntParser', + 'parser_absolute_path': + 'package:your_package/lib/utils/parsers/custom_int_parser.dart', + }, + ], + }); + + expect( + () => SWPConfig.fromYaml(yamlMap), + throwsA(isA().having( + (e) => e.message, + 'message', + equals( + "Config parameter 'field_parsers' values must be List of maps with 'apply_to_type', 'parser_name', and 'parser_absolute_path'."), + )), + ); + }); + + test( + 'should throw ConfigException for field parsers with missing parser_name', + () { + final yamlMap = YamlMap.wrap({ + 'schema_path': 'api/openapi.yaml', + 'output_directory': 'lib/api', + 'field_parsers': [ + { + 'apply_to_type': 'int', + 'parser_absolute_path': + 'package:your_package/lib/utils/parsers/custom_int_parser.dart', + }, + ], + }); + + expect( + () => SWPConfig.fromYaml(yamlMap), + throwsA(isA().having( + (e) => e.message, + 'message', + equals( + "Config parameter 'field_parsers' values must be List of maps with 'apply_to_type', 'parser_name', and 'parser_absolute_path'."), + )), + ); + }); + + test( + 'should throw ConfigException for field parsers with missing parser_absolute_path', + () { + final yamlMap = YamlMap.wrap({ + 'schema_path': 'api/openapi.yaml', + 'output_directory': 'lib/api', + 'field_parsers': [ + { + 'apply_to_type': 'int', + 'parser_name': 'CustomIntParser', + }, + ], + }); + + expect( + () => SWPConfig.fromYaml(yamlMap), + throwsA(isA().having( + (e) => e.message, + 'message', + equals( + "Config parameter 'field_parsers' values must be List of maps with 'apply_to_type', 'parser_name', and 'parser_absolute_path'."), + )), + ); + }); + + test('should throw ConfigException for field parsers with invalid format', + () { + final yamlMap = YamlMap.wrap({ + 'schema_path': 'api/openapi.yaml', + 'output_directory': 'lib/api', + 'field_parsers': ['invalid'], + }); + + expect( + () => SWPConfig.fromYaml(yamlMap), + throwsA(isA().having( + (e) => e.message, + 'message', + equals( + "Config parameter 'field_parsers' values must be List of maps with 'apply_to_type', 'parser_name', and 'parser_absolute_path'."), + )), + ); + }); + + test( + 'should throw ConfigException for field parsers with non-string apply_to_type', + () { + final yamlMap = YamlMap.wrap({ + 'schema_path': 'api/openapi.yaml', + 'output_directory': 'lib/api', + 'field_parsers': [ + { + 'apply_to_type': 123, + 'parser_name': 'CustomIntParser', + 'parser_absolute_path': + 'package:your_package/lib/utils/parsers/custom_int_parser.dart', + }, + ], + }); + + expect( + () => SWPConfig.fromYaml(yamlMap), + throwsA(isA().having( + (e) => e.message, + 'message', + equals( + "Config parameter 'field_parsers' values must be List of maps with 'apply_to_type', 'parser_name', and 'parser_absolute_path'."), + )), + ); + }); + + test( + 'should throw ConfigException for field parsers with non-string parser_name', + () { + final yamlMap = YamlMap.wrap({ + 'schema_path': 'api/openapi.yaml', + 'output_directory': 'lib/api', + 'field_parsers': [ + { + 'apply_to_type': 'int', + 'parser_name': 123, + 'parser_absolute_path': + 'package:your_package/lib/utils/parsers/custom_int_parser.dart', + }, + ], + }); + + expect( + () => SWPConfig.fromYaml(yamlMap), + throwsA(isA().having( + (e) => e.message, + 'message', + equals( + "Config parameter 'field_parsers' values must be List of maps with 'apply_to_type', 'parser_name', and 'parser_absolute_path'."), + )), + ); + }); + + test( + 'should throw ConfigException for field parsers with non-string parser_absolute_path', + () { + final yamlMap = YamlMap.wrap({ + 'schema_path': 'api/openapi.yaml', + 'output_directory': 'lib/api', + 'field_parsers': [ + { + 'apply_to_type': 'int', + 'parser_name': 'CustomIntParser', + 'parser_absolute_path': 123, + }, + ], + }); + + expect( + () => SWPConfig.fromYaml(yamlMap), + throwsA(isA().having( + (e) => e.message, + 'message', + equals( + "Config parameter 'field_parsers' values must be List of maps with 'apply_to_type', 'parser_name', and 'parser_absolute_path'."), + )), + ); + }); + }); } diff --git a/swagger_parser/test/e2e/e2e_test.dart b/swagger_parser/test/e2e/e2e_test.dart index 20d52bf9..68a61c3a 100644 --- a/swagger_parser/test/e2e/e2e_test.dart +++ b/swagger_parser/test/e2e/e2e_test.dart @@ -179,6 +179,40 @@ void main() { schemaFileName: 'openapi.yaml', ); }); + + test('field_parsers', () async { + await e2eTest( + 'field_parsers', + (outputDirectory, schemaPath) => SWPConfig( + outputDirectory: outputDirectory, + schemaPath: schemaPath, + // ignore: avoid_redundant_argument_values + jsonSerializer: JsonSerializer.jsonSerializable, + putClientsInFolder: true, + fieldParsers: [ + const FieldParser( + applyToType: 'int', + parserName: 'CustomIntParser', + parserAbsolutePath: + 'package:your_package/lib/utils/parsers/custom_int_parser.dart', + ), + const FieldParser( + applyToType: 'int?', + parserName: 'CustomNullableIntParser', + parserAbsolutePath: + 'package:your_package/lib/utils/parsers/custom_nullable_int_parser.dart', + ), + const FieldParser( + applyToType: 'bool', + parserName: 'CustomBoolParser', + parserAbsolutePath: + 'package:your_package/lib/utils/parsers/custom_bool_parser.dart', + ), + ], + ), + schemaFileName: 'openapi.yaml', + ); + }); }); group('basic', () { diff --git a/swagger_parser/test/e2e/tests/field_parsers/expected_files/clients/fallback_client.dart b/swagger_parser/test/e2e/tests/field_parsers/expected_files/clients/fallback_client.dart new file mode 100644 index 00000000..e099234e --- /dev/null +++ b/swagger_parser/test/e2e/tests/field_parsers/expected_files/clients/fallback_client.dart @@ -0,0 +1,23 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, unused_import, invalid_annotation_target, unnecessary_import + +import 'package:dio/dio.dart'; +import 'package:retrofit/retrofit.dart'; + +import '../models/test_model.dart'; + +part 'fallback_client.g.dart'; + +@RestApi() +abstract class FallbackClient { + factory FallbackClient(Dio dio, {String? baseUrl}) = _FallbackClient; + + @GET('/test') + Future getTest(); + + @POST('/test') + Future createTest({ + @Body() required TestModel body, + }); +} diff --git a/swagger_parser/test/e2e/tests/field_parsers/expected_files/export.dart b/swagger_parser/test/e2e/tests/field_parsers/expected_files/export.dart new file mode 100644 index 00000000..3605a7be --- /dev/null +++ b/swagger_parser/test/e2e/tests/field_parsers/expected_files/export.dart @@ -0,0 +1,10 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, unused_import, invalid_annotation_target, unnecessary_import + +// Clients +export 'clients/fallback_client.dart'; +// Data classes +export 'models/test_model.dart'; +// Root client +export 'rest_client.dart'; diff --git a/swagger_parser/test/e2e/tests/field_parsers/expected_files/models/test_model.dart b/swagger_parser/test/e2e/tests/field_parsers/expected_files/models/test_model.dart new file mode 100644 index 00000000..ddcb6a94 --- /dev/null +++ b/swagger_parser/test/e2e/tests/field_parsers/expected_files/models/test_model.dart @@ -0,0 +1,40 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, unused_import, invalid_annotation_target, unnecessary_import + +import 'package:json_annotation/json_annotation.dart'; + +import 'package:your_package/lib/utils/parsers/custom_int_parser.dart'; +import 'package:your_package/lib/utils/parsers/custom_nullable_int_parser.dart'; +import 'package:your_package/lib/utils/parsers/custom_bool_parser.dart'; +part 'test_model.g.dart'; + +@JsonSerializable() +class TestModel { + const TestModel({ + required this.id, + required this.count, + required this.isActive, + required this.score, + this.optionalCount, + this.optionalFlag, + this.name, + }); + + factory TestModel.fromJson(Map json) => + _$TestModelFromJson(json); + + @CustomIntParser() + final int id; + @CustomIntParser() + final int count; + @CustomBoolParser() + final bool isActive; + final num score; + @CustomNullableIntParser() + final int? optionalCount; + final bool? optionalFlag; + final String? name; + + Map toJson() => _$TestModelToJson(this); +} diff --git a/swagger_parser/test/e2e/tests/field_parsers/expected_files/rest_client.dart b/swagger_parser/test/e2e/tests/field_parsers/expected_files/rest_client.dart new file mode 100644 index 00000000..decd8eba --- /dev/null +++ b/swagger_parser/test/e2e/tests/field_parsers/expected_files/rest_client.dart @@ -0,0 +1,26 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, unused_import, invalid_annotation_target, unnecessary_import + +import 'package:dio/dio.dart'; + +import 'clients/fallback_client.dart'; + +/// Test API for Field Parsers `v1.0.0` +class RestClient { + RestClient( + Dio dio, { + String? baseUrl, + }) : _dio = dio, + _baseUrl = baseUrl; + + final Dio _dio; + final String? _baseUrl; + + static String get version => '1.0.0'; + + FallbackClient? _fallback; + + FallbackClient get fallback => + _fallback ??= FallbackClient(_dio, baseUrl: _baseUrl); +} diff --git a/swagger_parser/test/e2e/tests/field_parsers/generate_test.dart b/swagger_parser/test/e2e/tests/field_parsers/generate_test.dart new file mode 100644 index 00000000..006366fc --- /dev/null +++ b/swagger_parser/test/e2e/tests/field_parsers/generate_test.dart @@ -0,0 +1,34 @@ +import 'package:swagger_parser/swagger_parser.dart'; + +Future main() async { + final config = SWPConfig( + outputDirectory: 'test/e2e/tests/field_parsers/generated_files', + schemaPath: 'test/e2e/tests/field_parsers/openapi.yaml', + jsonSerializer: JsonSerializer.jsonSerializable, + putClientsInFolder: true, + fieldParsers: [ + const FieldParser( + applyToType: 'int', + parserName: 'CustomIntParser', + parserAbsolutePath: + 'package:your_package/lib/utils/parsers/custom_int_parser.dart', + ), + const FieldParser( + applyToType: 'int?', + parserName: 'CustomNullableIntParser', + parserAbsolutePath: + 'package:your_package/lib/utils/parsers/custom_nullable_int_parser.dart', + ), + const FieldParser( + applyToType: 'bool', + parserName: 'CustomBoolParser', + parserAbsolutePath: + 'package:your_package/lib/utils/parsers/custom_bool_parser.dart', + ), + ], + ); + + final processor = GenProcessor(config); + await processor.generateFiles(); + print('Files generated successfully!'); +} diff --git a/swagger_parser/test/e2e/tests/field_parsers/openapi.yaml b/swagger_parser/test/e2e/tests/field_parsers/openapi.yaml new file mode 100644 index 00000000..cf5d7eb6 --- /dev/null +++ b/swagger_parser/test/e2e/tests/field_parsers/openapi.yaml @@ -0,0 +1,56 @@ +openapi: 3.0.0 +info: + title: Test API for Field Parsers + version: 1.0.0 +paths: + /test: + get: + operationId: getTest + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/TestModel" + post: + operationId: createTest + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/TestModel" + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/TestModel" +components: + schemas: + TestModel: + type: object + required: + - id + - count + - isActive + - score + properties: + id: + type: integer + count: + type: integer + isActive: + type: boolean + score: + type: number + optionalCount: + type: integer + nullable: true + optionalFlag: + type: boolean + nullable: true + name: + type: string