From 651e18d21ab8d86b8c27b78e5beffabd7d8004e6 Mon Sep 17 00:00:00 2001 From: Christian Budde Christensen Date: Sat, 31 Aug 2024 17:22:08 +0100 Subject: [PATCH] feat: Support @oneOf --- .../lib/src/context/context.dart | 13 + .../lib/src/printer/base/input.dart | 94 ++-- .../lib/src/printer/base/property.dart | 7 + .../test/assets/input_oneOf/input.graphql | 11 + .../assets/input_oneOf/input.graphql.dart | 494 ++++++++++++++++++ .../test/assets/input_oneOf/options.json | 3 + 6 files changed, 592 insertions(+), 30 deletions(-) create mode 100644 packages/graphql_codegen/test/assets/input_oneOf/input.graphql create mode 100644 packages/graphql_codegen/test/assets/input_oneOf/input.graphql.dart create mode 100644 packages/graphql_codegen/test/assets/input_oneOf/options.json diff --git a/packages/graphql_codegen/lib/src/context/context.dart b/packages/graphql_codegen/lib/src/context/context.dart index 739a53be..b8c31155 100644 --- a/packages/graphql_codegen/lib/src/context/context.dart +++ b/packages/graphql_codegen/lib/src/context/context.dart @@ -491,6 +491,8 @@ abstract class Context { ) != null; + bool get isOneOf => false; + Name get path => throw StateError("Path not available"); Context withNameAndType( @@ -680,6 +682,17 @@ class ContextInput NameNode get currentTypeName => currentType.name; final bool isDefinitionContext = true; + + bool get isOneOf { + final hasOneOfDirective = currentType.directives + .any((directive) => directive.name.value == 'oneOf'); + + final allFieldsAreNullable = currentType.fields.every( + (field) => !field.type.isNonNull, + ); + + return hasOneOfDirective && allFieldsAreNullable; + } } class ContextOperation diff --git a/packages/graphql_codegen/lib/src/printer/base/input.dart b/packages/graphql_codegen/lib/src/printer/base/input.dart index 873bf50f..39cdb1ef 100644 --- a/packages/graphql_codegen/lib/src/printer/base/input.dart +++ b/packages/graphql_codegen/lib/src/printer/base/input.dart @@ -13,22 +13,22 @@ import 'package:graphql_codegen/src/printer/utils.dart'; List printInputClasses(PrintContext context) => _printInputClasses( - context, - context.namePrinter.printClassName, - context.context.properties, + context: context, + name: context.namePrinter.printClassName, + properties: context.context.properties, ); List printVariableClasses(PrintContext context) => _printInputClasses( - context, - context.namePrinter.printVariableClassName, - context.context.variables, + context: context, + name: context.namePrinter.printVariableClassName, + properties: context.context.variables, ); -List _printInputClasses( - PrintContext context, - String Function(Name) name, - Iterable properties, -) { +List _printInputClasses({ + required PrintContext context, + required String Function(Name) name, + required Iterable properties, +}) { final factoryParameters = ListBuilder( properties.map( (property) => Parameter( @@ -51,31 +51,65 @@ List _printInputClasses( (b) => b ..name = name(context.path) ..constructors = ListBuilder([ - Constructor( - (b) => b - ..factory = true - ..optionalParameters = factoryParameters - ..body = refer(name(context.path)).property('_').call([ - CodeExpression(Code( - """ + if (context.context.isOneOf) + ...properties.map( + (property) => Constructor( + (b) => b + ..requiredParameters = ListBuilder([ + Parameter( + (b) => b + ..name = context.namePrinter.printPropertyName( + property.name, + ) + ..type = asNonNullable( + printClassPropertyType( + context, + property, + ), + ), + ) + ]) + ..factory = true + ..name = context.namePrinter.printPropertyName(property.name) + ..body = refer(name(context.path)).property('_').call([ + literalMap( + { + property.name.value: refer( + context.namePrinter.printPropertyName( + property.name, + ), + ), + }, + ), + ]).code, + ), + ) + else + Constructor( + (b) => b + ..factory = true + ..optionalParameters = factoryParameters + ..body = refer(name(context.path)).property('_').call([ + CodeExpression(Code( + """ { ${properties.map((property) { - final key = property.name.value; - final value = - context.namePrinter.printPropertyName(property.name); - final entry = "r'${key}': ${value},"; - if (property.isRequired) { - return entry; - } - return """ + final key = property.name.value; + final value = + context.namePrinter.printPropertyName(property.name); + final entry = "r'${key}': ${value},"; + if (property.isRequired) { + return entry; + } + return """ if (${value} != null) ${entry} """; - }).join()} + }).join()} } """, - )), - ]).code, - ), + )), + ]).code, + ), Constructor((b) => b ..name = '_' ..requiredParameters = ListBuilder([ diff --git a/packages/graphql_codegen/lib/src/printer/base/property.dart b/packages/graphql_codegen/lib/src/printer/base/property.dart index 45044d0a..9c860211 100644 --- a/packages/graphql_codegen/lib/src/printer/base/property.dart +++ b/packages/graphql_codegen/lib/src/printer/base/property.dart @@ -197,6 +197,13 @@ TypeReference asNullable(TypeReference reference) => TypeReference( ..types = reference.types.toBuilder(), ); +TypeReference asNonNullable(TypeReference reference) => TypeReference( + (b) => b + ..isNullable = false + ..symbol = reference.symbol + ..types = reference.types.toBuilder(), + ); + TypeReference _asList(TypeReference reference) => TypeReference( (b) => b ..symbol = 'List' diff --git a/packages/graphql_codegen/test/assets/input_oneOf/input.graphql b/packages/graphql_codegen/test/assets/input_oneOf/input.graphql new file mode 100644 index 00000000..1ef09ccf --- /dev/null +++ b/packages/graphql_codegen/test/assets/input_oneOf/input.graphql @@ -0,0 +1,11 @@ +input I1 @oneOf { + v1: Int + v2: Int + v3: Int +} + +input I2 @oneOf { + v1: Int + v2: Int + v3: Int! +} diff --git a/packages/graphql_codegen/test/assets/input_oneOf/input.graphql.dart b/packages/graphql_codegen/test/assets/input_oneOf/input.graphql.dart new file mode 100644 index 00000000..81931fa1 --- /dev/null +++ b/packages/graphql_codegen/test/assets/input_oneOf/input.graphql.dart @@ -0,0 +1,494 @@ +class Input$I1 { + factory Input$I1.v1(int v1) => Input$I1._({'v1': v1}); + + factory Input$I1.v2(int v2) => Input$I1._({'v2': v2}); + + factory Input$I1.v3(int v3) => Input$I1._({'v3': v3}); + + Input$I1._(this._$data); + + factory Input$I1.fromJson(Map data) { + final result$data = {}; + if (data.containsKey('v1')) { + final l$v1 = data['v1']; + result$data['v1'] = (l$v1 as int?); + } + if (data.containsKey('v2')) { + final l$v2 = data['v2']; + result$data['v2'] = (l$v2 as int?); + } + if (data.containsKey('v3')) { + final l$v3 = data['v3']; + result$data['v3'] = (l$v3 as int?); + } + return Input$I1._(result$data); + } + + Map _$data; + + int? get v1 => (_$data['v1'] as int?); + + int? get v2 => (_$data['v2'] as int?); + + int? get v3 => (_$data['v3'] as int?); + + Map toJson() { + final result$data = {}; + if (_$data.containsKey('v1')) { + final l$v1 = v1; + result$data['v1'] = l$v1; + } + if (_$data.containsKey('v2')) { + final l$v2 = v2; + result$data['v2'] = l$v2; + } + if (_$data.containsKey('v3')) { + final l$v3 = v3; + result$data['v3'] = l$v3; + } + return result$data; + } + + CopyWith$Input$I1 get copyWith => CopyWith$Input$I1( + this, + (i) => i, + ); + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Input$I1) || runtimeType != other.runtimeType) { + return false; + } + final l$v1 = v1; + final lOther$v1 = other.v1; + if (_$data.containsKey('v1') != other._$data.containsKey('v1')) { + return false; + } + if (l$v1 != lOther$v1) { + return false; + } + final l$v2 = v2; + final lOther$v2 = other.v2; + if (_$data.containsKey('v2') != other._$data.containsKey('v2')) { + return false; + } + if (l$v2 != lOther$v2) { + return false; + } + final l$v3 = v3; + final lOther$v3 = other.v3; + if (_$data.containsKey('v3') != other._$data.containsKey('v3')) { + return false; + } + if (l$v3 != lOther$v3) { + return false; + } + return true; + } + + @override + int get hashCode { + final l$v1 = v1; + final l$v2 = v2; + final l$v3 = v3; + return Object.hashAll([ + _$data.containsKey('v1') ? l$v1 : const {}, + _$data.containsKey('v2') ? l$v2 : const {}, + _$data.containsKey('v3') ? l$v3 : const {}, + ]); + } +} + +abstract class CopyWith$Input$I1 { + factory CopyWith$Input$I1( + Input$I1 instance, + TRes Function(Input$I1) then, + ) = _CopyWithImpl$Input$I1; + + factory CopyWith$Input$I1.stub(TRes res) = _CopyWithStubImpl$Input$I1; + + TRes call({ + int? v1, + int? v2, + int? v3, + }); +} + +class _CopyWithImpl$Input$I1 implements CopyWith$Input$I1 { + _CopyWithImpl$Input$I1( + this._instance, + this._then, + ); + + final Input$I1 _instance; + + final TRes Function(Input$I1) _then; + + static const _undefined = {}; + + TRes call({ + Object? v1 = _undefined, + Object? v2 = _undefined, + Object? v3 = _undefined, + }) => + _then(Input$I1._({ + ..._instance._$data, + if (v1 != _undefined) 'v1': (v1 as int?), + if (v2 != _undefined) 'v2': (v2 as int?), + if (v3 != _undefined) 'v3': (v3 as int?), + })); +} + +class _CopyWithStubImpl$Input$I1 implements CopyWith$Input$I1 { + _CopyWithStubImpl$Input$I1(this._res); + + TRes _res; + + call({ + int? v1, + int? v2, + int? v3, + }) => + _res; +} + +class Input$I2 { + factory Input$I2({ + int? v1, + int? v2, + required int v3, + }) => + Input$I2._({ + if (v1 != null) r'v1': v1, + if (v2 != null) r'v2': v2, + r'v3': v3, + }); + + Input$I2._(this._$data); + + factory Input$I2.fromJson(Map data) { + final result$data = {}; + if (data.containsKey('v1')) { + final l$v1 = data['v1']; + result$data['v1'] = (l$v1 as int?); + } + if (data.containsKey('v2')) { + final l$v2 = data['v2']; + result$data['v2'] = (l$v2 as int?); + } + final l$v3 = data['v3']; + result$data['v3'] = (l$v3 as int); + return Input$I2._(result$data); + } + + Map _$data; + + int? get v1 => (_$data['v1'] as int?); + + int? get v2 => (_$data['v2'] as int?); + + int get v3 => (_$data['v3'] as int); + + Map toJson() { + final result$data = {}; + if (_$data.containsKey('v1')) { + final l$v1 = v1; + result$data['v1'] = l$v1; + } + if (_$data.containsKey('v2')) { + final l$v2 = v2; + result$data['v2'] = l$v2; + } + final l$v3 = v3; + result$data['v3'] = l$v3; + return result$data; + } + + CopyWith$Input$I2 get copyWith => CopyWith$Input$I2( + this, + (i) => i, + ); + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Input$I2) || runtimeType != other.runtimeType) { + return false; + } + final l$v1 = v1; + final lOther$v1 = other.v1; + if (_$data.containsKey('v1') != other._$data.containsKey('v1')) { + return false; + } + if (l$v1 != lOther$v1) { + return false; + } + final l$v2 = v2; + final lOther$v2 = other.v2; + if (_$data.containsKey('v2') != other._$data.containsKey('v2')) { + return false; + } + if (l$v2 != lOther$v2) { + return false; + } + final l$v3 = v3; + final lOther$v3 = other.v3; + if (l$v3 != lOther$v3) { + return false; + } + return true; + } + + @override + int get hashCode { + final l$v1 = v1; + final l$v2 = v2; + final l$v3 = v3; + return Object.hashAll([ + _$data.containsKey('v1') ? l$v1 : const {}, + _$data.containsKey('v2') ? l$v2 : const {}, + l$v3, + ]); + } +} + +abstract class CopyWith$Input$I2 { + factory CopyWith$Input$I2( + Input$I2 instance, + TRes Function(Input$I2) then, + ) = _CopyWithImpl$Input$I2; + + factory CopyWith$Input$I2.stub(TRes res) = _CopyWithStubImpl$Input$I2; + + TRes call({ + int? v1, + int? v2, + int? v3, + }); +} + +class _CopyWithImpl$Input$I2 implements CopyWith$Input$I2 { + _CopyWithImpl$Input$I2( + this._instance, + this._then, + ); + + final Input$I2 _instance; + + final TRes Function(Input$I2) _then; + + static const _undefined = {}; + + TRes call({ + Object? v1 = _undefined, + Object? v2 = _undefined, + Object? v3 = _undefined, + }) => + _then(Input$I2._({ + ..._instance._$data, + if (v1 != _undefined) 'v1': (v1 as int?), + if (v2 != _undefined) 'v2': (v2 as int?), + if (v3 != _undefined && v3 != null) 'v3': (v3 as int), + })); +} + +class _CopyWithStubImpl$Input$I2 implements CopyWith$Input$I2 { + _CopyWithStubImpl$Input$I2(this._res); + + TRes _res; + + call({ + int? v1, + int? v2, + int? v3, + }) => + _res; +} + +enum Enum$__TypeKind { + SCALAR, + OBJECT, + INTERFACE, + UNION, + ENUM, + INPUT_OBJECT, + LIST, + NON_NULL, + $unknown; + + factory Enum$__TypeKind.fromJson(String value) => + fromJson$Enum$__TypeKind(value); + + String toJson() => toJson$Enum$__TypeKind(this); +} + +String toJson$Enum$__TypeKind(Enum$__TypeKind e) { + switch (e) { + case Enum$__TypeKind.SCALAR: + return r'SCALAR'; + case Enum$__TypeKind.OBJECT: + return r'OBJECT'; + case Enum$__TypeKind.INTERFACE: + return r'INTERFACE'; + case Enum$__TypeKind.UNION: + return r'UNION'; + case Enum$__TypeKind.ENUM: + return r'ENUM'; + case Enum$__TypeKind.INPUT_OBJECT: + return r'INPUT_OBJECT'; + case Enum$__TypeKind.LIST: + return r'LIST'; + case Enum$__TypeKind.NON_NULL: + return r'NON_NULL'; + case Enum$__TypeKind.$unknown: + return r'$unknown'; + } +} + +Enum$__TypeKind fromJson$Enum$__TypeKind(String value) { + switch (value) { + case r'SCALAR': + return Enum$__TypeKind.SCALAR; + case r'OBJECT': + return Enum$__TypeKind.OBJECT; + case r'INTERFACE': + return Enum$__TypeKind.INTERFACE; + case r'UNION': + return Enum$__TypeKind.UNION; + case r'ENUM': + return Enum$__TypeKind.ENUM; + case r'INPUT_OBJECT': + return Enum$__TypeKind.INPUT_OBJECT; + case r'LIST': + return Enum$__TypeKind.LIST; + case r'NON_NULL': + return Enum$__TypeKind.NON_NULL; + default: + return Enum$__TypeKind.$unknown; + } +} + +enum Enum$__DirectiveLocation { + QUERY, + MUTATION, + SUBSCRIPTION, + FIELD, + FRAGMENT_DEFINITION, + FRAGMENT_SPREAD, + INLINE_FRAGMENT, + VARIABLE_DEFINITION, + SCHEMA, + SCALAR, + OBJECT, + FIELD_DEFINITION, + ARGUMENT_DEFINITION, + INTERFACE, + UNION, + ENUM, + ENUM_VALUE, + INPUT_OBJECT, + INPUT_FIELD_DEFINITION, + $unknown; + + factory Enum$__DirectiveLocation.fromJson(String value) => + fromJson$Enum$__DirectiveLocation(value); + + String toJson() => toJson$Enum$__DirectiveLocation(this); +} + +String toJson$Enum$__DirectiveLocation(Enum$__DirectiveLocation e) { + switch (e) { + case Enum$__DirectiveLocation.QUERY: + return r'QUERY'; + case Enum$__DirectiveLocation.MUTATION: + return r'MUTATION'; + case Enum$__DirectiveLocation.SUBSCRIPTION: + return r'SUBSCRIPTION'; + case Enum$__DirectiveLocation.FIELD: + return r'FIELD'; + case Enum$__DirectiveLocation.FRAGMENT_DEFINITION: + return r'FRAGMENT_DEFINITION'; + case Enum$__DirectiveLocation.FRAGMENT_SPREAD: + return r'FRAGMENT_SPREAD'; + case Enum$__DirectiveLocation.INLINE_FRAGMENT: + return r'INLINE_FRAGMENT'; + case Enum$__DirectiveLocation.VARIABLE_DEFINITION: + return r'VARIABLE_DEFINITION'; + case Enum$__DirectiveLocation.SCHEMA: + return r'SCHEMA'; + case Enum$__DirectiveLocation.SCALAR: + return r'SCALAR'; + case Enum$__DirectiveLocation.OBJECT: + return r'OBJECT'; + case Enum$__DirectiveLocation.FIELD_DEFINITION: + return r'FIELD_DEFINITION'; + case Enum$__DirectiveLocation.ARGUMENT_DEFINITION: + return r'ARGUMENT_DEFINITION'; + case Enum$__DirectiveLocation.INTERFACE: + return r'INTERFACE'; + case Enum$__DirectiveLocation.UNION: + return r'UNION'; + case Enum$__DirectiveLocation.ENUM: + return r'ENUM'; + case Enum$__DirectiveLocation.ENUM_VALUE: + return r'ENUM_VALUE'; + case Enum$__DirectiveLocation.INPUT_OBJECT: + return r'INPUT_OBJECT'; + case Enum$__DirectiveLocation.INPUT_FIELD_DEFINITION: + return r'INPUT_FIELD_DEFINITION'; + case Enum$__DirectiveLocation.$unknown: + return r'$unknown'; + } +} + +Enum$__DirectiveLocation fromJson$Enum$__DirectiveLocation(String value) { + switch (value) { + case r'QUERY': + return Enum$__DirectiveLocation.QUERY; + case r'MUTATION': + return Enum$__DirectiveLocation.MUTATION; + case r'SUBSCRIPTION': + return Enum$__DirectiveLocation.SUBSCRIPTION; + case r'FIELD': + return Enum$__DirectiveLocation.FIELD; + case r'FRAGMENT_DEFINITION': + return Enum$__DirectiveLocation.FRAGMENT_DEFINITION; + case r'FRAGMENT_SPREAD': + return Enum$__DirectiveLocation.FRAGMENT_SPREAD; + case r'INLINE_FRAGMENT': + return Enum$__DirectiveLocation.INLINE_FRAGMENT; + case r'VARIABLE_DEFINITION': + return Enum$__DirectiveLocation.VARIABLE_DEFINITION; + case r'SCHEMA': + return Enum$__DirectiveLocation.SCHEMA; + case r'SCALAR': + return Enum$__DirectiveLocation.SCALAR; + case r'OBJECT': + return Enum$__DirectiveLocation.OBJECT; + case r'FIELD_DEFINITION': + return Enum$__DirectiveLocation.FIELD_DEFINITION; + case r'ARGUMENT_DEFINITION': + return Enum$__DirectiveLocation.ARGUMENT_DEFINITION; + case r'INTERFACE': + return Enum$__DirectiveLocation.INTERFACE; + case r'UNION': + return Enum$__DirectiveLocation.UNION; + case r'ENUM': + return Enum$__DirectiveLocation.ENUM; + case r'ENUM_VALUE': + return Enum$__DirectiveLocation.ENUM_VALUE; + case r'INPUT_OBJECT': + return Enum$__DirectiveLocation.INPUT_OBJECT; + case r'INPUT_FIELD_DEFINITION': + return Enum$__DirectiveLocation.INPUT_FIELD_DEFINITION; + default: + return Enum$__DirectiveLocation.$unknown; + } +} + +const possibleTypesMap = >{}; diff --git a/packages/graphql_codegen/test/assets/input_oneOf/options.json b/packages/graphql_codegen/test/assets/input_oneOf/options.json new file mode 100644 index 00000000..78abb3c6 --- /dev/null +++ b/packages/graphql_codegen/test/assets/input_oneOf/options.json @@ -0,0 +1,3 @@ +{ + "addTypename": false +} \ No newline at end of file