Skip to content

Commit

Permalink
feat(dynamite): generate custom serializers for every built class
Browse files Browse the repository at this point in the history
Signed-off-by: Nikolas Rimikis <[email protected]>
  • Loading branch information
Leptopoda committed Nov 16, 2023
1 parent a86b79c commit 350daef
Show file tree
Hide file tree
Showing 3 changed files with 242 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import 'package:code_builder/code_builder.dart';
import 'package:dynamite/src/builder/resolve_type.dart';
import 'package:dynamite/src/builder/state.dart';
import 'package:dynamite/src/helpers/dart_helpers.dart';
import 'package:dynamite/src/helpers/dynamite.dart';
import 'package:dynamite/src/models/openapi.dart' as openapi;
import 'package:dynamite/src/models/type_result.dart';

Spec buildBuiltClassSerializer(
final State state,
final String identifier,
final openapi.OpenAPI spec,
final openapi.Schema schema,
) =>
Class(
(final b) => b
..name = '_\$${identifier}Serializer'
..implements.add(refer('StructuredSerializer<$identifier>'))
..constructors.add(
Constructor(
(final b) => b..constant = true,
),
)
..methods.addAll([
Method(
(final b) => b
..name = 'types'
..type = MethodType.getter
..lambda = true
..returns = refer('Iterable<Type>')
..annotations.add(refer('override'))
..body = Code('const [$identifier, _\$$identifier]'),
),
Method(
(final b) => b
..name = 'wireName'
..type = MethodType.getter
..lambda = true
..returns = refer('String')
..annotations.add(refer('override'))
..body = Code("r'$identifier'"),
),
Method((final b) {
b
..name = 'serialize'
..returns = refer('Iterable<Object?>')
..annotations.add(refer('override'))
..requiredParameters.addAll([
Parameter(
(final b) => b
..name = 'serializers'
..type = refer('Serializers'),
),
Parameter(
(final b) => b
..name = 'object'
..type = refer(identifier),
),
])
..optionalParameters.add(
Parameter(
(final b) => b
..name = 'specifiedType'
..type = refer('FullType')
..named = true
..defaultTo = const Code('FullType.unspecified'),
),
);

final properties = serializeProperty(state, identifier, spec, schema);
final nullableProperties = serializePropertyNullable(state, identifier, spec, schema);
final buffer = StringBuffer()
..write('final result = <Object?>[')
..writeAll(properties, '\n')
..write('];')
..writeln();

if (nullableProperties.isNotEmpty) {
buffer
..write('Object? value;')
..writeAll(nullableProperties, '\n')
..writeln();
}

buffer.write('return result;');

b.body = Code(buffer.toString());
}),
Method((final b) {
b
..name = 'deserialize'
..returns = refer(identifier)
..annotations.add(refer('override'))
..requiredParameters.addAll([
Parameter(
(final b) => b
..name = 'serializers'
..type = refer('Serializers'),
),
Parameter(
(final b) => b
..name = 'serialized'
..type = refer('Iterable<Object?>'),
),
])
..optionalParameters.add(
Parameter(
(final b) => b
..name = 'specifiedType'
..type = refer('FullType')
..named = true
..defaultTo = const Code('FullType.unspecified'),
),
)
..body = Code('''
final result = new ${identifier}Builder();
final iterator = serialized.iterator;
while (iterator.moveNext()) {
final key = iterator.current! as String;
iterator.moveNext();
final value = iterator.current;
switch (key) {
${deserializeProperty(state, identifier, spec, schema).join('\n')}
}
}
return result.build();
''');
}),
]),
);

Iterable<String> deserializeProperty(
final State state,
final String identifier,
final openapi.OpenAPI spec,
final openapi.Schema schema,
) sync* {
for (final property in schema.properties!.entries) {
final propertyName = property.key;
final propertySchema = property.value;
final dartName = toDartName(propertyName);
final result = resolveType(
spec,
state,
'${identifier}_${toDartName(propertyName, uppercaseFirstCharacter: true)}',
propertySchema,
nullable: isDartParameterNullable(schema.required.contains(propertyName), propertySchema),
);

yield "case '$propertyName':";
final deserialize = result.deserialize('value', 'serializers');

if (result is TypeResultBase || result is TypeResultEnum) {
yield 'result.$dartName = $deserialize;';
} else {
yield 'result.$dartName.replace($deserialize,);';
}
}
}

Iterable<String> serializePropertyNullable(
final State state,
final String identifier,
final openapi.OpenAPI spec,
final openapi.Schema schema,
) sync* {
for (final property in schema.properties!.entries) {
final propertyName = property.key;
final propertySchema = property.value;
final dartName = toDartName(propertyName);
final result = resolveType(
spec,
state,
'${identifier}_${toDartName(propertyName, uppercaseFirstCharacter: true)}',
propertySchema,
nullable: isDartParameterNullable(schema.required.contains(propertyName), propertySchema),
);
if (!result.nullable) {
continue;
}

yield '''
value = object.$dartName;
if (value != null) {
result
..add('$propertyName')
..add(${result.serialize('value', 'serializers')},);
}
''';
}
}

Iterable<String> serializeProperty(
final State state,
final String identifier,
final openapi.OpenAPI spec,
final openapi.Schema schema,
) sync* {
for (final property in schema.properties!.entries) {
final propertyName = property.key;
final propertySchema = property.value;
final dartName = toDartName(propertyName);
final result = resolveType(
spec,
state,
'${identifier}_${toDartName(propertyName, uppercaseFirstCharacter: true)}',
propertySchema,
nullable: isDartParameterNullable(schema.required.contains(propertyName), propertySchema),
);
if (result.nullable) {
continue;
}

yield '''
'$propertyName',
${result.serialize('object.$dartName', 'serializers')},
''';
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:dynamite/src/builder/built_object_serializer.dart';
import 'package:dynamite/src/builder/header_serializer.dart';
import 'package:dynamite/src/builder/resolve_interface.dart';
import 'package:dynamite/src/builder/resolve_type.dart';
Expand Down Expand Up @@ -47,12 +48,14 @@ TypeResultObject resolveObject(
buildBuiltClass(
identifier,
defaults: defaults,
customSerializer: isHeader,
customSerializer: true,
),
);

if (isHeader) {
state.output.add(buildHeaderSerializer(state, identifier, spec, schema));
} else {
state.output.add(buildBuiltClassSerializer(state, identifier, spec, schema));
}
}
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,27 @@ sealed class TypeResult {
/// Serializes the variable named [object].
///
/// The serialized result is an [Object]?
String serialize(final String object) => '_jsonSerializers.serialize($object, specifiedType: const $fullType)';
@nonVirtual
String serialize(final String object, [final String? serializerName]) =>
'${serializerName ?? '_jsonSerializers'}.serialize($object, specifiedType: const $fullType)';

/// Deserializes the variable named [object].
///
/// The serialized result will be of [name].
String deserialize(final String object) =>
'(_jsonSerializers.deserialize($object, specifiedType: const $fullType)! as $name)';
@nonVirtual
String deserialize(final String object, [final String? serializerName]) {
final buffer = StringBuffer()
..write(serializerName ?? '_jsonSerializers')
..write('.deserialize(')
..write(object)
..write(', specifiedType: const $fullType)');

if (!nullable) {
buffer.write('!');
}

return '($buffer as $name)';
}

/// Decodes the variable named [object].
String decode(final String object) => 'json.decode($object as String)';
Expand Down

0 comments on commit 350daef

Please sign in to comment.