diff --git a/Sources/Graphiti/Schema/Schema.swift b/Sources/Graphiti/Schema/Schema.swift index cf0076b6..c3406ef2 100644 --- a/Sources/Graphiti/Schema/Schema.swift +++ b/Sources/Graphiti/Schema/Schema.swift @@ -117,6 +117,50 @@ public final class SchemaBuilder { map(Type.self, to: interfaceType) } + public func union( + type: Type.Type, + members: [Any.Type] + ) throws { + let name = fixName(String(describing: Type.self)) + try union(name: name, type: type, members: members) + } + + public func union( + name: String, + type: Type.Type, + members: [Any.Type] + ) throws { + try union(name: name, type: type) { builder in + builder.types = members + } + } + + public func union( + type: Type.Type, + build: (UnionTypeBuilder) throws -> Void + ) throws { + let name = fixName(String(describing: Type.self)) + try union(name: name, type: type, build: build) + } + + public func union( + name: String, + type: Type.Type, + build: (UnionTypeBuilder) throws -> Void + ) throws { + let builder = UnionTypeBuilder() + try build(builder) + + let interfaceType = try GraphQLUnionType( + name: name, + description: builder.description, + resolveType: builder.resolveType, + types: builder.types.map { try getObjectType(from: $0) } + ) + + map(Type.self, to: interfaceType) + } + public func `enum`( type: Type.Type, build: (EnumTypeBuilder) throws -> Void @@ -517,3 +561,4 @@ public struct Schema { ) } } + diff --git a/Sources/Graphiti/Types/UnionType.swift b/Sources/Graphiti/Types/UnionType.swift new file mode 100644 index 00000000..005a7c5c --- /dev/null +++ b/Sources/Graphiti/Types/UnionType.swift @@ -0,0 +1,8 @@ +import GraphQL + +public final class UnionTypeBuilder { + public var description: String? = nil + public var resolveType: GraphQLTypeResolve? = nil + public var types: [Any.Type] = [] +} + diff --git a/Tests/GraphitiTests/StarWarsTests/StarWarsData.swift b/Tests/GraphitiTests/StarWarsTests/StarWarsData.swift index ae0a3358..1f838269 100644 --- a/Tests/GraphitiTests/StarWarsTests/StarWarsData.swift +++ b/Tests/GraphitiTests/StarWarsTests/StarWarsData.swift @@ -60,9 +60,19 @@ struct Droid : Character { let primaryFunction: String } +protocol SearchResult {} +extension Planet: SearchResult {} +extension Human: SearchResult {} +extension Droid: SearchResult {} + var tatooine = Planet(id:"10001", name: "Tatooine", diameter: 10465, rotationPeriod: 23, orbitalPeriod: 304,residents: [Human]() ) var alderaan = Planet(id: "10002", name: "Alderaan", diameter: 12500, rotationPeriod: 24, orbitalPeriod: 364, residents: [Human]()) +let planetData: [String: Planet] = [ + "10001": tatooine, + "10002": alderaan, +] + let luke = Human( id: "1000", name: "Luke Skywalker", @@ -193,3 +203,30 @@ func getSecretBackStory() throws -> String? { throw Secret(description: "secretBackstory is secret.") } +/** + * Allos us to query for either a Human, Droid, or Planet. + */ +func search(for value: String) -> [SearchResult] { + let value = value.lowercased() + var result: [SearchResult] = [] + + result.append(contentsOf: + planetData.filter({ + return $1.name.lowercased().range(of:value) != nil + }).map({ $1 }) + ) + + result.append(contentsOf: + humanData.filter({ + return $1.name.lowercased().range(of:value) != nil + }).map({ $1 }) + ) + + result.append(contentsOf: + droidData.filter({ + return $1.name.lowercased().range(of:value) != nil + }).map({ $1 }) + ) + + return result +} diff --git a/Tests/GraphitiTests/StarWarsTests/StarWarsIntrospectionTests.swift b/Tests/GraphitiTests/StarWarsTests/StarWarsIntrospectionTests.swift index 51f934b8..9a630490 100644 --- a/Tests/GraphitiTests/StarWarsTests/StarWarsIntrospectionTests.swift +++ b/Tests/GraphitiTests/StarWarsTests/StarWarsIntrospectionTests.swift @@ -40,10 +40,12 @@ class StarWarsIntrospectionTests : XCTestCase { [ "name": "Query", ], + [ + "name": "SearchResult", + ], [ "name": "String", ], - [ "name": "__Directive", ], @@ -125,6 +127,9 @@ class StarWarsIntrospectionTests : XCTestCase { [ "name": "Query", ], + [ + "name": "SearchResult", + ], [ "name": "String", ], @@ -467,6 +472,24 @@ class StarWarsIntrospectionTests : XCTestCase { ], ], ], + [ + "name": "search", + "args": [ + [ + "name": "query", + "description": "text to find", + "type": [ + "name": nil, + "kind": "NON_NULL", + "ofType": [ + "name": "String", + "kind": "SCALAR", + ] + ], + "defaultValue": nil, + ], + ], + ], ], ], ], diff --git a/Tests/GraphitiTests/StarWarsTests/StarWarsQueryTests.swift b/Tests/GraphitiTests/StarWarsTests/StarWarsQueryTests.swift index 765a21d6..c6d425db 100644 --- a/Tests/GraphitiTests/StarWarsTests/StarWarsQueryTests.swift +++ b/Tests/GraphitiTests/StarWarsTests/StarWarsQueryTests.swift @@ -490,6 +490,38 @@ class StarWarsQueryTests : XCTestCase { let result = try schema.execute(request: query) XCTAssertEqual(result, expected) } + + func testSearchQuery() throws { + let query = "query {" + + " search(query: \"o\") {" + + " ... on Planet {" + + " name " + + " diameter " + + " }" + + " ... on Human {" + + " name " + + " }" + + " ... on Droid {" + + " name " + + " primaryFunction " + + " }" + + " }" + + "}" + + let expected: Map = [ + "data": [ + "search": [ + [ "name": "Tatooine", "diameter": 10465 ], + [ "name": "Han Solo" ], + [ "name": "Leia Organa" ], + [ "name": "C-3PO", "primaryFunction": "Protocol" ], + ], + ], + ] + + let result = try starWarsSchema.execute(request: query) + XCTAssertEqual(result, expected) + } } extension StarWarsQueryTests { diff --git a/Tests/GraphitiTests/StarWarsTests/StarWarsSchema.swift b/Tests/GraphitiTests/StarWarsTests/StarWarsSchema.swift index 44eafc2f..3785845c 100644 --- a/Tests/GraphitiTests/StarWarsTests/StarWarsSchema.swift +++ b/Tests/GraphitiTests/StarWarsTests/StarWarsSchema.swift @@ -252,6 +252,15 @@ let starWarsSchema = try! Schema { schema in ) } + /** + * A search result can be a include a number of different types. + * + * This implements the following type system shorthand: + * + * union SearchResult = Planet | Human | Droid + */ + try schema.union(type: SearchResult.self, members: [ Planet.self, Human.self, Droid.self ]) + /** * This is the type that will be the root of our query, and the * entry point into our schema. It gives us the ability to fetch @@ -264,6 +273,7 @@ let starWarsSchema = try! Schema { schema in * hero(episode: Episode): Character * human(id: String!): Human * droid(id: String!): Droid + * search(query: String!): SearchResult * } */ try schema.query { query in @@ -298,6 +308,16 @@ let starWarsSchema = try! Schema { schema in try query.field(name: "droid") { (_, arguments: DroidArguments, _, _) in getDroid(id: arguments.id) } + + struct SearchArguments : Arguments { + let query: String + static let descriptions = ["query": "text to find"] + } + + try query.field(name: "search") { (_, arguments: SearchArguments, _, _) in + search(for: arguments.query) + } + } schema.types = [Human.self, Droid.self]