platform/packages/serialize/angel_serialize_generator/lib/serialize.dart

484 lines
16 KiB
Dart
Raw Normal View History

2021-05-15 14:37:52 +00:00
part of angel3_serialize_generator;
class SerializerGenerator extends GeneratorForAnnotation<Serializable> {
final bool autoSnakeCaseNames;
2019-04-30 15:44:01 +00:00
const SerializerGenerator({this.autoSnakeCaseNames = true});
@override
2021-05-02 06:02:08 +00:00
Future<String?> generateForAnnotatedElement(
Element element, ConstantReader annotation, BuildStep buildStep) async {
2022-02-27 04:16:31 +00:00
log.fine('Running SerializerGenerator');
2021-08-14 03:52:31 +00:00
2019-07-04 18:32:34 +00:00
if (element.kind != ElementKind.CLASS) {
throw 'Only classes can be annotated with a @Serializable() annotation.';
2019-07-04 18:32:34 +00:00
}
var ctx = await buildContext(element as ClassElement, annotation, buildStep,
2022-02-27 04:16:31 +00:00
buildStep.resolver, !autoSnakeCaseNames);
2021-08-14 03:52:31 +00:00
if (ctx == null) {
log.severe('Invalid builder context');
throw 'Invalid builder context';
}
var serializers = annotation.peek('serializers')?.listValue ?? [];
2021-08-14 03:52:31 +00:00
if (serializers.isEmpty) {
2022-02-27 04:16:31 +00:00
log.severe("No Serializers");
2021-08-14 03:52:31 +00:00
return null;
}
// Check if any serializer is recognized
if (!serializers.any((s) => Serializers.all.contains(s.toIntValue()))) {
2022-02-27 04:16:31 +00:00
log.severe("No recognizable Serializers");
return null;
}
2019-04-30 15:44:01 +00:00
var lib = Library((b) {
2021-08-08 03:10:35 +00:00
generateClass(
2021-08-14 03:52:31 +00:00
serializers.map((s) => s.toIntValue() ?? 0).toList(), ctx, b);
2018-03-09 12:39:21 +00:00
generateFieldsClass(ctx, b);
});
2021-08-15 08:04:21 +00:00
var buf = lib.accept(DartEmitter(useNullSafetySyntax: true));
return buf.toString();
}
/// Generate a serializer class.
2018-02-28 01:10:57 +00:00
void generateClass(
2021-08-08 03:10:35 +00:00
List<int> serializers, BuildContext ctx, LibraryBuilder file) {
2022-02-27 04:16:31 +00:00
log.fine('Generate serializer class');
2021-08-14 03:52:31 +00:00
2019-04-08 15:00:04 +00:00
// Generate canonical codecs, etc.
2021-08-14 03:52:31 +00:00
var pascal = ctx.modelClassNameRecase.pascalCase.replaceAll('?', '');
var camel = ctx.modelClassNameRecase.camelCase.replaceAll('?', '');
2022-02-27 04:16:31 +00:00
log.fine('Generating ${pascal}Serializer');
2019-04-08 15:00:04 +00:00
if (ctx.constructorParameters.isEmpty) {
2022-02-27 04:16:31 +00:00
log.fine("Constructor is empty");
2019-04-30 15:44:01 +00:00
file.body.add(Code('''
const ${pascal}Serializer ${camel}Serializer = ${pascal}Serializer();
2019-04-08 15:00:04 +00:00
2021-05-15 14:37:52 +00:00
class ${pascal}Encoder extends Converter<$pascal, Map> {
2019-04-08 15:00:04 +00:00
const ${pascal}Encoder();
@override
2021-05-15 14:37:52 +00:00
Map convert($pascal model) => ${pascal}Serializer.toMap(model);
2019-04-08 15:00:04 +00:00
}
2021-05-15 14:37:52 +00:00
class ${pascal}Decoder extends Converter<Map, $pascal> {
2019-04-08 15:00:04 +00:00
const ${pascal}Decoder();
@override
2021-05-15 14:37:52 +00:00
$pascal convert(Map map) => ${pascal}Serializer.fromMap(map);
2019-04-08 15:00:04 +00:00
}
'''));
}
2019-04-30 15:44:01 +00:00
file.body.add(Class((clazz) {
2021-05-15 14:37:52 +00:00
clazz.name = '${pascal}Serializer';
2019-04-08 15:00:04 +00:00
if (ctx.constructorParameters.isEmpty) {
2021-05-15 14:37:52 +00:00
clazz.extend = TypeReference((b) => b
..symbol = 'Codec'
..types.addAll([ctx.modelClassType, refer('Map')]));
2019-04-08 15:00:04 +00:00
// Add constructor, Codec impl, etc.
clazz.constructors.add(Constructor((b) => b..constant = true));
clazz.methods.add(Method((b) => b
..name = 'encoder'
2021-08-11 01:32:53 +00:00
..returns = refer('${pascal}Encoder')
2019-04-08 15:00:04 +00:00
..type = MethodType.getter
..annotations.add(refer('override'))
..body = refer('${pascal}Encoder').constInstance([]).code));
clazz.methods.add(Method((b) => b
..name = 'decoder'
2021-08-11 01:32:53 +00:00
..returns = refer('${pascal}Decoder')
2019-04-08 15:00:04 +00:00
..type = MethodType.getter
..annotations.add(refer('override'))
..body = refer('${pascal}Decoder').constInstance([]).code));
} else {
clazz.abstract = true;
}
2018-02-28 01:10:57 +00:00
if (serializers.contains(Serializers.map)) {
2018-02-28 02:10:43 +00:00
generateFromMapMethod(clazz, ctx, file);
2018-02-28 01:10:57 +00:00
}
if (serializers.contains(Serializers.map) ||
serializers.contains(Serializers.json)) {
generateToMapMethod(clazz, ctx, file);
}
}));
}
void generateToMapMethod(
2018-03-02 21:23:00 +00:00
ClassBuilder clazz, BuildContext ctx, LibraryBuilder file) {
2021-08-15 08:04:21 +00:00
var originalClassName = ctx.originalClassName;
if (originalClassName == null) {
log.warning('Unable to generate toMap(), classname is null');
return;
}
2019-04-30 15:44:01 +00:00
clazz.methods.add(Method((method) {
2018-02-28 01:10:57 +00:00
method
2018-02-28 01:41:19 +00:00
..static = true
2018-02-28 01:10:57 +00:00
..name = 'toMap'
2019-04-30 15:44:01 +00:00
..returns = Reference('Map<String, dynamic>')
..requiredParameters.add(Parameter((b) {
2018-02-28 01:41:19 +00:00
b
..name = 'model'
2021-08-15 08:04:21 +00:00
..type = TypeReference((b) => b
2021-09-25 06:32:32 +00:00
..symbol = originalClassName
2021-08-15 08:04:21 +00:00
..isNullable = true);
2018-02-28 01:41:19 +00:00
}));
2018-02-28 01:10:57 +00:00
2019-04-30 15:44:01 +00:00
var buf = StringBuffer();
2018-05-15 19:33:57 +00:00
2021-08-11 01:32:53 +00:00
/*
2018-05-15 19:33:57 +00:00
ctx.requiredFields.forEach((key, msg) {
if (ctx.excluded[key]?.canSerialize == false) return;
buf.writeln('''
if (model.$key == null) {
2019-04-30 15:44:01 +00:00
throw FormatException("$msg");
2018-05-15 19:33:57 +00:00
}
''');
});
2021-08-11 01:32:53 +00:00
*/
2018-05-15 19:33:57 +00:00
buf.writeln('return {');
2021-05-15 14:37:52 +00:00
var i = 0;
2018-02-28 01:10:57 +00:00
// Add named parameters
for (var field in ctx.fields) {
2019-01-09 19:25:05 +00:00
var type = ctx.resolveSerializedFieldType(field.name);
2018-02-28 01:10:57 +00:00
// Skip excluded fields
if (ctx.excluded[field.name]?.canSerialize == false) continue;
2018-02-28 01:10:57 +00:00
var alias = ctx.resolveFieldName(field.name);
if (i++ > 0) buf.write(', ');
2021-05-15 14:37:52 +00:00
var serializedRepresentation = 'model.${field.name}';
2018-02-28 01:10:57 +00:00
2018-06-28 01:58:42 +00:00
String serializerToMap(ReCase rc, String value) {
2018-12-08 22:29:06 +00:00
// if (rc.pascalCase == ctx.modelClassName) {
// return '($value)?.toJson()';
// }
2021-08-14 03:52:31 +00:00
return '${rc.pascalCase.replaceAll('?', '')}Serializer.toMap($value)';
2018-06-28 01:58:42 +00:00
}
2021-08-15 08:04:21 +00:00
var fieldNameSerializer = ctx.fieldInfo[field.name]?.serializer;
if (fieldNameSerializer != null) {
var name = MirrorSystem.getName(fieldNameSerializer);
2019-01-07 01:38:04 +00:00
serializedRepresentation = '$name(model.${field.name})';
}
2018-02-28 01:18:48 +00:00
// Serialize dates
2019-07-04 18:32:34 +00:00
else if (dateTimeTypeChecker.isAssignableFromType(type)) {
2018-02-28 02:10:43 +00:00
serializedRepresentation = 'model.${field.name}?.toIso8601String()';
2019-07-04 18:32:34 +00:00
}
2018-02-28 01:10:57 +00:00
2018-02-28 01:18:48 +00:00
// Serialize model classes via `XSerializer.toMap`
2019-01-09 19:25:05 +00:00
else if (isModelClass(type)) {
2021-05-18 11:32:47 +00:00
var rc = ReCase(type.getDisplayString(withNullability: true));
2021-09-11 06:37:53 +00:00
serializedRepresentation = serializerToMap(rc, 'model.${field.name}');
2019-01-09 19:25:05 +00:00
} else if (type is InterfaceType) {
if (isListOfModelType(type)) {
2021-05-18 11:32:47 +00:00
var name =
type.typeArguments[0].getDisplayString(withNullability: true);
2018-12-08 22:29:06 +00:00
if (name.startsWith('_')) name = name.substring(1);
2019-04-30 15:44:01 +00:00
var rc = ReCase(name);
2018-12-08 22:29:06 +00:00
var m = serializerToMap(rc, 'm');
2021-08-17 04:52:08 +00:00
var question =
(field.type.nullabilitySuffix == NullabilitySuffix.question)
? '?'
: '';
serializedRepresentation =
'model.${field.name}$question.map((m) => $m).toList()';
log.fine('serializedRepresentation => $serializedRepresentation');
2019-01-09 19:25:05 +00:00
} else if (isMapToModelType(type)) {
2021-05-18 11:32:47 +00:00
var rc = ReCase(
type.typeArguments[1].getDisplayString(withNullability: true));
2018-02-28 02:10:43 +00:00
serializedRepresentation =
2021-09-11 06:37:53 +00:00
'''model.${field.name}.keys.fold({}, (map, key) {
2022-02-27 04:16:31 +00:00
return (map as Map<dynamic,dynamic>?)?..[key] =
2018-07-11 13:06:03 +00:00
${serializerToMap(rc, 'model.${field.name}[key]')};
2018-02-28 01:43:43 +00:00
})''';
2019-01-09 19:25:05 +00:00
} else if (type.element.isEnum) {
2021-08-17 04:52:08 +00:00
var convert =
(field.type.nullabilitySuffix == NullabilitySuffix.question)
? '!'
: '';
2018-06-27 05:36:57 +00:00
serializedRepresentation = '''
2021-08-17 04:52:08 +00:00
model.${field.name} != null ?
${type.getDisplayString(withNullability: false)}.values.indexOf(model.${field.name}$convert)
: null
2018-06-27 05:36:57 +00:00
''';
2018-12-08 20:53:49 +00:00
} else if (const TypeChecker.fromRuntime(Uint8List)
2019-01-09 19:25:05 +00:00
.isAssignableFromType(type)) {
2021-08-17 07:29:10 +00:00
var convert =
(field.type.nullabilitySuffix == NullabilitySuffix.question)
? '!'
: '';
2018-12-08 20:53:49 +00:00
serializedRepresentation = '''
2021-08-17 07:29:10 +00:00
model.${field.name} != null ?
base64.encode(model.${field.name}$convert)
: null
2018-12-08 20:53:49 +00:00
''';
2018-02-28 01:41:19 +00:00
}
2018-02-28 01:18:48 +00:00
}
2018-02-28 01:10:57 +00:00
buf.write("'$alias': $serializedRepresentation");
}
buf.write('};');
2019-04-30 15:44:01 +00:00
method.body = Block.of([
2022-02-27 04:16:31 +00:00
Code(
'if (model == null) { throw FormatException("Required field [model] cannot be null"); }'),
2019-04-30 15:44:01 +00:00
Code(buf.toString()),
2018-06-28 01:58:42 +00:00
]);
}));
}
2018-02-28 02:10:43 +00:00
void generateFromMapMethod(
2018-03-02 21:23:00 +00:00
ClassBuilder clazz, BuildContext ctx, LibraryBuilder file) {
2019-04-30 15:44:01 +00:00
clazz.methods.add(Method((method) {
2018-02-28 02:10:43 +00:00
method
..static = true
..name = 'fromMap'
..returns = ctx.modelClassType
..requiredParameters.add(
2019-04-30 15:44:01 +00:00
Parameter((b) => b
2018-02-28 02:10:43 +00:00
..name = 'map'
2019-04-30 15:44:01 +00:00
..type = Reference('Map')),
2018-02-28 02:10:43 +00:00
);
2018-05-13 17:23:40 +00:00
// Add all `super` params
if (ctx.constructorParameters.isNotEmpty) {
for (var param in ctx.constructorParameters) {
2019-04-30 15:44:01 +00:00
method.requiredParameters.add(Parameter((b) => b
2018-05-13 17:23:40 +00:00
..name = param.name
..type = convertTypeReference(param.type)));
}
}
2019-04-30 15:44:01 +00:00
var buf = StringBuffer();
2018-05-15 19:33:57 +00:00
2021-08-17 07:29:10 +00:00
// Required Fields
2018-05-15 19:33:57 +00:00
ctx.requiredFields.forEach((key, msg) {
if (ctx.excluded[key]?.canDeserialize == false) return;
var name = ctx.resolveFieldName(key);
2021-09-11 06:37:53 +00:00
if (msg.contains("'")) {
buf.writeln('''
if (map['$name'] == null) {
throw FormatException("$msg");
}
''');
} else {
buf.writeln('''
if (map['$name'] == null) {
throw FormatException('$msg');
}
''');
2018-05-15 19:33:57 +00:00
}
});
2019-04-30 15:44:01 +00:00
buf.writeln('return ${ctx.modelClassName}(');
2021-05-15 14:37:52 +00:00
var i = 0;
2018-02-28 02:10:43 +00:00
2021-08-17 07:29:10 +00:00
// Parameters in the constructor
2018-05-13 17:23:40 +00:00
for (var param in ctx.constructorParameters) {
if (i++ > 0) buf.write(', ');
buf.write(param.name);
}
2021-08-17 07:29:10 +00:00
// Fields
2018-02-28 02:10:43 +00:00
for (var field in ctx.fields) {
2019-01-09 19:25:05 +00:00
var type = ctx.resolveSerializedFieldType(field.name);
if (ctx.excluded[field.name]?.canDeserialize == false) continue;
2018-02-28 02:10:43 +00:00
var alias = ctx.resolveFieldName(field.name);
if (i++ > 0) buf.write(', ');
2021-05-15 14:37:52 +00:00
var deserializedRepresentation =
2019-01-09 19:25:05 +00:00
"map['$alias'] as ${typeToString(type)}";
2022-02-27 04:16:31 +00:00
2021-08-17 04:52:08 +00:00
if (type.nullabilitySuffix == NullabilitySuffix.question) {
deserializedRepresentation += '?';
}
2021-08-15 08:04:21 +00:00
var defaultValue = 'null';
2018-12-31 01:18:44 +00:00
var existingDefault = ctx.defaults[field.name];
if (existingDefault != null) {
2021-08-15 08:04:21 +00:00
var d = dartObjectToString(existingDefault);
if (d != null) {
defaultValue = d;
2022-02-27 04:16:31 +00:00
if (!deserializedRepresentation.endsWith("?")) {
deserializedRepresentation += "?";
}
2021-08-15 08:04:21 +00:00
}
2018-12-31 01:18:44 +00:00
deserializedRepresentation =
'$deserializedRepresentation ?? $defaultValue';
}
2021-08-15 08:04:21 +00:00
var fieldNameDeserializer = ctx.fieldInfo[field.name]?.deserializer;
if (fieldNameDeserializer != null) {
var name = MirrorSystem.getName(fieldNameDeserializer);
2019-01-07 01:38:04 +00:00
deserializedRepresentation = "$name(map['$alias'])";
2019-07-04 18:32:34 +00:00
} else if (dateTimeTypeChecker.isAssignableFromType(type)) {
deserializedRepresentation = "map['$alias'] != null ? "
"(map['$alias'] is DateTime ? (map['$alias'] as DateTime) : DateTime.parse(map['$alias'].toString()))"
2021-05-15 14:37:52 +00:00
' : $defaultValue';
2019-07-04 18:32:34 +00:00
}
2018-02-28 02:10:43 +00:00
// Serialize model classes via `XSerializer.toMap`
2019-01-09 19:25:05 +00:00
else if (isModelClass(type)) {
2021-05-18 11:32:47 +00:00
var rc = ReCase(type.getDisplayString(withNullability: true));
2018-02-28 02:10:43 +00:00
deserializedRepresentation = "map['$alias'] != null"
2021-08-14 03:52:31 +00:00
" ? ${rc.pascalCase.replaceAll('?', '')}Serializer.fromMap(map['$alias'] as Map)"
2021-05-15 14:37:52 +00:00
' : $defaultValue';
2019-01-09 19:25:05 +00:00
} else if (type is InterfaceType) {
if (isListOfModelType(type)) {
2021-08-15 08:04:21 +00:00
if (defaultValue == 'null') {
defaultValue = '[]';
}
2021-05-18 11:32:47 +00:00
var rc = ReCase(
type.typeArguments[0].getDisplayString(withNullability: true));
2021-08-17 07:29:10 +00:00
2018-02-28 02:10:43 +00:00
deserializedRepresentation = "map['$alias'] is Iterable"
2019-04-30 15:44:01 +00:00
" ? List.unmodifiable(((map['$alias'] as Iterable)"
2021-05-15 14:37:52 +00:00
'.whereType<Map>())'
2021-08-14 03:52:31 +00:00
'.map(${rc.pascalCase.replaceAll('?', '')}Serializer.fromMap))'
2021-05-15 14:37:52 +00:00
' : $defaultValue';
2019-01-09 19:25:05 +00:00
} else if (isMapToModelType(type)) {
2021-08-17 07:29:10 +00:00
// TODO: This requires refractoring
2021-09-11 06:37:53 +00:00
if (defaultValue == 'null') {
defaultValue = '{}';
}
2021-05-18 11:32:47 +00:00
var rc = ReCase(
type.typeArguments[1].getDisplayString(withNullability: true));
2018-02-28 02:10:43 +00:00
deserializedRepresentation = '''
map['$alias'] is Map
2019-04-30 15:44:01 +00:00
? Map.unmodifiable((map['$alias'] as Map).keys.fold({}, (out, key) {
2021-08-14 03:52:31 +00:00
return out..[key] = ${rc.pascalCase.replaceAll('?', '')}Serializer
2018-06-29 15:30:47 +00:00
.fromMap(((map['$alias'] as Map)[key]) as Map);
2018-05-15 19:01:13 +00:00
}))
2018-12-31 01:18:44 +00:00
: $defaultValue
2018-02-28 02:10:43 +00:00
''';
2019-01-09 19:25:05 +00:00
} else if (type.element.isEnum) {
2018-06-27 05:36:57 +00:00
deserializedRepresentation = '''
2021-05-18 11:32:47 +00:00
map['$alias'] is ${type.getDisplayString(withNullability: true)}
2022-02-27 04:16:31 +00:00
? (map['$alias'] as ${type.getDisplayString(withNullability: true)}) ?? $defaultValue
2018-06-27 05:36:57 +00:00
:
(
map['$alias'] is int
2021-05-18 11:32:47 +00:00
? ${type.getDisplayString(withNullability: true)}.values[map['$alias'] as int]
2018-12-31 01:18:44 +00:00
: $defaultValue
2018-06-27 05:36:57 +00:00
)
''';
2021-08-17 07:29:10 +00:00
//log.warning('Code => $deserializedRepresentation');
2018-12-08 20:53:49 +00:00
} else if (const TypeChecker.fromRuntime(List)
2019-01-09 19:25:05 +00:00
.isAssignableFromType(type) &&
type.typeArguments.length == 1) {
2021-08-17 07:29:10 +00:00
if (defaultValue == 'null') {
defaultValue = '[]';
}
2019-01-09 19:25:05 +00:00
var arg = convertTypeReference(type.typeArguments[0])
2021-08-15 08:04:21 +00:00
.accept(DartEmitter(useNullSafetySyntax: true));
2018-12-08 20:53:49 +00:00
deserializedRepresentation = '''
map['$alias'] is Iterable
? (map['$alias'] as Iterable).cast<$arg>().toList()
2018-12-31 01:18:44 +00:00
: $defaultValue
2018-12-08 20:53:49 +00:00
''';
} else if (const TypeChecker.fromRuntime(Map)
2019-01-09 19:25:05 +00:00
.isAssignableFromType(type) &&
type.typeArguments.length == 2) {
var key = convertTypeReference(type.typeArguments[0])
2021-08-15 08:04:21 +00:00
.accept(DartEmitter(useNullSafetySyntax: true));
2019-01-09 19:25:05 +00:00
var value = convertTypeReference(type.typeArguments[1])
2021-08-15 08:04:21 +00:00
.accept(DartEmitter(useNullSafetySyntax: true));
2021-08-17 07:29:10 +00:00
if (defaultValue == 'null') {
defaultValue = '{}';
}
2018-12-08 20:53:49 +00:00
deserializedRepresentation = '''
map['$alias'] is Map
? (map['$alias'] as Map).cast<$key, $value>()
2018-12-31 01:18:44 +00:00
: $defaultValue
2018-12-08 20:53:49 +00:00
''';
} else if (const TypeChecker.fromRuntime(Uint8List)
2019-01-09 19:25:05 +00:00
.isAssignableFromType(type)) {
2018-12-08 20:53:49 +00:00
deserializedRepresentation = '''
map['$alias'] is Uint8List
? (map['$alias'] as Uint8List)
:
(
map['$alias'] is Iterable<int>
2019-04-30 15:44:01 +00:00
? Uint8List.fromList((map['$alias'] as Iterable<int>).toList())
2018-12-08 20:53:49 +00:00
:
(
map['$alias'] is String
2019-04-30 15:44:01 +00:00
? Uint8List.fromList(base64.decode(map['$alias'] as String))
2018-12-31 01:18:44 +00:00
: $defaultValue
2018-12-08 20:53:49 +00:00
)
)
''';
2018-02-28 02:10:43 +00:00
}
}
buf.write('${field.name}: $deserializedRepresentation');
}
buf.write(');');
2019-04-30 15:44:01 +00:00
method.body = Code(buf.toString());
2018-02-28 02:10:43 +00:00
}));
}
2018-03-09 12:39:21 +00:00
2021-08-14 03:52:31 +00:00
void generateFieldsClass(BuildContext ctx, LibraryBuilder file) {
2021-08-15 08:04:21 +00:00
//log.fine('Generate serializer fields');
2021-08-14 03:52:31 +00:00
2019-04-30 15:44:01 +00:00
file.body.add(Class((clazz) {
2018-03-09 12:39:21 +00:00
clazz
..abstract = true
2021-08-14 03:52:31 +00:00
..name = '${ctx.modelClassNameRecase.pascalCase}Fields';
2018-03-09 12:39:21 +00:00
2019-04-30 15:44:01 +00:00
clazz.fields.add(Field((b) {
b
..static = true
..modifier = FieldModifier.constant
2019-04-30 15:44:01 +00:00
..type = TypeReference((b) => b
..symbol = 'List'
..types.add(refer('String')))
..name = 'allFields'
..assignment = literalConstList(
ctx.fields.map((f) => refer(f.name)).toList(),
refer('String'))
.code;
}));
2018-03-09 12:39:21 +00:00
for (var field in ctx.fields) {
2019-04-30 15:44:01 +00:00
clazz.fields.add(Field((b) {
2018-03-09 12:39:21 +00:00
b
..static = true
..modifier = FieldModifier.constant
2019-04-30 15:44:01 +00:00
..type = Reference('String')
2018-03-09 12:39:21 +00:00
..name = field.name
2019-04-30 15:44:01 +00:00
..assignment = Code("'${ctx.resolveFieldName(field.name)}'");
2018-03-09 12:39:21 +00:00
}));
}
}));
}
}