Browse Source

refactor(dynamite): deduplicate built_value code generation and split out more type/object resolving

Signed-off-by: Nikolas Rimikis <leptopoda@users.noreply.github.com>
pull/694/head
Nikolas Rimikis 1 year ago
parent
commit
52e02c07a1
No known key found for this signature in database
GPG Key ID: 85ED1DE9786A4FF2
  1. 136
      packages/dynamite/dynamite/lib/src/builder/header_serializer.dart
  2. 229
      packages/dynamite/dynamite/lib/src/builder/ofs_builder.dart
  3. 112
      packages/dynamite/dynamite/lib/src/builder/resolve_enum.dart
  4. 323
      packages/dynamite/dynamite/lib/src/builder/resolve_object.dart
  5. 382
      packages/dynamite/dynamite/lib/src/builder/resolve_type.dart
  6. 9
      packages/dynamite/dynamite/lib/src/builder/state.dart
  7. 120
      packages/dynamite/dynamite/lib/src/helpers/built_value.dart
  8. 42
      packages/dynamite/dynamite/lib/src/openapi_builder.dart
  9. 2
      packages/dynamite/dynamite/pubspec.yaml

136
packages/dynamite/dynamite/lib/src/builder/header_serializer.dart

@ -0,0 +1,136 @@
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/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';
Spec buildHeaderSerializer(final State state, final String identifier, final OpenAPI spec, final Schema schema) =>
Class(
(final b) => b
..name = '_\$${state.classPrefix}${identifier}Serializer'
..implements.add(refer('StructuredSerializer<${state.classPrefix}$identifier>'))
..fields.addAll([
Field(
(final b) => b
..name = 'types'
..modifier = FieldModifier.final$
..type = refer('Iterable<Type>')
..annotations.add(refer('override'))
..assignment = Code('const [${state.classPrefix}$identifier, _\$${state.classPrefix}$identifier]'),
),
Field(
(final b) => b
..name = 'wireName'
..modifier = FieldModifier.final$
..type = refer('String')
..annotations.add(refer('override'))
..assignment = Code("r'${state.classPrefix}$identifier'"),
),
])
..methods.addAll([
Method((final b) {
b
..name = 'serialize'
..returns = refer('Iterable<Object?>')
..annotations.add(refer('override'))
..requiredParameters.addAll([
Parameter(
(final b) => b
..name = 'serializers'
..type = refer('Serializers'),
),
Parameter(
(final b) => b
..name = 'object'
..type = refer('${state.classPrefix}$identifier'),
),
])
..optionalParameters.add(
Parameter(
(final b) => b
..name = 'specifiedType'
..type = refer('FullType')
..named = true
..defaultTo = const Code('FullType.unspecified'),
),
)
..body = const Code('throw UnimplementedError();');
}),
Method((final b) {
b
..name = 'deserialize'
..returns = refer('${state.classPrefix}$identifier')
..annotations.add(refer('override'))
..requiredParameters.addAll([
Parameter(
(final b) => b
..name = 'serializers'
..type = refer('Serializers'),
),
Parameter(
(final b) => b
..name = 'serialized'
..type = refer('Iterable<Object?>'),
),
])
..optionalParameters.add(
Parameter(
(final b) => b
..name = 'specifiedType'
..type = refer('FullType')
..named = true
..defaultTo = const Code('FullType.unspecified'),
),
);
List<Code> deserializeProperty(final MapEntry<String, Schema> property) {
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),
);
return [
Code("case '$propertyName':"),
if (result.className != 'String') ...[
if (result is TypeResultBase || result is TypeResultEnum) ...[
Code(
'result.${toDartName(propertyName)} = ${result.deserialize(result.decode('value!'))};',
),
] else ...[
Code(
'result.${toDartName(propertyName)}.replace(${result.deserialize(result.decode('value!'))});',
),
],
] else ...[
Code(
'result.${toDartName(propertyName)} = value!;',
),
],
];
}
b.body = Block.of([
Code('final result = new ${state.classPrefix}${identifier}Builder();'),
const Code(''),
const Code('final iterator = serialized.iterator;'),
const Code('while (iterator.moveNext()) {'),
const Code('final key = iterator.current! as String;'),
const Code('iterator.moveNext();'),
const Code('final value = iterator.current! as String;'),
const Code('switch (key) {'),
for (final property in schema.properties!.entries) ...deserializeProperty(property),
const Code('}'),
const Code('}'),
const Code(''),
const Code('return result.build();'),
]);
}),
]),
);

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

