diff --git a/packages/dynamite/dynamite/lib/src/builder/built_object_serializer.dart b/packages/dynamite/dynamite/lib/src/builder/built_object_serializer.dart new file mode 100644 index 00000000..af89b64e --- /dev/null +++ b/packages/dynamite/dynamite/lib/src/builder/built_object_serializer.dart @@ -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') + ..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') + ..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 = [') + ..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'), + ), + ]) + ..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 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 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 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')}, +'''; + } +} diff --git a/packages/dynamite/dynamite/lib/src/builder/resolve_object.dart b/packages/dynamite/dynamite/lib/src/builder/resolve_object.dart index 3467494b..5a45134c 100644 --- a/packages/dynamite/dynamite/lib/src/builder/resolve_object.dart +++ b/packages/dynamite/dynamite/lib/src/builder/resolve_object.dart @@ -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'; @@ -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; diff --git a/packages/dynamite/dynamite/lib/src/models/type_result/type_result.dart b/packages/dynamite/dynamite/lib/src/models/type_result/type_result.dart index 5d7bd916..88670620 100644 --- a/packages/dynamite/dynamite/lib/src/models/type_result/type_result.dart +++ b/packages/dynamite/dynamite/lib/src/models/type_result/type_result.dart @@ -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)';