Skip to content

Commit

Permalink
Merge pull request #1001 from nextcloud/fix/dynamite/some_ofs
Browse files Browse the repository at this point in the history
make oneOf and anyOf throw at build time rather then on deserialization
  • Loading branch information
Leptopoda authored Oct 29, 2023
2 parents 35de0d6 + ec77029 commit 5c2b538
Show file tree
Hide file tree
Showing 14 changed files with 896 additions and 531 deletions.
347 changes: 191 additions & 156 deletions packages/dynamite/dynamite/lib/src/builder/ofs_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -101,29 +101,63 @@ TypeResult resolveOfs(
fields[result.name] = toFieldName(dartName, result.name);
}

state.output.addAll([
buildInterface(
identifier,
methods: BuiltList.from(
results.map(
(final result) => 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);
},
),
final interface = buildInterface(
identifier,
methods: BuiltList.from(
results.map(
(final result) => 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,
methods: BuiltList.build(
(final b) => b.add(
);

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' : '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,
methods: BuiltList.build(
(final b) => b
..add(
Method(
(final b) {
b
Expand All @@ -132,149 +166,150 @@ TypeResult resolveOfs(
..type = MethodType.getter;
},
),
),
),
)
..add(hook),
),
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 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 {',
'final value = ${result.deserialize('data')};',
if (result is TypeResultBase || result is TypeResultEnum)
'result.${fields[result.name]!} = value;'
else
'result.${fields[result.name]!}.replace(value);',
'} catch (_) {',
if (schema.discriminator != null) ...[
'rethrow;',
],
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}');",
'}',
if (schema.discriminator != null) ...[
'}',
],
'return result.build();',
].join(),
);
}),
]),
),
],
'return result.build();',
].join(),
);
}),
]),
);

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

Expand Down
Loading

0 comments on commit 5c2b538

Please sign in to comment.