@ -0,0 +1,229 @@
import 'package:built_collection/built_collection.dart';
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/models/open_api.dart';
import 'package:dynamite/src/models/schema.dart';
import 'package:dynamite/src/type_result/type_result.dart';
TypeResult resolveOfs(
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 results = schema.ofs!
.map(
(final s) => resolveType(
spec,
state,
'$identifier${schema.ofs!.indexOf(s)}',
s,
nullable: !(schema.allOf?.contains(s) ?? false),
),
)
.toList();
final fields = <String, String>{};
for (final result in results) {
final dartName = toDartName(result.name.replaceFirst(state.classPrefix, ''));
fields[result.name] = toFieldName(dartName, result.name.replaceFirst(state.classPrefix, ''));
}
state.output.addAll([
buildBuiltClass(
'${state.classPrefix}$identifier',
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 = fields[result.name]
..returns = refer(result.nullableName)
..type = MethodType.getter
..docs.addAll(s.formattedDescription);
},
),
);
}
}),
customSerializer: true,
),
Class(
(final b) => b
..name = '_\$${state.classPrefix}${identifier}Serializer'
..implements.add(refer('PrimitiveSerializer<${state.classPrefix}$identifier>'))
..fields.addAll([
Field(
(final b) => b
..name = 'types'
..modifier = FieldModifier.final$
..type = refer('Iterable<Type>')
..annotations.add(refer('override'))
..assignment = Code('const [${state.classPrefix}$identifier, _\$${state.classPrefix}$identifier]'),
),
Field(
(final b) => b
..name = 'wireName'
..modifier = FieldModifier.final$
..type = refer('String')
..annotations.add(refer('override'))
..assignment = Code("r'${state.classPrefix}$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('${state.classPrefix}$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('${state.classPrefix}$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 ${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 (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}');",
],
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(),
);
}),
]),
),
]);
}
return result;
}

112
packages/dynamite/dynamite/lib/src/builder/resolve_enum.dart

@ -0,0 +1,112 @@
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/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 resolveEnum(
final OpenAPI spec,
final State state,
final String identifier,
final Schema schema,
final TypeResult subResult, {
final bool nullable = false,
}) {
if (state.resolvedTypes.add(TypeResultEnum('${state.classPrefix}$identifier', subResult))) {
state.output.add(
Class(
(final b) => b
..name = '${state.classPrefix}$identifier'
..extend = refer('EnumClass')
..constructors.add(
Constructor(
(final b) => b
..name = '_'
..constant = true
..requiredParameters.add(
Parameter(
(final b) => b
..name = 'name'
..toSuper = true,
),
),
),
)
..fields.addAll(
schema.enum_!.map(
(final value) => Field(
(final b) {
final result = resolveType(
spec,
state,
'$identifier${toDartName(value.toString(), uppercaseFirstCharacter: true)}',
schema,
ignoreEnum: true,
);
b
..name = toDartName(value.toString())
..static = true
..modifier = FieldModifier.constant
..type = refer('${state.classPrefix}$identifier')
..assignment = Code(
'_\$${toCamelCase('${state.classPrefix}$identifier')}${toDartName(value.toString(), uppercaseFirstCharacter: true)}',
);
if (toDartName(value.toString()) != value.toString()) {
if (result.name != 'String' && result.name != 'int') {
throw Exception(
'Sorry enum values are a bit broken. '
'See https://github.com/google/json_serializable.dart/issues/616. '
'Please remove the enum values on ${state.classPrefix}$identifier.',
);
}
b.annotations.add(
refer('BuiltValueEnumConst').call([], {
'wireName': refer(valueToEscapedValue(result, value.toString())),
}),
);
}
},
),
),
)
..methods.addAll([
Method(
(final b) => b
..name = 'values'
..returns = refer('BuiltSet<${state.classPrefix}$identifier>')
..lambda = true
..static = true
..body = Code('_\$${toCamelCase('${state.classPrefix}$identifier')}Values')
..type = MethodType.getter,
),
Method(
(final b) => b
..name = 'valueOf'
..returns = refer('${state.classPrefix}$identifier')
..lambda = true
..static = true
..requiredParameters.add(
Parameter(
(final b) => b
..name = 'name'
..type = refer(subResult.name),
),
)
..body = Code('_\$valueOf${state.classPrefix}$identifier(name)'),
),
buildSerializer('${state.classPrefix}$identifier'),
]),
),
);
}
return TypeResultEnum(
'${state.classPrefix}$identifier',
subResult,
nullable: nullable,
);
}

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

