Skip to content

Commit

Permalink
Adding node interface support
Browse files Browse the repository at this point in the history
  • Loading branch information
nerdsupremacist committed May 9, 2021
1 parent 7ec5ed1 commit 1799680
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,22 @@
import Foundation
import GraphQL
import Runtime
import NIO

extension GraphQLObject {

static func resolveObject(using context: inout Resolution.Context) throws -> GraphQLObjectType {
let (typeProperties, typeMethods, inheritance) = try typeInfo(of: Self.self, .properties, .methods, .inheritance)

let isNode: Bool
if let type = Self.self as? Node.Type {
context.append(type: type)
isNode = true
} else {
isNode = false
}
let nodeFields = isNode ? ["id" : GraphQLNodeIdField] : [:]

let gettersThatShouldBeIgnored = Set(typeProperties.filter { $0.type is CustomGraphQLProperty.Type }.map { $0.name.deleting(prefix: "_") })
let propertyResults = try typeProperties.compactMap { try $0.resolve(for: Self.self, using: &context) }
let properties = propertyResults.reduce([:]) { dictionary, result in
Expand All @@ -16,11 +26,14 @@ extension GraphQLObject {

let methodMap = Dictionary(typeMethods.map { ($0.methodName.deleting(prefix: "$"), $0) }) { first, _ in first }
let methods = try methodMap.filter { !gettersThatShouldBeIgnored.contains($0.key) }.compactMapValues { try $0.resolve(for: Self.self, using: &context) }
let fields = properties.merging(methods) { $1 }

let fields = properties
.merging(methods) { $1 }
.merging(nodeFields) { $1 }

let interfaces = try inheritance
.compactMap { $0 as? GraphQLObject.Type }
.map { try context.resolveInterface(object: $0) } + propertyResults.flatMap(\.interfaces)
.map { try context.resolveInterface(object: $0) } + propertyResults.flatMap(\.interfaces) + [isNode ? GraphQLNode : nil].compactMap { $0 }

let type = try GraphQLObjectType(name: concreteTypeName, fields: fields, interfaces: interfaces.distinct(by: \.name)) { value, _, _ in value is Self }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ import Foundation
- And all the functions that return GraphQL Values are also included
- The store functions return `Void`, which is not a GraphQL Output. These methods are therefore not included.
*/
public protocol GraphQLObject : class, OutputResolvable, ConcreteResolvable, KeyPathListable {
public protocol GraphQLObject : AnyObject, OutputResolvable, ConcreteResolvable, KeyPathListable {
/**
Loads object from source.

Expand Down
28 changes: 28 additions & 0 deletions Sources/GraphZahl/Resolution/OutputResolvable/Node.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

import Foundation
import NIO
import ContextKit
import GraphQL

public protocol Node: GraphQLObject {
func id(context: MutableContext, eventLoop: EventLoopGroup) -> EventLoopFuture<String>

static func find(id: String, context: MutableContext, eventLoop: EventLoopGroup ) -> EventLoopFuture<Node?>
}

let GraphQLNodeIdField: GraphQLField = {
return GraphQLField(type: GraphQLNonNull(GraphQLID),
description: "The id of the object",
deprecationReason: nil,
args: [:]) { (source, _, context, eventLoop, _) -> Future<Any?> in

return (source as! Node).id(context: context as! MutableContext, eventLoop: eventLoop).map { $0 }
}
}()

let GraphQLNode: GraphQLInterfaceType = {
let fields = [
"id" : GraphQLNodeIdField
]
return try! GraphQLInterfaceType(name: "Node", fields: fields, resolveType: nil)
}()
37 changes: 31 additions & 6 deletions Sources/GraphZahl/Resolution/Resolution.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,21 @@ public enum Resolution {
let unresolvedReferences: [String : OutputResolvable.Type]
let viewerContextType: Any.Type
let generatorViewerContext: Any?
let nodeTypes: [Node.Type]

private init(resolved: [String : GraphQLType],
references: [String : GraphQLOutputType],
unresolvedReferences: [String : OutputResolvable.Type],
viewerContextType: Any.Type,
generatorViewerContext: Any?) {
generatorViewerContext: Any?,
nodeTypes: [Node.Type]) {

self.resolved = resolved
self.references = references
self.unresolvedReferences = unresolvedReferences
self.viewerContextType = viewerContextType
self.generatorViewerContext = generatorViewerContext
self.nodeTypes = nodeTypes
}
}

Expand All @@ -55,14 +58,28 @@ extension Resolution.Context {

}

extension Resolution.Context {

public mutating func append(type: Node.Type) {
self = Resolution.Context(resolved: resolved,
references: references,
unresolvedReferences: unresolvedReferences,
viewerContextType: viewerContextType,
generatorViewerContext: generatorViewerContext,
nodeTypes: nodeTypes + [type])
}

}

extension Resolution.Context {

public func appending(type: GraphQLType, as name: String) -> Resolution.Context {
return Resolution.Context(resolved: resolved.merging([name : type]) { $1 },
references: references,
unresolvedReferences: unresolvedReferences,
viewerContextType: viewerContextType,
generatorViewerContext: generatorViewerContext)
generatorViewerContext: generatorViewerContext,
nodeTypes: nodeTypes)
}

public mutating func append(type: GraphQLType, as name: String) {
Expand All @@ -78,7 +95,8 @@ extension Resolution.Context {
references: references.merging([name : type]) { $1 },
unresolvedReferences: unresolvedReferences,
viewerContextType: viewerContextType,
generatorViewerContext: generatorViewerContext)
generatorViewerContext: generatorViewerContext,
nodeTypes: nodeTypes)
}

public mutating func append(reference type: GraphQLOutputType, as name: String) {
Expand All @@ -94,7 +112,8 @@ extension Resolution.Context {
references: references,
unresolvedReferences: unresolvedReferences.merging([name : type]) { $1 },
viewerContextType: viewerContextType,
generatorViewerContext: generatorViewerContext)
generatorViewerContext: generatorViewerContext,
nodeTypes: nodeTypes)
}

public mutating func append(unresolved type: OutputResolvable.Type, as name: String) {
Expand All @@ -112,7 +131,8 @@ extension Resolution.Context {
references: references,
unresolvedReferences: unresolvedReferences,
viewerContextType: viewerContextType,
generatorViewerContext: generatorViewerContext)
generatorViewerContext: generatorViewerContext,
nodeTypes: nodeTypes)
}

public mutating func removeUnresolved(with name: String) {
Expand Down Expand Up @@ -249,7 +269,12 @@ extension Resolution.Context {
extension Resolution.Context {

static func empty(viewerContextType: Any.Type, viewerContext: Any?) -> Resolution.Context {
return Resolution.Context(resolved: [:], references: [:], unresolvedReferences: [:], viewerContextType: viewerContextType, generatorViewerContext: viewerContext)
return Resolution.Context(resolved: [:],
references: [:],
unresolvedReferences: [:],
viewerContextType: viewerContextType,
generatorViewerContext: viewerContext,
nodeTypes: [])
}

}
Expand Down
66 changes: 65 additions & 1 deletion Sources/GraphZahl/Resolution/Root/GraphQLSchema+resolve.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,44 @@

import Foundation
import GraphQL
import NIO
import ContextKit

extension GraphQLSchema {

static func resolve(viewerContext: ViewerContext?) throws -> GraphQL.GraphQLSchema {
var context = Resolution.Context.empty(viewerContextType: ViewerContext.self, viewerContext: viewerContext)

let query = try context.resolve(object: Query.self)
var query = try context.resolve(object: Query.self)

if !context.nodeTypes.isEmpty {
let newFields = [
"node" : nodeField(types: context.nodeTypes)
]

let fields = query.fields.mapValues { definition -> GraphQLField in
let args = Dictionary(uniqueKeysWithValues: definition.args.map { argDefinition in
return (
argDefinition.name,
GraphQLArgument(type: argDefinition.type,
description: argDefinition.description,
defaultValue: argDefinition.defaultValue)
)
})
return GraphQLField(type: definition.type,
description: definition.description,
deprecationReason: definition.deprecationReason,
args: args,
resolve: definition.resolve)
}
.merging(newFields) { $1 }

query = try GraphQLObjectType(name: query.name,
description: query.description,
fields: fields,
interfaces: query.interfaces,
isTypeOf: query.isTypeOf)
}

let mutation: GraphQLObjectType?
if Mutation.self != None.self {
Expand All @@ -24,3 +55,36 @@ extension GraphQLSchema {
}

}

private func nodeField(types: [Node.Type]) -> GraphQLField {
return GraphQLField(
type: GraphQLNode,
description: "Fetches an object given its ID",
deprecationReason: nil,
args: ["id" : GraphQLArgument(type: GraphQLNonNull(GraphQLID), description: "The ID of an object")]
) { (_, args, context, eventLoop, _) -> Future<Any?> in
let id = try args.get("id").stringValue(converting: true)
return findNode(id: id, types: types, context: context as! MutableContext, eventLoop: eventLoop).map { $0 }
}
}

private func findNode<C : Collection>(
id: String,
types: C,
context: MutableContext,
eventLoop: EventLoopGroup
) -> EventLoopFuture<Node?> where C.Element == Node.Type {
guard let type = types.first else {
return eventLoop.next().makeSucceededFuture(nil)
}

return type
.find(id: id, context: context, eventLoop: eventLoop)
.flatMap { node in
if let node = node {
return eventLoop.next().makeSucceededFuture(node)
}

return findNode(id: id, types: types.dropFirst(), context: context, eventLoop: eventLoop)
}
}

0 comments on commit 1799680

Please sign in to comment.