diff --git a/Package.resolved b/Package.resolved index c2d2323d..1b0bcc6a 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/GraphQLSwift/GraphQL.git", "state": { "branch": null, - "revision": "3cf2dbce764e7ccff8447d0b7d4634c0438449d3", - "version": "2.9.2" + "revision": "87649dbc3cdab0be0256c86235f2aec22ec1bfc1", + "version": "2.10.0" } }, { diff --git a/Package.swift b/Package.swift index f6463843..fb1dcccc 100644 --- a/Package.swift +++ b/Package.swift @@ -7,7 +7,7 @@ let package = Package( .library(name: "Graphiti", targets: ["Graphiti"]), ], dependencies: [ - .package(url: "https://github.com/GraphQLSwift/GraphQL.git", from: "2.9.2"), + .package(url: "https://github.com/GraphQLSwift/GraphQL.git", from: "2.10.0"), ], targets: [ .target(name: "Graphiti", dependencies: ["GraphQL"]), diff --git a/Sources/Graphiti/Input/Input.swift b/Sources/Graphiti/Input/Input.swift index c3296522..89302101 100644 --- a/Sources/Graphiti/Input/Input.swift +++ b/Sources/Graphiti/Input/Input.swift @@ -8,13 +8,15 @@ public final class Input< Resolver, Context > { + let isOneOf: Bool let fields: [InputFieldComponent] override func update(typeProvider: SchemaTypeProvider, coders _: Coders) throws { let inputObjectType = try GraphQLInputObjectType( name: name, description: description, - fields: fields(typeProvider: typeProvider) + fields: fields(typeProvider: typeProvider), + isOneOf: isOneOf ) try typeProvider.add(type: InputObjectType.self, as: inputObjectType) @@ -38,8 +40,10 @@ public final class Input< init( type _: InputObjectType.Type, name: String?, + isOneOf: Bool, fields: [InputFieldComponent] ) { + self.isOneOf = isOneOf self.fields = fields super.init( name: name ?? Reflection.name(for: InputObjectType.self), @@ -52,18 +56,20 @@ public extension Input { convenience init( _ type: InputObjectType.Type, as name: String? = nil, + isOneOf: Bool = false, @InputFieldComponentBuilder _ fields: () -> InputFieldComponent ) { - self.init(type: type, name: name, fields: [fields()]) + self.init(type: type, name: name, isOneOf: isOneOf, fields: [fields()]) } convenience init( _ type: InputObjectType.Type, as name: String? = nil, + isOneOf: Bool = false, @InputFieldComponentBuilder _ fields: () -> [InputFieldComponent] ) { - self.init(type: type, name: name, fields: fields()) + self.init(type: type, name: name, isOneOf: isOneOf, fields: fields()) } } diff --git a/Sources/Graphiti/Scalar/Scalar.swift b/Sources/Graphiti/Scalar/Scalar.swift index 52429813..d62720ba 100644 --- a/Sources/Graphiti/Scalar/Scalar.swift +++ b/Sources/Graphiti/Scalar/Scalar.swift @@ -9,11 +9,13 @@ import OrderedCollections /// you may provide your own serialize, parseValue, and parseLiteral implementations. open class Scalar: TypeComponent { // TODO: Change this no longer be an open class + let specifiedByURL: String? override func update(typeProvider: SchemaTypeProvider, coders: Coders) throws { let scalarType = try GraphQLScalarType( name: name, description: description, + specifiedByURL: specifiedByURL, serialize: { value in if let serialize = self.serialize { return try serialize(value, coders) @@ -70,10 +72,12 @@ open class Scalar: TypeComponent Map)? = nil, parseValue: ((Map, Coders) throws -> Map)? = nil, parseLiteral: ((GraphQL.Value, Coders) throws -> Map)? = nil ) { + specifiedByURL = specifiedBy self.serialize = serialize self.parseValue = parseValue self.parseLiteral = parseLiteral @@ -88,6 +92,7 @@ public extension Scalar { convenience init( _ type: ScalarType.Type, as name: String? = nil, + specifiedBy: String? = nil, serialize: ((Any, Coders) throws -> Map)? = nil, parseValue: ((Map, Coders) throws -> Map)? = nil, parseLiteral: ((GraphQL.Value, Coders) throws -> Map)? = nil @@ -95,6 +100,7 @@ public extension Scalar { self.init( type: type, name: name, + specifiedBy: specifiedBy, serialize: serialize, parseValue: parseValue, parseLiteral: parseLiteral diff --git a/Tests/GraphitiTests/DirectiveTests/DirectiveTests.swift b/Tests/GraphitiTests/DirectiveTests/DirectiveTests.swift index e2bc7dde..2beb6360 100644 --- a/Tests/GraphitiTests/DirectiveTests/DirectiveTests.swift +++ b/Tests/GraphitiTests/DirectiveTests/DirectiveTests.swift @@ -74,4 +74,89 @@ class DirectiveTests: XCTestCase { XCTAssertEqual(response, expected) } + + func testOneOfAcceptsGoodValue() throws { + try XCTAssertEqual( + OneOfAPI().execute( + request: """ + query { + test(input: {a: "abc"}) { + a + b + } + } + """, + context: NoContext(), + on: group + ).wait(), + GraphQLResult( + data: [ + "test": [ + "a": "abc", + "b": .null, + ], + ] + ) + ) + } + + func testOneOfRejectsBadValue() throws { + try XCTAssertEqual( + OneOfAPI().execute( + request: """ + query { + test(input: {a: "abc", b: 123}) { + a + b + } + } + """, + context: NoContext(), + on: group + ).wait().errors[0].message, + #"OneOf Input Object "TestInputObject" must specify exactly one key."# + ) + } + + struct OneOfAPI: API { + struct TestObject: Codable { + let a: String? + let b: Int? + } + + struct TestInputObject: Codable { + let a: String? + let b: Int? + } + + struct TestArguments: Codable { + let input: TestInputObject + } + + struct OneOfResolver { + func test(context _: NoContext, arguments: TestArguments) -> TestObject { + return TestObject(a: arguments.input.a, b: arguments.input.b) + } + } + + let resolver = OneOfResolver() + + let schema = try! Schema { + Type(TestObject.self) { + Field("a", at: \.a) + Field("b", at: \.b) + } + + Input(TestInputObject.self, isOneOf: true) { + InputField("a", at: \.a) + InputField("b", at: \.b) + } + + Query { + Field("test", at: OneOfResolver.test, as: TestObject.self) { + Argument("input", at: \.input) + } + } + } + } } diff --git a/Tests/GraphitiTests/HelloWorldTests/HelloWorldAsyncTests.swift b/Tests/GraphitiTests/HelloWorldTests/HelloWorldAsyncTests.swift index 780728e6..e31d05a4 100644 --- a/Tests/GraphitiTests/HelloWorldTests/HelloWorldAsyncTests.swift +++ b/Tests/GraphitiTests/HelloWorldTests/HelloWorldAsyncTests.swift @@ -353,7 +353,7 @@ import XCTest ) subscribers.append(subscriber) } - return ConcurrentEventStream.init(asyncStream) + return ConcurrentEventStream(asyncStream) } }