@ -1,6 +1,9 @@
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/header_serializer.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/dart_helpers.dart'; import 'package:dynamite/src/helpers/dart_helpers.dart';
import 'package:dynamite/src/helpers/dynamite.dart'; import 'package:dynamite/src/helpers/dynamite.dart';
import 'package:dynamite/src/helpers/type_result.dart'; import 'package:dynamite/src/helpers/type_result.dart';
@ -10,7 +13,6 @@ import 'package:dynamite/src/type_result/type_result.dart';
TypeResult resolveObject( TypeResult resolveObject(
final OpenAPI spec, final OpenAPI spec,
final String variablePrefix,
final State state, final State state,
final String identifier, final String identifier,
final Schema schema, { final Schema schema, {
@ -22,291 +24,66 @@ TypeResult resolveObject(
nullable: nullable, nullable: nullable,
); );
if (state.resolvedTypes.add(result)) { if (state.resolvedTypes.add(result)) {
state.output.add( final defaults = <String>[];
Class( for (final property in schema.properties!.entries) {
(final b) { final propertySchema = property.value;
b if (propertySchema.default_ != null) {
..name = '${state.classPrefix}$identifier' final value = propertySchema.default_!.toString();
..docs.addAll(schema.formattedDescription) final result = resolveType(
..abstract = true spec,
..implements.add( state,
refer( propertySchema.type!,
'Built<${state.classPrefix}$identifier, ${state.classPrefix}${identifier}Builder>', propertySchema,
), );
) defaults.add('..${toDartName(property.key)} = ${valueToEscapedValue(result, value)}');
..constructors.addAll([ }
Constructor( }
(final b) => b
..factory = true
..lambda = true
..optionalParameters.add(
Parameter(
(final b) => b
..name = 'b'
..type = refer('void Function(${state.classPrefix}${identifier}Builder)?'),
),
)
..redirect = refer('_\$${state.classPrefix}$identifier'),
),
Constructor(
(final b) => b
..name = '_'
..constant = true,
),
Constructor(
(final b) => b
..factory = true
..name = 'fromJson'
..lambda = true
..requiredParameters.add(
Parameter(
(final b) => b
..name = 'json'
..type = refer('Map<String, dynamic>'),
),
)
..body = const Code('_jsonSerializers.deserializeWith(serializer, json)!'),
),
])
..methods.addAll([
Method(
(final b) => b
..name = 'toJson'
..returns = refer('Map<String, dynamic>')
..lambda = true
..body = const Code('_jsonSerializers.serializeWith(serializer, this)! as Map<String, dynamic>'),
),
for (final property in schema.properties!.entries) ...[
Method(
(final b) {
final propertyName = property.key;
final propertySchema = property.value;
final result = resolveType(
spec,
variablePrefix,
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),
}),
);
}
},
),
],
Method((final b) {
b
..name = 'serializer'
..returns = refer('Serializer<${state.classPrefix}$identifier>')
..lambda = true
..static = true
..body = Code(
isHeader
? '_\$${state.classPrefix}${identifier}Serializer()'
: "_\$${toCamelCase('${state.classPrefix}$identifier')}Serializer",
)
..type = MethodType.getter;
if (isHeader) {
b.annotations.add(refer('BuiltValueSerializer').call([], {'custom': refer('true')}));
}
}),
]);
final defaults = <String>[]; state.output.add(
buildBuiltClass(
'${state.classPrefix}$identifier',
BuiltList.build((final b) {
for (final property in schema.properties!.entries) { for (final property in schema.properties!.entries) {
final propertySchema = property.value; b.add(
if (propertySchema.default_ != null) {
final value = propertySchema.default_!.toString();
final result = resolveType(
spec,
variablePrefix,
state,
propertySchema.type!,
propertySchema,
);
defaults.add('..${toDartName(property.key)} = ${valueToEscapedValue(result, value)}');
}
}
if (defaults.isNotEmpty) {
b.methods.add(
Method( Method(
(final b) => b (final b) {
..name = '_defaults'
..returns = refer('void')
..static = true
..lambda = true
..annotations.add(
refer('BuiltValueHook').call(
[],
{
'initializeBuilder': refer('true'),
},
),
)
..requiredParameters.add(
Parameter(
(final b) => b
..name = 'b'
..type = refer('${state.classPrefix}${identifier}Builder'),
),
)
..body = Code(
<String?>[
'b',
...defaults,
].join(),
),
),
);
}
},
),
);
if (isHeader) {
state.output.add(
Class(
(final b) => b
..name = '_\$${state.classPrefix}${identifier}Serializer'
..implements.add(refer('StructuredSerializer<${state.classPrefix}$identifier>'))
..fields.addAll([
Field(
(final b) => b
..name = 'types'
..modifier = FieldModifier.final$
..type = refer('Iterable<Type>')
..annotations.add(refer('override'))
..assignment = Code('const [${state.classPrefix}$identifier, _\$${state.classPrefix}$identifier]'),
),
Field(
(final b) => b
..name = 'wireName'
..modifier = FieldModifier.final$
..type = refer('String')
..annotations.add(refer('override'))
..assignment = Code("r'${state.classPrefix}$identifier'"),
),
])
..methods.addAll([
Method((final b) {
b
..name = 'serialize'
..returns = refer('Iterable<Object?>')
..annotations.add(refer('override'))
..requiredParameters.addAll([
Parameter(
(final b) => b
..name = 'serializers'
..type = refer('Serializers'),
),
Parameter(
(final b) => b
..name = 'object'
..type = refer('${state.classPrefix}$identifier'),
),
])
..optionalParameters.add(
Parameter(
(final b) => b
..name = 'specifiedType'
..type = refer('FullType')
..named = true
..defaultTo = const Code('FullType.unspecified'),
),
)
..body = const Code('throw UnimplementedError();');
}),
Method((final b) {
b
..name = 'deserialize'
..returns = refer('${state.classPrefix}$identifier')
..annotations.add(refer('override'))
..requiredParameters.addAll([
Parameter(
(final b) => b
..name = 'serializers'
..type = refer('Serializers'),
),
Parameter(
(final b) => b
..name = 'serialized'
..type = refer('Iterable<Object?>'),
),
])
..optionalParameters.add(
Parameter(
(final b) => b
..name = 'specifiedType'
..type = refer('FullType')
..named = true
..defaultTo = const Code('FullType.unspecified'),
),
);
List<Code> deserializeProperty(final MapEntry<String, Schema> property) {
final propertyName = property.key; final propertyName = property.key;
final propertySchema = property.value; final propertySchema = property.value;
final result = resolveType( final result = resolveType(
spec, spec,
variablePrefix,
state, state,
'${identifier}_${toDartName(propertyName, uppercaseFirstCharacter: true)}', '${identifier}_${toDartName(propertyName, uppercaseFirstCharacter: true)}',
propertySchema, propertySchema,
nullable: isDartParameterNullable(schema.required?.contains(propertyName), propertySchema), nullable: isDartParameterNullable(
schema.required?.contains(propertyName),
propertySchema,
),
); );
return [ b
Code("case '$propertyName':"), ..name = toDartName(propertyName)
if (result.className != 'String') ...[ ..returns = refer(result.nullableName)
if (result is TypeResultBase || result is TypeResultEnum) ...[ ..type = MethodType.getter
Code( ..docs.addAll(propertySchema.formattedDescription);
'result.${toDartName(propertyName)} = ${result.deserialize(result.decode('value!'))};',
),
] else ...[
Code(
'result.${toDartName(propertyName)}.replace(${result.deserialize(result.decode('value!'))});',
),
],
] else ...[
Code(
'result.${toDartName(propertyName)} = value!;',
),
],
];
}
b.body = Block.of([ if (toDartName(propertyName) != propertyName) {
Code('final result = new ${state.classPrefix}${identifier}Builder();'), b.annotations.add(
const Code(''), refer('BuiltValueField').call([], {
const Code('final iterator = serialized.iterator;'), 'wireName': literalString(propertyName),
const Code('while (iterator.moveNext()) {'), }),
const Code('final key = iterator.current! as String;'), );
const Code('iterator.moveNext();'), }
const Code('final value = iterator.current! as String;'), },
const Code('switch (key) {'), ),
for (final property in schema.properties!.entries) ...deserializeProperty(property), );
const Code('}'), }
const Code('}'), }),
const Code(''), defaults: defaults,
const Code('return result.build();'), customSerializer: isHeader,
]); ),
}), );
]), if (isHeader) {
), state.output.add(buildHeaderSerializer(state, identifier, spec, schema));
);
} }
} }
return result; return result;

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

