diff --git a/packages/dynamite/dynamite/lib/src/builder/ofs_builder.dart b/packages/dynamite/dynamite/lib/src/builder/ofs_builder.dart index 5412459e..e0ceca56 100644 --- a/packages/dynamite/dynamite/lib/src/builder/ofs_builder.dart +++ b/packages/dynamite/dynamite/lib/src/builder/ofs_builder.dart @@ -22,37 +22,42 @@ TypeResult resolveAllOf( if (state.resolvedTypes.add(result)) { final interfaces = {}; + final methods = ListBuilder(); for (final s in schema.allOf!) { - final TypeResultObject interfaceClass; - if (s.ref != null) { - final object = resolveType( + if (s.properties != null) { + final interfaceClass = resolveInterface( spec, state, - identifier, + '${identifier}_${schema.allOf!.indexOf(s)}', s, - nullable: nullable, ); - - if (object is! TypeResultObject) { - throw StateError('allOf does only allow objects. Please change $identifier'); - } - - interfaceClass = object; + interfaces.add(interfaceClass); } else { - interfaceClass = resolveInterface( + final object = resolveType( spec, state, - '${identifier}_${schema.allOf!.indexOf(s)}', + identifier, s, + nullable: nullable, ); - } - interfaces.add(interfaceClass); + if (object is TypeResultObject) { + interfaces.add(object); + } else { + final property = generateProperty( + object, + object.className, + s.formattedDescription, + ); + + methods.add(property); + } + } } state.output.add( - buildInterface(identifier, interfaces: interfaces), + buildInterface(identifier, interfaces: interfaces, methods: methods.build()), ); state.output.add( diff --git a/packages/dynamite/dynamite/lib/src/builder/resolve_interface.dart b/packages/dynamite/dynamite/lib/src/builder/resolve_interface.dart index 0a67357d..3339a9fc 100644 --- a/packages/dynamite/dynamite/lib/src/builder/resolve_interface.dart +++ b/packages/dynamite/dynamite/lib/src/builder/resolve_interface.dart @@ -14,6 +14,12 @@ TypeResultObject resolveInterface( final String identifier, final openapi.Schema schema, ) { + final properties = schema.properties?.entries; + + if (properties == null || properties.isEmpty) { + throw StateError('The schema must have a non empty properties field.'); + } + final result = TypeResultObject( identifier, ); @@ -22,40 +28,29 @@ TypeResultObject resolveInterface( final $interface = buildInterface( identifier, methods: 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.addAll( + properties.map((final property) { + final propertyName = property.key; + final propertySchema = property.value; - b - ..name = toDartName(propertyName) - ..returns = refer(result.nullableName) - ..type = MethodType.getter - ..docs.addAll(propertySchema.formattedDescription); + final result = resolveType( + spec, + state, + '${identifier}_${toDartName(propertyName, uppercaseFirstCharacter: true)}', + propertySchema, + nullable: isDartParameterNullable( + schema.required.contains(propertyName), + propertySchema, + ), + ); - if (toDartName(propertyName) != propertyName) { - b.annotations.add( - refer('BuiltValueField').call([], { - 'wireName': literalString(propertyName), - }), - ); - } - }, - ), - ); - } + return generateProperty( + result, + propertyName, + propertySchema.formattedDescription, + ); + }), + ); }), ); @@ -65,6 +60,30 @@ TypeResultObject resolveInterface( return result; } +Method generateProperty( + final TypeResult type, + final String propertyName, + final Iterable description, +) => + Method( + (final b) { + final name = toFieldName(toDartName(propertyName), type.name); + b + ..name = name + ..returns = refer(type.nullableName) + ..type = MethodType.getter + ..docs.addAll(description); + + if (name != propertyName) { + b.annotations.add( + refer('BuiltValueField').call([], { + 'wireName': literalString(propertyName), + }), + ); + } + }, + ); + Spec buildInterface( final String identifier, { final BuiltList? methods, diff --git a/packages/dynamite/dynamite_end_to_end_test/lib/all_of.openapi.dart b/packages/dynamite/dynamite_end_to_end_test/lib/all_of.openapi.dart index 4fa14e4a..569cda93 100644 --- a/packages/dynamite/dynamite_end_to_end_test/lib/all_of.openapi.dart +++ b/packages/dynamite/dynamite_end_to_end_test/lib/all_of.openapi.dart @@ -79,12 +79,82 @@ abstract class OneObjectAllOf implements OneObjectAllOfInterface, Built get serializer => _$oneObjectAllOfSerializer; } +@BuiltValue(instantiable: false) +abstract interface class PrimitiveAllOfInterface { + @BuiltValueField(wireName: 'int') + int get $int; + @BuiltValueField(wireName: 'String') + String get string; +} + +abstract class PrimitiveAllOf implements PrimitiveAllOfInterface, Built { + factory PrimitiveAllOf([final void Function(PrimitiveAllOfBuilder)? b]) = _$PrimitiveAllOf; + + const PrimitiveAllOf._(); + + factory PrimitiveAllOf.fromJson(final Map json) => + _jsonSerializers.deserializeWith(serializer, json)!; + + Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; + + static Serializer get serializer => _$primitiveAllOfSerializer; +} + +@BuiltValue(instantiable: false) +abstract interface class MixedAllOf_1Interface { + @BuiltValueField(wireName: 'attribute-allOf') + String get attributeAllOf; +} + +@BuiltValue(instantiable: false) +abstract interface class MixedAllOfInterface implements MixedAllOf_1Interface { + @BuiltValueField(wireName: 'String') + String get string; +} + +abstract class MixedAllOf implements MixedAllOfInterface, Built { + factory MixedAllOf([final void Function(MixedAllOfBuilder)? b]) = _$MixedAllOf; + + const MixedAllOf._(); + + factory MixedAllOf.fromJson(final Map json) => _jsonSerializers.deserializeWith(serializer, json)!; + + Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; + + static Serializer get serializer => _$mixedAllOfSerializer; +} + +@BuiltValue(instantiable: false) +abstract interface class OneValueAllOfInterface { + @BuiltValueField(wireName: 'String') + String get string; +} + +abstract class OneValueAllOf implements OneValueAllOfInterface, Built { + factory OneValueAllOf([final void Function(OneValueAllOfBuilder)? b]) = _$OneValueAllOf; + + const OneValueAllOf._(); + + factory OneValueAllOf.fromJson(final Map json) => + _jsonSerializers.deserializeWith(serializer, json)!; + + Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; + + static Serializer get serializer => _$oneValueAllOfSerializer; +} + // coverage:ignore-start final Serializers _serializers = (Serializers().toBuilder() ..addBuilderFactory(const FullType(ObjectAllOf), ObjectAllOf.new) ..add(ObjectAllOf.serializer) ..addBuilderFactory(const FullType(OneObjectAllOf), OneObjectAllOf.new) - ..add(OneObjectAllOf.serializer)) + ..add(OneObjectAllOf.serializer) + ..addBuilderFactory(const FullType(PrimitiveAllOf), PrimitiveAllOf.new) + ..add(PrimitiveAllOf.serializer) + ..addBuilderFactory(const FullType(MixedAllOf), MixedAllOf.new) + ..add(MixedAllOf.serializer) + ..addBuilderFactory(const FullType(OneValueAllOf), OneValueAllOf.new) + ..add(OneValueAllOf.serializer)) .build(); final Serializers _jsonSerializers = (_serializers.toBuilder() diff --git a/packages/dynamite/dynamite_end_to_end_test/lib/all_of.openapi.g.dart b/packages/dynamite/dynamite_end_to_end_test/lib/all_of.openapi.g.dart index 5fe644f5..eca1b3a5 100644 --- a/packages/dynamite/dynamite_end_to_end_test/lib/all_of.openapi.g.dart +++ b/packages/dynamite/dynamite_end_to_end_test/lib/all_of.openapi.g.dart @@ -8,6 +8,9 @@ part of 'all_of.openapi.dart'; Serializer _$objectAllOfSerializer = _$ObjectAllOfSerializer(); Serializer _$oneObjectAllOfSerializer = _$OneObjectAllOfSerializer(); +Serializer _$primitiveAllOfSerializer = _$PrimitiveAllOfSerializer(); +Serializer _$mixedAllOfSerializer = _$MixedAllOfSerializer(); +Serializer _$oneValueAllOfSerializer = _$OneValueAllOfSerializer(); class _$ObjectAllOfSerializer implements StructuredSerializer { @override @@ -90,6 +93,130 @@ class _$OneObjectAllOfSerializer implements StructuredSerializer } } +class _$PrimitiveAllOfSerializer implements StructuredSerializer { + @override + final Iterable types = const [PrimitiveAllOf, _$PrimitiveAllOf]; + @override + final String wireName = 'PrimitiveAllOf'; + + @override + Iterable serialize(Serializers serializers, PrimitiveAllOf object, + {FullType specifiedType = FullType.unspecified}) { + final result = [ + 'int', + serializers.serialize(object.$int, specifiedType: const FullType(int)), + 'String', + serializers.serialize(object.string, specifiedType: const FullType(String)), + ]; + + return result; + } + + @override + PrimitiveAllOf deserialize(Serializers serializers, Iterable serialized, + {FullType specifiedType = FullType.unspecified}) { + final result = PrimitiveAllOfBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'int': + result.$int = serializers.deserialize(value, specifiedType: const FullType(int))! as int; + break; + case 'String': + result.string = serializers.deserialize(value, specifiedType: const FullType(String))! as String; + break; + } + } + + return result.build(); + } +} + +class _$MixedAllOfSerializer implements StructuredSerializer { + @override + final Iterable types = const [MixedAllOf, _$MixedAllOf]; + @override + final String wireName = 'MixedAllOf'; + + @override + Iterable serialize(Serializers serializers, MixedAllOf object, + {FullType specifiedType = FullType.unspecified}) { + final result = [ + 'String', + serializers.serialize(object.string, specifiedType: const FullType(String)), + 'attribute-allOf', + serializers.serialize(object.attributeAllOf, specifiedType: const FullType(String)), + ]; + + return result; + } + + @override + MixedAllOf deserialize(Serializers serializers, Iterable serialized, + {FullType specifiedType = FullType.unspecified}) { + final result = MixedAllOfBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'String': + result.string = serializers.deserialize(value, specifiedType: const FullType(String))! as String; + break; + case 'attribute-allOf': + result.attributeAllOf = serializers.deserialize(value, specifiedType: const FullType(String))! as String; + break; + } + } + + return result.build(); + } +} + +class _$OneValueAllOfSerializer implements StructuredSerializer { + @override + final Iterable types = const [OneValueAllOf, _$OneValueAllOf]; + @override + final String wireName = 'OneValueAllOf'; + + @override + Iterable serialize(Serializers serializers, OneValueAllOf object, + {FullType specifiedType = FullType.unspecified}) { + final result = [ + 'String', + serializers.serialize(object.string, specifiedType: const FullType(String)), + ]; + + return result; + } + + @override + OneValueAllOf deserialize(Serializers serializers, Iterable serialized, + {FullType specifiedType = FullType.unspecified}) { + final result = OneValueAllOfBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'String': + result.string = serializers.deserialize(value, specifiedType: const FullType(String))! as String; + break; + } + } + + return result.build(); + } +} + abstract mixin class ObjectAllOf_0InterfaceBuilder { void replace(ObjectAllOf_0Interface other); void update(void Function(ObjectAllOf_0InterfaceBuilder) updates); @@ -298,4 +425,296 @@ class OneObjectAllOfBuilder implements Builder + (PrimitiveAllOfBuilder()..update(updates))._build(); + + _$PrimitiveAllOf._({required this.$int, required this.string}) : super._() { + BuiltValueNullFieldError.checkNotNull($int, r'PrimitiveAllOf', '\$int'); + BuiltValueNullFieldError.checkNotNull(string, r'PrimitiveAllOf', 'string'); + } + + @override + PrimitiveAllOf rebuild(void Function(PrimitiveAllOfBuilder) updates) => (toBuilder()..update(updates)).build(); + + @override + PrimitiveAllOfBuilder toBuilder() => PrimitiveAllOfBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is PrimitiveAllOf && $int == other.$int && string == other.string; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, $int.hashCode); + _$hash = $jc(_$hash, string.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'PrimitiveAllOf') + ..add('\$int', $int) + ..add('string', string)) + .toString(); + } +} + +class PrimitiveAllOfBuilder implements Builder, PrimitiveAllOfInterfaceBuilder { + _$PrimitiveAllOf? _$v; + + int? _$int; + int? get $int => _$this._$int; + set $int(covariant int? $int) => _$this._$int = $int; + + String? _string; + String? get string => _$this._string; + set string(covariant String? string) => _$this._string = string; + + PrimitiveAllOfBuilder(); + + PrimitiveAllOfBuilder get _$this { + final $v = _$v; + if ($v != null) { + _$int = $v.$int; + _string = $v.string; + _$v = null; + } + return this; + } + + @override + void replace(covariant PrimitiveAllOf other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$PrimitiveAllOf; + } + + @override + void update(void Function(PrimitiveAllOfBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + PrimitiveAllOf build() => _build(); + + _$PrimitiveAllOf _build() { + final _$result = _$v ?? + _$PrimitiveAllOf._( + $int: BuiltValueNullFieldError.checkNotNull($int, r'PrimitiveAllOf', '\$int'), + string: BuiltValueNullFieldError.checkNotNull(string, r'PrimitiveAllOf', 'string')); + replace(_$result); + return _$result; + } +} + +abstract mixin class MixedAllOf_1InterfaceBuilder { + void replace(MixedAllOf_1Interface other); + void update(void Function(MixedAllOf_1InterfaceBuilder) updates); + String? get attributeAllOf; + set attributeAllOf(String? attributeAllOf); +} + +abstract mixin class MixedAllOfInterfaceBuilder implements MixedAllOf_1InterfaceBuilder { + void replace(covariant MixedAllOfInterface other); + void update(void Function(MixedAllOfInterfaceBuilder) updates); + String? get string; + set string(covariant String? string); + + String? get attributeAllOf; + set attributeAllOf(covariant String? attributeAllOf); +} + +class _$MixedAllOf extends MixedAllOf { + @override + final String string; + @override + final String attributeAllOf; + + factory _$MixedAllOf([void Function(MixedAllOfBuilder)? updates]) => (MixedAllOfBuilder()..update(updates))._build(); + + _$MixedAllOf._({required this.string, required this.attributeAllOf}) : super._() { + BuiltValueNullFieldError.checkNotNull(string, r'MixedAllOf', 'string'); + BuiltValueNullFieldError.checkNotNull(attributeAllOf, r'MixedAllOf', 'attributeAllOf'); + } + + @override + MixedAllOf rebuild(void Function(MixedAllOfBuilder) updates) => (toBuilder()..update(updates)).build(); + + @override + MixedAllOfBuilder toBuilder() => MixedAllOfBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is MixedAllOf && string == other.string && attributeAllOf == other.attributeAllOf; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, string.hashCode); + _$hash = $jc(_$hash, attributeAllOf.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'MixedAllOf') + ..add('string', string) + ..add('attributeAllOf', attributeAllOf)) + .toString(); + } +} + +class MixedAllOfBuilder implements Builder, MixedAllOfInterfaceBuilder { + _$MixedAllOf? _$v; + + String? _string; + String? get string => _$this._string; + set string(covariant String? string) => _$this._string = string; + + String? _attributeAllOf; + String? get attributeAllOf => _$this._attributeAllOf; + set attributeAllOf(covariant String? attributeAllOf) => _$this._attributeAllOf = attributeAllOf; + + MixedAllOfBuilder(); + + MixedAllOfBuilder get _$this { + final $v = _$v; + if ($v != null) { + _string = $v.string; + _attributeAllOf = $v.attributeAllOf; + _$v = null; + } + return this; + } + + @override + void replace(covariant MixedAllOf other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$MixedAllOf; + } + + @override + void update(void Function(MixedAllOfBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + MixedAllOf build() => _build(); + + _$MixedAllOf _build() { + final _$result = _$v ?? + _$MixedAllOf._( + string: BuiltValueNullFieldError.checkNotNull(string, r'MixedAllOf', 'string'), + attributeAllOf: BuiltValueNullFieldError.checkNotNull(attributeAllOf, r'MixedAllOf', 'attributeAllOf')); + replace(_$result); + return _$result; + } +} + +abstract mixin class OneValueAllOfInterfaceBuilder { + void replace(OneValueAllOfInterface other); + void update(void Function(OneValueAllOfInterfaceBuilder) updates); + String? get string; + set string(String? string); +} + +class _$OneValueAllOf extends OneValueAllOf { + @override + final String string; + + factory _$OneValueAllOf([void Function(OneValueAllOfBuilder)? updates]) => + (OneValueAllOfBuilder()..update(updates))._build(); + + _$OneValueAllOf._({required this.string}) : super._() { + BuiltValueNullFieldError.checkNotNull(string, r'OneValueAllOf', 'string'); + } + + @override + OneValueAllOf rebuild(void Function(OneValueAllOfBuilder) updates) => (toBuilder()..update(updates)).build(); + + @override + OneValueAllOfBuilder toBuilder() => OneValueAllOfBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is OneValueAllOf && string == other.string; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, string.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'OneValueAllOf')..add('string', string)).toString(); + } +} + +class OneValueAllOfBuilder implements Builder, OneValueAllOfInterfaceBuilder { + _$OneValueAllOf? _$v; + + String? _string; + String? get string => _$this._string; + set string(covariant String? string) => _$this._string = string; + + OneValueAllOfBuilder(); + + OneValueAllOfBuilder get _$this { + final $v = _$v; + if ($v != null) { + _string = $v.string; + _$v = null; + } + return this; + } + + @override + void replace(covariant OneValueAllOf other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$OneValueAllOf; + } + + @override + void update(void Function(OneValueAllOfBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + OneValueAllOf build() => _build(); + + _$OneValueAllOf _build() { + final _$result = + _$v ?? _$OneValueAllOf._(string: BuiltValueNullFieldError.checkNotNull(string, r'OneValueAllOf', 'string')); + replace(_$result); + return _$result; + } +} + // ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/packages/dynamite/dynamite_end_to_end_test/lib/all_of.openapi.json b/packages/dynamite/dynamite_end_to_end_test/lib/all_of.openapi.json index c8afda95..27281110 100644 --- a/packages/dynamite/dynamite_end_to_end_test/lib/all_of.openapi.json +++ b/packages/dynamite/dynamite_end_to_end_test/lib/all_of.openapi.json @@ -48,6 +48,44 @@ } } ] + }, + "PrimitiveAllOf": { + "description": "All of with an primitive values.", + "allOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ] + }, + "MixedAllOf": { + "description": "All of with object and primitive value.", + "allOf": [ + { + "type": "string" + }, + { + "type": "object", + "required": [ + "attribute-allOf" + ], + "properties": { + "attribute-allOf": { + "type": "string" + } + } + } + ] + }, + "OneValueAllOf": { + "description": "All of with one primitive value.", + "allOf": [ + { + "type": "string" + } + ] } } }, diff --git a/packages/dynamite/dynamite_end_to_end_test/test/all_of_test.dart b/packages/dynamite/dynamite_end_to_end_test/test/all_of_test.dart new file mode 100644 index 00000000..28c44a43 --- /dev/null +++ b/packages/dynamite/dynamite_end_to_end_test/test/all_of_test.dart @@ -0,0 +1,78 @@ +import 'package:dynamite_end_to_end_test/all_of.openapi.dart'; +import 'package:test/test.dart'; + +void main() { + test('ObjectAllOf', () { + final object = ObjectAllOf( + (final b) => b + ..attribute1AllOf = 'attribute1AllOfValue' + ..attribute2AllOf = 'attribute2AllOfValue', + ); + + final json = { + 'attribute1-allOf': 'attribute1AllOfValue', + 'attribute2-allOf': 'attribute2AllOfValue', + }; + + expect(object.toJson(), equals(json)); + expect(ObjectAllOf.fromJson(json), equals(object)); + }); + + test('OneObjectAllOf', () { + final object = OneObjectAllOf( + (final b) => b..attributeAllOf = 'attributeAllOfValue', + ); + + final json = { + 'attribute-allOf': 'attributeAllOfValue', + }; + + expect(object.toJson(), equals(json)); + expect(OneObjectAllOf.fromJson(json), equals(object)); + }); + + test('PrimitiveAllOf', () { + final object = PrimitiveAllOf( + (final b) => b + ..string = 'stringValue' + ..$int = 62, + ); + + final json = { + 'String': 'stringValue', + 'int': 62, + }; + + expect(object.toJson(), equals(json)); + expect(PrimitiveAllOf.fromJson(json), equals(object)); + }); + + test('MixedAllOf', () { + final object = MixedAllOf( + (final b) => b + ..string = 'stringValue' + ..attributeAllOf = 'attributeAllOfValue', + ); + + final json = { + 'String': 'stringValue', + 'attribute-allOf': 'attributeAllOfValue', + }; + + expect(object.toJson(), equals(json)); + expect(MixedAllOf.fromJson(json), equals(object)); + }); + + test('OneValueAllOf', () { + final object = OneValueAllOf( + (final b) => b..string = 'stringValue', + ); + + final json = { + 'String': 'stringValue', + }; + + expect(object.toJson(), equals(json)); + expect(OneValueAllOf.fromJson(json), equals(object)); + }); +}