Skip to content

Commit

Permalink
fix(dynamite,nextcloud): make oneOf and anyOf throw at build time rat…
Browse files Browse the repository at this point in the history
…her then on deserialization

Signed-off-by: Nikolas Rimikis <leptopoda@users.noreply.github.com>
  • Loading branch information
Leptopoda committed Oct 22, 2023
1 parent 16ec4de commit 93899cd
Show file tree
Hide file tree
Showing 10 changed files with 455 additions and 216 deletions.
356 changes: 195 additions & 161 deletions packages/dynamite/dynamite/lib/src/builder/ofs_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -101,181 +101,215 @@ TypeResult resolveOfs(
fields[result.name] = toFieldName(dartName, result.name);
}

state.output.addAll([
buildInterface(
identifier,
methods: BuiltList.build((final b) {
final interface = buildInterface(
identifier,
methods: BuiltList.build((final b) {
b.add(
Method(
(final b) {
b
..name = 'data'
..returns = refer('JsonObject')
..type = MethodType.getter;
},
),
);

for (final result in results) {
b.add(
Method(
(final b) {
final s = schema.ofs![results.indexOf(result)];
b
..name = 'data'
..returns = refer('JsonObject')
..type = MethodType.getter;
..name = fields[result.name]
..returns = refer(result.nullableName)
..type = MethodType.getter
..docs.addAll(s.formattedDescription);
},
),
);
}
}),
);

for (final result in results) {
b.add(
Method(
(final b) {
final s = schema.ofs![results.indexOf(result)];
b
..name = fields[result.name]
..returns = refer(result.nullableName)
..type = MethodType.getter
..docs.addAll(s.formattedDescription);
},
),
);
}
}),
),
buildBuiltClass(
identifier,
customSerializer: true,
),
Class(
(final b) => b
..name = '_\$${identifier}Serializer'
..implements.add(refer('PrimitiveSerializer<$identifier>'))
..fields.addAll([
Field(
(final b) => b
..name = 'types'
..modifier = FieldModifier.final$
..type = refer('Iterable<Type>')
..annotations.add(refer('override'))
..assignment = Code('const [$identifier, _\$$identifier]'),
),
Field(
(final b) => b
..name = 'wireName'
..modifier = FieldModifier.final$
..type = refer('String')
..annotations.add(refer('override'))
..assignment = Code("r'$identifier'"),
),
])
..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(identifier),
),
])
..optionalParameters.add(
Parameter(
(final b) => b
..name = 'specifiedType'
..type = refer('FullType')
..named = true
..defaultTo = const Code('FullType.unspecified'),
),
)
..body = const Code('return object.data.value;');
}),
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 = 'data'
..type = refer('Object'),
),
])
..optionalParameters.add(
Parameter(
(final b) => b
..name = 'specifiedType'
..type = refer('FullType')
..named = true
..defaultTo = const Code('FullType.unspecified'),
),
)
..body = Code(
<String>[
'final result = new ${identifier}Builder()',
'..data = JsonObject(data);',
final hook = Method((final b) {
b
..name = '_validate'
..returns = refer('void')
..annotations.add(
refer('BuiltValueHook').call([], {'finalizeBuilder': literalTrue}),
)
..static = true
..requiredParameters.add(
Parameter(
(final b) => b
..name = 'b'
..type = refer('${identifier}Builder'),
),
);

final buffer = StringBuffer()
..writeln('// When this is rebuild from another builder')
..writeln('if (b._data == null) { return;}')
..writeln()
..writeln('final match = ')
..writeln('[${fields.values.map((final e) => 'b._$e').join(',')}]')
..writeln(schema.oneOf != null ? '.singleWhereOrNull' : '.firstWhereOrNull')
..writeln('((final x) => x != null);')
..writeln('if ( match == null) {')
..writeln(
'throw StateError("Need ${schema.oneOf != null ? 'exactly one' : 'at least'} one of ${fields.values.map((final e) => "'$e'").join(',')} for \${b._data}");',
)
..writeln('}');

b.body = Code(buffer.toString());
});

final $class = buildBuiltClass(
identifier,
customSerializer: true,
extraMethods: [hook],
);

final serializer = Class(
(final b) => b
..name = '_\$${identifier}Serializer'
..implements.add(refer('PrimitiveSerializer<$identifier>'))
..fields.addAll([
Field(
(final b) => b
..name = 'types'
..modifier = FieldModifier.final$
..type = refer('Iterable<Type>')
..annotations.add(refer('override'))
..assignment = Code('const [$identifier, _\$$identifier]'),
),
Field(
(final b) => b
..name = 'wireName'
..modifier = FieldModifier.final$
..type = refer('String')
..annotations.add(refer('override'))
..assignment = Code("r'$identifier'"),
),
])
..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(identifier),
),
])
..optionalParameters.add(
Parameter(
(final b) => b
..name = 'specifiedType'
..type = refer('FullType')
..named = true
..defaultTo = const Code('FullType.unspecified'),
),
)
..body = const Code('return object.data.value;');
}),
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 = 'data'
..type = refer('Object'),
),
])
..optionalParameters.add(
Parameter(
(final b) => b
..name = 'specifiedType'
..type = refer('FullType')
..named = true
..defaultTo = const Code('FullType.unspecified'),
),
)
..body = Code(
<String>[
'final result = ${identifier}Builder()',
'..data = JsonObject(data);',
if (schema.discriminator != null) ...[
'if (data is! Iterable) {',
r"throw StateError('Expected an Iterable but got ${data.runtimeType}');",
'}',
'',
'String? discriminator;',
'',
'final iterator = data.iterator;',
'while (iterator.moveNext()) {',
'final key = iterator.current! as String;',
'iterator.moveNext();',
'final Object? value = iterator.current;',
"if (key == '${schema.discriminator!.propertyName}') {",
'discriminator = value! as String;',
'break;',
'}',
'}',
],
for (final result in results) ...[
if (schema.discriminator != null) ...[
'if (data is! Iterable) {',
r"throw StateError('Expected an Iterable but got ${data.runtimeType}');",
'}',
'',
'String? discriminator;',
'',
'final iterator = data.iterator;',
'while (iterator.moveNext()) {',
'final key = iterator.current! as String;',
'iterator.moveNext();',
'final Object? value = iterator.current;',
"if (key == '${schema.discriminator!.propertyName}') {",
'discriminator = value! as String;',
'break;',
'}',
'}',
],
for (final result in results) ...[
if (schema.discriminator != null) ...[
"if (discriminator == '${result.name}'",
if (schema.discriminator!.mapping != null && schema.discriminator!.mapping!.isNotEmpty) ...[
for (final key in schema.discriminator!.mapping!.entries
.where(
(final entry) => entry.value.endsWith('/${result.name}'),
)
.map((final entry) => entry.key)) ...[
" || discriminator == '$key'",
],
') {',
"if (discriminator == '${result.name}'",
if (schema.discriminator!.mapping != null && schema.discriminator!.mapping!.isNotEmpty) ...[
for (final key in schema.discriminator!.mapping!.entries
.where(
(final entry) => entry.value.endsWith('/${result.name}'),
)
.map((final entry) => entry.key)) ...[
" || discriminator == '$key'",
],
],
'try {',
if (result is TypeResultBase || result is TypeResultEnum) ...[
'result._${fields[result.name]!} = ${result.deserialize('data')};',
] else ...[
'result._${fields[result.name]!} = ${result.deserialize('data')}.toBuilder();',
],
'} catch (_) {',
if (schema.discriminator != null) ...[
'rethrow;',
],
'}',
if (schema.discriminator != null) ...[
'}',
') {',
],
],
if (schema.oneOf != null) ...[
"assert([${fields.values.map((final e) => 'result._$e').join(',')}].where((final x) => x != null).length >= 1, 'Need oneOf for \${result._data}');",
'try {',
if (result is TypeResultBase || result is TypeResultEnum) ...[
'result._${fields[result.name]!} = ${result.deserialize('data')};',
] else ...[
'result._${fields[result.name]!} = ${result.deserialize('data')}.toBuilder();',
],
if (schema.anyOf != null) ...[
"assert([${fields.values.map((final e) => 'result._$e').join(',')}].where((final x) => x != null).length >= 1, 'Need anyOf for \${result._data}');",
'} catch (_) {',
if (schema.discriminator != null) ...[
'rethrow;',
],
'return result.build();',
].join(),
);
}),
]),
),
'}',
if (schema.discriminator != null) ...[
'}',
],
],
'return result.build();',
].join(),
);
}),
]),
);

state.output.addAll([
interface,
$class,
serializer,
]);
}

Expand Down
5 changes: 5 additions & 0 deletions packages/dynamite/dynamite/lib/src/helpers/built_value.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Spec buildBuiltClass(
final String className, {
final Iterable<String>? defaults,
final bool customSerializer = false,
final Iterable<Method>? extraMethods,
}) =>
Class(
(final b) {
Expand Down Expand Up @@ -61,6 +62,10 @@ Spec buildBuiltClass(
),
);
}

if (extraMethods != null) {
b.methods.addAll(extraMethods);
}
},
);

Expand Down
Loading

0 comments on commit 93899cd

Please sign in to comment.