Browse Source

dynamite: migrate ContentString for built_value

built_value works on types and does not know the concept of fields thus we need to implement a custom class for every contentString
Signed-off-by: Nikolas Rimikis <rimikis.nikolas@gmail.com>
pull/194/head
Nikolas Rimikis 2 years ago
parent
commit
571b1f6ad5
No known key found for this signature in database
GPG Key ID: 85ED1DE9786A4FF2
  1. 364
      packages/dynamite/lib/src/openapi_builder.dart
  2. 6
      packages/dynamite/lib/src/type_result/list.dart
  3. 23
      packages/dynamite/lib/src/type_result/object.dart

364
packages/dynamite/lib/src/openapi_builder.dart

@ -780,10 +780,9 @@ class OpenAPIBuilder implements Builder {
if (dartParameterNullable) { if (dartParameterNullable) {
code.write('if (${_toDartName(parameter.name)} != null) {'); code.write('if (${_toDartName(parameter.name)} != null) {');
} }
final isPlainList = result is TypeResultList && !result.fromContentString;
final value = result.encode( final value = result.encode(
result.serialize(_toDartName(parameter.name)), result.serialize(_toDartName(parameter.name)),
onlyChildren: isPlainList && parameter.in_ == 'query', onlyChildren: result is TypeResultList && parameter.in_ == 'query',
// Objects inside the query always have to be interpreted in some way // Objects inside the query always have to be interpreted in some way
mimeType: 'application/json', mimeType: 'application/json',
); );
@ -798,7 +797,7 @@ class OpenAPIBuilder implements Builder {
break; break;
case 'query': case 'query':
code.write( code.write(
"queryParameters['${parameter.name}${isPlainList ? '[]' : ''}'] = $value;", "queryParameters['${parameter.name}${result is TypeResultList ? '[]' : ''}'] = $value;",
); );
break; break;
case 'header': case 'header':
@ -1039,7 +1038,11 @@ class OpenAPIBuilder implements Builder {
'$name,', '$name,',
], ],
'])', '])',
r'final Serializers serializers = (_$serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build();', r'final Serializers serializers = (_$serializers.toBuilder()..addPlugin(StandardJsonPlugin())..addAll(const [',
if (state.hasContentString) ...[
r'_$ContentStringSerializer()',
],
'])).build();',
'', '',
'// coverage:ignore-start', '// coverage:ignore-start',
'T deserialize$prefix<T>(final Object data) => serializers.deserialize(data, specifiedType: FullType(T))! as T;', 'T deserialize$prefix<T>(final Object data) => serializers.deserialize(data, specifiedType: FullType(T))! as T;',
@ -1049,6 +1052,199 @@ class OpenAPIBuilder implements Builder {
]); ]);
} }
if (state.hasContentString) {
output.addAll([
Class(
(final b) => b
..name = 'ContentString'
..abstract = true
..types.add(refer('T'))
..implements.add(
refer(
'Built<ContentString<T>, ContentStringBuilder<T>>',
),
)
..constructors.addAll([
Constructor(
(final b) => b
..name = '_'
..constant = true,
),
Constructor(
(final b) => b
..factory = true
..lambda = true
..optionalParameters.add(
Parameter(
(final b) => b
..name = 'b'
..type = refer('void Function(ContentStringBuilder<T>)?'),
),
)
..redirect = refer(r'_$ContentString<T>'),
),
])
..methods.addAll([
Method(
(final b) => b
..name = 'content'
..returns = refer('T')
..type = MethodType.getter
..docs.addAll(_descriptionToDocs('decoded contentString')),
),
Method(
(final b) => b
..static = true
..name = 'fromJson'
..lambda = true
..returns = refer('ContentString')
..requiredParameters.add(
Parameter(
(final b) => b
..name = 'json'
..type = refer('Object'),
),
)
..body = const Code('serializers.deserializeWith(serializer, json)!'),
),
Method(
(final b) => b
..name = 'toJson'
..returns = refer('Map<String, dynamic>')
..lambda = true
..body = const Code('serializers.serializeWith(serializer, this)! as Map<String, dynamic>'),
),
Method(
(final b) => b
..name = 'serializer'
..returns = refer('Serializer<ContentString>')
..lambda = true
..static = true
..annotations.add(refer('BuiltValueSerializer').call([], {'custom': literalTrue}))
..body = const Code(r'_$ContentStringSerializer()')
..type = MethodType.getter,
),
]),
).accept(emitter).toString(),
Class(
(final b) => b
..name = r'_$ContentStringSerializer'
..implements.add(refer('PrimitiveSerializer<ContentString<Object?>>'))
..constructors.add(
Constructor(
(final b) => b..constant = true,
),
)
..fields.addAll([
Field(
(final b) => b
..name = 'types'
..modifier = FieldModifier.final$
..type = refer('Iterable<Type>')
..annotations.add(refer('override'))
..assignment = const Code(r'const [ContentString, _$ContentString]'),
),
Field(
(final b) => b
..name = 'wireName'
..modifier = FieldModifier.final$
..type = refer('String')
..annotations.add(refer('override'))
..assignment = literalString('ContentString').code,
),
])
..methods.addAll([
Method((final b) {
b
..name = 'serialize'
..returns = refer('Object')
..annotations.add(refer('override'))
..requiredParameters.addAll([
Parameter(
(final b) => b
..name = 'serializers'
..type = refer('Serializers'),
),
Parameter(
(final b) => b
..name = 'object'
..type = refer('ContentString<Object?>'),
),
])
..optionalParameters.add(
Parameter(
(final b) => b
..name = 'specifiedType'
..type = refer('FullType')
..named = true
..defaultTo = const Code('FullType.unspecified'),
),
)
..body = Block.of([
const Code(
'final isUnderspecified = specifiedType.isUnspecified || specifiedType.parameters.isEmpty;',
),
const Code('if (!isUnderspecified) serializers.expectBuilder(specifiedType);'),
const Code(
'final parameterT = isUnderspecified ? FullType.object : specifiedType.parameters[0];',
),
const Code(''),
const Code('final result = serializers.serialize(object.content, specifiedType: parameterT);'),
const Code(''),
const Code(r'return json.encode("$result");'),
]);
}),
Method((final b) {
b
..name = 'deserialize'
..returns = refer('ContentString<Object?>')
..annotations.add(refer('override'))
..requiredParameters.addAll([
Parameter(
(final b) => b
..name = 'serializers'
..type = refer('Serializers'),
),
Parameter(
(final b) => b
..name = 'serialized'
..type = refer('Object?'),
),
])
..optionalParameters.add(
Parameter(
(final b) => b
..name = 'specifiedType'
..type = refer('FullType')
..named = true
..defaultTo = const Code('FullType.unspecified'),
),
)
..body = Block.of([
const Code(
'final isUnderspecified = specifiedType.isUnspecified || specifiedType.parameters.isEmpty;',
),
const Code('if (!isUnderspecified) serializers.expectBuilder(specifiedType);'),
const Code(
'final parameterT = isUnderspecified ? FullType.object : specifiedType.parameters[0];',
),
const Code(''),
const Code(
'final result = isUnderspecified? ContentStringBuilder<Object?>(): serializers.newBuilder(specifiedType) as ContentStringBuilder<Object?>;',
),
const Code(''),
const Code(
'result.content = serializers.deserialize(json.decode(serialized as String), specifiedType: parameterT);',
),
const Code(''),
const Code('return result.build();'),
]);
}),
]),
).accept(emitter).toString()
]);
}
final formatter = DartFormatter( final formatter = DartFormatter(
pageWidth: 120, pageWidth: 120,
); );
@ -1061,12 +1257,6 @@ class OpenAPIBuilder implements Builder {
RegExp( RegExp(
r'Map<String, dynamic> toJson\(\) => _\$.*ToJson\(this\);', r'Map<String, dynamic> toJson\(\) => _\$.*ToJson\(this\);',
), ),
RegExp(
r'factory .*\.fromJsonString\(String data\) => .*\.fromJson\(json\.decode\(data\)(?: as Map<String, dynamic>)?\);',
),
RegExp(
r'static String toJsonString\(.* data\) => json\.encode\(data(?:\.toJson\(\))?\);',
),
RegExp( RegExp(
r'dynamic toJson\(\) => _data;', r'dynamic toJson\(\) => _data;',
), ),
@ -1255,6 +1445,7 @@ class State {
final resolvedTypes = <String>[]; final resolvedTypes = <String>[];
final registeredJsonObjects = <String>[]; final registeredJsonObjects = <String>[];
final output = <Spec>[]; final output = <Spec>[];
bool hasContentString = false;
} }
TypeResult resolveObject( TypeResult resolveObject(
@ -1264,7 +1455,6 @@ TypeResult resolveObject(
final Schema schema, { final Schema schema, {
required final Map<String, String>? extraJsonSerializableValues, required final Map<String, String>? extraJsonSerializableValues,
required final Map<String, Map<String, String>>? extraJsonKeyValues, required final Map<String, Map<String, String>>? extraJsonKeyValues,
final bool fromContentString = false,
}) { }) {
if (!state.resolvedTypes.contains('${state.prefix}$identifier')) { if (!state.resolvedTypes.contains('${state.prefix}$identifier')) {
state.resolvedTypes.add('${state.prefix}$identifier'); state.resolvedTypes.add('${state.prefix}$identifier');
@ -1317,21 +1507,6 @@ TypeResult resolveObject(
) )
..body = const Code('serializers.deserializeWith(serializer, json)!'), ..body = const Code('serializers.deserializeWith(serializer, json)!'),
), ),
Method(
(final b) => b
..static = true
..name = 'fromJsonString'
..lambda = true
..returns = refer('${state.prefix}$identifier')
..requiredParameters.add(
Parameter(
(final b) => b
..name = 'data'
..type = refer('String'),
),
)
..body = const Code('serializers.fromJson(serializer, data)!'),
),
Method( Method(
(final b) => b (final b) => b
..name = 'toJson' ..name = 'toJson'
@ -1339,21 +1514,6 @@ TypeResult resolveObject(
..lambda = true ..lambda = true
..body = const Code('serializers.serializeWith(serializer, this)! as Map<String, dynamic>'), ..body = const Code('serializers.serializeWith(serializer, this)! as Map<String, dynamic>'),
), ),
Method(
(final b) => b
..name = 'toJsonString'
..returns = refer('String?')
..lambda = true
..static = true
..requiredParameters.add(
Parameter(
(final b) => b
..name = 'data'
..type = refer(_makeNullable('${state.prefix}$identifier', true)),
),
)
..body = const Code('data == null ? null : serializers.toJson(serializer, data)'),
),
for (final propertyName in schema.properties!.keys) ...[ for (final propertyName in schema.properties!.keys) ...[
Method( Method(
(final b) { (final b) {
@ -1381,7 +1541,6 @@ TypeResult resolveObject(
..type = MethodType.getter ..type = MethodType.getter
..docs.addAll(_descriptionToDocs(propertySchema.description)); ..docs.addAll(_descriptionToDocs(propertySchema.description));
final hasDifferentName = _toDartName(propertyName) != propertyName; final hasDifferentName = _toDartName(propertyName) != propertyName;
final isContentString = propertySchema.isContentString;
final hasExtraJsonKeyValues = final hasExtraJsonKeyValues =
extraJsonKeyValues != null && extraJsonKeyValues.containsKey(propertyName); extraJsonKeyValues != null && extraJsonKeyValues.containsKey(propertyName);
@ -1396,64 +1555,6 @@ TypeResult resolveObject(
}, },
}; };
if (isContentString) {
if (result is! TypeResultObject && result is! TypeResultList) {
print("The content string $identifier.$propertyName can't be decoded automatically.");
}
var fromJson = '${result.name}.fromJsonString';
var toJson = '${result.name}.toJsonString';
if (result is TypeResultList) {
fromJson = '_${_toDartName('${state.prefix}${identifier}FromJsonString')}';
if (!state.resolvedTypes.contains(fromJson)) {
state.resolvedTypes.add(fromJson);
state.output.add(
Method(
(final b) => b
..name = fromJson
..returns = refer(result.name)
..lambda = true
..requiredParameters.addAll([
Parameter(
(final b) => b
..name = 'data'
..type = refer('String'),
),
])
..body = Code('${result.deserialize(result.decode('data'))};'),
),
);
}
toJson = '_${_toDartName('${state.prefix}${identifier}ToJsonString')}';
if (!state.resolvedTypes.contains(toJson)) {
state.resolvedTypes.add(toJson);
state.output.add(
Method(
(final b) => b
..name = toJson
..returns = refer('String?')
..lambda = true
..requiredParameters.addAll([
Parameter(
(final b) => b
..name = 'data'
..type = refer(_makeNullable(result.name, true)),
),
])
..body = Code(
'data == null ? null : ${result.encode(result.serialize('data'), mimeType: 'application/json')};',
),
),
);
}
}
arguments.addAll({
'fromJson': refer(fromJson),
'toJson': refer(toJson),
});
}
if (arguments.isNotEmpty) { if (arguments.isNotEmpty) {
b.annotations.add( b.annotations.add(
refer('BuiltValueField').call([], arguments), refer('BuiltValueField').call([], arguments),
@ -1523,10 +1624,7 @@ TypeResult resolveObject(
), ),
); );
} }
return TypeResultObject( return TypeResultObject('${state.prefix}$identifier');
'${state.prefix}$identifier',
fromContentString: fromContentString,
);
} }
TypeResult resolveType( TypeResult resolveType(
@ -1537,7 +1635,6 @@ TypeResult resolveType(
final Map<String, String>? extraJsonSerializableValues, final Map<String, String>? extraJsonSerializableValues,
final Map<String, Map<String, String>>? extraJsonKeyValues, final Map<String, Map<String, String>>? extraJsonKeyValues,
final bool ignoreEnum = false, final bool ignoreEnum = false,
final bool fromContentString = false,
}) { }) {
TypeResult? result; TypeResult? result;
if (schema.ref == null && schema.ofs == null && schema.type == null) { if (schema.ref == null && schema.ofs == null && schema.type == null) {
@ -1551,7 +1648,6 @@ TypeResult resolveType(
name, name,
spec.components!.schemas![name]!, spec.components!.schemas![name]!,
extraJsonSerializableValues: extraJsonSerializableValues, extraJsonSerializableValues: extraJsonSerializableValues,
fromContentString: fromContentString,
); );
} else if (schema.ofs != null) { } else if (schema.ofs != null) {
if (!state.resolvedTypes.contains('${state.prefix}$identifier')) { if (!state.resolvedTypes.contains('${state.prefix}$identifier')) {
@ -1641,21 +1737,6 @@ TypeResult resolveType(
) )
..body = const Code('serializers.deserializeWith(serializer, json)!'), ..body = const Code('serializers.deserializeWith(serializer, json)!'),
), ),
Method(
(final b) => b
..static = true
..name = 'fromJsonString'
..lambda = true
..returns = refer('${state.prefix}$identifier')
..requiredParameters.add(
Parameter(
(final b) => b
..name = 'data'
..type = refer('String'),
),
)
..body = const Code('serializers.fromJson(serializer, data)!'),
),
Method( Method(
(final b) => b (final b) => b
..name = 'toJson' ..name = 'toJson'
@ -1663,21 +1744,6 @@ TypeResult resolveType(
..lambda = true ..lambda = true
..body = const Code('serializers.serializeWith(serializer, this)! as Map<String, dynamic>'), ..body = const Code('serializers.serializeWith(serializer, this)! as Map<String, dynamic>'),
), ),
Method(
(final b) => b
..name = 'toJsonString'
..returns = refer('String?')
..lambda = true
..static = true
..requiredParameters.add(
Parameter(
(final b) => b
..name = 'data'
..type = refer(_makeNullable('${state.prefix}$identifier', true)),
),
)
..body = const Code('data == null ? null : serializers.toJson(serializer, data)'),
),
Method( Method(
(final b) => b (final b) => b
..name = 'serializer' ..name = 'serializer'
@ -1825,6 +1891,18 @@ TypeResult resolveType(
} }
result = TypeResultObject('${state.prefix}$identifier'); result = TypeResultObject('${state.prefix}$identifier');
} else if (schema.isContentString) {
result = resolveType(
spec,
state,
identifier,
schema.contentSchema!,
);
//state.hasContentString = true;
//result = TypeResultObject('ContentString<${result.name}>');
print("Current ContentString support is limited and won't be decoded automatically. "
'See https://github.com/provokateurin/nextcloud-neon/issues/281 for further information.');
} else { } else {
switch (schema.type) { switch (schema.type) {
case 'boolean': case 'boolean':
@ -1843,18 +1921,6 @@ TypeResult resolveType(
break; break;
} }
if (schema.isContentString) {
result = resolveType(
spec,
state,
identifier,
schema.contentSchema!,
extraJsonSerializableValues: extraJsonSerializableValues,
fromContentString: true,
);
break;
}
result = TypeResultBase('String'); result = TypeResultBase('String');
break; break;
case 'array': case 'array':
@ -1869,7 +1935,6 @@ TypeResult resolveType(
result = TypeResultList( result = TypeResultList(
'BuiltList<${subResult.name}>', 'BuiltList<${subResult.name}>',
subResult, subResult,
fromContentString: fromContentString,
); );
} else { } else {
result = TypeResultList( result = TypeResultList(
@ -1919,7 +1984,6 @@ TypeResult resolveType(
schema, schema,
extraJsonSerializableValues: extraJsonSerializableValues, extraJsonSerializableValues: extraJsonSerializableValues,
extraJsonKeyValues: extraJsonKeyValues, extraJsonKeyValues: extraJsonKeyValues,
fromContentString: fromContentString,
); );
break; break;
} }

6
packages/dynamite/lib/src/type_result/list.dart

@ -3,12 +3,10 @@ part of '../../dynamite.dart';
class TypeResultList extends TypeResult { class TypeResultList extends TypeResult {
TypeResultList( TypeResultList(
super.name, super.name,
this.subType, { this.subType,
this.fromContentString = false, );
});
final TypeResult subType; final TypeResult subType;
final bool fromContentString;
@override @override
String serialize(final String object) => '$object.map((final e) => ${subType.serialize('e')})'; String serialize(final String object) => '$object.map((final e) => ${subType.serialize('e')})';

23
packages/dynamite/lib/src/type_result/object.dart

@ -1,20 +1,11 @@
part of '../../dynamite.dart'; part of '../../dynamite.dart';
class TypeResultObject extends TypeResult { class TypeResultObject extends TypeResult {
TypeResultObject( TypeResultObject(super.name)
super.name, { : assert(name != 'JsonObject' && name != 'Object' && name != 'dynamic', 'Use TypeResultBase instead');
this.fromContentString = false,
}) : assert(name != 'JsonObject' && name != 'Object' && name != 'dynamic', 'Use TypeResultBase instead');
final bool fromContentString;
@override @override
String serialize(final String object) { String serialize(final String object) => '$object.toJson()';
if (fromContentString) {
return '$name.toJsonString($object)';
}
return '$object.toJson()';
}
@override @override
String encode( String encode(
@ -33,12 +24,8 @@ class TypeResultObject extends TypeResult {
} }
@override @override
String deserialize(final String object, {final bool toBuilder = false}) { String deserialize(final String object, {final bool toBuilder = false}) =>
if (fromContentString) { '$name.fromJson($object as Object)${toBuilder ? '.toBuilder()' : ''}';
return '$name.fromJsonString($object as String)${toBuilder ? '.toBuilder()' : ''}';
}
return '$name.fromJson($object as Object)${toBuilder ? '.toBuilder()' : ''}';
}
@override @override
String decode(final String object) => 'json.decode($object as String)'; String decode(final String object) => 'json.decode($object as String)';

Loading…
Cancel
Save