Skip to content

Commit

Permalink
Add support for "dart_mappable"
Browse files Browse the repository at this point in the history
  • Loading branch information
dagyu committed Nov 23, 2023
1 parent 2ce4e33 commit f135bc2
Show file tree
Hide file tree
Showing 10 changed files with 270 additions and 19 deletions.
7 changes: 7 additions & 0 deletions swagger_parser/example/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions swagger_parser/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ environment:

dependencies:
dio: ^5.3.3
dart_mappable: ^4.0.1
freezed_annotation: ^2.4.1
json_annotation: ^4.8.1
retrofit: ^4.0.3
Expand All @@ -14,6 +15,7 @@ dev_dependencies:
build_runner: ^2.4.6
carapacik_lints: ^1.4.2
freezed: ^2.4.5
dart_mappable_builder: ^4.0.1
json_serializable: ^6.7.1
retrofit_generator: ^8.0.1
swagger_parser:
Expand Down
11 changes: 11 additions & 0 deletions swagger_parser/example/swagger_parser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down
4 changes: 3 additions & 1 deletion swagger_parser/lib/src/generator/models/json_serializer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import 'package:collection/collection.dart';
enum JsonSerializer {
json_serializable,

freezed;
freezed,

dart_mappable;

/// Returns [JsonSerializer] from string
static JsonSerializer? fromString(String string) => values.firstWhereOrNull(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -74,6 +75,11 @@ enum ProgrammingLanguage {
dataClass,
markFileAsGenerated: markFilesAsGenerated,
);
case JsonSerializer.dart_mappable:
return dartDartMappableDtoTemplate(
dataClass,
markFileAsGenerated: markFilesAsGenerated,
);
}
}
case kotlin:
Expand Down
Original file line number Diff line number Diff line change
@@ -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.dart_mappable)}
${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<UniversalType> parameters) {
final sortedByRequired =
List<UniversalType>.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<UniversalType> parameters) {
final sortedByRequired =
List<UniversalType>.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)}';
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -23,26 +15,63 @@ String dartEnumDtoTemplate(
required bool enumsToJson,
required bool unknownEnumValue,
required bool markFileAsGenerated,
}) {
if (jsonSerializer == JsonSerializer.dart_mappable) {
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.dart_mappable)}
${descriptionComment(enumClass.description)}@JsonEnum()
part '${enumClass.name.toSnake}.mapper.dart';
${descriptionComment(enumClass.description)}@MappableEnum()
enum $className {
$values$unkownEnumValueStr$constructorStr$fromJsonStr$jsonFieldStr$toJsonStr
$values
}
''';
}
Expand Down Expand Up @@ -76,5 +105,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;';
Original file line number Diff line number Diff line change
@@ -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.json_serializable:
return "import 'package:json_annotation/json_annotation.dart';";
case JsonSerializer.dart_mappable:
return "import 'package:dart_mappable/dart_mappable.dart';";
}
}
2 changes: 2 additions & 0 deletions swagger_parser/lib/src/utils/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ String dartImports({required Set<String> 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, {
Expand Down
87 changes: 87 additions & 0 deletions swagger_parser/test/generator/data_classes_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.dart_mappable,
);
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',
Expand Down

0 comments on commit f135bc2

Please sign in to comment.