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: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/state.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/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(
final OpenAPI spec,
final State state,
@ -15,6 +71,10 @@ TypeResult resolveOfs(
final Schema schema, {
final bool nullable = false,
}) {
if (schema.allOf != null) {
throw StateError('allOf should be handled with inheritance');
}
final result = TypeResultObject(
'${state.classPrefix}$identifier',
nullable: nullable,
@ -28,7 +88,7 @@ TypeResult resolveOfs(
state,
'$identifier${schema.ofs!.indexOf(s)}',
s,
nullable: !(schema.allOf?.contains(s) ?? false),
nullable: true,
),
)
.toList();
@ -42,7 +102,7 @@ TypeResult resolveOfs(
state.output.addAll([
buildBuiltClass(
'${state.classPrefix}$identifier',
BuiltList.build((final b) {
methods: BuiltList.build((final b) {
b.add(
Method(
(final b) {
@ -152,69 +212,59 @@ TypeResult resolveOfs(
<String>[
'final result = new ${state.classPrefix}${identifier}Builder()',
'..data = JsonObject(data);',
if (schema.allOf != null) ...[
for (final result in results) ...[
if (result is TypeResultBase || result is TypeResultEnum) ...[
'result.${fields[result.name]!} = ${result.deserialize('data')};',
] else ...[
'result.${fields[result.name]!}.replace(${result.deserialize('data')});',
],
],
] else ...[
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.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'",
],
') {',
"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) ...[
"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;',
],
'}',
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();',
].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/resolve_interface.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/helpers/type_result.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';
TypeResult resolveObject(
TypeResultObject resolveObject(
final OpenAPI spec,
final State state,
final String identifier,
@ -39,49 +37,23 @@ TypeResult resolveObject(
}
}
state.output.add(
final interfaceClass = resolveInterface(
spec,
state,
identifier,
schema,
);
state.output.addAll([
buildBuiltClass(
'${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,
customSerializer: isHeader,
interfaces: [
interfaceClass,
],
),
);
]);
if (isHeader) {
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]!,
nullable: nullable,
);
} else if (schema.allOf != null) {
result = resolveAllOf(
spec,
state,
identifier,
schema,
nullable: nullable,
);
} else if (schema.ofs != null) {
result = resolveOfs(
spec,

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

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

Loading…
Cancel
Save