@ -1,15 +1,13 @@
import 'package:code_builder/code_builder.dart'; import 'package:dynamite/src/builder/ofs_builder.dart';
import 'package:dynamite/src/builder/resolve_enum.dart';
import 'package:dynamite/src/builder/resolve_object.dart'; import 'package:dynamite/src/builder/resolve_object.dart';
import 'package:dynamite/src/builder/state.dart'; import 'package:dynamite/src/builder/state.dart';
import 'package:dynamite/src/helpers/dart_helpers.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 resolveType( TypeResult resolveType(
final OpenAPI spec, final OpenAPI spec,
final String variablePrefix,
final State state, final State state,
final String identifier, final String identifier,
final Schema schema, { final Schema schema, {
@ -27,281 +25,22 @@ TypeResult resolveType(
final name = schema.ref!.split('/').last; final name = schema.ref!.split('/').last;
result = resolveType( result = resolveType(
spec, spec,
variablePrefix,
state, state,
name, name,
spec.components!.schemas![name]!, spec.components!.schemas![name]!,
nullable: nullable, nullable: nullable,
); );
} else if (schema.ofs != null) { } else if (schema.ofs != null) {
result = TypeResultObject( result = resolveOfs(
'${state.classPrefix}$identifier', spec,
state,
identifier,
schema,
nullable: nullable, nullable: nullable,
); );
if (state.resolvedTypes.add(result)) {
final results = schema.ofs!
.map(
(final s) => resolveType(
spec,
variablePrefix,
state,
'$identifier${schema.ofs!.indexOf(s)}',
s,
nullable: !(schema.allOf?.contains(s) ?? false),
),
)
.toList();
final fields = <String, String>{};
for (final result in results) {
final dartName = toDartName(result.name.replaceFirst(state.classPrefix, ''));
fields[result.name] = toFieldName(dartName, result.name.replaceFirst(state.classPrefix, ''));
}
state.output.addAll([
Class(
(final b) {
b
..name = '${state.classPrefix}$identifier'
..abstract = true
..implements.add(
refer(
'Built<${state.classPrefix}$identifier, ${state.classPrefix}${identifier}Builder>',
),
)
..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(${state.classPrefix}${identifier}Builder)?'),
),
)
..redirect = refer('_\$${state.classPrefix}$identifier'),
),
])
..methods.addAll([
Method(
(final b) {
b
..name = 'data'
..returns = refer('JsonObject')
..type = MethodType.getter;
},
),
for (final result in results) ...[
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);
},
),
],
Method(
(final b) => b
..static = true
..name = 'fromJson'
..lambda = true
..returns = refer('${state.classPrefix}$identifier')
..requiredParameters.add(
Parameter(
(final b) => b
..name = 'json'
..type = refer('Object'),
),
)
..body = const Code('_jsonSerializers.deserializeWith(serializer, json)!'),
),
Method(
(final b) => b
..name = 'toJson'
..returns = refer('Map<String, dynamic>')
..lambda = true
..body = const Code('_jsonSerializers.serializeWith(serializer, this)! as Map<String, dynamic>'),
),
Method(
(final b) => b
..name = 'serializer'
..returns = refer('Serializer<${state.classPrefix}$identifier>')
..lambda = true
..static = true
..annotations.add(refer('BuiltValueSerializer').call([], {'custom': refer('true')}))
..body = Code('_\$${state.classPrefix}${identifier}Serializer()')
..type = MethodType.getter,
),
]);
},
),
Class(
(final b) => b
..name = '_\$${state.classPrefix}${identifier}Serializer'
..implements.add(refer('PrimitiveSerializer<${state.classPrefix}$identifier>'))
..fields.addAll([
Field(
(final b) => b
..name = 'types'
..modifier = FieldModifier.final$
..type = refer('Iterable<Type>')
..annotations.add(refer('override'))
..assignment = Code('const [${state.classPrefix}$identifier, _\$${state.classPrefix}$identifier]'),
),
Field(
(final b) => b
..name = 'wireName'
..modifier = FieldModifier.final$
..type = refer('String')
..annotations.add(refer('override'))
..assignment = Code("r'${state.classPrefix}$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('${state.classPrefix}$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('${state.classPrefix}$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 ${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 (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}');",
],
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(),
);
}),
]),
),
]);
}
} else if (schema.isContentString) { } else if (schema.isContentString) {
final subResult = resolveType( final subResult = resolveType(
spec, spec,
variablePrefix,
state, state,
identifier, identifier,
schema.contentSchema!, schema.contentSchema!,
@ -346,7 +85,6 @@ TypeResult resolveType(
if (schema.items != null) { if (schema.items != null) {
final subResult = resolveType( final subResult = resolveType(
spec, spec,
variablePrefix,
state, state,
identifier, identifier,
schema.items!, schema.items!,
@ -379,7 +117,6 @@ TypeResult resolveType(
} else { } else {
final subResult = resolveType( final subResult = resolveType(
spec, spec,
variablePrefix,
state, state,
identifier, identifier,
schema.additionalProperties!, schema.additionalProperties!,
@ -399,7 +136,6 @@ TypeResult resolveType(
} else { } else {
result = resolveObject( result = resolveObject(
spec, spec,
variablePrefix,
state, state,
identifier, identifier,
schema, schema,
@ -411,105 +147,11 @@ TypeResult resolveType(
if (result != null) { if (result != null) {
if (!ignoreEnum && schema.enum_ != null) { if (!ignoreEnum && schema.enum_ != null) {
if (state.resolvedTypes.add(TypeResultEnum('${state.classPrefix}$identifier', result))) { result = resolveEnum(
state.output.add( spec,
Class( state,
(final b) => b identifier,
..name = '${state.classPrefix}$identifier' schema,
..extend = refer('EnumClass')
..constructors.add(
Constructor(
(final b) => b
..name = '_'
..constant = true
..requiredParameters.add(
Parameter(
(final b) => b
..name = 'name'
..toSuper = true,
),
),
),
)
..fields.addAll(
schema.enum_!.map(
(final value) => Field(
(final b) {
final result = resolveType(
spec,
variablePrefix,
state,
'$identifier${toDartName(value.toString(), uppercaseFirstCharacter: true)}',
schema,
ignoreEnum: true,
);
b
..name = toDartName(value.toString())
..static = true
..modifier = FieldModifier.constant
..type = refer('${state.classPrefix}$identifier')
..assignment = Code(
'_\$${toCamelCase('${state.classPrefix}$identifier')}${toDartName(value.toString(), uppercaseFirstCharacter: true)}',
);
if (toDartName(value.toString()) != value.toString()) {
if (result.name != 'String' && result.name != 'int') {
throw Exception(
'Sorry enum values are a bit broken. '
'See https://github.com/google/json_serializable.dart/issues/616. '
'Please remove the enum values on ${state.classPrefix}$identifier.',
);
}
b.annotations.add(
refer('BuiltValueEnumConst').call([], {
'wireName': refer(valueToEscapedValue(result, value.toString())),
}),
);
}
},
),
),
)
..methods.addAll([
Method(
(final b) => b
..name = 'values'
..returns = refer('BuiltSet<${state.classPrefix}$identifier>')
..lambda = true
..static = true
..body = Code('_\$${toCamelCase('${state.classPrefix}$identifier')}Values')
..type = MethodType.getter,
),
Method(
(final b) => b
..name = 'valueOf'
..returns = refer('${state.classPrefix}$identifier')
..lambda = true
..static = true
..requiredParameters.add(
Parameter(
(final b) => b
..name = 'name'
..type = refer(result!.name),
),
)
..body = Code('_\$valueOf${state.classPrefix}$identifier(name)'),
),
Method(
(final b) => b
..name = 'serializer'
..returns = refer('Serializer<${state.classPrefix}$identifier>')
..lambda = true
..static = true
..body = Code("_\$${toCamelCase('${state.classPrefix}$identifier')}Serializer")
..type = MethodType.getter,
),
]),
),
);
}
result = TypeResultEnum(
'${state.classPrefix}$identifier',
result, result,
nullable: nullable, nullable: nullable,
); );

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

@ -1,14 +1,15 @@
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/type_result/type_result.dart'; import 'package:dynamite/src/type_result/type_result.dart';
class State { class State {
State({ State(final String prefix)
required this.classPrefix, : classPrefix = toDartName(prefix, uppercaseFirstCharacter: true),
required this.variablePrefix, variablePrefix = toDartName(prefix);
});
final String classPrefix; final String classPrefix;
final String variablePrefix; final String variablePrefix;
final output = <Spec>[]; final output = <Spec>[];
final resolvedTypes = <TypeResult>{}; final resolvedTypes = <TypeResult>{};
} }

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

@ -0,0 +1,120 @@
import 'package:built_collection/built_collection.dart';
import 'package:code_builder/code_builder.dart';
import 'package:dynamite/src/helpers/dart_helpers.dart';
Spec buildBuiltClass(
final String className,
final BuiltList<Method> methods, {
final Iterable<String>? defaults,
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),
]);
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
..name = 'toJson'
..returns = refer('Map<String, dynamic>')
..lambda = true
..body = const Code('_jsonSerializers.serializeWith(serializer, this)! as Map<String, dynamic>'),
);
Method buildSerializer(final String className, {final bool isCustom = false}) => Method((final b) {
b
..name = 'serializer'
..returns = refer('Serializer<$className>')
..lambda = true
..static = true
..body = Code(
isCustom ? '_\$${className}Serializer()' : '_\$${toCamelCase(className)}Serializer',
)
..type = MethodType.getter;
if (isCustom) {
b.annotations.add(refer('BuiltValueSerializer').call([], {'custom': literalTrue}));
}
});
Constructor builtValueConstructor(final String className) => Constructor(
(final b) => b
..factory = true
..lambda = true
..optionalParameters.add(
Parameter(
(final b) => b
..name = 'b'
..type = refer('void Function(${className}Builder)?'),
),
)
..redirect = refer('_\$$className'),
);
Constructor get hiddenConstructor => Constructor(
(final b) => b
..name = '_'
..constant = true,
);
Constructor get fromJsonConstructor => Constructor(
(final b) => b
..factory = true
..name = 'fromJson'
..lambda = true
..requiredParameters.add(
Parameter(
(final b) => b
..name = 'json'
..type = refer('Map<String, dynamic>'),
),
)
..body = const Code('_jsonSerializers.deserializeWith(serializer, json)!'),
);

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

@ -39,8 +39,7 @@ class OpenAPIBuilder implements Builder {
await buildStep.readAsString(inputId), await buildStep.readAsString(inputId),
) as Map<String, dynamic>, ) as Map<String, dynamic>,
); );
final classPrefix = toDartName(spec.info.title, uppercaseFirstCharacter: true);
final variablePrefix = toDartName(spec.info.title);
final supportedVersions = ['3.0.3', '3.1.0']; final supportedVersions = ['3.0.3', '3.1.0'];
if (!supportedVersions.contains(spec.version)) { if (!supportedVersions.contains(spec.version)) {
throw Exception('Only OpenAPI ${supportedVersions.join(', ')} are supported'); throw Exception('Only OpenAPI ${supportedVersions.join(', ')} are supported');
@ -71,10 +70,7 @@ class OpenAPIBuilder implements Builder {
: a.compareTo(b), : a.compareTo(b),
); );
final state = State( final state = State(spec.info.title);
classPrefix: classPrefix,
variablePrefix: variablePrefix,
);
final output = <String>[ final output = <String>[
'// ignore_for_file: camel_case_types', '// ignore_for_file: camel_case_types',
@ -97,7 +93,7 @@ class OpenAPIBuilder implements Builder {
'', '',
Class( Class(
(final b) => b (final b) => b
..name = '${classPrefix}Response' ..name = '${state.classPrefix}Response'
..types.addAll([ ..types.addAll([
refer('T'), refer('T'),
refer('U'), refer('U'),
@ -125,14 +121,14 @@ class OpenAPIBuilder implements Builder {
..annotations.add(refer('override')) ..annotations.add(refer('override'))
..lambda = true ..lambda = true
..body = Code( ..body = Code(
"'${classPrefix}Response(data: \$data, headers: \$headers)'", "'${state.classPrefix}Response(data: \$data, headers: \$headers)'",
), ),
), ),
), ),
).accept(emitter).toString(), ).accept(emitter).toString(),
Class( Class(
(final b) => b (final b) => b
..name = '${classPrefix}ApiException' ..name = '${state.classPrefix}ApiException'
..extend = refer('DynamiteApiException') ..extend = refer('DynamiteApiException')
..constructors.add( ..constructors.add(
Constructor( Constructor(
@ -152,7 +148,7 @@ class OpenAPIBuilder implements Builder {
Method( Method(
(final b) => b (final b) => b
..name = 'fromResponse' ..name = 'fromResponse'
..returns = refer('Future<${classPrefix}ApiException>') ..returns = refer('Future<${state.classPrefix}ApiException>')
..static = true ..static = true
..modifier = MethodModifier.async ..modifier = MethodModifier.async
..requiredParameters.add( ..requiredParameters.add(
@ -170,7 +166,7 @@ class OpenAPIBuilder implements Builder {
const Code("body = 'binary';"), const Code("body = 'binary';"),
const Code('}'), const Code('}'),
const Code(''), const Code(''),
Code('return ${classPrefix}ApiException('), Code('return ${state.classPrefix}ApiException('),
const Code('response.statusCode,'), const Code('response.statusCode,'),
const Code('response.responseHeaders,'), const Code('response.responseHeaders,'),
const Code('body,'), const Code('body,'),
@ -184,7 +180,7 @@ class OpenAPIBuilder implements Builder {
..annotations.add(refer('override')) ..annotations.add(refer('override'))
..lambda = true ..lambda = true
..body = Code( ..body = Code(
"'${classPrefix}ApiException(statusCode: \$statusCode, headers: \$headers, body: \$body)'", "'${state.classPrefix}ApiException(statusCode: \$statusCode, headers: \$headers, body: \$body)'",
), ),
), ),
]), ]),
@ -291,7 +287,7 @@ class OpenAPIBuilder implements Builder {
Field( Field(
(final b) => b (final b) => b
..name = '_rootClient' ..name = '_rootClient'
..type = refer('${classPrefix}Client') ..type = refer('${state.classPrefix}Client')
..modifier = FieldModifier.final$, ..modifier = FieldModifier.final$,
), ),
) )
@ -308,7 +304,7 @@ class OpenAPIBuilder implements Builder {
); );
} }
b b
..name = '$classPrefix${isRootClient ? 'Client' : clientName(tag)}' ..name = '${state.classPrefix}${isRootClient ? 'Client' : clientName(tag)}'
..docs.addAll(spec.formattedTagsFor(tag)) ..docs.addAll(spec.formattedTagsFor(tag))
..methods.addAll( ..methods.addAll(
[ [
@ -320,8 +316,9 @@ class OpenAPIBuilder implements Builder {
..name = toDartName(tag == null ? t : t.substring('$tag/'.length)) ..name = toDartName(tag == null ? t : t.substring('$tag/'.length))
..lambda = true ..lambda = true
..type = MethodType.getter ..type = MethodType.getter
..returns = refer('$classPrefix${clientName(t)}') ..returns = refer('${state.classPrefix}${clientName(t)}')
..body = Code('$classPrefix${clientName(t)}(${isRootClient ? 'this' : '_rootClient'})'), ..body =
Code('${state.classPrefix}${clientName(t)}(${isRootClient ? 'this' : '_rootClient'})'),
), ),
], ],
for (final pathEntry in paths.entries) ...[ for (final pathEntry in paths.entries) ...[
@ -389,7 +386,6 @@ class OpenAPIBuilder implements Builder {
final result = resolveType( final result = resolveType(
spec, spec,
variablePrefix,
state, state,
toDartName( toDartName(
'$operationId-${parameter.name}', '$operationId-${parameter.name}',
@ -495,13 +491,12 @@ class OpenAPIBuilder implements Builder {
final result = resolveType( final result = resolveType(
spec, spec,
variablePrefix,
state, state,
toDartName('$operationId-request-$mimeType', uppercaseFirstCharacter: true), toDartName('$operationId-request-$mimeType', uppercaseFirstCharacter: true),
mediaType.schema!, mediaType.schema!,
nullable: dartParameterNullable, nullable: dartParameterNullable,
); );
final parameterName = toDartName(result.name.replaceFirst(classPrefix, '')); final parameterName = toDartName(result.name.replaceFirst(state.classPrefix, ''));
switch (mimeType) { switch (mimeType) {
case 'application/json': case 'application/json':
case 'application/x-www-form-urlencoded': case 'application/x-www-form-urlencoded':
@ -561,7 +556,6 @@ class OpenAPIBuilder implements Builder {
'${tag != null ? toDartName(tag, uppercaseFirstCharacter: true) : null}${toDartName(operationId, uppercaseFirstCharacter: true)}Headers'; '${tag != null ? toDartName(tag, uppercaseFirstCharacter: true) : null}${toDartName(operationId, uppercaseFirstCharacter: true)}Headers';
final result = resolveObject( final result = resolveObject(
spec, spec,
variablePrefix,
state, state,
identifier, identifier,
Schema( Schema(
@ -591,7 +585,6 @@ class OpenAPIBuilder implements Builder {
final result = resolveType( final result = resolveType(
spec, spec,
variablePrefix,
state, state,
toDartName( toDartName(
'$operationId-response-$statusCode-$mimeType', '$operationId-response-$statusCode-$mimeType',
@ -631,9 +624,9 @@ class OpenAPIBuilder implements Builder {
} }
if (headersType != null && dataType != null) { if (headersType != null && dataType != null) {
b.returns = refer('Future<${classPrefix}Response<$dataType, $headersType>>'); b.returns = refer('Future<${state.classPrefix}Response<$dataType, $headersType>>');
code.write( code.write(
'return ${classPrefix}Response<$dataType, $headersType>(${dataNeedsAwait ?? false ? 'await ' : ''}$dataValue, $headersValue,);', 'return ${state.classPrefix}Response<$dataType, $headersType>(${dataNeedsAwait ?? false ? 'await ' : ''}$dataValue, $headersValue,);',
); );
} else if (headersType != null) { } else if (headersType != null) {
b.returns = refer('Future<$headersType>'); b.returns = refer('Future<$headersType>');
@ -649,7 +642,7 @@ class OpenAPIBuilder implements Builder {
code.write('}'); code.write('}');
} }
code.write( code.write(
'throw await ${classPrefix}ApiException.fromResponse(_response); // coverage:ignore-line\n', 'throw await ${state.classPrefix}ApiException.fromResponse(_response); // coverage:ignore-line\n',
); );
} else { } else {
b.returns = refer('Future'); b.returns = refer('Future');
@ -674,7 +667,6 @@ class OpenAPIBuilder implements Builder {
} else { } else {
final result = resolveType( final result = resolveType(
spec, spec,
variablePrefix,
state, state,
identifier, identifier,
schema.value, schema.value,

2
packages/dynamite/dynamite/pubspec.yaml

@ -6,6 +6,7 @@ environment:
dependencies: dependencies:
build: ^2.4.1 build: ^2.4.1
built_collection: ^5.1.1
built_value: ^8.6.2 built_value: ^8.6.2
code_builder: ^4.6.0 code_builder: ^4.6.0
collection: ^1.17.2 collection: ^1.17.2
@ -17,7 +18,6 @@ dependencies:
dev_dependencies: dev_dependencies:
build_runner: ^2.4.6 build_runner: ^2.4.6
built_collection: ^5.1.1
built_value_generator: ^8.6.2 built_value_generator: ^8.6.2
json_serializable: ^6.7.1 json_serializable: ^6.7.1
neon_lints: neon_lints:

Loading…
Cancel
Save