Browse Source

feat(dynamite): use inheritance for allOf

Signed-off-by: Nikolas Rimikis <leptopoda@users.noreply.github.com>
pull/704/head
Nikolas Rimikis 1 year ago
parent
commit
3d0508edc0
No known key found for this signature in database
GPG Key ID: 85ED1DE9786A4FF2
  1. 168
      packages/dynamite/dynamite/lib/src/builder/ofs_builder.dart
  2. 91
      packages/dynamite/dynamite/lib/src/builder/resolve_interface.dart
  3. 56
      packages/dynamite/dynamite/lib/src/builder/resolve_object.dart
  4. 8
      packages/dynamite/dynamite/lib/src/builder/resolve_type.dart
  5. 1
      packages/dynamite/dynamite/lib/src/builder/state.dart
  6. 114
      packages/dynamite/dynamite/lib/src/helpers/built_value.dart

168
packages/dynamite/dynamite/lib/src/builder/ofs_builder.dart

@ -1,5 +1,6 @@
import 'package:built_collection/built_collection.dart'; import 'package:built_collection/built_collection.dart';
import 'package:code_builder/code_builder.dart'; import 'package:code_builder/code_builder.dart';
import 'package:dynamite/src/builder/resolve_interface.dart';
import 'package:dynamite/src/builder/resolve_type.dart'; import 'package:dynamite/src/builder/resolve_type.dart';
import 'package:dynamite/src/builder/state.dart'; import 'package:dynamite/src/builder/state.dart';
import 'package:dynamite/src/helpers/built_value.dart'; import 'package:dynamite/src/helpers/built_value.dart';
@ -8,6 +9,61 @@ import 'package:dynamite/src/models/open_api.dart';
import 'package:dynamite/src/models/schema.dart'; import 'package:dynamite/src/models/schema.dart';
import 'package:dynamite/src/type_result/type_result.dart'; import 'package:dynamite/src/type_result/type_result.dart';
TypeResult resolveAllOf(
final OpenAPI spec,
final State state,
final String identifier,
final Schema schema, {
final bool nullable = false,
}) {
final result = TypeResultObject(
'${state.classPrefix}$identifier',
nullable: nullable,
);
if (state.resolvedTypes.add(result)) {
final interfaces = <TypeResultObject>{};
for (final s in schema.allOf!) {
final TypeResultObject interfaceClass;
if (s.ref != null) {
final object = resolveType(
spec,
state,
identifier,
s,
nullable: nullable,
);
if (object is! TypeResultObject) {
throw StateError('allOf does only allow objects. Please change $identifier');
}
interfaceClass = object;
} else {
final interfaceName = schema.ofs!.length == 1 ? identifier : '${identifier}_${schema.allOf!.indexOf(s)}';
interfaceClass = resolveInterface(
spec,
state,
interfaceName,
s,
);
}
interfaces.add(interfaceClass);
}
state.output.add(
buildBuiltClass(
'${state.classPrefix}$identifier',
interfaces: interfaces,
),
);
}
return result;
}
TypeResult resolveOfs( TypeResult resolveOfs(
final OpenAPI spec, final OpenAPI spec,
final State state, final State state,
@ -15,6 +71,10 @@ TypeResult resolveOfs(
final Schema schema, { final Schema schema, {
final bool nullable = false, final bool nullable = false,
}) { }) {
if (schema.allOf != null) {
throw StateError('allOf should be handled with inheritance');
}
final result = TypeResultObject( final result = TypeResultObject(
'${state.classPrefix}$identifier', '${state.classPrefix}$identifier',
nullable: nullable, nullable: nullable,
@ -28,7 +88,7 @@ TypeResult resolveOfs(
state, state,
'$identifier${schema.ofs!.indexOf(s)}', '$identifier${schema.ofs!.indexOf(s)}',
s, s,
nullable: !(schema.allOf?.contains(s) ?? false), nullable: true,
), ),
) )
.toList(); .toList();
@ -42,7 +102,7 @@ TypeResult resolveOfs(
state.output.addAll([ state.output.addAll([
buildBuiltClass( buildBuiltClass(
'${state.classPrefix}$identifier', '${state.classPrefix}$identifier',
BuiltList.build((final b) { methods: BuiltList.build((final b) {
b.add( b.add(
Method( Method(
(final b) { (final b) {
@ -152,69 +212,59 @@ TypeResult resolveOfs(
<String>[ <String>[
'final result = new ${state.classPrefix}${identifier}Builder()', 'final result = new ${state.classPrefix}${identifier}Builder()',
'..data = JsonObject(data);', '..data = JsonObject(data);',
if (schema.allOf != null) ...[ if (schema.discriminator != null) ...[
for (final result in results) ...[ 'if (data is! Iterable) {',
if (result is TypeResultBase || result is TypeResultEnum) ...[ r"throw StateError('Expected an Iterable but got ${data.runtimeType}');",
'result.${fields[result.name]!} = ${result.deserialize('data')};', '}',
] else ...[ '',
'result.${fields[result.name]!}.replace(${result.deserialize('data')});', 'String? discriminator;',
], '',
], 'final iterator = data.iterator;',
] else ...[ '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 (schema.discriminator != null) ...[
'if (data is! Iterable) {', "if (discriminator == '${result.name.replaceFirst(state.classPrefix, '')}'",
r"throw StateError('Expected an Iterable but got ${data.runtimeType}');", if (schema.discriminator!.mapping != null && schema.discriminator!.mapping!.isNotEmpty) ...[
'}', for (final key in schema.discriminator!.mapping!.entries
'', .where(
'String? discriminator;', (final entry) =>
'', entry.value.endsWith('/${result.name.replaceFirst(state.classPrefix, '')}'),
'final iterator = data.iterator;', )
'while (iterator.moveNext()) {', .map((final entry) => entry.key)) ...[
'final key = iterator.current! as String;', " || discriminator == '$key'",
'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.replaceFirst(state.classPrefix, '')}'",
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.replaceFirst(state.classPrefix, '')}'),
)
.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) ...[ 'try {',
"assert([${fields.values.map((final e) => 'result._$e').join(',')}].where((final x) => x != null).length >= 1, 'Need oneOf for \${result._data}');", 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) ...[ '} catch (_) {',
"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) ...[
'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}');",
],
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}');",
], ],
'return result.build();', 'return result.build();',
].join(), ].join(),

91
packages/dynamite/dynamite/lib/src/builder/resolve_interface.dart

@ -0,0 +1,91 @@
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/built_value.dart';
import 'package:dynamite/src/helpers/dart_helpers.dart';
import 'package:dynamite/src/helpers/dynamite.dart';
import 'package:dynamite/src/models/open_api.dart';
import 'package:dynamite/src/models/schema.dart';
import 'package:dynamite/src/type_result/type_result.dart';
TypeResultObject resolveInterface(
final OpenAPI spec,
final State state,
final String identifier,
final Schema schema,
) {
final result = TypeResultObject(
'${state.classPrefix}$identifier',
);
if (state.resolvedInterfaces.add(result)) {
final className = '${state.classPrefix}$identifier$interfaceSuffix';
state.output.add(
Class((final b) {
b
..abstract = true
..modifier = ClassModifier.interface
..name = className
..annotations.add(refer('BuiltValue').call([], {'instantiable': literalFalse}));
for (final property in schema.properties!.entries) {
b.methods.add(
Method(
(final b) {
final propertyName = property.key;
final propertySchema = property.value;
final result = resolveType(
spec,
state,
'${identifier}_${toDartName(propertyName, uppercaseFirstCharacter: true)}',
propertySchema,
nullable: isDartParameterNullable(
schema.required?.contains(propertyName),
propertySchema,
),
);
b
..name = toDartName(propertyName)
..returns = refer(result.nullableName)
..type = MethodType.getter
..docs.addAll(propertySchema.formattedDescription);
if (toDartName(propertyName) != propertyName) {
b.annotations.add(
refer('BuiltValueField').call([], {
'wireName': literalString(propertyName),
}),
);
}
},
),
);
}
b.methods.addAll([
Method(
(final b) => b
..returns = refer(className)
..name = 'rebuild'
..requiredParameters.add(
Parameter(
(final b) => b
..name = 'updates'
..type = refer('void Function(${className}Builder)'),
),
),
),
Method(
(final b) => b
..returns = refer('${className}Builder')
..name = 'toBuilder',
),
]);
}),
);
}
return result;
}

56
packages/dynamite/dynamite/lib/src/builder/resolve_object.dart

@ -1,17 +1,15 @@
import 'package:built_collection/built_collection.dart';
import 'package:code_builder/code_builder.dart';
import 'package:dynamite/src/builder/header_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'; import 'package:dynamite/src/builder/resolve_type.dart';
import 'package:dynamite/src/builder/state.dart'; import 'package:dynamite/src/builder/state.dart';
import 'package:dynamite/src/helpers/built_value.dart'; import 'package:dynamite/src/helpers/built_value.dart';
import 'package:dynamite/src/helpers/dart_helpers.dart'; import 'package:dynamite/src/helpers/dart_helpers.dart';
import 'package:dynamite/src/helpers/dynamite.dart';
import 'package:dynamite/src/helpers/type_result.dart'; import 'package:dynamite/src/helpers/type_result.dart';
import 'package:dynamite/src/models/open_api.dart'; import 'package:dynamite/src/models/open_api.dart';
import 'package:dynamite/src/models/schema.dart'; import 'package:dynamite/src/models/schema.dart';
import 'package:dynamite/src/type_result/type_result.dart'; import 'package:dynamite/src/type_result/type_result.dart';
TypeResult resolveObject( TypeResultObject resolveObject(
final OpenAPI spec, final OpenAPI spec,
final State state, final State state,
final String identifier, final String identifier,
@ -39,49 +37,23 @@ TypeResult resolveObject(
} }
} }
state.output.add( final interfaceClass = resolveInterface(
spec,
state,
identifier,
schema,
);
state.output.addAll([
buildBuiltClass( buildBuiltClass(
'${state.classPrefix}$identifier', '${state.classPrefix}$identifier',
BuiltList.build((final b) {
for (final property in schema.properties!.entries) {
b.add(
Method(
(final b) {
final propertyName = property.key;
final propertySchema = property.value;
final result = resolveType(
spec,
state,
'${identifier}_${toDartName(propertyName, uppercaseFirstCharacter: true)}',
propertySchema,
nullable: isDartParameterNullable(
schema.required?.contains(propertyName),
propertySchema,
),
);
b
..name = toDartName(propertyName)
..returns = refer(result.nullableName)
..type = MethodType.getter
..docs.addAll(propertySchema.formattedDescription);
if (toDartName(propertyName) != propertyName) {
b.annotations.add(
refer('BuiltValueField').call([], {
'wireName': literalString(propertyName),
}),
);
}
},
),
);
}
}),
defaults: defaults, defaults: defaults,
customSerializer: isHeader, customSerializer: isHeader,
interfaces: [
interfaceClass,
],
), ),
); ]);
if (isHeader) { if (isHeader) {
state.output.add(buildHeaderSerializer(state, identifier, spec, schema)); state.output.add(buildHeaderSerializer(state, identifier, spec, schema));
} }

8
packages/dynamite/dynamite/lib/src/builder/resolve_type.dart

@ -30,6 +30,14 @@ TypeResult resolveType(
spec.components!.schemas![name]!, spec.components!.schemas![name]!,
nullable: nullable, nullable: nullable,
); );
} else if (schema.allOf != null) {
result = resolveAllOf(
spec,
state,
identifier,
schema,
nullable: nullable,
);
} else if (schema.ofs != null) { } else if (schema.ofs != null) {
result = resolveOfs( result = resolveOfs(
spec, spec,

1
packages/dynamite/dynamite/lib/src/builder/state.dart

@ -12,4 +12,5 @@ class State {
final output = <Spec>[]; final output = <Spec>[];
final resolvedTypes = <TypeResult>{}; final resolvedTypes = <TypeResult>{};
final resolvedInterfaces = <TypeResult>{};
} }

114
packages/dynamite/dynamite/lib/src/helpers/built_value.dart

@ -1,65 +1,73 @@
import 'package:built_collection/built_collection.dart'; import 'package:built_collection/built_collection.dart';
import 'package:code_builder/code_builder.dart'; import 'package:code_builder/code_builder.dart';
import 'package:dynamite/src/helpers/dart_helpers.dart'; import 'package:dynamite/src/helpers/dart_helpers.dart';
import 'package:dynamite/src/type_result/type_result.dart';
const interfaceSuffix = 'Interface';
Spec buildBuiltClass( Spec buildBuiltClass(
final String className, final String className, {
final BuiltList<Method> methods, { final BuiltList<Method>? methods,
final Iterable<String>? defaults, final Iterable<String>? defaults,
final Iterable<TypeResultObject>? interfaces,
final bool customSerializer = false, final bool customSerializer = false,
}) => }) {
Class( assert((interfaces == null) != (methods == null), 'Either provide an interface or methods.');
(final b) {
b return Class(
..name = className (final b) {
..abstract = true b
..implements.add( ..name = className
refer( ..abstract = true
'Built<$className, ${className}Builder>', ..implements.addAll([
), ...?interfaces?.map((final i) => refer('${i.name}$interfaceSuffix')),
) refer(
..constructors.addAll([ 'Built<$className, ${className}Builder>',
builtValueConstructor(className), ),
hiddenConstructor, ])
fromJsonConstructor, ..constructors.addAll([
]) builtValueConstructor(className),
..methods.addAll([ hiddenConstructor,
toJsonMethod, fromJsonConstructor,
...methods, ])
buildSerializer(className, isCustom: customSerializer), ..methods.addAll([
]); toJsonMethod,
...?methods,
buildSerializer(className, isCustom: customSerializer),
]);
if (defaults != null && defaults.isNotEmpty) { if (defaults != null && defaults.isNotEmpty) {
b.methods.add( b.methods.add(
Method( Method(
(final b) => b (final b) => b
..name = '_defaults' ..name = '_defaults'
..returns = refer('void') ..returns = refer('void')
..static = true ..static = true
..lambda = true ..lambda = true
..annotations.add( ..annotations.add(
refer('BuiltValueHook').call([], { refer('BuiltValueHook').call([], {
'initializeBuilder': literalTrue, 'initializeBuilder': literalTrue,
}), }),
) )
..requiredParameters.add( ..requiredParameters.add(
Parameter( Parameter(
(final b) => b (final b) => b
..name = 'b' ..name = 'b'
..type = refer('${className}Builder'), ..type = refer('${className}Builder'),
),
)
..body = Code(
<String?>[
'b',
...defaults,
].join(),
), ),
), )
); ..body = Code(
} <String?>[
}, 'b',
); ...defaults,
].join(),
),
),
);
}
},
);
}
Method get toJsonMethod => Method( Method get toJsonMethod => Method(
(final b) => b (final b) => b

Loading…
Cancel
Save