diff --git a/Package.resolved b/Package.resolved index 4b4f5302..2dfbef0e 100644 --- a/Package.resolved +++ b/Package.resolved @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/apple/swift-nio.git", "state": { "branch": null, - "revision": "120acb15c39aa3217e9888e515de160378fbcc1e", - "version": "2.18.0" + "revision": "154f1d32366449dcccf6375a173adf4ed2a74429", + "version": "2.38.0" } } ] diff --git a/Package.swift b/Package.swift index c05a35e9..a4c4e9bd 100644 --- a/Package.swift +++ b/Package.swift @@ -7,7 +7,7 @@ let package = Package( .library(name: "GraphQL", targets: ["GraphQL"]), ], dependencies: [ - .package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.10.1")), + .package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.38.0")), .package(url: "https://github.com/apple/swift-collections", .upToNextMajor(from: "1.0.0")), ], targets: [ diff --git a/Sources/GraphQL/Execution/Execute.swift b/Sources/GraphQL/Execution/Execute.swift index 486eb994..8efa2fd3 100644 --- a/Sources/GraphQL/Execution/Execute.swift +++ b/Sources/GraphQL/Execution/Execute.swift @@ -359,23 +359,21 @@ func buildExecutionContext( for definition in documentAST.definitions { switch definition { - case let definition as OperationDefinition: + case .executableDefinition(.operation(let definition)): guard !(operationName == nil && possibleOperation != nil) else { throw GraphQLError( message: "Must provide operation name if query contains multiple operations." ) } - + if operationName == nil || definition.name?.value == operationName { possibleOperation = definition } - - case let definition as FragmentDefinition: + case .executableDefinition(.fragment(let definition)): fragments[definition.name.value] = definition - default: throw GraphQLError( - message: "GraphQL cannot execute a request containing a \(definition.kind).", + message: "GraphQL cannot execute a request containing a \(type(of: definition)).", nodes: [definition] ) } @@ -502,7 +500,7 @@ func collectFields( for selection in selectionSet.selections { switch selection { - case let field as Field: + case .field(let field): let shouldInclude = try shouldIncludeNode( exeContext: exeContext, directives: field.directives @@ -519,7 +517,7 @@ func collectFields( } fields[name]?.append(field) - case let inlineFragment as InlineFragment: + case .inlineFragment(let inlineFragment): let shouldInclude = try shouldIncludeNode( exeContext: exeContext, directives: inlineFragment.directives @@ -542,34 +540,34 @@ func collectFields( fields: &fields, visitedFragmentNames: &visitedFragmentNames ) - case let fragmentSpread as FragmentSpread: + case .fragmentSpread(let fragmentSpread): let fragmentName = fragmentSpread.name.value - + let shouldInclude = try shouldIncludeNode( exeContext: exeContext, directives: fragmentSpread.directives ) - + guard visitedFragmentNames[fragmentName] == nil && shouldInclude else { continue } - + visitedFragmentNames[fragmentName] = true - + guard let fragment = exeContext.fragments[fragmentName] else { continue } - + let fragmentConditionMatches = try doesFragmentConditionMatch( exeContext: exeContext, fragment: fragment, type: runtimeType ) - + guard fragmentConditionMatches else { continue } - + try collectFields( exeContext: exeContext, runtimeType: runtimeType, @@ -577,8 +575,6 @@ func collectFields( fields: &fields, visitedFragmentNames: &visitedFragmentNames ) - default: - break } } @@ -629,7 +625,7 @@ func doesFragmentConditionMatch( return true } - guard let conditionalType = typeFromAST(schema: exeContext.schema, inputTypeAST: typeConditionAST) else { + guard let conditionalType = typeFromAST(schema: exeContext.schema, inputTypeAST: .namedType(typeConditionAST)) else { return true } @@ -637,7 +633,7 @@ func doesFragmentConditionMatch( return true } - if let abstractType = conditionalType as? GraphQLAbstractType { + if let abstractType = conditionalType as? (any GraphQLAbstractType) { return exeContext.schema.isSubType( abstractType: abstractType, maybeSubType: type @@ -765,7 +761,7 @@ func resolveOrError( // in the execution context. func completeValueCatchingError( exeContext: ExecutionContext, - returnType: GraphQLType, + returnType: any GraphQLType, fieldASTs: [Field], info: GraphQLResolveInfo, path: IndexPath, @@ -817,7 +813,7 @@ func completeValueCatchingError( // location information. func completeValueWithLocatedError( exeContext: ExecutionContext, - returnType: GraphQLType, + returnType: any GraphQLType, fieldASTs: [Field], info: GraphQLResolveInfo, path: IndexPath, @@ -867,7 +863,7 @@ func completeValueWithLocatedError( */ func completeValue( exeContext: ExecutionContext, - returnType: GraphQLType, + returnType: any GraphQLType, fieldASTs: [Field], info: GraphQLResolveInfo, path: IndexPath, @@ -916,13 +912,13 @@ func completeValue( // If field type is a leaf type, Scalar or Enum, serialize to a valid value, // returning .null if serialization is not possible. - if let returnType = returnType as? GraphQLLeafType { + if let returnType = returnType as? any GraphQLLeafType { return exeContext.eventLoopGroup.next().makeSucceededFuture(try completeLeafValue(returnType: returnType, result: r)) } // If field type is an abstract type, Interface or Union, determine the // runtime Object type and complete for that type. - if let returnType = returnType as? GraphQLAbstractType { + if let returnType = returnType as? any GraphQLAbstractType { return try completeAbstractValue( exeContext: exeContext, returnType: returnType, @@ -999,7 +995,7 @@ func completeListValue( * Complete a Scalar or Enum by serializing to a valid value, returning * .null if serialization is not possible. */ -func completeLeafValue(returnType: GraphQLLeafType, result: Any?) throws -> Map { +func completeLeafValue(returnType: any GraphQLLeafType, result: Any?) throws -> Map { guard let result = result else { return .null } @@ -1023,7 +1019,7 @@ func completeLeafValue(returnType: GraphQLLeafType, result: Any?) throws -> Map */ func completeAbstractValue( exeContext: ExecutionContext, - returnType: GraphQLAbstractType, + returnType: any GraphQLAbstractType, fieldASTs: [Field], info: GraphQLResolveInfo, path: IndexPath, @@ -1046,7 +1042,7 @@ func completeAbstractValue( } // If resolveType returns a string, we assume it's a GraphQLObjectType name. - var runtimeType: GraphQLType? + var runtimeType: (any GraphQLType)? switch resolveResult { case .name(let name): @@ -1143,7 +1139,7 @@ func defaultResolveType( value: Any, eventLoopGroup: EventLoopGroup, info: GraphQLResolveInfo, - abstractType: GraphQLAbstractType + abstractType: any GraphQLAbstractType ) throws -> TypeResolveResult? { let possibleTypes = info.schema.getPossibleTypes(abstractType: abstractType) diff --git a/Sources/GraphQL/Execution/Values.swift b/Sources/GraphQL/Execution/Values.swift index 0336f4e4..d314a913 100644 --- a/Sources/GraphQL/Execution/Values.swift +++ b/Sources/GraphQL/Execution/Values.swift @@ -82,7 +82,7 @@ func getVariableValue(schema: GraphQLSchema, definitionAST: VariableDefinition, let type = typeFromAST(schema: schema, inputTypeAST: definitionAST.type) let variable = definitionAST.variable - guard let inputType = type as? GraphQLInputType else { + guard let inputType = type as? (any GraphQLInputType) else { throw GraphQLError( message: "Variable \"$\(variable.name.value)\" expected value of type " + @@ -112,11 +112,11 @@ func getVariableValue(schema: GraphQLSchema, definitionAST: VariableDefinition, /** * Given a type and any value, return a runtime value coerced to match the type. */ -func coerceValue(value: Map, type: GraphQLInputType) throws -> Map { +func coerceValue(value: Map, type: any GraphQLInputType) throws -> Map { if let nonNull = type as? GraphQLNonNull { // Note: we're not checking that the result of coerceValue is non-null. // We only call this function after calling validate. - guard let nonNullType = nonNull.ofType as? GraphQLInputType else { + guard let nonNullType = nonNull.ofType as? (any GraphQLInputType) else { throw GraphQLError(message: "NonNull must wrap an input type") } return try coerceValue(value: value, type: nonNullType) @@ -127,7 +127,7 @@ func coerceValue(value: Map, type: GraphQLInputType) throws -> Map { } if let list = type as? GraphQLList { - guard let itemType = list.ofType as? GraphQLInputType else { + guard let itemType = list.ofType as? (any GraphQLInputType) else { throw GraphQLError(message: "Input list must wrap an input type") } @@ -168,7 +168,7 @@ func coerceValue(value: Map, type: GraphQLInputType) throws -> Map { return .dictionary(object) } - if let leafType = type as? GraphQLLeafType { + if let leafType = type as? (any GraphQLLeafType) { return try leafType.parseValue(value: value) } diff --git a/Sources/GraphQL/Language/AST.swift b/Sources/GraphQL/Language/AST.swift index 51ba839f..f4eb820a 100644 --- a/Sources/GraphQL/Language/AST.swift +++ b/Sources/GraphQL/Language/AST.swift @@ -133,42 +133,30 @@ extension Token : CustomStringConvertible { } } -public enum NodeResult { - case node(Node) - case array([Node]) - - public var isNode: Bool { - if case .node = self { - return true - } - return false - } - - public var isArray: Bool { - if case .array = self { - return true - } - return false - } -} - /** * The list of all possible AST node types. */ -public protocol Node { - var kind: Kind { get } +public protocol Node: TextOutputStreamable { var loc: Location? { get } - func get(key: String) -> NodeResult? - func set(value: Node?, key: String) + mutating func descend(descender: inout Descender) } extension Node { - public func get(key: String) -> NodeResult? { - return nil + public var printed: String { + var s = "" + self.write(to: &s) + return s } +} - public func set(value: Node?, key: String) { - +private protocol EnumNode: Node { + var underlyingNode: Node { get } +} +extension EnumNode { + public var loc: Location? { underlyingNode.loc } + + public func write(to target: inout Target) where Target : TextOutputStream { + underlyingNode.write(to: &target) } } @@ -178,15 +166,18 @@ extension OperationDefinition : Node {} extension VariableDefinition : Node {} extension Variable : Node {} extension SelectionSet : Node {} +extension Selection : Node {} extension Field : Node {} extension Argument : Node {} extension FragmentSpread : Node {} extension InlineFragment : Node {} extension FragmentDefinition : Node {} +extension Value : Node {} extension IntValue : Node {} extension FloatValue : Node {} extension StringValue : Node {} extension BooleanValue : Node {} +extension NullValue : Node {} extension EnumValue : Node {} extension ListValue : Node {} extension ObjectValue : Node {} @@ -209,85 +200,152 @@ extension InputObjectTypeDefinition : Node {} extension TypeExtensionDefinition : Node {} extension DirectiveDefinition : Node {} -public final class Name { - public let kind: Kind = .name +public struct Name { public let loc: Location? public let value: String - init(loc: Location? = nil, value: String) { + public init(loc: Location? = nil, value: String) { self.loc = loc self.value = value } + + public mutating func descend(descender: inout Descender) { } + + public func write(to target: inout Target) where Target : TextOutputStream { + target.write(value) + } } -extension Name : Equatable { +extension Name: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(value) + } public static func == (lhs: Name, rhs: Name) -> Bool { return lhs.value == rhs.value } } -public final class Document { - public let kind: Kind = .document +public struct Document { public let loc: Location? - public let definitions: [Definition] + public var definitions: [Definition] init(loc: Location? = nil, definitions: [Definition]) { self.loc = loc self.definitions = definitions } - public func get(key: String) -> NodeResult? { - switch key { - case "definitions": - guard !definitions.isEmpty else { - return nil - } - return .array(definitions) - default: - return nil + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.definitions) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + definitions.forEach { + $0.write(to: &target) + target.write("\n\n") } } } extension Document : Equatable { public static func == (lhs: Document, rhs: Document) -> Bool { - guard lhs.definitions.count == rhs.definitions.count else { - return false + return lhs.definitions == rhs.definitions + } +} + +public struct ExecutableDocument { + public let loc: Location? + public var definitions: [ExecutableDefinition] + + init(loc: Location? = nil, definitions: [ExecutableDefinition]) { + self.loc = loc + self.definitions = definitions + } + + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.definitions) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + definitions.forEach { + $0.write(to: &target) + target.write("\n\n") } + } +} - for (l, r) in zip(lhs.definitions, rhs.definitions) { - guard l == r else { - return false - } +extension ExecutableDocument: Equatable { + public static func == (lhs: ExecutableDocument, rhs: ExecutableDocument) -> Bool { + return lhs.definitions == rhs.definitions + } +} + +public enum Definition: EnumNode, Equatable { + case executableDefinition(ExecutableDefinition) + case typeSystemDefinitionOrExtension(TypeSystemDefinitionOrExtension) + + var underlyingNode: Node { + switch self { + case let .executableDefinition(x): + return x + case let .typeSystemDefinitionOrExtension(x): + return x } + } - return true + public mutating func descend(descender: inout Descender) { + switch self { + case var .executableDefinition(x): + descender.descend(enumCase: &x) + self = .executableDefinition(x) + case var .typeSystemDefinitionOrExtension(x): + descender.descend(enumCase: &x) + self = .typeSystemDefinitionOrExtension(x) + } } } -public protocol Definition : Node {} -extension OperationDefinition : Definition {} -extension FragmentDefinition : Definition {} +public enum ExecutableDefinition: EnumNode, Equatable { + case operation(OperationDefinition) + case fragment(FragmentDefinition) -public func == (lhs: Definition, rhs: Definition) -> Bool { - switch lhs { - case let l as OperationDefinition: - if let r = rhs as? OperationDefinition { - return l == r + fileprivate var underlyingNode: Node { + switch self { + case let .fragment(fragmentDef): + return fragmentDef + case let .operation(operationDef): + return operationDef } - case let l as FragmentDefinition: - if let r = rhs as? FragmentDefinition { - return l == r + } + + public mutating func descend(descender: inout Descender) { + switch self { + case var .fragment(x): + descender.descend(enumCase: &x) + self = .fragment(x) + case var .operation(x): + descender.descend(enumCase: &x) + self = .operation(x) } - case let l as TypeSystemDefinition: - if let r = rhs as? TypeSystemDefinition { - return l == r + } +} + +public enum TypeSystemDefinitionOrExtension: EnumNode, Equatable { + case typeSystemDefinition(TypeSystemDefinition) + + fileprivate var underlyingNode: Node { + switch self { + case let .typeSystemDefinition(x): + return x } - default: - return false } - return false + public mutating func descend(descender: inout Descender) { + switch self { + case var .typeSystemDefinition(x): + descender.descend(enumCase: &x) + self = .typeSystemDefinition(x) + } + } } public enum OperationType : String { @@ -297,14 +355,13 @@ public enum OperationType : String { case subscription = "subscription" } -public final class OperationDefinition { - public let kind: Kind = .operationDefinition +public struct OperationDefinition { public let loc: Location? - public let operation: OperationType - public let name: Name? - public let variableDefinitions: [VariableDefinition] - public let directives: [Directive] - public let selectionSet: SelectionSet + public var operation: OperationType + public var name: Name? + public var variableDefinitions: [VariableDefinition] + public var directives: [Directive] + public var selectionSet: SelectionSet init(loc: Location? = nil, operation: OperationType, name: Name? = nil, variableDefinitions: [VariableDefinition] = [], directives: [Directive] = [], selectionSet: SelectionSet) { self.loc = loc @@ -315,48 +372,52 @@ public final class OperationDefinition { self.selectionSet = selectionSet } - public func get(key: String) -> NodeResult? { - switch key { - case "name": - return name.map({ .node($0) }) - case "variableDefinitions": - guard !variableDefinitions.isEmpty else { - return nil + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.name) + descender.descend(&self, \.variableDefinitions) + descender.descend(&self, \.directives) + descender.descend(&self, \.selectionSet) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + let anonymous = operation == .query && directives.isEmpty && variableDefinitions.isEmpty + if !anonymous { + target.write(operation.rawValue) + target.write(" ") + name?.write(to: &target) + if let first = variableDefinitions.first { + target.write(" (") + first.write(to: &target) + variableDefinitions.suffix(from: 1).forEach { + target.write(", ") + $0.write(to: &target) + } + target.write(")") } - return .array(variableDefinitions) - case "directives": - guard !variableDefinitions.isEmpty else { - return nil + if !directives.isEmpty { + directives.write(to: &target) } - return .array(directives) - case "selectionSet": - return .node(selectionSet) - default: - return nil } + target.write(" ") + selectionSet.write(to: &target) } } -extension OperationDefinition : Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(ObjectIdentifier(self)) - } - +extension OperationDefinition: Equatable { public static func == (lhs: OperationDefinition, rhs: OperationDefinition) -> Bool { return lhs.operation == rhs.operation && - lhs.name == rhs.name && - lhs.variableDefinitions == rhs.variableDefinitions && - lhs.directives == rhs.directives && - lhs.selectionSet == rhs.selectionSet + lhs.name == rhs.name && + lhs.variableDefinitions == rhs.variableDefinitions && + lhs.directives == rhs.directives && + lhs.selectionSet == rhs.selectionSet } } -public final class VariableDefinition { - public let kind: Kind = .variableDefinition +public struct VariableDefinition { public let loc: Location? - public let variable: Variable - public let type: Type - public let defaultValue: Value? + public var variable: Variable + public var type: Type + public var defaultValue: Value? init(loc: Location? = nil, variable: Variable, type: Type, defaultValue: Value? = nil) { self.loc = loc @@ -364,17 +425,20 @@ public final class VariableDefinition { self.type = type self.defaultValue = defaultValue } - - public func get(key: String) -> NodeResult? { - switch key { - case "variable": - return .node(variable) - case "type": - return .node(type) - case "defaultValue": - return defaultValue.map({ .node($0) }) - default: - return nil + + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.variable) + descender.descend(&self, \.type) + descender.descend(&self, \.defaultValue) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + variable.write(to: &target) + target.write(": ") + type.write(to: &target) + if let defaultValue = defaultValue { + target.write(" = ") + defaultValue.write(to: &target) } } } @@ -401,23 +465,22 @@ extension VariableDefinition : Equatable { } } -public final class Variable { - public let kind: Kind = .variable +public struct Variable { public let loc: Location? - public let name: Name + public var name: Name init(loc: Location? = nil, name: Name) { self.loc = loc self.name = name } - - public func get(key: String) -> NodeResult? { - switch key { - case "name": - return .node(name) - default: - return nil - } + + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.name) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + target.write("$") + name.write(to: &target) } } @@ -427,85 +490,85 @@ extension Variable : Equatable { } } -public final class SelectionSet { - public let kind: Kind = .selectionSet +public struct SelectionSet { public let loc: Location? - public let selections: [Selection] + public var selections: [Selection] - init(loc: Location? = nil, selections: [Selection]) { + public init(loc: Location? = nil, selections: [Selection]) { self.loc = loc self.selections = selections } - - public func get(key: String) -> NodeResult? { - switch key { - case "selections": - guard !selections.isEmpty else { - return nil - } - return .array(selections) - default: - return nil + + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.selections) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + target.write("{\n") + selections.forEach { + $0.write(to: &target) + target.write("\n") } + target.write("}") } } -extension SelectionSet : Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(ObjectIdentifier(self)) - } - +extension SelectionSet: Equatable { public static func == (lhs: SelectionSet, rhs: SelectionSet) -> Bool { guard lhs.selections.count == rhs.selections.count else { return false } - + for (l, r) in zip(lhs.selections, rhs.selections) { guard l == r else { return false } } - + return true } } -public protocol Selection : Node {} -extension Field : Selection {} -extension FragmentSpread : Selection {} -extension InlineFragment : Selection {} - -public func == (lhs: Selection, rhs: Selection) -> Bool { - switch lhs { - case let l as Field: - if let r = rhs as? Field { - return l == r - } - case let l as FragmentSpread: - if let r = rhs as? FragmentSpread { - return l == r +public enum Selection: EnumNode, Equatable { + case field(Field) + case fragmentSpread(FragmentSpread) + case inlineFragment(InlineFragment) + + fileprivate var underlyingNode: Node { + switch self { + case let .field(field): + return field + case let .fragmentSpread(fragmentSpread): + return fragmentSpread + case let .inlineFragment(inlineFragment): + return inlineFragment } - case let l as InlineFragment: - if let r = rhs as? InlineFragment { - return l == r + } + + public mutating func descend(descender: inout Descender) { + switch self { + case var .field(x): + descender.descend(enumCase: &x) + self = .field(x) + case var .fragmentSpread(x): + descender.descend(enumCase: &x) + self = .fragmentSpread(x) + case var .inlineFragment(x): + descender.descend(enumCase: &x) + self = .inlineFragment(x) } - default: - return false } - - return false } -public final class Field { - public let kind: Kind = .field +public struct Field { public let loc: Location? - public let alias: Name? - public let name: Name - public let arguments: [Argument] - public let directives: [Directive] - public let selectionSet: SelectionSet? + public var alias: Name? + public var name: Name + public var arguments: [Argument] + public var directives: [Directive] + public var selectionSet: SelectionSet? - init(loc: Location? = nil, alias: Name? = nil, name: Name, arguments: [Argument] = [], directives: [Directive] = [], selectionSet: SelectionSet? = nil) { + public init(loc: Location? = nil, alias: Name? = nil, name: Name, arguments: [Argument] = [], directives: [Directive] = [], selectionSet: SelectionSet? = nil) { self.loc = loc self.alias = alias self.name = name @@ -513,27 +576,33 @@ public final class Field { self.directives = directives self.selectionSet = selectionSet } - - public func get(key: String) -> NodeResult? { - switch key { - case "alias": - return alias.map({ .node($0) }) - case "name": - return .node(name) - case "arguments": - guard !arguments.isEmpty else { - return nil - } - return .array(arguments) - case "directives": - guard !directives.isEmpty else { - return nil - } - return .array(directives) - case "selectionSet": - return selectionSet.map({ .node($0) }) - default: - return nil + + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.alias) + descender.descend(&self, \.name) + descender.descend(&self, \.arguments) + descender.descend(&self, \.directives) + descender.descend(&self, \.selectionSet) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + if let alias = alias { + alias.write(to: &target) + target.write(": ") + } + name.write(to: &target) + if !arguments.isEmpty { + target.write( "(") + arguments.write(to: &target) + target.write(")") + } + if !directives.isEmpty { + target.write(" ") + directives.write(to: &target) + } + if let selectionSet = selectionSet { + target.write(" ") + selectionSet.write(to: &target) } } } @@ -548,26 +617,37 @@ extension Field : Equatable { } } -public final class Argument { - public let kind: Kind = .argument +public struct Argument { public let loc: Location? - public let name: Name - public let value: Value + public var name: Name + public var value: Value init(loc: Location? = nil, name: Name, value: Value) { self.loc = loc self.name = name self.value = value } + + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.name) + descender.descend(&self, \.value) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + name.write(to: &target) + target.write(": ") + value.write(to: &target) + } +} - public func get(key: String) -> NodeResult? { - switch key { - case "name": - return .node(name) - case "value": - return .node(value) - default: - return nil +extension Array where Element == Argument { + func write(to target: inout Target) where Target : TextOutputStream { + if let first = first { + first.write(to: &target) + } + suffix(from: 1).forEach { + target.write(", ") + $0.write(to: &target) } } } @@ -579,15 +659,10 @@ extension Argument : Equatable { } } -public protocol Fragment : Selection {} -extension FragmentSpread : Fragment {} -extension InlineFragment : Fragment {} - -public final class FragmentSpread { - public let kind: Kind = .fragmentSpread +public struct FragmentSpread { public let loc: Location? - public let name: Name - public let directives: [Directive] + public var name: Name + public var directives: [Directive] init(loc: Location? = nil, name: Name, directives: [Directive] = []) { self.loc = loc @@ -595,17 +670,17 @@ public final class FragmentSpread { self.directives = directives } - public func get(key: String) -> NodeResult? { - switch key { - case "name": - return .node(name) - case "directives": - guard !directives.isEmpty else { - return nil - } - return .array(directives) - default: - return nil + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.name) + descender.descend(&self, \.directives) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + target.write("...") + name.write(to: &target) + if !directives.isEmpty { + target.write(" ") + directives.write(to: &target) } } } @@ -633,12 +708,11 @@ extension FragmentDefinition : HasTypeCondition { } } -public final class InlineFragment { - public let kind: Kind = .inlineFragment +public struct InlineFragment { public let loc: Location? - public let typeCondition: NamedType? - public let directives: [Directive] - public let selectionSet: SelectionSet + public var typeCondition: NamedType? + public var directives: [Directive] + public var selectionSet: SelectionSet init(loc: Location? = nil, typeCondition: NamedType? = nil, directives: [Directive] = [], selectionSet: SelectionSet) { self.loc = loc @@ -646,23 +720,25 @@ public final class InlineFragment { self.directives = directives self.selectionSet = selectionSet } -} - -extension InlineFragment { - public func get(key: String) -> NodeResult? { - switch key { - case "typeCondition": - return typeCondition.map({ .node($0) }) - case "directives": - guard !directives.isEmpty else { - return nil - } - return .array(directives) - case "selectionSet": - return .node(selectionSet) - default: - return nil + + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.typeCondition) + descender.descend(&self, \.directives) + descender.descend(&self, \.selectionSet) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + target.write("...") + if let typeCondition = typeCondition { + target.write(" on ") + typeCondition.write(to: &target) + } + if !directives.isEmpty { + target.write(" ") + directives.write(to: &target) } + target.write(" ") + selectionSet.write(to: &target) } } @@ -674,13 +750,12 @@ extension InlineFragment : Equatable { } } -public final class FragmentDefinition { - public let kind: Kind = .fragmentDefinition +public struct FragmentDefinition { public let loc: Location? - public let name: Name - public let typeCondition: NamedType - public let directives: [Directive] - public let selectionSet: SelectionSet + public var name: Name + public var typeCondition: NamedType + public var directives: [Directive] + public var selectionSet: SelectionSet init(loc: Location? = nil, name: Name, typeCondition: NamedType, directives: [Directive] = [], selectionSet: SelectionSet) { self.loc = loc @@ -690,30 +765,28 @@ public final class FragmentDefinition { self.selectionSet = selectionSet } - public func get(key: String) -> NodeResult? { - switch key { - case "name": - return .node(name) - case "typeCondition": - return .node(typeCondition) - case "directives": - guard !directives.isEmpty else { - return nil - } - return .array(directives) - case "selectionSet": - return .node(selectionSet) - default: - return nil + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.name) + descender.descend(&self, \.typeCondition) + descender.descend(&self, \.directives) + descender.descend(&self, \.selectionSet) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + target.write("fragment ") + name.write(to: &target) + target.write(" on ") + typeCondition.write(to: &target) + if !directives.isEmpty { + target.write(" ") + directives.write(to: &target) } + target.write(" ") + selectionSet.write(to: &target) } } -extension FragmentDefinition : Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(ObjectIdentifier(self)) - } - +extension FragmentDefinition: Equatable { public static func == (lhs: FragmentDefinition, rhs: FragmentDefinition) -> Bool { return lhs.name == rhs.name && lhs.typeCondition == rhs.typeCondition && @@ -722,64 +795,74 @@ extension FragmentDefinition : Hashable { } } -public protocol Value : Node {} -extension Variable : Value {} -extension IntValue : Value {} -extension FloatValue : Value {} -extension StringValue : Value {} -extension BooleanValue : Value {} -extension NullValue : Value {} -extension EnumValue : Value {} -extension ListValue : Value {} -extension ObjectValue : Value {} - -public func == (lhs: Value, rhs: Value) -> Bool { - switch lhs { - case let l as Variable: - if let r = rhs as? Variable { - return l == r - } - case let l as IntValue: - if let r = rhs as? IntValue { - return l == r - } - case let l as FloatValue: - if let r = rhs as? FloatValue { - return l == r - } - case let l as StringValue: - if let r = rhs as? StringValue { - return l == r - } - case let l as BooleanValue: - if let r = rhs as? BooleanValue { - return l == r - } - case let l as NullValue: - if let r = rhs as? NullValue { - return l == r - } - case let l as EnumValue: - if let r = rhs as? EnumValue { - return l == r +public enum Value: EnumNode, Equatable { + var underlyingNode: Node { + switch self { + case let .variable(x): + return x + case let .intValue(x): + return x + case let .floatValue(x): + return x + case let .stringValue(x): + return x + case let .booleanValue(x): + return x + case let .nullValue(x): + return x + case let .enumValue(x): + return x + case let .listValue(x): + return x + case let .objectValue(x): + return x } - case let l as ListValue: - if let r = rhs as? ListValue { - return l == r - } - case let l as ObjectValue: - if let r = rhs as? ObjectValue { - return l == r + } + + public mutating func descend(descender: inout Descender) { + switch self { + case var .variable(x): + descender.descend(enumCase: &x) + self = .variable(x) + case var .intValue(x): + descender.descend(enumCase: &x) + self = .intValue(x) + case var .floatValue(x): + descender.descend(enumCase: &x) + self = .floatValue(x) + case var .stringValue(x): + descender.descend(enumCase: &x) + self = .stringValue(x) + case var .booleanValue(x): + descender.descend(enumCase: &x) + self = .booleanValue(x) + case var .nullValue(x): + descender.descend(enumCase: &x) + self = .nullValue(x) + case var .enumValue(x): + descender.descend(enumCase: &x) + self = .enumValue(x) + case var .listValue(x): + descender.descend(enumCase: &x) + self = .listValue(x) + case var .objectValue(x): + descender.descend(enumCase: &x) + self = .objectValue(x) } - default: - return false } - - return false -} - -public final class IntValue { - public let kind: Kind = .intValue + + case variable(Variable) + case intValue(IntValue) + case floatValue(FloatValue) + case stringValue(StringValue) + case booleanValue(BooleanValue) + case nullValue(NullValue) + case enumValue(EnumValue) + case listValue(ListValue) + case objectValue(ObjectValue) +} + +public struct IntValue { public let loc: Location? public let value: String @@ -787,6 +870,12 @@ public final class IntValue { self.loc = loc self.value = value } + + public mutating func descend(descender: inout Descender) { } + + public func write(to target: inout Target) where Target : TextOutputStream { + target.write(value) + } } extension IntValue : Equatable { @@ -795,8 +884,7 @@ extension IntValue : Equatable { } } -public final class FloatValue { - public let kind: Kind = .floatValue +public struct FloatValue { public let loc: Location? public let value: String @@ -804,6 +892,12 @@ public final class FloatValue { self.loc = loc self.value = value } + + public mutating func descend(descender: inout Descender) { } + + public func write(to target: inout Target) where Target : TextOutputStream { + target.write(value) + } } extension FloatValue : Equatable { @@ -812,8 +906,7 @@ extension FloatValue : Equatable { } } -public final class StringValue { - public let kind: Kind = .stringValue +public struct StringValue { public let loc: Location? public let value: String public let block: Bool? @@ -823,6 +916,19 @@ public final class StringValue { self.value = value self.block = block } + + public mutating func descend(descender: inout Descender) { } + + public func write(to target: inout Target) where Target : TextOutputStream { + if block ?? false { + //TODO: Implement this! + fatalError("Needs implemented") + } else { + target.write("\"") + target.write(value) + target.write("\"") + } + } } extension StringValue : Equatable { @@ -831,8 +937,7 @@ extension StringValue : Equatable { } } -public final class BooleanValue { - public let kind: Kind = .booleanValue +public struct BooleanValue { public let loc: Location? public let value: Bool @@ -840,6 +945,12 @@ public final class BooleanValue { self.loc = loc self.value = value } + + public mutating func descend(descender: inout Descender) { } + + public func write(to target: inout Target) where Target : TextOutputStream { + target.write(value ? "true" : "false") + } } extension BooleanValue : Equatable { @@ -848,13 +959,18 @@ extension BooleanValue : Equatable { } } -public final class NullValue { - public let kind: Kind = .nullValue +public struct NullValue { public let loc: Location? init(loc: Location? = nil) { self.loc = loc } + + public mutating func descend(descender: inout Descender) { } + + public func write(to target: inout Target) where Target : TextOutputStream { + target.write("null") + } } extension NullValue : Equatable { @@ -863,8 +979,7 @@ extension NullValue : Equatable { } } -public final class EnumValue { - public let kind: Kind = .enumValue +public struct EnumValue { public let loc: Location? public let value: String @@ -872,6 +987,12 @@ public final class EnumValue { self.loc = loc self.value = value } + + public mutating func descend(descender: inout Descender) { } + + public func write(to target: inout Target) where Target : TextOutputStream { + target.write(value) + } } extension EnumValue : Equatable { @@ -880,15 +1001,29 @@ extension EnumValue : Equatable { } } -public final class ListValue { - public let kind: Kind = .listValue +public struct ListValue { public let loc: Location? - public let values: [Value] + public var values: [Value] init(loc: Location? = nil, values: [Value]) { self.loc = loc self.values = values } + + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.values) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + target.write("[") + if let first = values.first { + first.write(to: &target) + values.suffix(from: 1).forEach { + target.write(", ") + $0.write(to: &target) + } + } + } } extension ListValue : Equatable { @@ -907,15 +1042,30 @@ extension ListValue : Equatable { } } -public final class ObjectValue { - public let kind: Kind = .objectValue +public struct ObjectValue { public let loc: Location? - public let fields: [ObjectField] + public var fields: [ObjectField] init(loc: Location? = nil, fields: [ObjectField]) { self.loc = loc self.fields = fields } + + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.fields) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + target.write("{") + if let first = fields.first { + first.write(to: &target) + fields.suffix(from: 1).forEach { + target.write(", ") + $0.write(to: &target) + } + } + target.write("}") + } } extension ObjectValue : Equatable { @@ -924,17 +1074,27 @@ extension ObjectValue : Equatable { } } -public final class ObjectField { - public let kind: Kind = .objectField +public struct ObjectField { public let loc: Location? - public let name: Name - public let value: Value + public var name: Name + public var value: Value init(loc: Location? = nil, name: Name, value: Value) { self.loc = loc self.name = name self.value = value } + + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.name) + descender.descend(&self, \.value) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + name.write(to: &target) + target.write(": ") + value.write(to: &target) + } } extension ObjectField : Equatable { @@ -944,17 +1104,31 @@ extension ObjectField : Equatable { } } -public final class Directive { - public let kind: Kind = .directive +public struct Directive { public let loc: Location? - public let name: Name - public let arguments: [Argument] + public var name: Name + public var arguments: [Argument] init(loc: Location? = nil, name: Name, arguments: [Argument] = []) { self.loc = loc self.name = name self.arguments = arguments } + + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.name) + descender.descend(&self, \.arguments) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + target.write("@") + name.write(to: &target) + if !arguments.isEmpty { + target.write("(") + arguments.write(to: &target) + target.write(")") + } + } } extension Directive : Equatable { @@ -964,49 +1138,64 @@ extension Directive : Equatable { } } -public protocol Type : Node {} -extension NamedType : Type {} -extension ListType : Type {} -extension NonNullType : Type {} - -public func == (lhs: Type, rhs: Type) -> Bool { - switch lhs { - case let l as NamedType: - if let r = rhs as? NamedType { - return l == r +extension Array where Element == Directive { + public func write(to target: inout Target) where Target : TextOutputStream { + if let first = first { + first.write(to: &target) + suffix(from: 1).forEach { + $0.write(to: &target) + target.write(" ") + } } - case let l as ListType: - if let r = rhs as? ListType { - return l == r + } +} + +public indirect enum Type: EnumNode, Equatable { + var underlyingNode: Node { + switch self { + case let .namedType(x): + return x + case let .listType(x): + return x + case let .nonNullType(x): + return x } - case let l as NonNullType: - if let r = rhs as? NonNullType { - return l == r + } + + public mutating func descend(descender: inout Descender) { + switch self { + case var .namedType(x): + descender.descend(enumCase: &x) + self = .namedType(x) + case var .listType(x): + descender.descend(enumCase: &x) + self = .listType(x) + case var .nonNullType(x): + descender.descend(enumCase: &x) + self = .nonNullType(x) } - default: - return false } - - return false + + case namedType(NamedType) + case listType(ListType) + case nonNullType(NonNullType) } -public final class NamedType { - public let kind: Kind = .namedType +public struct NamedType { public let loc: Location? - public let name: Name + public var name: Name init(loc: Location? = nil, name: Name) { self.loc = loc self.name = name } - public func get(key: String) -> NodeResult? { - switch key { - case "name": - return .node(name) - default: - return nil - } + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.name) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + name.write(to: &target) } } @@ -1016,15 +1205,24 @@ extension NamedType : Equatable { } } -public final class ListType { - public let kind: Kind = .listType +public struct ListType { public let loc: Location? - public let type: Type + public var type: Type init(loc: Location? = nil, type: Type) { self.loc = loc self.type = type } + + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.type) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + target.write("[") + type.write(to: &target) + target.write("]") + } } extension ListType : Equatable { @@ -1033,74 +1231,81 @@ extension ListType : Equatable { } } -public protocol NonNullableType : Type {} -extension ListType : NonNullableType {} -extension NamedType : NonNullableType {} - -public final class NonNullType { - public let kind: Kind = .nonNullType - public let loc: Location? - public let type: NonNullableType - - init(loc: Location? = nil, type: NonNullableType) { - self.loc = loc - self.type = type +public enum NonNullType: EnumNode, Equatable { + var underlyingNode: Node { + switch self { + case let .namedType(x): + return x + case let .listType(x): + return x + } } - - public func get(key: String) -> NodeResult? { - switch key { - case "type": - return .node(type) - default: - return nil + + public mutating func descend(descender: inout Descender) { + switch self { + case var .namedType(x): + descender.descend(enumCase: &x) + self = .namedType(x) + case var .listType(x): + descender.descend(enumCase: &x) + self = .listType(x) } } -} - -extension NonNullType : Equatable { - public static func == (lhs: NonNullType, rhs: NonNullType) -> Bool { - return lhs.type == rhs.type + + case namedType(NamedType) + case listType(ListType) + + var type: Type { + switch self { + case let .namedType(x): + return .namedType(x) + case let .listType(x): + return .listType(x) + } + } + + public func write(to target: inout Target) where Target : TextOutputStream { + type.write(to: &target) + target.write("!") } } -// Type System Definition -// experimental non-spec addition. -public protocol TypeSystemDefinition : Definition {} -extension SchemaDefinition : TypeSystemDefinition {} -extension TypeExtensionDefinition : TypeSystemDefinition {} -extension DirectiveDefinition : TypeSystemDefinition {} - -public func == (lhs: TypeSystemDefinition, rhs: TypeSystemDefinition) -> Bool { - switch lhs { - case let l as SchemaDefinition: - if let r = rhs as? SchemaDefinition { - return l == r - } - case let l as TypeExtensionDefinition: - if let r = rhs as? TypeExtensionDefinition { - return l == r +public enum TypeSystemDefinition: EnumNode, Equatable { + var underlyingNode: Node { + switch self { + case let .schemaDefinition(x): + return x + case let .typeDefinition(x): + return x + case let .directiveDefinition(x): + return x } - case let l as DirectiveDefinition: - if let r = rhs as? DirectiveDefinition { - return l == r - } - case let l as TypeDefinition: - if let r = rhs as? TypeDefinition { - return l == r + } + + public mutating func descend(descender: inout Descender) { + switch self { + case var .schemaDefinition(x): + descender.descend(enumCase: &x) + self = .schemaDefinition(x) + case var .typeDefinition(x): + descender.descend(enumCase: &x) + self = .typeDefinition(x) + case var .directiveDefinition(x): + descender.descend(enumCase: &x) + self = .directiveDefinition(x) } - default: - return false } - - return false + + case schemaDefinition(SchemaDefinition) + case typeDefinition(TypeDefinition) + case directiveDefinition(DirectiveDefinition) } -public final class SchemaDefinition { - public let kind: Kind = .schemaDefinition +public struct SchemaDefinition { public let loc: Location? - public let description: StringValue? - public let directives: [Directive] - public let operationTypes: [OperationTypeDefinition] + public var description: StringValue? + public var directives: [Directive] + public var operationTypes: [OperationTypeDefinition] init(loc: Location? = nil, description: StringValue? = nil, directives: [Directive], operationTypes: [OperationTypeDefinition]) { self.loc = loc @@ -1108,6 +1313,16 @@ public final class SchemaDefinition { self.directives = directives self.operationTypes = operationTypes } + + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.description) + descender.descend(&self, \.directives) + descender.descend(&self, \.operationTypes) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + fatalError("TODO") + } } extension SchemaDefinition : Equatable { @@ -1118,17 +1333,24 @@ extension SchemaDefinition : Equatable { } } -public final class OperationTypeDefinition { - public let kind: Kind = .operationDefinition +public struct OperationTypeDefinition { public let loc: Location? public let operation: OperationType - public let type: NamedType + public var type: NamedType init(loc: Location? = nil, operation: OperationType, type: NamedType) { self.loc = loc self.operation = operation self.type = type } + + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.type) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + fatalError("TODO") + } } extension OperationTypeDefinition : Equatable { @@ -1138,53 +1360,60 @@ extension OperationTypeDefinition : Equatable { } } -public protocol TypeDefinition : TypeSystemDefinition {} -extension ScalarTypeDefinition : TypeDefinition {} -extension ObjectTypeDefinition : TypeDefinition {} -extension InterfaceTypeDefinition : TypeDefinition {} -extension UnionTypeDefinition : TypeDefinition {} -extension EnumTypeDefinition : TypeDefinition {} -extension InputObjectTypeDefinition : TypeDefinition {} - -public func == (lhs: TypeDefinition, rhs: TypeDefinition) -> Bool { - switch lhs { - case let l as ScalarTypeDefinition: - if let r = rhs as? ScalarTypeDefinition { - return l == r - } - case let l as ObjectTypeDefinition: - if let r = rhs as? ObjectTypeDefinition { - return l == r - } - case let l as InterfaceTypeDefinition: - if let r = rhs as? InterfaceTypeDefinition { - return l == r - } - case let l as UnionTypeDefinition: - if let r = rhs as? UnionTypeDefinition { - return l == r - } - case let l as EnumTypeDefinition: - if let r = rhs as? EnumTypeDefinition { - return l == r +public enum TypeDefinition: EnumNode, Equatable { + case scalarTypeDefinition(ScalarTypeDefinition) + case objectTypeDefinition(ObjectTypeDefinition) + case interfaceTypeDefinition(InterfaceTypeDefinition) + case unionTypeDefinition(UnionTypeDefinition) + case enumTypeDefinition(EnumTypeDefinition) + case inputObjectTypeDefinition(InputObjectTypeDefinition) + + fileprivate var underlyingNode: Node { + switch self { + case let .scalarTypeDefinition(x): + return x + case let .objectTypeDefinition(x): + return x + case let .interfaceTypeDefinition(x): + return x + case let .unionTypeDefinition(x): + return x + case let .enumTypeDefinition(x): + return x + case let .inputObjectTypeDefinition(x): + return x } - case let l as InputObjectTypeDefinition: - if let r = rhs as? InputObjectTypeDefinition { - return l == r + } + + public mutating func descend(descender: inout Descender) { + switch self { + case var .scalarTypeDefinition(x): + descender.descend(enumCase: &x) + self = .scalarTypeDefinition(x) + case var .objectTypeDefinition(x): + descender.descend(enumCase: &x) + self = .objectTypeDefinition(x) + case var .interfaceTypeDefinition(x): + descender.descend(enumCase: &x) + self = .interfaceTypeDefinition(x) + case var .unionTypeDefinition(x): + descender.descend(enumCase: &x) + self = .unionTypeDefinition(x) + case var .enumTypeDefinition(x): + descender.descend(enumCase: &x) + self = .enumTypeDefinition(x) + case var .inputObjectTypeDefinition(x): + descender.descend(enumCase: &x) + self = .inputObjectTypeDefinition(x) } - default: - return false } - - return false } -public final class ScalarTypeDefinition { - public let kind: Kind = .scalarTypeDefinition +public struct ScalarTypeDefinition { public let loc: Location? - public let description: StringValue? - public let name: Name - public let directives: [Directive] + public var description: StringValue? + public var name: Name + public var directives: [Directive] init(loc: Location? = nil, description: StringValue? = nil, name: Name, directives: [Directive] = []) { self.loc = loc @@ -1192,6 +1421,16 @@ public final class ScalarTypeDefinition { self.name = name self.directives = directives } + + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.description) + descender.descend(&self, \.name) + descender.descend(&self, \.directives) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + fatalError("TODO") + } } extension ScalarTypeDefinition : Equatable { @@ -1202,14 +1441,13 @@ extension ScalarTypeDefinition : Equatable { } } -public final class ObjectTypeDefinition { - public let kind: Kind = .objectTypeDefinition +public struct ObjectTypeDefinition { public let loc: Location? - public let description: StringValue? - public let name: Name - public let interfaces: [NamedType] - public let directives: [Directive] - public let fields: [FieldDefinition] + public var description: StringValue? + public var name: Name + public var interfaces: [NamedType] + public var directives: [Directive] + public var fields: [FieldDefinition] init(loc: Location? = nil, description: StringValue? = nil, name: Name, interfaces: [NamedType] = [], directives: [Directive] = [], fields: [FieldDefinition] = []) { self.loc = loc @@ -1219,6 +1457,18 @@ public final class ObjectTypeDefinition { self.directives = directives self.fields = fields } + + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.description) + descender.descend(&self, \.name) + descender.descend(&self, \.interfaces) + descender.descend(&self, \.directives) + descender.descend(&self, \.fields) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + fatalError("TODO") + } } extension ObjectTypeDefinition : Equatable { @@ -1231,14 +1481,13 @@ extension ObjectTypeDefinition : Equatable { } } -public final class FieldDefinition { - public let kind: Kind = .fieldDefinition +public struct FieldDefinition { public let loc: Location? - public let description: StringValue? - public let name: Name - public let arguments: [InputValueDefinition] - public let type: Type - public let directives: [Directive] + public var description: StringValue? + public var name: Name + public var arguments: [InputValueDefinition] + public var type: Type + public var directives: [Directive] init(loc: Location? = nil, description: StringValue? = nil, name: Name, arguments: [InputValueDefinition] = [], type: Type, directives: [Directive] = []) { self.loc = loc @@ -1248,6 +1497,18 @@ public final class FieldDefinition { self.type = type self.directives = directives } + + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.description) + descender.descend(&self, \.name) + descender.descend(&self, \.arguments) + descender.descend(&self, \.type) + descender.descend(&self, \.directives) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + fatalError("TODO") + } } extension FieldDefinition : Equatable { @@ -1260,14 +1521,13 @@ extension FieldDefinition : Equatable { } } -public final class InputValueDefinition { - public let kind: Kind = .inputValueDefinition +public struct InputValueDefinition { public let loc: Location? - public let description: StringValue? - public let name: Name - public let type: Type - public let defaultValue: Value? - public let directives: [Directive] + public var description: StringValue? + public var name: Name + public var type: Type + public var defaultValue: Value? + public var directives: [Directive] init(loc: Location? = nil, description: StringValue? = nil, name: Name, type: Type, defaultValue: Value? = nil, directives: [Directive] = []) { self.loc = loc @@ -1277,6 +1537,18 @@ public final class InputValueDefinition { self.defaultValue = defaultValue self.directives = directives } + + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.description) + descender.descend(&self, \.name) + descender.descend(&self, \.type) + descender.descend(&self, \.defaultValue) + descender.descend(&self, \.directives) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + fatalError("TODO") + } } extension InputValueDefinition : Equatable { @@ -1305,14 +1577,13 @@ extension InputValueDefinition : Equatable { } } -public final class InterfaceTypeDefinition { - public let kind: Kind = .interfaceTypeDefinition +public struct InterfaceTypeDefinition { public let loc: Location? - public let description: StringValue? - public let name: Name - public let interfaces: [NamedType] - public let directives: [Directive] - public let fields: [FieldDefinition] + public var description: StringValue? + public var name: Name + public var interfaces: [NamedType] + public var directives: [Directive] + public var fields: [FieldDefinition] init( loc: Location? = nil, @@ -1329,6 +1600,18 @@ public final class InterfaceTypeDefinition { self.directives = directives self.fields = fields } + + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.description) + descender.descend(&self, \.name) + descender.descend(&self, \.interfaces) + descender.descend(&self, \.directives) + descender.descend(&self, \.fields) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + fatalError("TODO") + } } extension InterfaceTypeDefinition : Equatable { @@ -1340,13 +1623,12 @@ extension InterfaceTypeDefinition : Equatable { } } -public final class UnionTypeDefinition { - public let kind: Kind = .unionTypeDefinition +public struct UnionTypeDefinition { public let loc: Location? - public let description: StringValue? - public let name: Name - public let directives: [Directive] - public let types: [NamedType] + public var description: StringValue? + public var name: Name + public var directives: [Directive] + public var types: [NamedType] init(loc: Location? = nil, description: StringValue? = nil, name: Name, directives: [Directive] = [], types: [NamedType]) { self.loc = loc @@ -1355,6 +1637,17 @@ public final class UnionTypeDefinition { self.directives = directives self.types = types } + + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.description) + descender.descend(&self, \.name) + descender.descend(&self, \.directives) + descender.descend(&self, \.types) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + fatalError("TODO") + } } extension UnionTypeDefinition : Equatable { @@ -1366,13 +1659,12 @@ extension UnionTypeDefinition : Equatable { } } -public final class EnumTypeDefinition { - public let kind: Kind = .enumTypeDefinition +public struct EnumTypeDefinition { public let loc: Location? - public let description: StringValue? - public let name: Name - public let directives: [Directive] - public let values: [EnumValueDefinition] + public var description: StringValue? + public var name: Name + public var directives: [Directive] + public var values: [EnumValueDefinition] init(loc: Location? = nil, description: StringValue? = nil, name: Name, directives: [Directive] = [], values: [EnumValueDefinition]) { self.loc = loc @@ -1381,6 +1673,17 @@ public final class EnumTypeDefinition { self.directives = directives self.values = values } + + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.description) + descender.descend(&self, \.name) + descender.descend(&self, \.directives) + descender.descend(&self, \.values) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + fatalError("TODO") + } } extension EnumTypeDefinition : Equatable { @@ -1392,12 +1695,11 @@ extension EnumTypeDefinition : Equatable { } } -public final class EnumValueDefinition { - public let kind: Kind = .enumValueDefinition +public struct EnumValueDefinition { public let loc: Location? - public let description: StringValue? - public let name: Name - public let directives: [Directive] + public var description: StringValue? + public var name: Name + public var directives: [Directive] init(loc: Location? = nil, description: StringValue? = nil, name: Name, directives: [Directive] = []) { self.loc = loc @@ -1405,6 +1707,16 @@ public final class EnumValueDefinition { self.name = name self.directives = directives } + + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.description) + descender.descend(&self, \.name) + descender.descend(&self, \.directives) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + fatalError("TODO") + } } extension EnumValueDefinition : Equatable { @@ -1415,13 +1727,12 @@ extension EnumValueDefinition : Equatable { } } -public final class InputObjectTypeDefinition { - public let kind: Kind = .inputObjectTypeDefinition +public struct InputObjectTypeDefinition { public let loc: Location? - public let description: StringValue? - public let name: Name - public let directives: [Directive] - public let fields: [InputValueDefinition] + public var description: StringValue? + public var name: Name + public var directives: [Directive] + public var fields: [InputValueDefinition] init(loc: Location? = nil, description: StringValue? = nil, name: Name, directives: [Directive] = [], fields: [InputValueDefinition]) { self.loc = loc @@ -1430,6 +1741,17 @@ public final class InputObjectTypeDefinition { self.directives = directives self.fields = fields } + + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.description) + descender.descend(&self, \.name) + descender.descend(&self, \.directives) + descender.descend(&self, \.fields) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + fatalError("TODO") + } } extension InputObjectTypeDefinition : Equatable { @@ -1441,15 +1763,22 @@ extension InputObjectTypeDefinition : Equatable { } } -public final class TypeExtensionDefinition { - public let kind: Kind = .typeExtensionDefinition +public struct TypeExtensionDefinition { public let loc: Location? - public let definition: ObjectTypeDefinition + public var definition: ObjectTypeDefinition init(loc: Location? = nil, definition: ObjectTypeDefinition) { self.loc = loc self.definition = definition } + + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.definition) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + fatalError("TODO") + } } extension TypeExtensionDefinition : Equatable { @@ -1458,13 +1787,12 @@ extension TypeExtensionDefinition : Equatable { } } -public final class DirectiveDefinition { - public let kind: Kind = .directiveDefinition +public struct DirectiveDefinition { public let loc: Location? - public let description: StringValue? - public let name: Name - public let arguments: [InputValueDefinition] - public let locations: [Name] + public var description: StringValue? + public var name: Name + public var arguments: [InputValueDefinition] + public var locations: [Name] init(loc: Location? = nil, description: StringValue? = nil, name: Name, arguments: [InputValueDefinition] = [], locations: [Name]) { self.loc = loc @@ -1473,6 +1801,17 @@ public final class DirectiveDefinition { self.arguments = arguments self.locations = locations } + + public mutating func descend(descender: inout Descender) { + descender.descend(&self, \.description) + descender.descend(&self, \.name) + descender.descend(&self, \.arguments) + descender.descend(&self, \.locations) + } + + public func write(to target: inout Target) where Target : TextOutputStream { + fatalError("TODO") + } } extension DirectiveDefinition : Equatable { diff --git a/Sources/GraphQL/Language/Kinds.swift b/Sources/GraphQL/Language/Kinds.swift deleted file mode 100644 index a1c3e1c5..00000000 --- a/Sources/GraphQL/Language/Kinds.swift +++ /dev/null @@ -1,39 +0,0 @@ -public enum Kind { - case name - case document - case operationDefinition - case variableDefinition - case variable - case selectionSet - case field - case argument - case fragmentSpread - case inlineFragment - case fragmentDefinition - case intValue - case floatValue - case stringValue - case booleanValue - case nullValue - case enumValue - case listValue - case objectValue - case objectField - case directive - case namedType - case listType - case nonNullType - case schemaDefinition - case operationTypeDefinition - case scalarTypeDefinition - case objectTypeDefinition - case fieldDefinition - case inputValueDefinition - case interfaceTypeDefinition - case unionTypeDefinition - case enumTypeDefinition - case enumValueDefinition - case inputObjectTypeDefinition - case typeExtensionDefinition - case directiveDefinition -} diff --git a/Sources/GraphQL/Language/Parser.swift b/Sources/GraphQL/Language/Parser.swift index 7c19ed71..1b382f85 100644 --- a/Sources/GraphQL/Language/Parser.swift +++ b/Sources/GraphQL/Language/Parser.swift @@ -168,24 +168,24 @@ func parseDocument(lexer: Lexer) throws -> Document { */ func parseDefinition(lexer: Lexer) throws -> Definition { if peek(lexer: lexer, kind: .openingBrace) { - return try parseOperationDefinition(lexer: lexer) + return .executableDefinition(.operation(try parseOperationDefinition(lexer: lexer))) } if peek(lexer: lexer, kind: .name) { switch lexer.token.value! { // Note: subscription is an experimental non-spec addition. case "query", "mutation", "subscription": - return try parseOperationDefinition(lexer: lexer); + return .executableDefinition(.operation(try parseOperationDefinition(lexer: lexer))) case "fragment": - return try parseFragmentDefinition(lexer: lexer) + return .executableDefinition(.fragment(try parseFragmentDefinition(lexer: lexer))) // Note: the Type System IDL is an experimental non-spec addition. case "schema", "scalar", "type", "interface", "union", "enum", "input", "extend", "directive": - return try parseTypeSystemDefinition(lexer: lexer) + return .typeSystemDefinitionOrExtension(.typeSystemDefinition(try parseTypeSystemDefinition(lexer: lexer))) default: break } } else if peekDescription(lexer: lexer) { - return try parseTypeSystemDefinition(lexer: lexer) + return .typeSystemDefinitionOrExtension(.typeSystemDefinition(try parseTypeSystemDefinition(lexer: lexer))) } throw unexpected(lexer: lexer) @@ -318,7 +318,7 @@ func parseSelection(lexer: Lexer) throws -> Selection { * * Alias : Name : */ -func parseField(lexer: Lexer) throws -> Field { +func parseField(lexer: Lexer) throws -> Selection { let start = lexer.token; let nameOrAlias = try parseName(lexer: lexer) @@ -333,13 +333,15 @@ func parseField(lexer: Lexer) throws -> Field { name = nameOrAlias } - return Field( - loc: loc(lexer: lexer, startToken: start), - alias: alias, - name: name, - arguments: try parseArguments(lexer: lexer), - directives: try parseDirectives(lexer: lexer), - selectionSet: peek(lexer: lexer, kind: .openingBrace) ? try parseSelectionSet(lexer: lexer) : nil + return .field( + Field( + loc: loc(lexer: lexer, startToken: start), + alias: alias, + name: name, + arguments: try parseArguments(lexer: lexer), + directives: try parseDirectives(lexer: lexer), + selectionSet: peek(lexer: lexer, kind: .openingBrace) ? try parseSelectionSet(lexer: lexer) : nil + ) ) } @@ -378,14 +380,16 @@ func parseArgument(lexer: Lexer) throws -> Argument { * * InlineFragment : ... TypeCondition? Directives? SelectionSet */ -func parseFragment(lexer: Lexer) throws -> Fragment { +func parseFragment(lexer: Lexer) throws -> Selection { let start = lexer.token try expect(lexer: lexer, kind: .spread) if peek(lexer: lexer, kind: .name) && lexer.token.value != "on" { - return FragmentSpread( - loc: loc(lexer: lexer, startToken: start), - name: try parseFragmentName(lexer: lexer), - directives: try parseDirectives(lexer: lexer) + return .fragmentSpread( + FragmentSpread( + loc: loc(lexer: lexer, startToken: start), + name: try parseFragmentName(lexer: lexer), + directives: try parseDirectives(lexer: lexer) + ) ) } @@ -395,11 +399,13 @@ func parseFragment(lexer: Lexer) throws -> Fragment { try lexer.advance() typeCondition = try parseNamedType(lexer: lexer) } - return InlineFragment( - loc: loc(lexer: lexer, startToken: start), - typeCondition: typeCondition, - directives: try parseDirectives(lexer: lexer), - selectionSet: try parseSelectionSet(lexer: lexer) + return .inlineFragment( + InlineFragment( + loc: loc(lexer: lexer, startToken: start), + typeCondition: typeCondition, + directives: try parseDirectives(lexer: lexer), + selectionSet: try parseSelectionSet(lexer: lexer) + ) ) } @@ -453,45 +459,45 @@ func parseValueLiteral(lexer: Lexer, isConst: Bool) throws -> Value { let token = lexer.token switch token.kind { case .openingBracket: - return try parseList(lexer: lexer, isConst: isConst) + return .listValue(try parseList(lexer: lexer, isConst: isConst)) case .openingBrace: - return try parseObject(lexer: lexer, isConst: isConst) + return .objectValue(try parseObject(lexer: lexer, isConst: isConst)) case .int: try lexer.advance() - return IntValue( + return .intValue(IntValue( loc: loc(lexer: lexer, startToken: token), value: token.value! - ) + )) case .float: try lexer.advance() - return FloatValue( + return .floatValue(FloatValue( loc: loc(lexer: lexer, startToken: token), value: token.value! - ) + )) case .string, .blockstring: - return try parseStringLiteral(lexer: lexer, startToken: token) + return .stringValue(try parseStringLiteral(lexer: lexer, startToken: token)) case .name: if (token.value == "true" || token.value == "false") { try lexer.advance() - return BooleanValue( + return .booleanValue(BooleanValue( loc: loc(lexer: lexer, startToken: token), value: token.value == "true" - ) + )) } else if token.value == "null" { try lexer.advance() - return NullValue( + return .nullValue(NullValue( loc: loc(lexer: lexer, startToken: token) - ) + )) } else { try lexer.advance() - return EnumValue( + return .enumValue(EnumValue( loc: loc(lexer: lexer, startToken: token), value: token.value! - ) + )) } case .dollar: if !isConst { - return try parseVariable(lexer: lexer) + return .variable(try parseVariable(lexer: lexer)) } default: break @@ -616,19 +622,23 @@ func parseTypeReference(lexer: Lexer) throws -> Type { if try skip(lexer: lexer, kind: .openingBracket) { type = try parseTypeReference(lexer: lexer) try expect(lexer: lexer, kind: .closingBracket) - type = ListType( + type = .listType(ListType( loc: loc(lexer: lexer, startToken: start), type: type - ) + )) } else { - type = try parseNamedType(lexer: lexer) + type = .namedType(try parseNamedType(lexer: lexer)) } if try skip(lexer: lexer, kind: .bang) { - return NonNullType( - loc: loc(lexer: lexer, startToken: start), - type: type as! NonNullableType - ) + switch type { + case let .namedType(x): + return .nonNullType(.namedType(x)) + case let .listType(x): + return .nonNullType(.listType(x)) + default: + fatalError() + } } return type @@ -666,22 +676,31 @@ func parseTypeSystemDefinition(lexer: Lexer) throws -> TypeSystemDefinition { let keywordToken = peekDescription(lexer: lexer) ? try lexer.lookahead() : lexer.token - + if keywordToken.kind == .name { switch keywordToken.value! { - case "schema": return try parseSchemaDefinition(lexer: lexer); - case "scalar": return try parseScalarTypeDefinition(lexer: lexer); - case "type": return try parseObjectTypeDefinition(lexer: lexer); - case "interface": return try parseInterfaceTypeDefinition(lexer: lexer); - case "union": return try parseUnionTypeDefinition(lexer: lexer); - case "enum": return try parseEnumTypeDefinition(lexer: lexer); - case "input": return try parseInputObjectTypeDefinition(lexer: lexer); - case "extend": return try parseTypeExtensionDefinition(lexer: lexer); - case "directive": return try parseDirectiveDefinition(lexer: lexer); - default: break + case "schema": + return .schemaDefinition(try parseSchemaDefinition(lexer: lexer)) + case "scalar": + return .typeDefinition(.scalarTypeDefinition(try parseScalarTypeDefinition(lexer: lexer))) + case "type": + return .typeDefinition(.objectTypeDefinition(try parseObjectTypeDefinition(lexer: lexer))) + case "interface": + return .typeDefinition(.interfaceTypeDefinition(try parseInterfaceTypeDefinition(lexer: lexer))) + case "union": + return .typeDefinition(.unionTypeDefinition(try parseUnionTypeDefinition(lexer: lexer))) + case "enum": + return .typeDefinition(.enumTypeDefinition(try parseEnumTypeDefinition(lexer: lexer))) + case "input": + return .typeDefinition(.inputObjectTypeDefinition(try parseInputObjectTypeDefinition(lexer: lexer))) +// case "extend": +// return try parseTypeExtensionDefinition(lexer: lexer); + case "directive": + return .directiveDefinition(try parseDirectiveDefinition(lexer: lexer)) + default: + break } } - throw unexpected(lexer: lexer, atToken: keywordToken) } diff --git a/Sources/GraphQL/Language/Visitor.swift b/Sources/GraphQL/Language/Visitor.swift index 0057c556..5df2015f 100644 --- a/Sources/GraphQL/Language/Visitor.swift +++ b/Sources/GraphQL/Language/Visitor.swift @@ -1,50 +1,118 @@ -let QueryDocumentKeys: [Kind: [String]] = [ - .name: [], - - .document: ["definitions"], - .operationDefinition: ["name", "variableDefinitions", "directives", "selectionSet"], - .variableDefinition: ["variable", "type", "defaultValue"], - .variable: ["name"], - .selectionSet: ["selections"], - .field: ["alias", "name", "arguments", "directives", "selectionSet"], - .argument: ["name", "value"], - - .fragmentSpread: ["name", "directives"], - .inlineFragment: ["typeCondition", "directives", "selectionSet"], - .fragmentDefinition: ["name", "typeCondition", "directives", "selectionSet"], - - .intValue: [], - .floatValue: [], - .stringValue: [], - .booleanValue: [], - .enumValue: [], - .listValue: ["values"], - .objectValue: ["fields"], - .objectField: ["name", "value"], - - .directive: ["name", "arguments"], - - .namedType: ["name"], - .listType: ["type"], - .nonNullType: ["type"], - - .schemaDefinition: ["directives", "operationTypes"], - .operationTypeDefinition: ["type"], - - .scalarTypeDefinition: ["name", "directives"], - .objectTypeDefinition: ["name", "interfaces", "directives", "fields"], - .fieldDefinition: ["name", "arguments", "type", "directives"], - .inputValueDefinition: ["name", "type", "defaultValue", "directives"], - .interfaceTypeDefinition: ["name", "interfaces", "directives", "fields"], - .unionTypeDefinition: ["name", "directives", "types"], - .enumTypeDefinition: ["name", "directives", "values"], - .enumValueDefinition: ["name", "directives"], - .inputObjectTypeDefinition: ["name", "directives", "fields"], - - .typeExtensionDefinition: ["definition"], - - .directiveDefinition: ["name", "arguments", "locations"], -] +public struct Descender { + + private let visitor: Visitor + + fileprivate var parentStack: [VisitorParent] = [] + private var path: [AnyKeyPath] = [] + private var isBreaking = false + + /** + The meat of the traversal + + - Returns + `true` if node should be removed + */ + fileprivate mutating func go(node: inout H, key: AnyKeyPath?) -> Bool { + if isBreaking { return false } + let parent = parentStack.last + let newPath: [AnyKeyPath] + if let key = key { + newPath = path + [key] + } else { + newPath = path + } + + var shouldRemove = false + + switch visitor.enter(node: node, key: key, parent: parent, path: newPath, ancestors: parentStack) { + case .skip: + return false + case .continue: + break + case let .node(newNode): + if let newNode = newNode { + node = newNode + } else { + // TODO: Should we still be traversing the children here? + shouldRemove = true + } + case .break: + isBreaking = true + return false + } + parentStack.append(.node(node)) + if let key = key { + path.append(key) + } + node.descend(descender: &self) + if key != nil { + path.removeLast() + } + parentStack.removeLast() + + if isBreaking { return shouldRemove } + + switch visitor.leave(node: node, key: key, parent: parent, path: newPath, ancestors: parentStack) { + case .skip, .continue: + return shouldRemove + case let .node(newNode): + if let newNode = newNode { + node = newNode + return false + } else { + // TODO: Should we still be traversing the children here? + return true + } + case .break: + isBreaking = true + return shouldRemove + } + } + + + mutating func descend(_ node: inout T, _ kp: WritableKeyPath) { + if go(node: &node[keyPath: kp], key: kp) { + fatalError("Can't remove this node") + } + } + mutating func descend(_ node: inout T, _ kp: WritableKeyPath) { + guard var oldVal = node[keyPath: kp] else { + return + } + if go(node: &oldVal, key: kp) { + node[keyPath: kp] = nil + } else { + node[keyPath: kp] = oldVal + } + } + mutating func descend(_ node: inout T, _ kp: WritableKeyPath) { + var toRemove: [Int] = [] + + parentStack.append(.array(node[keyPath: kp])) + + var i = node[keyPath: kp].startIndex + while i != node[keyPath: kp].endIndex { + if go(node: &node[keyPath: kp][i], key: \[U].[i]) { + toRemove.append(i) + } + i = node[keyPath: kp].index(after: i) + } + parentStack.removeLast() + toRemove.forEach { node[keyPath: kp].remove(at: $0) } + } + + mutating func descend(enumCase: inout T) { + if go(node: &enumCase, key: nil) { + //TODO: figure this out + fatalError("What happens here?") + } + } + + fileprivate init(visitor: Visitor) { + self.visitor = visitor + } +} + /** * visit() will walk through an AST using a depth first traversal, calling @@ -60,306 +128,566 @@ let QueryDocumentKeys: [Kind: [String]] = [ * a new version of the AST with the changes applied will be returned from the * visit function. * - * let editedAST = visit(ast, Visitor( - * enter: { node, key, parent, path, ancestors in + * struct MyVisitor: Visitor { + * func enter(inlineFragment: InlineFragment, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { * return - * .continue: no action - * .skip: skip visiting this node - * .break: stop visiting altogether - * .node(nil): delete this node - * .node(newNode): replace this node with the returned value - * }, - * leave: { node, key, parent, path, ancestors in + * .continue // no action + * .skip // skip visiting this node + * .break // stop visiting altogether + * .node(nil) // delete this node + * .node(newNode) // replace this node with the returned value + * } + * func leave(inlineFragment: InlineFragment, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { * return - * .continue: no action - * .skip: no action - * .break: stop visiting altogether - * .node(nil): delete this node - * .node(newNode): replace this node with the returned value + * .continue // no action + * .skip // skip visiting this node + * .break // stop visiting altogether + * .node(nil) // delete this node + * .node(newNode) // replace this node with the returned value * } - * )) + * } + * let editedAST = visit(ast, visitor: MyVisitor()) + * */ @discardableResult -func visit(root: Node, visitor: Visitor, keyMap: [Kind: [String]] = [:]) -> Node { - let visitorKeys = keyMap.isEmpty ? QueryDocumentKeys : keyMap - - var stack: Stack? = nil - var inArray = false - var keys: [IndexPathElement] = ["root"] - var index: Int = -1 - var edits: [(key: IndexPathElement, node: Node)] = [] - var parent: NodeResult? = nil - var path: [IndexPathElement] = [] - var ancestors: [NodeResult] = [] - var newRoot = root - - repeat { - index += 1 - let isLeaving = index == keys.count - var key: IndexPathElement? = nil - var node: NodeResult? = nil - let isEdited = isLeaving && !edits.isEmpty - - if !isLeaving { - key = parent != nil ? inArray ? index : keys[index] : nil - - if let parent = parent { - switch parent { - case .node(let parent): - node = parent.get(key: key!.keyValue!) - case .array(let parent): - node = .node(parent[key!.indexValue!]) - } - } else { - node = .node(newRoot) - } +public func visit(root: T, visitor: V) -> T { + var descender = Descender(visitor: visitor) + + var result = root + if descender.go(node: &result, key: nil) { + fatalError("Root node in the AST was removed") + } + return result +} - if node == nil { - continue - } +public enum VisitorParent { + case node(Node) + case array([Node]) - if parent != nil { - path.append(key!) - } - } else { - key = ancestors.isEmpty ? nil : path.popLast() - node = parent - parent = ancestors.popLast() - - if isEdited { -// if inArray { -// node = node.slice() -// } else { -// let clone = node -// node = clone -// } -// -// var editOffset = 0 -// -// for ii in 0.. VisitResult + func leave(name: Name, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult - if case .node(let n) = node! { - if !isLeaving { - result = visitor.enter( - node: n, - key: key, - parent: parent, - path: path, - ancestors: ancestors - ) - } else { - result = visitor.leave( - node: n, - key: key, - parent: parent, - path: path, - ancestors: ancestors - ) - } + func enter(document: Document, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(document: Document, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + + func enter(definition: Definition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(definition: Definition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + + func enter(executableDefinition: ExecutableDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(executableDefinition: ExecutableDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult - if case .break = result { - break - } + func enter(operationDefinition: OperationDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(operationDefinition: OperationDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult - if case .skip = result, !isLeaving { - _ = path.popLast() - continue - } else if case .node(let n) = result { - edits.append((key!, n!)) - - if !isLeaving { - if let n = n { - node = .node(n) - } else { - _ = path.popLast() - continue - } - } - } - } + func enter(variableDefinition: VariableDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(variableDefinition: VariableDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult -// if case .continue = result, isEdited { -// edits.append((key!, node!)) -// } + func enter(variable: Variable, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(variable: Variable, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult - if !isLeaving { - stack = Stack(index: index, keys: keys, edits: edits, inArray: inArray, prev: stack) - inArray = node!.isArray + func enter(selectionSet: SelectionSet, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(selectionSet: SelectionSet, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + + func enter(selection: Selection, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(selection: Selection, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult - switch node! { - case .node(let node): - keys = visitorKeys[node.kind] ?? [] - case .array(let array): - keys = array.map({ _ in "root" }) - } + func enter(field: Field, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(field: Field, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult - index = -1 - edits = [] + func enter(argument: Argument, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(argument: Argument, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult - if let parent = parent { - ancestors.append(parent) - } + func enter(fragmentSpread: FragmentSpread, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(fragmentSpread: FragmentSpread, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult - parent = node - } - } while stack != nil + func enter(inlineFragment: InlineFragment, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(inlineFragment: InlineFragment, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult - if !edits.isEmpty { - newRoot = edits[edits.count - 1].node - } + func enter(fragmentDefinition: FragmentDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(fragmentDefinition: FragmentDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + + func enter(value: Value, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(value: Value, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult - return newRoot -} + func enter(intValue: IntValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(intValue: IntValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult -final class Stack { - let index: Int - let keys: [IndexPathElement] - let edits: [(key: IndexPathElement, node: Node)] - let inArray: Bool - let prev: Stack? - - init(index: Int, keys: [IndexPathElement], edits: [(key: IndexPathElement, node: Node)], inArray: Bool, prev: Stack?) { - self.index = index - self.keys = keys - self.edits = edits - self.inArray = inArray - self.prev = prev - } -} + func enter(floatValue: FloatValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(floatValue: FloatValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult -/** - * Creates a new visitor instance which delegates to many visitors to run in - * parallel. Each visitor will be visited for each node before moving on. - * - * If a prior visitor edits a node, no following visitors will see that node. - */ -func visitInParallel(visitors: [Visitor]) -> Visitor { - var skipping: [Node?] = [Node?](repeating: nil, count: visitors.count) - - return Visitor( - enter: { node, key, parent, path, ancestors in - for i in 0.. VisitResult + func leave(stringValue: StringValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult - return .continue - }, - leave: { node, key, parent, path, ancestors in - for i in 0.. VisitResult + func leave(booleanValue: BooleanValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + + func enter(nullValue: NullValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(nullValue: NullValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult - return .continue - } - ) -} + func enter(enumValue: EnumValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(enumValue: EnumValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult -enum VisitResult { - case `continue` - case skip - case `break` - case node(Node?) + func enter(listValue: ListValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(listValue: ListValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult - var isContinue: Bool { - if case .continue = self { - return true - } - return false - } + func enter(objectValue: ObjectValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(objectValue: ObjectValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + + func enter(objectField: ObjectField, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(objectField: ObjectField, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + + func enter(directive: Directive, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(directive: Directive, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + + func enter(namedType: NamedType, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(namedType: NamedType, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + + func enter(listType: ListType, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(listType: ListType, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + + func enter(nonNullType: NonNullType, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(nonNullType: NonNullType, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + + func enter(schemaDefinition: SchemaDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(schemaDefinition: SchemaDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + + func enter(operationTypeDefinition: OperationTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(operationTypeDefinition: OperationTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + + func enter(scalarTypeDefinition: ScalarTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(scalarTypeDefinition: ScalarTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + + func enter(objectTypeDefinition: ObjectTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(objectTypeDefinition: ObjectTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + + func enter(fieldDefinition: FieldDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(fieldDefinition: FieldDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + + func enter(inputValueDefinition: InputValueDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(inputValueDefinition: InputValueDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + + func enter(interfaceTypeDefinition: InterfaceTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(interfaceTypeDefinition: InterfaceTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + + func enter(unionTypeDefinition: UnionTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(unionTypeDefinition: UnionTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + + func enter(enumTypeDefinition: EnumTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(enumTypeDefinition: EnumTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + + func enter(enumValueDefinition: EnumValueDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(enumValueDefinition: EnumValueDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + + func enter(inputObjectTypeDefinition: InputObjectTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(inputObjectTypeDefinition: InputObjectTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + + func enter(typeExtensionDefinition: TypeExtensionDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(typeExtensionDefinition: TypeExtensionDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + + func enter(directiveDefinition: DirectiveDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + func leave(directiveDefinition: DirectiveDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult + + func enter(node: T, key: AnyKeyPath?, parent: VisitorParent?, path: [AnyKeyPath], ancestors: [VisitorParent]) -> VisitResult + func leave(node: T, key: AnyKeyPath?, parent: VisitorParent?, path: [AnyKeyPath], ancestors: [VisitorParent]) -> VisitResult } -struct Visitor { - typealias Visit = (Node, IndexPathElement?, NodeResult?, [IndexPathElement], [NodeResult]) -> VisitResult - private let enter: Visit - private let leave: Visit +public extension Visitor { + func enter(name: Name, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(name: Name, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(document: Document, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(document: Document, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(definition: Definition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(definition: Definition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(executableDefinition: ExecutableDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(executableDefinition: ExecutableDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(operationDefinition: OperationDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(operationDefinition: OperationDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } - init(enter: @escaping Visit = ignore, leave: @escaping Visit = ignore) { - self.enter = enter - self.leave = leave - } + func enter(variableDefinition: VariableDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(variableDefinition: VariableDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(variable: Variable, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(variable: Variable, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(selectionSet: SelectionSet, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(selectionSet: SelectionSet, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(selection: Selection, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(selection: Selection, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } - func enter(node: Node, key: IndexPathElement?, parent: NodeResult?, path: [IndexPathElement], ancestors: [NodeResult]) -> VisitResult { - return enter(node, key, parent, path, ancestors) + func enter(field: Field, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(field: Field, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(argument: Argument, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(argument: Argument, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(fragmentSpread: FragmentSpread, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(fragmentSpread: FragmentSpread, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(inlineFragment: InlineFragment, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(inlineFragment: InlineFragment, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(fragmentDefinition: FragmentDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(fragmentDefinition: FragmentDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(value: Value, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(value: Value, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(intValue: IntValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(intValue: IntValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(floatValue: FloatValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(floatValue: FloatValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(stringValue: StringValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(stringValue: StringValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(booleanValue: BooleanValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(booleanValue: BooleanValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(nullValue: NullValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(nullValue: NullValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(enumValue: EnumValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(enumValue: EnumValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(listValue: ListValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(listValue: ListValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(objectValue: ObjectValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(objectValue: ObjectValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(objectField: ObjectField, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(objectField: ObjectField, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(directive: Directive, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(directive: Directive, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(type: Type, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(type: Type, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(namedType: NamedType, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(namedType: NamedType, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(listType: ListType, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(listType: ListType, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(nonNullType: NonNullType, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(nonNullType: NonNullType, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(schemaDefinition: SchemaDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(schemaDefinition: SchemaDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(operationTypeDefinition: OperationTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(operationTypeDefinition: OperationTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(scalarTypeDefinition: ScalarTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(scalarTypeDefinition: ScalarTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(objectTypeDefinition: ObjectTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(objectTypeDefinition: ObjectTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(fieldDefinition: FieldDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(fieldDefinition: FieldDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(inputValueDefinition: InputValueDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(inputValueDefinition: InputValueDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(interfaceTypeDefinition: InterfaceTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(interfaceTypeDefinition: InterfaceTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(unionTypeDefinition: UnionTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(unionTypeDefinition: UnionTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(enumTypeDefinition: EnumTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(enumTypeDefinition: EnumTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(enumValueDefinition: EnumValueDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(enumValueDefinition: EnumValueDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(inputObjectTypeDefinition: InputObjectTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(inputObjectTypeDefinition: InputObjectTypeDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(typeExtensionDefinition: TypeExtensionDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(typeExtensionDefinition: TypeExtensionDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(directiveDefinition: DirectiveDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + func leave(directiveDefinition: DirectiveDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { .continue } + + func enter(node: T, key: AnyKeyPath?, parent: VisitorParent?, path: [AnyKeyPath], ancestors: [VisitorParent]) -> VisitResult { + switch node { + case let name as Name: + return enter(name: name, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let document as Document: + return enter(document: document, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let definition as Definition: + return enter(definition: definition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let executableDefinition as ExecutableDefinition: + return enter(executableDefinition: executableDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let operationDefinition as OperationDefinition: + return enter(operationDefinition: operationDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let variableDefinition as VariableDefinition: + return enter(variableDefinition: variableDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let variable as Variable: + return enter(variable: variable, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let selectionSet as SelectionSet: + return enter(selectionSet: selectionSet, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let selection as Selection: + return enter(selection: selection, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let field as Field: + return enter(field: field, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let argument as Argument: + return enter(argument: argument, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let fragmentSpread as FragmentSpread: + return enter(fragmentSpread: fragmentSpread, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let inlineFragment as InlineFragment: + return enter(inlineFragment: inlineFragment, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let fragmentDefinition as FragmentDefinition: + return enter(fragmentDefinition: fragmentDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let value as Value: + return enter(value: value, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let intValue as IntValue: + return enter(intValue: intValue, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let floatValue as FloatValue: + return enter(floatValue: floatValue, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let stringValue as StringValue: + return enter(stringValue: stringValue, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let booleanValue as BooleanValue: + return enter(booleanValue: booleanValue, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let nullValue as NullValue: + return enter(nullValue: nullValue, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let enumValue as EnumValue: + return enter(enumValue: enumValue, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let listValue as ListValue: + return enter(listValue: listValue, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let objectValue as ObjectValue: + return enter(objectValue: objectValue, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let objectField as ObjectField: + return enter(objectField: objectField, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let directive as Directive: + return enter(directive: directive, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let type as Type: + return enter(type: type, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let namedType as NamedType: + return enter(namedType: namedType, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let listType as ListType: + return enter(listType: listType, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let nonNullType as NonNullType: + return enter(nonNullType: nonNullType, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let schemaDefinition as SchemaDefinition: + return enter(schemaDefinition: schemaDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let operationTypeDefinition as OperationTypeDefinition: + return enter(operationTypeDefinition: operationTypeDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let scalarTypeDefinition as ScalarTypeDefinition: + return enter(scalarTypeDefinition: scalarTypeDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let objectTypeDefinition as ObjectTypeDefinition: + return enter(objectTypeDefinition: objectTypeDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let fieldDefinition as FieldDefinition: + return enter(fieldDefinition: fieldDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let inputValueDefinition as InputValueDefinition: + return enter(inputValueDefinition: inputValueDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let interfaceTypeDefinition as InterfaceTypeDefinition: + return enter(interfaceTypeDefinition: interfaceTypeDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let unionTypeDefinition as UnionTypeDefinition: + return enter(unionTypeDefinition: unionTypeDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let enumTypeDefinition as EnumTypeDefinition: + return enter(enumTypeDefinition: enumTypeDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let enumValueDefinition as EnumValueDefinition: + return enter(enumValueDefinition: enumValueDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let inputObjectTypeDefinition as InputObjectTypeDefinition: + return enter(inputObjectTypeDefinition: inputObjectTypeDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let typeExtensionDefinition as TypeExtensionDefinition: + return enter(typeExtensionDefinition: typeExtensionDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let directiveDefinition as DirectiveDefinition: + return enter(directiveDefinition: directiveDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + default: + fatalError() + } } - func leave(node: Node, key: IndexPathElement?, parent: NodeResult?, path: [IndexPathElement], ancestors: [NodeResult]) -> VisitResult { - return leave(node, key, parent, path, ancestors) + func leave(node: T, key: AnyKeyPath?, parent: VisitorParent?, path: [AnyKeyPath], ancestors: [VisitorParent]) -> VisitResult { + switch node { + case let name as Name: + return leave(name: name, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let document as Document: + return leave(document: document, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let definition as Definition: + return leave(definition: definition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let executableDefinition as ExecutableDefinition: + return leave(executableDefinition: executableDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let operationDefinition as OperationDefinition: + return leave(operationDefinition: operationDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let variableDefinition as VariableDefinition: + return leave(variableDefinition: variableDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let variable as Variable: + return leave(variable: variable, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let selectionSet as SelectionSet: + return leave(selectionSet: selectionSet, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let selection as Selection: + return leave(selection: selection, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let field as Field: + return leave(field: field, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let argument as Argument: + return leave(argument: argument, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let fragmentSpread as FragmentSpread: + return leave(fragmentSpread: fragmentSpread, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let inlineFragment as InlineFragment: + return leave(inlineFragment: inlineFragment, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let fragmentDefinition as FragmentDefinition: + return leave(fragmentDefinition: fragmentDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let value as Value: + return leave(value: value, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let intValue as IntValue: + return leave(intValue: intValue, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let floatValue as FloatValue: + return leave(floatValue: floatValue, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let stringValue as StringValue: + return leave(stringValue: stringValue, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let booleanValue as BooleanValue: + return leave(booleanValue: booleanValue, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let nullValue as NullValue: + return leave(nullValue: nullValue, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let enumValue as EnumValue: + return leave(enumValue: enumValue, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let listValue as ListValue: + return leave(listValue: listValue, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let objectValue as ObjectValue: + return leave(objectValue: objectValue, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let objectField as ObjectField: + return leave(objectField: objectField, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let directive as Directive: + return leave(directive: directive, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let type as Type: + return leave(type: type, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let namedType as NamedType: + return leave(namedType: namedType, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let listType as ListType: + return leave(listType: listType, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let nonNullType as NonNullType: + return leave(nonNullType: nonNullType, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let schemaDefinition as SchemaDefinition: + return leave(schemaDefinition: schemaDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let operationTypeDefinition as OperationTypeDefinition: + return leave(operationTypeDefinition: operationTypeDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let scalarTypeDefinition as ScalarTypeDefinition: + return leave(scalarTypeDefinition: scalarTypeDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let objectTypeDefinition as ObjectTypeDefinition: + return leave(objectTypeDefinition: objectTypeDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let fieldDefinition as FieldDefinition: + return leave(fieldDefinition: fieldDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let inputValueDefinition as InputValueDefinition: + return leave(inputValueDefinition: inputValueDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let interfaceTypeDefinition as InterfaceTypeDefinition: + return leave(interfaceTypeDefinition: interfaceTypeDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let unionTypeDefinition as UnionTypeDefinition: + return leave(unionTypeDefinition: unionTypeDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let enumTypeDefinition as EnumTypeDefinition: + return leave(enumTypeDefinition: enumTypeDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let enumValueDefinition as EnumValueDefinition: + return leave(enumValueDefinition: enumValueDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let inputObjectTypeDefinition as InputObjectTypeDefinition: + return leave(inputObjectTypeDefinition: inputObjectTypeDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let typeExtensionDefinition as TypeExtensionDefinition: + return leave(typeExtensionDefinition: typeExtensionDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + case let directiveDefinition as DirectiveDefinition: + return leave(directiveDefinition: directiveDefinition, key: key, parent: parent, ancestors: ancestors) as! VisitResult + default: + fatalError() + } } } -func ignore(node: Node, key: IndexPathElement?, parent: NodeResult?, path: [IndexPathElement], ancestors: [NodeResult]) -> VisitResult { - return .continue -} +/** + * A visitor which maintains a provided TypeInfo instance alongside another visitor. + */ +public struct VisitorWithTypeInfo: Visitor { + let visitor: Visitor + let typeInfo: TypeInfo + public init(visitor: Visitor, typeInfo: TypeInfo) { + self.visitor = visitor + self.typeInfo = typeInfo + } + public func enter(node: T, key: AnyKeyPath?, parent: VisitorParent?, path: [AnyKeyPath], ancestors: [VisitorParent]) -> VisitResult { + typeInfo.enter(node: node) + + let result = visitor.enter( + node: node, + key: key, + parent: parent, + path: path, + ancestors: ancestors + ) + + if case .continue = result {} else { + typeInfo.leave(node: node) + if case .node(let node) = result, let n = node { + typeInfo.enter(node: n) + } + + } + return result + } + public func leave(node: T, key: AnyKeyPath?, parent: VisitorParent?, path: [AnyKeyPath], ancestors: [VisitorParent]) -> VisitResult { + let result = visitor.leave( + node: node, + key: key, + parent: parent, + path: path, + ancestors: ancestors + ) + + typeInfo.leave(node: node) + return result + } +} /** - * Creates a new visitor instance which maintains a provided TypeInfo instance - * along with visiting visitor. + A visitor which visits delegates to many visitors to run in parallel. + + Each visitor will be visited for each node before moving on. + If a prior visitor edits a node, no following visitors will see that node. */ -func visitWithTypeInfo(typeInfo: TypeInfo, visitor: Visitor) -> Visitor { - return Visitor( - enter: { node, key, parent, path, ancestors in - typeInfo.enter(node: node) - +class ParallelVisitor: Visitor { + let visitors: [Visitor] + + private var skipping: [SkipStatus] + private enum SkipStatus { + case skipping([AnyKeyPath]) + case breaking + case continuing + } + + init(visitors: [Visitor]) { + self.visitors = visitors + self.skipping = [SkipStatus](repeating: .continuing, count: visitors.count) + } + + public func enter(node: T, key: AnyKeyPath?, parent: VisitorParent?, path: [AnyKeyPath], ancestors: [VisitorParent]) -> VisitResult { + for (i, visitor) in visitors.enumerated() { + guard case .continuing = skipping[i] else { + continue + } let result = visitor.enter( node: node, key: key, @@ -367,28 +695,76 @@ func visitWithTypeInfo(typeInfo: TypeInfo, visitor: Visitor) -> Visitor { path: path, ancestors: ancestors ) - - if !result.isContinue { - typeInfo.leave(node: node) - - if case .node(let node) = result, let n = node { - typeInfo.enter(node: n) + switch result { + case .node: + return result + case .break: + skipping[i] = .breaking + case .skip: + skipping[i] = .skipping(path) + case .continue: + break + } + } + return .continue + } + public func leave(node: T, key: AnyKeyPath?, parent: VisitorParent?, path: [AnyKeyPath], ancestors: [VisitorParent]) -> VisitResult { + for (i, visitor) in visitors.enumerated() { + switch skipping[i] { + case .skipping(path): + // We've come back to leave the node we were skipping + // So unset the skipping status so that the visitor will resume traversing + skipping[i] = .continuing + case .skipping, .breaking: + break + case .continuing: + let result = visitor.leave( + node: node, + key: key, + parent: parent, + path: path, + ancestors: ancestors + ) + switch result { + case .break: + skipping[i] = .breaking + case .node: + return result + default: + break } } + } + return .continue + } +} - return result - }, - leave: { node, key, parent, path, ancestors in - let result = visitor.leave( - node: node, - key: key, - parent: parent, - path: path, - ancestors: ancestors - ) +public enum VisitResult { + case `continue`, skip, `break`, node(T?) +} - typeInfo.leave(node: node) - return result +fileprivate enum SomeVisitResult2 { + case `continue`, skip, `break`, node(Node?) + static func from(_ visitResult: VisitResult) -> SomeVisitResult2 { + switch visitResult { + case .continue: + return .continue + case .skip: + return .skip + case .break: + return .break + case .node(let node): + return .node(node) } - ) + } } + +/** + * Creates a new visitor instance which maintains a provided TypeInfo instance + * along with visiting visitor. + */ + +/** + * Creates a new visitor instance which maintains a provided TypeInfo instance + * along with visiting visitor. + */ diff --git a/Sources/GraphQL/Type/Definition.swift b/Sources/GraphQL/Type/Definition.swift index 57649903..2bb8f0d5 100644 --- a/Sources/GraphQL/Type/Definition.swift +++ b/Sources/GraphQL/Type/Definition.swift @@ -1,10 +1,11 @@ import Foundation import NIO +import OrderedCollections /** * These are all of the possible kinds of types. */ -public protocol GraphQLType : CustomDebugStringConvertible, Encodable, KeySubscriptable {} +public protocol GraphQLType : CustomDebugStringConvertible, Encodable, KeySubscriptable, AnyObject {} extension GraphQLScalarType : GraphQLType {} extension GraphQLObjectType : GraphQLType {} extension GraphQLInterfaceType : GraphQLType {} @@ -27,9 +28,9 @@ extension GraphQLNonNull : GraphQLInputType {} //extension GraphQLList : GraphQLInputType where Element : GraphQLInputType {} //extension GraphQLNonNull : GraphQLInputType where Element : (GraphQLScalarType | GraphQLEnumType | GraphQLInputObjectType | GraphQLList) {} -func isInputType(type: GraphQLType?) -> Bool { +func isInputType(type: (any GraphQLType)?) -> Bool { let namedType = getNamedType(type: type) - return namedType is GraphQLInputType + return namedType is any GraphQLInputType } /** @@ -59,7 +60,7 @@ public protocol GraphQLLeafType : GraphQLNamedType { extension GraphQLScalarType : GraphQLLeafType {} extension GraphQLEnumType : GraphQLLeafType {} -func isLeafType(type: GraphQLType?) -> Bool { +func isLeafType(type: (any GraphQLType)?) -> Bool { let namedType = getNamedType(type: type) return namedType is GraphQLScalarType || namedType is GraphQLEnumType @@ -84,7 +85,7 @@ extension GraphQLInputObjectType : GraphQLTypeReferenceContainer {} /** * These types may describe the parent context of a selection set. */ -public protocol GraphQLAbstractType : GraphQLNamedType { +public protocol GraphQLAbstractType : GraphQLCompositeType { var resolveType: GraphQLTypeResolve? { get } } @@ -103,21 +104,31 @@ extension GraphQLEnumType : GraphQLNullableType {} extension GraphQLInputObjectType : GraphQLNullableType {} extension GraphQLList : GraphQLNullableType {} -func getNullableType(type: GraphQLType?) -> GraphQLNullableType? { +func getNullableType(type: (any GraphQLType)?) -> (any GraphQLNullableType)? { if let type = type as? GraphQLNonNull { return type.ofType } - return type as? GraphQLNullableType + return type as? any GraphQLNullableType } /** * These named types do not include modifiers like List or NonNull. */ -public protocol GraphQLNamedType : GraphQLNullableType { +public protocol GraphQLNamedType : GraphQLNullableType, Hashable { var name: String { get } } +extension GraphQLNamedType { + public static func == (lhs: Self, rhs: Self) -> Bool { + lhs.name == rhs.name + } + + public func hash(into hasher: inout Hasher) { + name.hash(into: &hasher) + } +} + extension GraphQLScalarType : GraphQLNamedType {} extension GraphQLObjectType : GraphQLNamedType {} extension GraphQLInterfaceType : GraphQLNamedType {} @@ -125,21 +136,21 @@ extension GraphQLUnionType : GraphQLNamedType {} extension GraphQLEnumType : GraphQLNamedType {} extension GraphQLInputObjectType : GraphQLNamedType {} -public func getNamedType(type: GraphQLType?) -> GraphQLNamedType? { +public func getNamedType(type: (any GraphQLType)?) -> (any GraphQLNamedType)? { var unmodifiedType = type - while let type = unmodifiedType as? GraphQLWrapperType { + while let type = unmodifiedType as? (any GraphQLWrapperType) { unmodifiedType = type.wrappedType } - return unmodifiedType as? GraphQLNamedType + return unmodifiedType as? (any GraphQLNamedType) } /** * These types wrap other types. */ protocol GraphQLWrapperType : GraphQLType { - var wrappedType: GraphQLType { get } + var wrappedType: any GraphQLType { get } } extension GraphQLList : GraphQLWrapperType {} @@ -165,6 +176,7 @@ extension GraphQLNonNull : GraphQLWrapperType {} public final class GraphQLScalarType { public let name: String public let description: String? + public let specifiedByURL: String? public let kind: TypeKind = .scalar let serialize: (Any) throws -> Map @@ -174,11 +186,13 @@ public final class GraphQLScalarType { public init( name: String, description: String? = nil, + specifiedByURL: String? = nil, serialize: @escaping (Any) throws -> Map ) throws { try assertValid(name: name) self.name = name self.description = description + self.specifiedByURL = specifiedByURL self.serialize = serialize self.parseValue = nil self.parseLiteral = nil @@ -187,6 +201,7 @@ public final class GraphQLScalarType { public init( name: String, description: String? = nil, + specifiedByURL: String? = nil, serialize: @escaping (Any) throws -> Map, parseValue: @escaping (Map) throws -> Map, parseLiteral: @escaping (Value) throws -> Map @@ -194,6 +209,7 @@ public final class GraphQLScalarType { try assertValid(name: name) self.name = name self.description = description + self.specifiedByURL = specifiedByURL self.serialize = serialize self.parseValue = parseValue self.parseLiteral = parseLiteral @@ -297,30 +313,52 @@ extension GraphQLScalarType : Hashable { public final class GraphQLObjectType { public let name: String public let description: String? - public let fields: GraphQLFieldDefinitionMap - public let interfaces: [GraphQLInterfaceType] + private let fieldsThunk: () -> GraphQLFieldMap + public lazy var fields: GraphQLFieldDefinitionMap = { + try! defineFieldMap( + name: name, + fields: fieldsThunk() + ) + }() + private let interfacesThunk: () -> [GraphQLInterfaceType] + public lazy var interfaces: [GraphQLInterfaceType] = { + try! defineInterfaces( + name: name, + hasTypeOf: isTypeOf != nil, + interfaces: interfacesThunk() + ) + }() public let isTypeOf: GraphQLIsTypeOf? public let kind: TypeKind = .object - public init( + public convenience init( name: String, description: String? = nil, fields: GraphQLFieldMap, interfaces: [GraphQLInterfaceType] = [], isTypeOf: GraphQLIsTypeOf? = nil + ) throws { + try self.init( + name: name, + description: description, + fields: { fields }, + interfaces: { interfaces }, + isTypeOf: isTypeOf + ) + } + + public init( + name: String, + description: String? = nil, + fields: @escaping () -> GraphQLFieldMap, + interfaces: @escaping () -> [GraphQLInterfaceType], + isTypeOf: GraphQLIsTypeOf? = nil ) throws { try assertValid(name: name) self.name = name self.description = description - self.fields = try defineFieldMap( - name: name, - fields: fields - ) - self.interfaces = try defineInterfaces( - name: name, - hasTypeOf: isTypeOf != nil, - interfaces: interfaces - ) + self.fieldsThunk = fields + self.interfacesThunk = interfaces self.isTypeOf = isTypeOf } @@ -339,6 +377,15 @@ extension GraphQLObjectType : Encodable { case interfaces case kind } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(name, forKey: .name) + try container.encode(description, forKey: .description) + try container.encode(fields, forKey: .fields) + try container.encode(interfaces, forKey: .interfaces) + try container.encode(kind, forKey: .kind) + } } extension GraphQLObjectType : KeySubscriptable { @@ -432,19 +479,19 @@ func defineInterfaces( return [] } - if !hasTypeOf { - for interface in interfaces { - guard interface.resolveType != nil else { - throw GraphQLError( - message: - "Interface Type \(interface.name) does not provide a \"resolveType\" " + - "function and implementing Type \(name) does not provide a " + - "\"isTypeOf\" function. There is no way to resolve this implementing " + - "type during execution." - ) - } - } - } +// if !hasTypeOf { +// for interface in interfaces { +// guard interface.resolveType != nil else { +// throw GraphQLError( +// message: +// "Interface Type \(interface.name) does not provide a \"resolveType\" " + +// "function and implementing Type \(name) does not provide a " + +// "\"isTypeOf\" function. There is no way to resolve this implementing " + +// "type during execution." +// ) +// } +// } +// } return interfaces } @@ -500,8 +547,8 @@ public typealias GraphQLFieldResolveInput = ( public struct GraphQLResolveInfo { public let fieldName: String public let fieldASTs: [Field] - public let returnType: GraphQLOutputType - public let parentType: GraphQLCompositeType + public let returnType: any GraphQLOutputType + public let parentType: any GraphQLCompositeType public let path: IndexPath public let schema: GraphQLSchema public let fragments: [String: FragmentDefinition] @@ -510,10 +557,10 @@ public struct GraphQLResolveInfo { public let variableValues: [String: Any] } -public typealias GraphQLFieldMap = [String: GraphQLField] +public typealias GraphQLFieldMap = OrderedDictionary public struct GraphQLField { - public let type: GraphQLOutputType + public let type: any GraphQLOutputType public let args: GraphQLArgumentConfigMap public let deprecationReason: String? public let description: String? @@ -521,7 +568,7 @@ public struct GraphQLField { public let subscribe: GraphQLFieldResolve? public init( - type: GraphQLOutputType, + type: any GraphQLOutputType, description: String? = nil, deprecationReason: String? = nil, args: GraphQLArgumentConfigMap = [:] @@ -535,7 +582,7 @@ public struct GraphQLField { } public init( - type: GraphQLOutputType, + type: any GraphQLOutputType, description: String? = nil, deprecationReason: String? = nil, args: GraphQLArgumentConfigMap = [:], @@ -551,7 +598,7 @@ public struct GraphQLField { } public init( - type: GraphQLOutputType, + type: any GraphQLOutputType, description: String? = nil, deprecationReason: String? = nil, args: GraphQLArgumentConfigMap = [:], @@ -570,12 +617,12 @@ public struct GraphQLField { } } -public typealias GraphQLFieldDefinitionMap = [String: GraphQLFieldDefinition] +public typealias GraphQLFieldDefinitionMap = OrderedDictionary public final class GraphQLFieldDefinition { public let name: String public let description: String? - public internal(set) var type: GraphQLOutputType + public internal(set) var type: any GraphQLOutputType public let args: [GraphQLArgumentDefinition] public let resolve: GraphQLFieldResolve? public let subscribe: GraphQLFieldResolve? @@ -584,7 +631,7 @@ public final class GraphQLFieldDefinition { init( name: String, - type: GraphQLOutputType, + type: any GraphQLOutputType, description: String? = nil, deprecationReason: String? = nil, args: [GraphQLArgumentDefinition] = [], @@ -604,7 +651,7 @@ public final class GraphQLFieldDefinition { func replaceTypeReferences(typeMap: TypeMap) throws { let resolvedType = try resolveTypeReference(type: type, typeMap: typeMap) - guard let outputType = resolvedType as? GraphQLOutputType else { + guard let outputType = resolvedType as? (any GraphQLOutputType) else { throw GraphQLError( message: "Resolved type \"\(resolvedType)\" is not a valid output type." ) @@ -656,15 +703,15 @@ extension GraphQLFieldDefinition : KeySubscriptable { } } -public typealias GraphQLArgumentConfigMap = [String: GraphQLArgument] +public typealias GraphQLArgumentConfigMap = OrderedDictionary public struct GraphQLArgument { - public let type: GraphQLInputType + public let type: any GraphQLInputType public let description: String? public let defaultValue: Map? public init( - type: GraphQLInputType, + type: any GraphQLInputType, description: String? = nil, defaultValue: Map? = nil ) { @@ -676,13 +723,13 @@ public struct GraphQLArgument { public struct GraphQLArgumentDefinition { public let name: String - public let type: GraphQLInputType + public let type: any GraphQLInputType public let defaultValue: Map? public let description: String? init( name: String, - type: GraphQLInputType, + type: any GraphQLInputType, defaultValue: Map? = nil, description: String? = nil ) { @@ -753,27 +800,49 @@ public final class GraphQLInterfaceType { public let name: String public let description: String? public let resolveType: GraphQLTypeResolve? - public let fields: GraphQLFieldDefinitionMap - public let interfaces: [GraphQLInterfaceType] + private let fieldsThunk: () -> GraphQLFieldMap + public lazy var fields: GraphQLFieldDefinitionMap = { + try! defineFieldMap( + name: name, + fields: fieldsThunk() + ) + }() + public lazy var interfaces: [GraphQLInterfaceType] = { + try! interfacesThunk() + }() + private let interfacesThunk: () throws -> [GraphQLInterfaceType] public let kind: TypeKind = .interface - - public init( + + public convenience init( name: String, description: String? = nil, interfaces: [GraphQLInterfaceType] = [], fields: GraphQLFieldMap, resolveType: GraphQLTypeResolve? = nil + ) throws { + try self.init( + name: name, + description: description, + interfaces: { interfaces }, + fields: { fields }, + resolveType: resolveType + ) + } + + public init( + name: String, + description: String? = nil, + interfaces: @escaping () throws -> [GraphQLInterfaceType], + fields: @escaping () -> GraphQLFieldMap, + resolveType: GraphQLTypeResolve? = nil ) throws { try assertValid(name: name) self.name = name self.description = description - self.fields = try defineFieldMap( - name: name, - fields: fields - ) + self.fieldsThunk = fields - self.interfaces = interfaces + self.interfacesThunk = interfaces self.resolveType = resolveType } @@ -791,6 +860,14 @@ extension GraphQLInterfaceType : Encodable { case fields case kind } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(name, forKey: .name) + try container.encode(description, forKey: .description) + try container.encode(fields, forKey: .fields) + try container.encode(kind, forKey: .kind) + } } extension GraphQLInterfaceType : KeySubscriptable { @@ -855,26 +932,44 @@ public final class GraphQLUnionType { public let name: String public let description: String? public let resolveType: GraphQLTypeResolve? - public let types: [GraphQLObjectType] + private let typesThunk: () -> [GraphQLObjectType] + public lazy var types = { + typesThunk() +// try! defineTypes( +// name: name, +// hasResolve: resolveType != nil, +// types: typesThunk() +// ) + }() public let possibleTypeNames: [String: Bool] public let kind: TypeKind = .union + + public convenience init( + name: String, + description: String? = nil, + resolveType: GraphQLTypeResolve? = nil, + types: [GraphQLObjectType] + ) throws { + try self.init( + name: name, + description: description, + resolveType: resolveType, + types: { types } + ) + } public init( name: String, description: String? = nil, resolveType: GraphQLTypeResolve? = nil, - types: [GraphQLObjectType] + types: @escaping () -> [GraphQLObjectType] ) throws { try assertValid(name: name) self.name = name self.description = description self.resolveType = resolveType - self.types = try defineTypes( - name: name, - hasResolve: resolveType != nil, - types: types - ) + self.typesThunk = types self.possibleTypeNames = [:] } @@ -887,6 +982,14 @@ extension GraphQLUnionType : Encodable { case types case kind } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(name, forKey: .name) + try container.encode(description, forKey: .description) + try container.encode(types, forKey: .types) + try container.encode(kind, forKey: .kind) + } } extension GraphQLUnionType : KeySubscriptable { @@ -1024,7 +1127,7 @@ public final class GraphQLEnumType { } public func parseLiteral(valueAST: Value) -> Map { - if let enumValue = valueAST as? EnumValue { + if case .enumValue(let enumValue) = valueAST { return nameLookup[enumValue.value]?.value ?? .null } @@ -1103,7 +1206,7 @@ func defineEnumValues( return definitions } -public typealias GraphQLEnumValueMap = [String: GraphQLEnumValue] +public typealias GraphQLEnumValueMap = OrderedDictionary public struct GraphQLEnumValue { public let value: Map @@ -1176,21 +1279,32 @@ extension GraphQLEnumValueDefinition : KeySubscriptable { public final class GraphQLInputObjectType { public let name: String public let description: String? - public let fields: InputObjectFieldDefinitionMap + public let fieldsThunk: () -> InputObjectFieldMap + public lazy var fields: InputObjectFieldDefinitionMap = { + try! defineInputObjectFieldMap( + name: name, + fields: fieldsThunk() + ) + }() public let kind: TypeKind = .inputObject - public init( + public convenience init( name: String, description: String? = nil, fields: InputObjectFieldMap = [:] + ) throws { + try self.init(name: name, description: description, fields: { fields }) + } + + public init( + name: String, + description: String? = nil, + fields: @escaping () -> InputObjectFieldMap ) throws { try assertValid(name: name) self.name = name self.description = description - self.fields = try defineInputObjectFieldMap( - name: name, - fields: fields - ) + self.fieldsThunk = fields } func replaceTypeReferences(typeMap: TypeMap) throws { @@ -1207,6 +1321,14 @@ extension GraphQLInputObjectType : Encodable { case fields case kind } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(name, forKey: .name) + try container.encode(description, forKey: .description) + try container.encode(fields, forKey: .fields) + try container.encode(kind, forKey: .kind) + } } extension GraphQLInputObjectType : KeySubscriptable { @@ -1273,28 +1395,28 @@ func defineInputObjectFieldMap( } public struct InputObjectField { - public let type: GraphQLInputType + public let type: any GraphQLInputType public let defaultValue: Map? public let description: String? - public init(type: GraphQLInputType, defaultValue: Map? = nil, description: String? = nil) { + public init(type: any GraphQLInputType, defaultValue: Map? = nil, description: String? = nil) { self.type = type self.defaultValue = defaultValue self.description = description } } -public typealias InputObjectFieldMap = [String: InputObjectField] +public typealias InputObjectFieldMap = OrderedDictionary public final class InputObjectFieldDefinition { public let name: String - public internal(set) var type: GraphQLInputType + public internal(set) var type: any GraphQLInputType public let description: String? public let defaultValue: Map? init( name: String, - type: GraphQLInputType, + type: any GraphQLInputType, description: String? = nil, defaultValue: Map? = nil ) { @@ -1307,7 +1429,7 @@ public final class InputObjectFieldDefinition { func replaceTypeReferences(typeMap: TypeMap) throws { let resolvedType = try resolveTypeReference(type: type, typeMap: typeMap) - guard let inputType = resolvedType as? GraphQLInputType else { + guard let inputType = resolvedType as? (any GraphQLInputType) else { throw GraphQLError( message: "Resolved type \"\(resolvedType)\" is not a valid input type." ) @@ -1351,7 +1473,7 @@ extension InputObjectFieldDefinition : KeySubscriptable { } } -public typealias InputObjectFieldDefinitionMap = [String: InputObjectFieldDefinition] +public typealias InputObjectFieldDefinitionMap = OrderedDictionary /** * List Modifier @@ -1372,10 +1494,10 @@ public typealias InputObjectFieldDefinitionMap = [String: InputObjectFieldDefini * */ public final class GraphQLList { - public let ofType: GraphQLType + public let ofType: any GraphQLType public let kind: TypeKind = .list - public init(_ type: GraphQLType) { + public init(_ type: any GraphQLType) { self.ofType = type } @@ -1383,7 +1505,7 @@ public final class GraphQLList { self.ofType = GraphQLTypeReference(name) } - var wrappedType: GraphQLType { + var wrappedType: any GraphQLType { return ofType } @@ -1456,10 +1578,10 @@ extension GraphQLList : Hashable { * Note: the enforcement of non-nullability occurs within the executor. */ public final class GraphQLNonNull { - public let ofType: GraphQLNullableType + public let ofType: any GraphQLNullableType public let kind: TypeKind = .nonNull - public init(_ type: GraphQLNullableType) { + public init(_ type: any GraphQLNullableType) { self.ofType = type } @@ -1467,14 +1589,14 @@ public final class GraphQLNonNull { self.ofType = GraphQLTypeReference(name) } - var wrappedType: GraphQLType { + var wrappedType: any GraphQLType { return ofType } func replaceTypeReferences(typeMap: TypeMap) throws -> GraphQLNonNull { let resolvedType = try resolveTypeReference(type: ofType, typeMap: typeMap) - guard let nullableType = resolvedType as? GraphQLNullableType else { + guard let nullableType = resolvedType as? (any GraphQLNullableType) else { throw GraphQLError( message: "Resolved type \"\(resolvedType)\" is not a valid nullable type." ) diff --git a/Sources/GraphQL/Type/Directives.swift b/Sources/GraphQL/Type/Directives.swift index ef6e5722..7370e6ad 100644 --- a/Sources/GraphQL/Type/Directives.swift +++ b/Sources/GraphQL/Type/Directives.swift @@ -1,4 +1,4 @@ -public enum DirectiveLocation : String, Encodable { +public enum DirectiveLocation : String, Codable { // Operations case query = "QUERY" case mutation = "MUTATION" diff --git a/Sources/GraphQL/Type/Introspection.swift b/Sources/GraphQL/Type/Introspection.swift index 1ccb4e97..d8290bac 100644 --- a/Sources/GraphQL/Type/Introspection.swift +++ b/Sources/GraphQL/Type/Introspection.swift @@ -10,7 +10,7 @@ let __Schema = try! GraphQLObjectType( "types": GraphQLField( type: GraphQLNonNull(GraphQLList(GraphQLNonNull(__Type))), description: "A list of all types supported by this server.", - resolve: { schema, _, _, _ -> [GraphQLNamedType]? in + resolve: { schema, _, _, _ -> [any GraphQLNamedType]? in guard let schema = schema as? GraphQLSchema else { return nil } @@ -179,15 +179,11 @@ let __DirectiveLocation = try! GraphQLEnumType( let __Type: GraphQLObjectType = try! GraphQLObjectType( name: "__Type", - description: - "The fundamental unit of any GraphQL Schema is the type. There are " + - "many kinds of types in GraphQL as represented by the `__TypeKind` enum." + - "\n\nDepending on the kind of a type, certain fields describe " + - "information about that type. Scalar types provide no information " + - "beyond a name and description, while Enum types provide their values. " + - "Object and Interface types provide the fields they describe. Abstract " + - "types, Union and Interface, provide the Object types possible " + - "at runtime. List and NonNull types compose other types.", + description: """ + The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum. + + Depending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name, description and optional `specifiedByURL`, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types. + """, fields: [ "kind": GraphQLField( type: GraphQLNonNull(__TypeKind), @@ -216,6 +212,7 @@ let __Type: GraphQLObjectType = try! GraphQLObjectType( ), "name": GraphQLField(type: GraphQLString), "description": GraphQLField(type: GraphQLString), + "specifiedByURL": GraphQLField(type: GraphQLString), "fields": GraphQLField( type: GraphQLList(GraphQLNonNull(__Field)), args: [ @@ -268,7 +265,7 @@ let __Type: GraphQLObjectType = try! GraphQLObjectType( "possibleTypes": GraphQLField( type: GraphQLList(GraphQLNonNull(GraphQLTypeReference("__Type"))), resolve: { type, args, _, info -> [GraphQLObjectType]? in - guard let type = type as? GraphQLAbstractType else { + guard let type = type as? any GraphQLAbstractType else { return nil } @@ -476,3 +473,7 @@ let TypeNameMetaFieldDef = GraphQLFieldDefinition( eventLoopGroup.next().makeSucceededFuture(info.parentType.name) } ) + +let introspectionTypes: [any GraphQLNamedType] = [ + __Schema, __Directive, __DirectiveLocation, __Type, __Field, __InputValue, __EnumValue, __TypeKind +] diff --git a/Sources/GraphQL/Type/Scalars.swift b/Sources/GraphQL/Type/Scalars.swift index c04c9baa..caad5071 100644 --- a/Sources/GraphQL/Type/Scalars.swift +++ b/Sources/GraphQL/Type/Scalars.swift @@ -6,7 +6,7 @@ public let GraphQLInt = try! GraphQLScalarType( serialize: { try map(from: $0) } , parseValue: { try .int($0.intValue(converting: true)) }, parseLiteral: { ast in - if let ast = ast as? IntValue, let int = Int(ast.value) { + if case .intValue(let ast) = ast, let int = Int(ast.value) { return .int(int) } @@ -23,11 +23,11 @@ public let GraphQLFloat = try! GraphQLScalarType( serialize: { try map(from: $0) } , parseValue: { try .double($0.doubleValue(converting: true)) }, parseLiteral: { ast in - if let ast = ast as? FloatValue, let double = Double(ast.value) { + if case .floatValue(let ast) = ast, let double = Double(ast.value) { return .double(double) } - if let ast = ast as? IntValue, let double = Double(ast.value) { + if case .intValue(let ast) = ast, let double = Double(ast.value) { return .double(double) } @@ -44,7 +44,7 @@ public let GraphQLString = try! GraphQLScalarType( serialize: { try map(from: $0) } , parseValue: { try .string($0.stringValue(converting: true)) }, parseLiteral: { ast in - if let ast = ast as? StringValue { + if case .stringValue(let ast) = ast { return .string(ast.value) } @@ -58,7 +58,7 @@ public let GraphQLBoolean = try! GraphQLScalarType( serialize: { try map(from: $0) } , parseValue: { try .bool($0.boolValue(converting: true)) }, parseLiteral: { ast in - if let ast = ast as? BooleanValue { + if case .booleanValue(let ast) = ast { return .bool(ast.value) } @@ -77,14 +77,18 @@ public let GraphQLID = try! GraphQLScalarType( serialize: { try map(from: $0) }, parseValue: { try .string($0.stringValue(converting: true)) }, parseLiteral: { ast in - if let ast = ast as? StringValue { + if case .stringValue(let ast) = ast { return .string(ast.value) } - if let ast = ast as? IntValue { + if case .intValue(let ast) = ast { return .string(ast.value) } return .null } ) + +public let specifiedScalarTypes = [ + GraphQLString, GraphQLInt, GraphQLFloat, GraphQLBoolean, GraphQLID +] diff --git a/Sources/GraphQL/Type/Schema.swift b/Sources/GraphQL/Type/Schema.swift index 0fff7452..c0a5d64e 100644 --- a/Sources/GraphQL/Type/Schema.swift +++ b/Sources/GraphQL/Type/Schema.swift @@ -38,7 +38,7 @@ public final class GraphQLSchema { query: GraphQLObjectType, mutation: GraphQLObjectType? = nil, subscription: GraphQLObjectType? = nil, - types: [GraphQLNamedType] = [], + types: [any GraphQLNamedType] = [], directives: [GraphQLDirective] = [] ) throws { self.queryType = query @@ -49,7 +49,7 @@ public final class GraphQLSchema { self.directives = directives.isEmpty ? specifiedDirectives : directives // Build type map now to detect any errors within this schema. - var initialTypes: [GraphQLNamedType] = [ + var initialTypes: [any GraphQLNamedType] = [ queryType, ] @@ -89,11 +89,11 @@ public final class GraphQLSchema { } } - public func getType(name: String) -> GraphQLNamedType? { + public func getType(name: String) -> (any GraphQLNamedType)? { return typeMap[name] } - public func getPossibleTypes(abstractType: GraphQLAbstractType) -> [GraphQLObjectType] { + public func getPossibleTypes(abstractType: any GraphQLAbstractType) -> [GraphQLObjectType] { if let unionType = abstractType as? GraphQLUnionType { return unionType.types } @@ -113,20 +113,24 @@ public final class GraphQLSchema { // @deprecated: use isSubType instead - will be removed in the future. public func isPossibleType( - abstractType: GraphQLAbstractType, + abstractType: any GraphQLAbstractType, possibleType: GraphQLObjectType ) throws -> Bool { isSubType(abstractType: abstractType, maybeSubType: possibleType) } public func isSubType( - abstractType: GraphQLAbstractType, - maybeSubType: GraphQLNamedType + abstractType: any GraphQLCompositeType, + maybeSubType: any GraphQLNamedType ) -> Bool { var map = subTypeMap[abstractType.name] if map == nil { map = [:] + + if let objectType = abstractType as? GraphQLObjectType { + map?[objectType.name] = true + } if let unionType = abstractType as? GraphQLUnionType { for type in unionType.types { @@ -171,7 +175,7 @@ extension GraphQLSchema : Encodable { } } -public typealias TypeMap = [String: GraphQLNamedType] +public typealias TypeMap = [String: any GraphQLNamedType] public struct InterfaceImplementations { public let objects: [GraphQLObjectType] @@ -187,22 +191,19 @@ public struct InterfaceImplementations { } func collectImplementations( - types: [GraphQLNamedType] + types: [any GraphQLNamedType] ) -> [String : InterfaceImplementations] { var implementations: [String: InterfaceImplementations] = [:] - for type in types { + var typesToCheck = types + while let type = typesToCheck.popLast() { if let type = type as? GraphQLInterfaceType { if implementations[type.name] == nil { implementations[type.name] = InterfaceImplementations() } - // Store implementations by interface. - for iface in type.interfaces { - implementations[iface.name] = InterfaceImplementations( - interfaces: (implementations[iface.name]?.interfaces ?? []) + [type] - ) - } + // Depth first search of other interface implementations + typesToCheck += type.interfaces } if let type = type as? GraphQLObjectType { @@ -218,14 +219,14 @@ func collectImplementations( return implementations } -func typeMapReducer(typeMap: TypeMap, type: GraphQLType) throws -> TypeMap { +func typeMapReducer(typeMap: TypeMap, type: any GraphQLType) throws -> TypeMap { var typeMap = typeMap - if let type = type as? GraphQLWrapperType { + if let type = type as? (any GraphQLWrapperType) { return try typeMapReducer(typeMap: typeMap, type: type.wrappedType) } - guard let type = type as? GraphQLNamedType else { + guard let type = type as? (any GraphQLNamedType) else { return typeMap // Should never happen } @@ -352,13 +353,13 @@ func assert( func replaceTypeReferences(typeMap: TypeMap) throws { for type in typeMap { - if let typeReferenceContainer = type.value as? GraphQLTypeReferenceContainer { + if let typeReferenceContainer = type.value as? (any GraphQLTypeReferenceContainer) { try typeReferenceContainer.replaceTypeReferences(typeMap: typeMap) } } } -func resolveTypeReference(type: GraphQLType, typeMap: TypeMap) throws -> GraphQLType { +func resolveTypeReference(type: any GraphQLType, typeMap: TypeMap) throws -> any GraphQLType { if let type = type as? GraphQLTypeReference { guard let resolvedType = typeMap[type.name] else { throw GraphQLError( @@ -380,8 +381,8 @@ func resolveTypeReference(type: GraphQLType, typeMap: TypeMap) throws -> GraphQL return type } -func resolveTypeReferences(types: [GraphQLType], typeMap: TypeMap) throws -> [GraphQLType] { - var resolvedTypes: [GraphQLType] = [] +func resolveTypeReferences(types: [any GraphQLType], typeMap: TypeMap) throws -> [any GraphQLType] { + var resolvedTypes: [any GraphQLType] = [] for type in types { try resolvedTypes.append(resolveTypeReference(type: type, typeMap: typeMap)) diff --git a/Sources/GraphQL/Utilities/ASTFromValue.swift b/Sources/GraphQL/Utilities/ASTFromValue.swift index daafe086..5f3eeb60 100644 --- a/Sources/GraphQL/Utilities/ASTFromValue.swift +++ b/Sources/GraphQL/Utilities/ASTFromValue.swift @@ -18,12 +18,12 @@ import Foundation */ func astFromValue( value: Map, - type: GraphQLInputType + type: any GraphQLInputType ) throws -> Value? { if let type = type as? GraphQLNonNull { // Note: we're not checking that the result is non-null. // This function is not responsible for validating the input value. - return try astFromValue(value: value, type: type.ofType as! GraphQLInputType) + return try astFromValue(value: value, type: type.ofType as! (any GraphQLInputType)) } guard value != .null else { @@ -33,7 +33,7 @@ func astFromValue( // Convert array to GraphQL list. If the GraphQLType is a list, but // the value is not an array, convert the value using the list's item type. if let type = type as? GraphQLList { - let itemType = type.ofType as! GraphQLInputType + let itemType = type.ofType as! (any GraphQLInputType) if case .array(let value) = value { var valuesASTs: [Value] = [] @@ -44,7 +44,7 @@ func astFromValue( } } - return ListValue(values: valuesASTs) + return .listValue(ListValue(values: valuesASTs)) } return try astFromValue(value: value, type: itemType) @@ -69,10 +69,10 @@ func astFromValue( } } - return ObjectValue(fields: fieldASTs) + return .objectValue(ObjectValue(fields: fieldASTs)) } - guard let leafType = type as? GraphQLLeafType else { + guard let leafType = type as? (any GraphQLLeafType) else { throw GraphQLError( message: "Must provide Input Type, cannot use: \(type)" ) @@ -90,11 +90,11 @@ func astFromValue( if case let .number(number) = serialized { switch number.storageType { case .bool: - return BooleanValue(value: number.boolValue) + return .booleanValue(BooleanValue(value: number.boolValue)) case .int: - return IntValue(value: String(number.intValue)) + return .intValue(IntValue(value: String(number.intValue))) case .double: - return FloatValue(value: String(number.doubleValue)) + return .floatValue(FloatValue(value: String(number.doubleValue))) case .unknown: break } @@ -103,12 +103,12 @@ func astFromValue( if case let .string(string) = serialized { // Enum types use Enum literals. if type is GraphQLEnumType { - return EnumValue(value: string) + return .enumValue(EnumValue(value: string)) } // ID types can use Int literals. if type == GraphQLID && Int(string) != nil { - return IntValue(value: string) + return .intValue(IntValue(value: string)) } // Use JSON stringify, which uses the same string encoding as GraphQL, @@ -119,7 +119,7 @@ func astFromValue( let data = try JSONEncoder().encode(Wrapper(map: serialized)) let string = String(data: data, encoding: .utf8)! - return StringValue(value: String(string.dropFirst(8).dropLast(2))) + return .stringValue(StringValue(value: String(string.dropFirst(8).dropLast(2)))) } throw GraphQLError(message: "Cannot convert value to AST: \(serialized)") diff --git a/Sources/GraphQL/Utilities/BuildClientSchema.swift b/Sources/GraphQL/Utilities/BuildClientSchema.swift new file mode 100644 index 00000000..9452da66 --- /dev/null +++ b/Sources/GraphQL/Utilities/BuildClientSchema.swift @@ -0,0 +1,184 @@ +enum BuildClientSchemaError: Error { + case invalid(String) +} +public func buildClientSchema(introspection: IntrospectionQuery) throws -> GraphQLSchema { + let schemaIntrospection = introspection.__schema + var typeMap = [String: any GraphQLNamedType]() + typeMap = try schemaIntrospection.types.reduce(into: [String: any GraphQLNamedType]()) { + $0[$1.x.name] = try buildType($1.x) + } + + + // Include standard types only if they are used + for stdType in specifiedScalarTypes + introspectionTypes { + if (typeMap[stdType.name] != nil) { + typeMap[stdType.name] = stdType + } + } + + func getNamedType(name: String) throws -> any GraphQLNamedType { + guard let type = typeMap[name] else { + throw BuildClientSchemaError.invalid("Couldn't find type named \(name)") + } + return type + } + + func buildImplementationsList(interfaces: [IntrospectionTypeRef]) throws -> [GraphQLInterfaceType] { + try interfaces.map { + switch $0 { + case .named(_, let name): + return try getInterfaceType(name: name) + default: + throw BuildClientSchemaError.invalid("Expected named type ref") + } + } + } + + func getInterfaceType(name: String) throws -> GraphQLInterfaceType { + guard let type = try getNamedType(name: name) as? GraphQLInterfaceType else { + throw BuildClientSchemaError.invalid("Expected interface type") + } + return type + } + + func getType(_ typeRef: IntrospectionTypeRef) throws -> any GraphQLType { + switch typeRef { + case .list(let ofType): + return GraphQLList(try getType(ofType)) + case .nonNull(let ofType): + guard let type = try getType(ofType) as? (any GraphQLNullableType) else { + throw BuildClientSchemaError.invalid("Expected nullable type") + } + return GraphQLNonNull(type) + case .named(_, let name): + return try getNamedType(name: name) + } + } + + func buildFieldDefMap(fields: [IntrospectionField]) throws -> GraphQLFieldMap { + try fields.reduce( + into: GraphQLFieldMap()) { + guard let type = try getType($1.type) as? GraphQLOutputType else { + throw BuildClientSchemaError.invalid("Introspection must provide output type for fields") + } + $0[$1.name] = GraphQLField( + type: type, + description: $1.description, + deprecationReason: $1.deprecationReason, + args: try buildInputValueDefMap(args: $1.args) + ) + } + } + + func buildInputValueDefMap(args: [IntrospectionInputValue]) throws -> GraphQLArgumentConfigMap { + return try args.reduce(into: GraphQLArgumentConfigMap()) { + $0[$1.name] = try buildInputValue(inputValue: $1) + } + } + + func buildInputValue(inputValue: IntrospectionInputValue) throws -> GraphQLArgument { + guard let type = try getType(inputValue.type) as? (any GraphQLInputType) else { + throw BuildClientSchemaError.invalid("Introspection must provide input type for arguments") + } + let defaultValue = try inputValue.defaultValue.map { + try valueFromAST(valueAST: parseValue(source: $0), type: type) + } + return GraphQLArgument(type: type, description: inputValue.description, defaultValue: defaultValue) + } + + func buildInputObjectFieldMap(args: [IntrospectionInputValue]) throws -> InputObjectFieldMap { + try args.reduce(into: [:]) { acc, inputValue in + guard let type = try getType(inputValue.type) as? (any GraphQLInputType) else { + throw BuildClientSchemaError.invalid("Introspection must provide input type for arguments") + } + let defaultValue = try inputValue.defaultValue.map { + try valueFromAST(valueAST: parseValue(source: $0), type: type) + } + acc[inputValue.name] = InputObjectField(type: type, defaultValue: defaultValue, description: inputValue.description) + } + } + + func buildType(_ type: IntrospectionType) throws -> any GraphQLNamedType { + switch type { + case let type as IntrospectionScalarType: + return try GraphQLScalarType( + name: type.name, + description: type.description, + specifiedByURL: type.specifiedByURL, + serialize: { try map(from: $0) } + ) + case let type as IntrospectionObjectType: + return try GraphQLObjectType( + name: type.name, + description: type.description, + fields: { try! buildFieldDefMap(fields: type.fields ?? []) }, + interfaces: { try! buildImplementationsList(interfaces: type.interfaces ?? []) } + ) + case let type as IntrospectionInterfaceType: + return try GraphQLInterfaceType( + name: type.name, + description: type.description, + interfaces: { try buildImplementationsList(interfaces: type.interfaces ?? []) }, + fields: { try! buildFieldDefMap(fields: type.fields ?? []) }, + resolveType: nil + ) + case let type as IntrospectionUnionType: + return try GraphQLUnionType( + name: type.name, + description: type.description, + types: { try! type.possibleTypes.map(getObjectType) } + ) + case let type as IntrospectionEnumType: + return try GraphQLEnumType( + name: type.name, + description: type.description, + values: type.enumValues.reduce(into: GraphQLEnumValueMap()) { + $0[$1.name] = GraphQLEnumValue( + value: Map.null, + description: $1.description, + deprecationReason: $1.deprecationReason + ) + } + ) + case let type as IntrospectionInputObjectType: + return try GraphQLInputObjectType( + name: type.name, + description: type.description, + fields: { try! buildInputObjectFieldMap(args: type.inputFields) } + ) + default: + fatalError() + } + } + + func getObjectType(_ type: IntrospectionTypeRef) throws -> GraphQLObjectType { + guard case .named(_, let name) = type else { + throw BuildClientSchemaError.invalid("Expected name ref") + } + return try getObjectType(name: name) + } + + func getObjectType(name: String) throws -> GraphQLObjectType { + guard let type = try getNamedType(name: name) as? GraphQLObjectType else { + throw BuildClientSchemaError.invalid("Expected object type") + } + return type + } + +// let directives = [] + + let mutationType: GraphQLObjectType? + if let mutationTypeRef = schemaIntrospection.mutationType { + mutationType = try getObjectType(name: mutationTypeRef.name) + } else { + mutationType = nil + } + + return try GraphQLSchema( + query: try getObjectType(name: schemaIntrospection.queryType.name), + mutation: mutationType, + subscription: nil, + types: Array(typeMap.values), + directives: [] + ) +} diff --git a/Sources/GraphQL/Utilities/GetIntrospectionQuery.swift b/Sources/GraphQL/Utilities/GetIntrospectionQuery.swift new file mode 100644 index 00000000..2da0356d --- /dev/null +++ b/Sources/GraphQL/Utilities/GetIntrospectionQuery.swift @@ -0,0 +1,382 @@ +public func getIntrospectionQuery( + descriptions: Bool = true, + specifiedByURL: Bool = false, + directiveIsRepeatable: Bool = false, + schemaDescription: Bool = false, + inputValueDeprecation: Bool = false +) -> String { + + let descriptions = descriptions ? "description" : "" + let specifiedByURL = specifiedByURL ? "specifiedByURL" : "" + let directiveIsRepeatable = directiveIsRepeatable ? "isRepeatable" : "" + let schemaDescription = schemaDescription ? descriptions : "" + + func inputDeprecation(_ str: String) -> String { + return inputValueDeprecation ? str : "" + } + + return """ + query IntrospectionQuery { + __schema { + \(schemaDescription) + queryType { name } + mutationType { name } + subscriptionType { name } + types { + ...FullType + } + directives { + name + \(descriptions) + \(directiveIsRepeatable) + locations + args\(inputDeprecation("(includeDeprecated: true)")) { + ...InputValue + } + } + } + } + fragment FullType on __Type { + kind + name + \(descriptions) + \(specifiedByURL) + fields(includeDeprecated: true) { + name + \(descriptions) + args\(inputDeprecation("(includeDeprecated: true)")) { + ...InputValue + } + type { + ...TypeRef + } + isDeprecated + deprecationReason + } + inputFields\(inputDeprecation("(includeDeprecated: true)")) { + ...InputValue + } + interfaces { + ...TypeRef + } + enumValues(includeDeprecated: true) { + name + \(descriptions) + isDeprecated + deprecationReason + } + possibleTypes { + ...TypeRef + } + } + fragment InputValue on __InputValue { + name + \(descriptions) + type { ...TypeRef } + defaultValue + \(inputDeprecation("isDeprecated")) + \(inputDeprecation("deprecationReason")) + } + fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + } + } + } + """ +} + +public struct IntrospectionQuery: Codable { + let __schema: IntrospectionSchema +} + +struct IntrospectionSchema: Codable { + let description: String? + let queryType: IntrospectionKindlessNamedTypeRef + let mutationType: IntrospectionKindlessNamedTypeRef? + let subscriptionType: IntrospectionKindlessNamedTypeRef? + let types: [AnyIntrospectionType] +// let directives: [IntrospectionDirective] + +// func encode(to encoder: Encoder) throws { +// try types.encode(to: encoder) +// } +} + +protocol IntrospectionType: Codable { + var kind: TypeKind2 { get } + var name: String { get } +} + +enum IntrospectionTypeCodingKeys: String, CodingKey { + case kind +} +//enum IntrospectionType: Codable { +// case scalar(name: String, description: String?, specifiedByURL: String?) +// +// enum IntrospectionScalarTypeCodingKeys: CodingKey { +// case kind, name, description, specifiedByURL +// } +// func encode(to encoder: Encoder) throws { +// switch self { +// case .scalar(let name, let description, let specifiedByURL): +// var container = encoder.container(keyedBy: IntrospectionScalarTypeCodingKeys.self) +// try container.encode(TypeKind2.scalar, forKey: .kind) +// try container.encode(name, forKey: .name) +// try container.encode(description, forKey: .description) +// try container.encode(specifiedByURL, forKey: .specifiedByURL) +// } +// } +// init(from decoder: Decoder) throws { +// let container = try decoder.container(keyedBy: IntrospectionTypeCodingKeys.self) +// switch try container.decode(TypeKind2.self, forKey: .kind) { +// case .scalar: +// let container = try decoder.container(keyedBy: IntrospectionScalarTypeCodingKeys.self) +// self = .scalar( +// name: try container.decode(String.self, forKey: .name), +// description: try container.decode(String.self, forKey: .description), +// specifiedByURL: try container.decode(String.self, forKey: .description) +// ) +// } +// } +//} + +enum TypeKind2 : String, Codable { + case scalar = "SCALAR" + case object = "OBJECT" + case interface = "INTERFACE" + case union = "UNION" + case `enum` = "ENUM" + case inputObject = "INPUT_OBJECT" +// case list = "LIST" +// case nonNull = "NON_NULL" +} + +struct AnyIntrospectionType: Codable { + let x: IntrospectionType + func encode(to encoder: Encoder) throws { + try x.encode(to: encoder) + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: IntrospectionTypeCodingKeys.self) + switch try container.decode(TypeKind2.self, forKey: .kind) { + case .scalar: + x = try IntrospectionScalarType(from: decoder) + case .object: + x = try IntrospectionObjectType(from: decoder) + case .interface: + x = try IntrospectionInterfaceType(from: decoder) + case .union: + x = try IntrospectionUnionType(from: decoder) + case .enum: + x = try IntrospectionEnumType(from: decoder) + case .inputObject: + x = try IntrospectionInputObjectType(from: decoder) + } + } +} + +//protocol IntrospectionOutputType: Codable {} +//extension IntrospectionScalarType: IntrospectionOutputType {} +// +//protocol IntrospectionInputType: Codable {} +//extension IntrospectionScalarType: IntrospectionInputType {} +//extension IntrospectionEnumType: IntrospectionInputType {} +//extension IntrospectionInputObjectType: IntrospectionInputType {} +// +struct IntrospectionScalarType: IntrospectionType { + var kind = TypeKind2.scalar + let name: String + let description: String? + let specifiedByURL: String? +} + +struct IntrospectionObjectType: IntrospectionType { + var kind = TypeKind2.object + let name: String + let description: String? + let fields: [IntrospectionField]? + let interfaces: [IntrospectionTypeRef]? +} + +struct IntrospectionInterfaceType: IntrospectionType { + var kind = TypeKind2.interface + let name: String + let description: String? + let fields: [IntrospectionField]? + let interfaces: [IntrospectionTypeRef]? + let possibleTypes: [IntrospectionTypeRef] +} + +struct IntrospectionUnionType: IntrospectionType { + var kind = TypeKind2.union + let name: String + let description: String? + let possibleTypes: [IntrospectionTypeRef] +} + +struct IntrospectionEnumType: IntrospectionType { + var kind = TypeKind2.enum + let name: String + let description: String? + let enumValues: [IntrospectionEnumValue] +} + +struct IntrospectionInputObjectType: IntrospectionType { + var kind = TypeKind2.inputObject + let name: String + let description: String? + let inputFields: [IntrospectionInputValue] +} +// +//protocol IntrospectionTypeRef: Codable {} +//extension IntrospectionNamedTypeRef: IntrospectionTypeRef {} +// +//protocol IntrospectionOutputTypeRef: Codable {} +//extension IntrospectionNamedTypeRef: IntrospectionOutputTypeRef where T: IntrospectionOutputType {} +//extension IntrospectionListTypeRef: IntrospectionOutputTypeRef where T: IntrospectionOutputType {} + +//struct AnyIntrospectionOutputTypeRef: Codable { +// let typeRef: IntrospectionOutputTypeRef +// func encode(to encoder: Encoder) throws { +// try typeRef.encode(to: encoder) +// } +// init(from decoder: Decoder) throws { +// let container = try decoder.container(keyedBy: IntrospectionTypeCodingKeys.self) +// switch try container.decode(TypeKind2.self, forKey: .kind) { +// case .list: +// typeRef = try IntrospectionListTypeRef<<#T: IntrospectionTypeRef#>>(from: decoder) +// default: +// fatalError() +// } +// } +//} + +//protocol IntrospectionInputTypeRef: Codable {} +//extension IntrospectionNamedTypeRef: IntrospectionInputTypeRef where T: IntrospectionInputType {} +// +//struct IntrospectionListTypeRef: IntrospectionType { +// static var kind: TypeKind2 { TypeKind2.list } +// let ofType: T +//} + +//struct IntrospectionNamedTypeRef: Codable { +// var kind: TypeKind2 = T.kind +// let name: String +//} + +indirect enum IntrospectionTypeRef: Codable { + case named(kind: TypeKind2, name: String) + case list(ofType: IntrospectionTypeRef) + case nonNull(ofType: IntrospectionTypeRef) + + enum NamedTypeRefCodingKeys: CodingKey { + case kind, name + } + enum ListTypeRefCodingKeys: CodingKey { + case kind, ofType + } + + enum TypeRefKind: Codable { + case list, nonNull, named(TypeKind2) + } + + func encode(to encoder: Encoder) throws { + switch self { + case .named(let kind, let name): + var container = encoder.container(keyedBy: NamedTypeRefCodingKeys.self) + try container.encode(kind, forKey: .kind) + try container.encode(name, forKey: .name) + case .list(let ofType): + var container = encoder.container(keyedBy: ListTypeRefCodingKeys.self) + try container.encode("LIST", forKey: .kind) + try container.encode(ofType, forKey: .ofType) + case .nonNull(let ofType): + var container = encoder.container(keyedBy: ListTypeRefCodingKeys.self) + try container.encode("NON_NULL", forKey: .kind) + try container.encode(ofType, forKey: .ofType) + } + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: NamedTypeRefCodingKeys.self) + if let kind = try? container.decode(TypeKind2.self, forKey: .kind) { + self = .named(kind: kind, name: try container.decode(String.self, forKey: .name)) + } else { + let container = try decoder.container(keyedBy: ListTypeRefCodingKeys.self) + let kind = try container.decode(String.self, forKey: .kind) + switch kind { + case "LIST": + self = .list(ofType: try container.decode(IntrospectionTypeRef.self, forKey: .ofType)) + case "NON_NULL": + self = .nonNull(ofType: try container.decode(IntrospectionTypeRef.self, forKey: .ofType)) + default: + fatalError() + } + } + } +} + +struct IntrospectionKindlessNamedTypeRef: Codable { + let name: String +} + +struct IntrospectionField: Codable { + let name: String + let description: String? + let args: [IntrospectionInputValue] + let type: IntrospectionTypeRef + let isDeprecated: Bool + let deprecationReason: String? +} + +struct IntrospectionInputValue: Codable { + let name: String + let description: String? + let type: IntrospectionTypeRef + let defaultValue: String? + let isDeprecated: Bool? + let deprecationReason: String? +} + +struct IntrospectionEnumValue: Codable { + let name: String + let description: String? + let isDeprecated: Bool + let deprecationReason: String? +} + +struct IntrospectionDirective: Codable { + let name: String + let description: String? + let isRepeatable: Bool? + let locations: [DirectiveLocation] + let args: [IntrospectionInputValue] +} diff --git a/Sources/GraphQL/Utilities/IsValidValue.swift b/Sources/GraphQL/Utilities/IsValidValue.swift index a7a30395..fe269daf 100644 --- a/Sources/GraphQL/Utilities/IsValidValue.swift +++ b/Sources/GraphQL/Utilities/IsValidValue.swift @@ -3,10 +3,10 @@ * accepted for that type. This is primarily useful for validating the * runtime values of query variables. */ -func validate(value: Map, forType type: GraphQLInputType) throws -> [String] { +func validate(value: Map, forType type: any GraphQLInputType) throws -> [String] { // A value must be provided if the type is non-null. if let nonNullType = type as? GraphQLNonNull { - guard let wrappedType = nonNullType.ofType as? GraphQLInputType else { + guard let wrappedType = nonNullType.ofType as? (any GraphQLInputType) else { throw GraphQLError(message: "Input non-null type must wrap another input type") } @@ -27,7 +27,7 @@ func validate(value: Map, forType type: GraphQLInputType) throws -> [String] { // Lists accept a non-list value as a list of one. if let listType = type as? GraphQLList { - guard let itemType = listType.ofType as? GraphQLInputType else { + guard let itemType = listType.ofType as? (any GraphQLInputType) else { throw GraphQLError(message: "Input list type must wrap another input type") } @@ -75,7 +75,7 @@ func validate(value: Map, forType type: GraphQLInputType) throws -> [String] { return errors } - if let leafType = type as? GraphQLLeafType { + if let leafType = type as? (any GraphQLLeafType) { // Scalar/Enum input checks to ensure the type can parse the value to // a non-null value. do { diff --git a/Sources/GraphQL/Utilities/TypeComparators.swift b/Sources/GraphQL/Utilities/TypeComparators.swift index 2c686b1a..bbd8787f 100644 --- a/Sources/GraphQL/Utilities/TypeComparators.swift +++ b/Sources/GraphQL/Utilities/TypeComparators.swift @@ -1,7 +1,7 @@ /** * Provided two types, return true if the types are equal (invariant). */ -func isEqualType(_ typeA: GraphQLType, _ typeB: GraphQLType) -> Bool { +public func isEqualType(_ typeA: any GraphQLType, _ typeB: any GraphQLType) -> Bool { // Equivalent types are equal. if typeA == typeB { return true @@ -21,7 +21,7 @@ func isEqualType(_ typeA: GraphQLType, _ typeB: GraphQLType) -> Bool { return false } -func == (lhs: GraphQLType, rhs: GraphQLType) -> Bool { +func == (lhs: any GraphQLType, rhs: any GraphQLType) -> Bool { switch lhs { case let l as GraphQLScalarType: if let r = rhs as? GraphQLScalarType { @@ -70,10 +70,10 @@ func == (lhs: GraphQLType, rhs: GraphQLType) -> Bool { * Provided a type and a super type, return true if the first type is either * equal or a subset of the second super type (covariant). */ -func isTypeSubTypeOf( +public func isTypeSubTypeOf( _ schema: GraphQLSchema, - _ maybeSubType: GraphQLType, - _ superType: GraphQLType + _ maybeSubType: any GraphQLType, + _ superType: any GraphQLType ) throws -> Bool { // Equivalent type is a valid subtype if maybeSubType == superType { @@ -106,7 +106,7 @@ func isTypeSubTypeOf( // If superType type is an abstract type, check if it is super type of maybeSubType. if - let superType = superType as? GraphQLAbstractType, + let superType = superType as? (any GraphQLAbstractType), let maybeSubType = maybeSubType as? GraphQLObjectType, schema.isSubType( abstractType: superType, @@ -117,7 +117,7 @@ func isTypeSubTypeOf( } if - let superType = superType as? GraphQLAbstractType, + let superType = superType as? (any GraphQLAbstractType), let maybeSubType = maybeSubType as? GraphQLInterfaceType, schema.isSubType( abstractType: superType, @@ -142,16 +142,16 @@ func isTypeSubTypeOf( */ func doTypesOverlap( schema: GraphQLSchema, - typeA: GraphQLCompositeType, - typeB: GraphQLCompositeType + typeA: any GraphQLCompositeType, + typeB: any GraphQLCompositeType ) -> Bool { // Equivalent types overlap if typeA == typeB { return true } - if let typeA = typeA as? GraphQLAbstractType { - if let typeB = typeB as? GraphQLAbstractType { + if let typeA = typeA as? (any GraphQLAbstractType) { + if let typeB = typeB as? (any GraphQLAbstractType) { // If both types are abstract, then determine if there is any intersection // between possible concrete types of each. return schema.getPossibleTypes(abstractType: typeA).contains { typeA in @@ -171,10 +171,7 @@ func doTypesOverlap( } } - if - let typeA = typeA as? GraphQLObjectType, - let typeB = typeB as? GraphQLAbstractType - { + if let typeB = typeB as? (any GraphQLAbstractType) { // Determine if the former type is a possible concrete type of the latter. return schema.isSubType( abstractType: typeB, diff --git a/Sources/GraphQL/Utilities/TypeFromAST.swift b/Sources/GraphQL/Utilities/TypeFromAST.swift index 39501ad8..9bd228fb 100644 --- a/Sources/GraphQL/Utilities/TypeFromAST.swift +++ b/Sources/GraphQL/Utilities/TypeFromAST.swift @@ -1,19 +1,15 @@ -func typeFromAST(schema: GraphQLSchema, inputTypeAST: Type) -> GraphQLType? { - if let listType = inputTypeAST as? ListType { +public func typeFromAST(schema: GraphQLSchema, inputTypeAST: Type) -> (any GraphQLType)? { + switch inputTypeAST { + case let .listType(listType): if let innerType = typeFromAST(schema: schema, inputTypeAST: listType.type) { return GraphQLList(innerType) } - } - - if let nonNullType = inputTypeAST as? NonNullType { + case let .nonNullType(nonNullType): if let innerType = typeFromAST(schema: schema, inputTypeAST: nonNullType.type) { - return GraphQLNonNull(innerType as! GraphQLNullableType) + return GraphQLNonNull(innerType as! (any GraphQLNullableType)) } + case let .namedType(namedType): + return schema.getType(name: namedType.name.value) } - - guard let namedType = inputTypeAST as? NamedType else { - return nil - } - - return schema.getType(name: namedType.name.value) + return nil } diff --git a/Sources/GraphQL/Utilities/TypeInfo.swift b/Sources/GraphQL/Utilities/TypeInfo.swift index 1483e69c..f11738bb 100644 --- a/Sources/GraphQL/Utilities/TypeInfo.swift +++ b/Sources/GraphQL/Utilities/TypeInfo.swift @@ -3,16 +3,16 @@ * of the current field and type definitions at any point in a GraphQL document * AST during a recursive descent by calling `enter(node: node)` and `leave(node: node)`. */ -final class TypeInfo { +public final class TypeInfo { let schema: GraphQLSchema; - var typeStack: [GraphQLOutputType?] - var parentTypeStack: [GraphQLCompositeType?] - var inputTypeStack: [GraphQLInputType?] + var typeStack: [(any GraphQLOutputType)?] + var parentTypeStack: [(any GraphQLCompositeType)?] + var inputTypeStack: [(any GraphQLInputType)?] var fieldDefStack: [GraphQLFieldDefinition?] var directive: GraphQLDirective? var argument: GraphQLArgumentDefinition? - init(schema: GraphQLSchema) { + public init(schema: GraphQLSchema) { self.schema = schema self.typeStack = [] self.parentTypeStack = [] @@ -22,41 +22,41 @@ final class TypeInfo { self.argument = nil } - var type: GraphQLOutputType? { + public var type: (any GraphQLOutputType)? { if !typeStack.isEmpty { return typeStack[typeStack.count - 1] } return nil } - var parentType: GraphQLCompositeType? { + public var parentType: (any GraphQLCompositeType)? { if !parentTypeStack.isEmpty { return parentTypeStack[parentTypeStack.count - 1] } return nil } - var inputType: GraphQLInputType? { + public var inputType: (any GraphQLInputType)? { if !inputTypeStack.isEmpty { return inputTypeStack[inputTypeStack.count - 1] } return nil } - var fieldDef: GraphQLFieldDefinition? { + public var fieldDef: GraphQLFieldDefinition? { if !fieldDefStack.isEmpty { return fieldDefStack[fieldDefStack.count - 1] } return nil } - func enter(node: Node) { + public func enter(node: Node) { switch node { case is SelectionSet: let namedType = getNamedType(type: type) - var compositeType: GraphQLCompositeType? = nil + var compositeType: (any GraphQLCompositeType)? = nil - if let type = namedType as? GraphQLCompositeType { + if let type = namedType as? (any GraphQLCompositeType) { compositeType = type } @@ -76,7 +76,7 @@ final class TypeInfo { directive = schema.getDirective(name: node.name.value) case let node as OperationDefinition: - var type: GraphQLOutputType? = nil + var type: (any GraphQLOutputType)? = nil switch node.operation { case .query: @@ -91,19 +91,19 @@ final class TypeInfo { case let node as InlineFragment: let typeConditionAST = node.typeCondition - let outputType = typeConditionAST != nil ? typeFromAST(schema: schema, inputTypeAST: typeConditionAST!) : self.type - typeStack.append(outputType as? GraphQLOutputType) + let outputType = typeConditionAST != nil ? typeFromAST(schema: schema, inputTypeAST: .namedType(typeConditionAST!)) : self.type + typeStack.append(outputType as? (any GraphQLOutputType)) case let node as FragmentDefinition: - let outputType = typeFromAST(schema: schema, inputTypeAST: node.typeCondition) - typeStack.append(outputType as? GraphQLOutputType) + let outputType = typeFromAST(schema: schema, inputTypeAST: .namedType(node.typeCondition)) + typeStack.append(outputType as? (any GraphQLOutputType)) case let node as VariableDefinition: let inputType = typeFromAST(schema: schema, inputTypeAST: node.type) - inputTypeStack.append(inputType as? GraphQLInputType) + inputTypeStack.append(inputType as? (any GraphQLInputType)) case let node as Argument: - var argType: GraphQLInputType? = nil + var argType: (any GraphQLInputType)? = nil if let directive = self.directive { if let argDef = directive.args.find({ $0.name == node.name.value }) { @@ -121,7 +121,7 @@ final class TypeInfo { case is ListType: // could be ListValue if let listType = getNullableType(type: self.inputType) as? GraphQLList { - inputTypeStack.append(listType.ofType as? GraphQLInputType) + inputTypeStack.append(listType.ofType as? (any GraphQLInputType)) } inputTypeStack.append(nil) @@ -137,7 +137,7 @@ final class TypeInfo { } } - func leave(node: Node) { + public func leave(node: Node) { switch node { case is SelectionSet: _ = parentTypeStack.popLast() @@ -149,7 +149,7 @@ final class TypeInfo { case is Directive: directive = nil - case is OperationDefinition, is InlineFragment, is FragmentDefinition: + case is Definition, is InlineFragment: _ = typeStack.popLast() case is VariableDefinition: @@ -173,10 +173,10 @@ final class TypeInfo { * statically evaluated environment we do not always have an Object type, * and need to handle Interface and Union types. */ -func getFieldDef(schema: GraphQLSchema, parentType: GraphQLType, fieldAST: Field) -> GraphQLFieldDefinition? { +public func getFieldDef(schema: GraphQLSchema, parentType: any GraphQLType, fieldAST: Field) -> GraphQLFieldDefinition? { let name = fieldAST.name.value - if let parentType = parentType as? GraphQLNamedType { + if let parentType = parentType as? (any GraphQLNamedType) { if name == SchemaMetaFieldDef.name && schema.queryType.name == parentType.name { return SchemaMetaFieldDef } diff --git a/Sources/GraphQL/Utilities/ValueFromAST.swift b/Sources/GraphQL/Utilities/ValueFromAST.swift index 6e92be72..d20305be 100644 --- a/Sources/GraphQL/Utilities/ValueFromAST.swift +++ b/Sources/GraphQL/Utilities/ValueFromAST.swift @@ -17,18 +17,18 @@ import OrderedCollections * | Enum Value | .string | * */ -func valueFromAST(valueAST: Value, type: GraphQLInputType, variables: [String: Map] = [:]) throws -> Map { +func valueFromAST(valueAST: Value, type: any GraphQLInputType, variables: [String: Map] = [:]) throws -> Map { if let nonNull = type as? GraphQLNonNull { // Note: we're not checking that the result of valueFromAST is non-null. // We're assuming that this query has been validated and the value used // here is of the correct type. - guard let nonNullType = nonNull.ofType as? GraphQLInputType else { + guard let nonNullType = nonNull.ofType as? (any GraphQLInputType) else { throw GraphQLError(message: "NonNull must wrap an input type") } return try valueFromAST(valueAST: valueAST, type: nonNullType, variables: variables) } - if let variable = valueAST as? Variable { + if case .variable(let variable) = valueAST { let variableName = variable.name.value // if (!variables || !variables.hasOwnProperty(variableName)) { @@ -45,11 +45,11 @@ func valueFromAST(valueAST: Value, type: GraphQLInputType, variables: [String: M } if let list = type as? GraphQLList { - guard let itemType = list.ofType as? GraphQLInputType else { + guard let itemType = list.ofType as? (any GraphQLInputType) else { throw GraphQLError(message: "Input list must wrap an input type") } - if let listValue = valueAST as? ListValue { + if case .listValue(let listValue) = valueAST { let values = try listValue.values.map { item in try valueFromAST( valueAST: item, @@ -71,7 +71,7 @@ func valueFromAST(valueAST: Value, type: GraphQLInputType, variables: [String: M } if let objectType = type as? GraphQLInputObjectType { - guard let objectValue = valueAST as? ObjectValue else { + guard case .objectValue(let objectValue) = valueAST else { throw GraphQLError(message: "Input object must be object type") } @@ -98,7 +98,7 @@ func valueFromAST(valueAST: Value, type: GraphQLInputType, variables: [String: M return .dictionary(object) } - if let leafType = type as? GraphQLLeafType { + if let leafType = type as? (any GraphQLLeafType) { return try leafType.parseLiteral(valueAST: valueAST) } diff --git a/Sources/GraphQL/Validation/Rules/FieldsOnCorrectTypeRule.swift b/Sources/GraphQL/Validation/Rules/FieldsOnCorrectTypeRule.swift index 0cb46a24..81426d29 100644 --- a/Sources/GraphQL/Validation/Rules/FieldsOnCorrectTypeRule.swift +++ b/Sources/GraphQL/Validation/Rules/FieldsOnCorrectTypeRule.swift @@ -23,48 +23,45 @@ func undefinedFieldMessage( * A GraphQL document is only valid if all fields selected are defined by the * parent type, or are an allowed meta field such as __typename. */ -func FieldsOnCorrectTypeRule(context: ValidationContext) -> Visitor { - return Visitor( - enter: { node, key, parent, path, ancestors in - if let node = node as? Field { - if let type = context.parentType { - let fieldDef = context.fieldDef - if fieldDef == nil { - // This field doesn't exist, lets look for suggestions. - let schema = context.schema - let fieldName = node.name.value - - // First determine if there are any suggested types to condition on. - let suggestedTypeNames = getSuggestedTypeNames( - schema: schema, - type: type, - fieldName: fieldName - ) - - // If there are no suggested types, then perhaps this was a typo? - let suggestedFieldNames = !suggestedTypeNames.isEmpty ? [] : getSuggestedFieldNames( - schema: schema, - type: type, - fieldName: fieldName - ) - - // Report an error, including helpful suggestions. - context.report(error: GraphQLError( - message: undefinedFieldMessage( - fieldName: fieldName, - type: type.name, - suggestedTypeNames: suggestedTypeNames, - suggestedFieldNames: suggestedFieldNames - ), - nodes: [node] - )) - } - } - } - +struct FieldsOnCorrectTypeRule: ValidationRule { + let context: ValidationContext + func enter(field: Field, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { + guard let type = context.parentType else { return .continue } - ) + guard context.fieldDef == nil else { + return .continue + } + // This field doesn't exist, lets look for suggestions. + let schema = context.schema + let fieldName = field.name.value + + // First determine if there are any suggested types to condition on. + let suggestedTypeNames = getSuggestedTypeNames( + schema: schema, + type: type, + fieldName: fieldName + ) + + // If there are no suggested types, then perhaps this was a typo? + let suggestedFieldNames = !suggestedTypeNames.isEmpty ? [] : getSuggestedFieldNames( + schema: schema, + type: type, + fieldName: fieldName + ) + + // Report an error, including helpful suggestions. + context.report(error: GraphQLError( + message: undefinedFieldMessage( + fieldName: fieldName, + type: type.name, + suggestedTypeNames: suggestedTypeNames, + suggestedFieldNames: suggestedFieldNames + ), + nodes: [field] + )) + return .continue + } } /** @@ -75,10 +72,10 @@ func FieldsOnCorrectTypeRule(context: ValidationContext) -> Visitor { */ func getSuggestedTypeNames( schema: GraphQLSchema, - type: GraphQLOutputType, + type: any GraphQLOutputType, fieldName: String ) -> [String] { - if let type = type as? GraphQLAbstractType { + if let type = type as? (any GraphQLAbstractType) { var suggestedObjectTypes: [String] = [] var interfaceUsageCount: [String: Int] = [:] @@ -119,7 +116,7 @@ func getSuggestedTypeNames( */ func getSuggestedFieldNames( schema: GraphQLSchema, - type: GraphQLOutputType, + type: any GraphQLOutputType, fieldName: String ) -> [String] { if let type = type as? GraphQLObjectType { diff --git a/Sources/GraphQL/Validation/Rules/KnownArgumentNamesRule.swift b/Sources/GraphQL/Validation/Rules/KnownArgumentNamesRule.swift index e9b15925..f8ab9ff7 100644 --- a/Sources/GraphQL/Validation/Rules/KnownArgumentNamesRule.swift +++ b/Sources/GraphQL/Validation/Rules/KnownArgumentNamesRule.swift @@ -16,27 +16,25 @@ import Foundation return message } - func KnownArgumentNamesRule(context: ValidationContext) -> Visitor { - return Visitor( - enter: { node, key, parent, path, ancestors in - if let node = node as? Argument, context.argument == nil, let field = context.fieldDef, let type = context.parentType { - let argumentName = node.name.value - let suggestedArgumentNames = getSuggestedArgumentNames(schema: context.schema, field: field, argumentName: argumentName) - - context.report(error: GraphQLError( - message: undefinedArgumentMessage( - fieldName: field.name, - type: type.name, - argumentName: argumentName, - suggestedArgumentNames: suggestedArgumentNames - ), - nodes: [node] - )) - } - - return .continue +struct KnownArgumentNamesRule: ValidationRule { + let context: ValidationContext + func enter(argument: Argument, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { + if context.argument == nil, let field = context.fieldDef, let type = context.parentType { + let argumentName = argument.name.value + let suggestedArgumentNames = getSuggestedArgumentNames(schema: context.schema, field: field, argumentName: argumentName) + + context.report(error: GraphQLError( + message: undefinedArgumentMessage( + fieldName: field.name, + type: type.name, + argumentName: argumentName, + suggestedArgumentNames: suggestedArgumentNames + ), + nodes: [argument] + )) } - ) + return .continue + } } func getSuggestedArgumentNames( diff --git a/Sources/GraphQL/Validation/Rules/KnownFragmentNamesRule.swift b/Sources/GraphQL/Validation/Rules/KnownFragmentNamesRule.swift new file mode 100644 index 00000000..e3754841 --- /dev/null +++ b/Sources/GraphQL/Validation/Rules/KnownFragmentNamesRule.swift @@ -0,0 +1,26 @@ +import Foundation + +/** + * Known fragment names + * + * A GraphQL document is only valid if all `...Fragment` fragment spreads refer + * to fragments defined in the same document. + * + * See https://spec.graphql.org/draft/#sec-Fragment-spread-target-defined + */ +struct KnownFragmentNamesRule: ValidationRule { + let context: ValidationContext + + func enter(fragmentSpread: FragmentSpread, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { + let fragmentName = fragmentSpread.name.value + if context.getFragment(name: fragmentName) == nil { + context.report( + error: GraphQLError( + message: "Unknown fragment \(fragmentName)", + nodes: [fragmentSpread.name] + ) + ) + } + return .continue + } +} diff --git a/Sources/GraphQL/Validation/Rules/NoUnusedVariablesRule.swift b/Sources/GraphQL/Validation/Rules/NoUnusedVariablesRule.swift index 7daa3bd5..4bc06424 100644 --- a/Sources/GraphQL/Validation/Rules/NoUnusedVariablesRule.swift +++ b/Sources/GraphQL/Validation/Rules/NoUnusedVariablesRule.swift @@ -4,51 +4,37 @@ * A GraphQL operation is only valid if all variables defined by an operation * are used, either directly or within a spread fragment. */ -func NoUnusedVariablesRule(context: ValidationContext) -> Visitor { - var variableDefs: [VariableDefinition] = [] - - return Visitor( - enter: { node, _, _, _, _ in - if node is OperationDefinition { - variableDefs = [] - return .continue - } +class NoUnusedVariablesRule: ValidationRule { + private var variableDefs: [VariableDefinition] = [] + let context: ValidationContext + required init(context: ValidationContext) { self.context = context } + + func enter(operationDefinition: OperationDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { + variableDefs = [] + return .continue + } + + func enter(variableDefinition: VariableDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { + variableDefs.append(variableDefinition) + return .continue + } + + func leave(operationDefinition: OperationDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { + let usages = Set(context.getRecursiveVariableUsages(operation: operationDefinition).map { $0.node.name }) + + for variableDef in variableDefs where !usages.contains(variableDef.variable.name) { + let variableName = variableDef.variable.name.value - if let def = node as? VariableDefinition { - variableDefs.append(def) - return .continue - } - - return .continue - }, - leave: { node, _, _, _, _ -> VisitResult in - guard let operation = node as? OperationDefinition else { - return .continue - } - - var variableNameUsed: [String: Bool] = [:] - let usages = context.getRecursiveVariableUsages(operation: operation) - - for usage in usages { - variableNameUsed[usage.node.name.value] = true - } - - for variableDef in variableDefs { - let variableName = variableDef.variable.name.value - - if variableNameUsed[variableName] != true { - context.report( - error: GraphQLError( - message: operation.name.map { - "Variable \"$\(variableName)\" is never used in operation \"\($0.value)\"." - } ?? "Variable \"$\(variableName)\" is never used.", - nodes: [variableDef] - ) - ) - } - } - - return .continue + context.report( + error: GraphQLError( + message: operationDefinition.name.map { + "Variable \"$\(variableName)\" is never used in operation \"\($0.value)\"." + } ?? "Variable \"$\(variableName)\" is never used.", + nodes: [variableDef] + ) + ) } - ) + + return .continue + } } diff --git a/Sources/GraphQL/Validation/Rules/PossibleFragmentSpreadsRule.swift b/Sources/GraphQL/Validation/Rules/PossibleFragmentSpreadsRule.swift index 86b46241..aade7393 100644 --- a/Sources/GraphQL/Validation/Rules/PossibleFragmentSpreadsRule.swift +++ b/Sources/GraphQL/Validation/Rules/PossibleFragmentSpreadsRule.swift @@ -5,79 +5,78 @@ * be true: if there is a non-empty intersection of the possible parent types, * and possible types which pass the type condition. */ -func PossibleFragmentSpreadsRule(context: ValidationContext) -> Visitor { - return Visitor( - enter: { node, key, parent, path, ancestors in - if let node = node as? InlineFragment { - guard - let fragType = context.type as? GraphQLCompositeType, - let parentType = context.parentType - else { - return .continue - } - - let isThereOverlap = doTypesOverlap( - schema: context.schema, - typeA: fragType, - typeB: parentType - ) - - guard !isThereOverlap else { - return .continue - } - - context.report( - error: GraphQLError( - message: "Fragment cannot be spread here as objects of type \"\(parentType)\" can never be of type \"\(fragType)\".", - nodes: [node] - ) - ) - } - - if let node = node as? FragmentSpread { - let fragName = node.name.value - - guard - let fragType = getFragmentType(context: context, name: fragName), - let parentType = context.parentType - else { - return .continue - } - - let isThereOverlap = doTypesOverlap( - schema: context.schema, - typeA: fragType, - typeB: parentType - ) - - guard !isThereOverlap else { - return .continue - } - - context.report( - error: GraphQLError( - message: "Fragment \"\(fragName)\" cannot be spread here as objects of type \"\(parentType)\" can never be of type \"\(fragType)\".", - nodes: [node] - ) - ) - } - +struct PossibleFragmentSpreadsRule: ValidationRule { + let context: ValidationContext + + func enter(inlineFragment: InlineFragment, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { + guard + let fragType = context.type as? (any GraphQLCompositeType), + let parentType = context.parentType + else { return .continue } - ) + + let isThereOverlap = doTypesOverlap( + schema: context.schema, + typeA: fragType, + typeB: parentType + ) + + guard !isThereOverlap else { + return .continue + } + + context.report( + error: GraphQLError( + message: "Fragment cannot be spread here as objects of type \"\(parentType)\" can never be of type \"\(fragType)\".", + nodes: [inlineFragment] + ) + ) + return .continue + } + + func enter(fragmentSpread: FragmentSpread, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { + + let fragName = fragmentSpread.name.value + + guard + let fragType = getFragmentType(context: context, name: fragName), + let parentType = context.parentType + else { + return .continue + } + + let isThereOverlap = doTypesOverlap( + schema: context.schema, + typeA: fragType, + typeB: parentType + ) + + guard !isThereOverlap else { + return .continue + } + + context.report( + error: GraphQLError( + message: "Fragment \"\(fragName)\" cannot be spread here as objects of type \"\(parentType)\" can never be of type \"\(fragType)\".", + nodes: [fragmentSpread] + ) + ) + return .continue + } } func getFragmentType( context: ValidationContext, name: String -) -> GraphQLCompositeType? { +) -> (any GraphQLCompositeType)? { if let fragment = context.getFragment(name: name) { let type = typeFromAST( schema: context.schema, - inputTypeAST: fragment.typeCondition + inputTypeAST: .namedType(fragment.typeCondition) ) - if let type = type as? GraphQLCompositeType { + if let type = type as? (any GraphQLCompositeType) { return type } } diff --git a/Sources/GraphQL/Validation/Rules/ProvidedNonNullArgumentsRule.swift b/Sources/GraphQL/Validation/Rules/ProvidedNonNullArgumentsRule.swift index c9335e6a..f7f1f769 100644 --- a/Sources/GraphQL/Validation/Rules/ProvidedNonNullArgumentsRule.swift +++ b/Sources/GraphQL/Validation/Rules/ProvidedNonNullArgumentsRule.swift @@ -9,33 +9,32 @@ func missingArgumentsMessage( return "Field \"\(fieldName)\" on type \"\(type)\" is missing required arguments \(arguments)." } - func ProvidedNonNullArgumentsRule(context: ValidationContext) -> Visitor { - return Visitor( - leave: { node, key, parent, path, ancestors in - if let node = node as? Field, let field = context.fieldDef, let type = context.parentType { - let requiredArguments = Set( - field - .args - .filter { $0.type is GraphQLNonNull && $0.defaultValue == nil } - .map { $0.name } - ) - - let providedArguments = Set(node.arguments.map { $0.name.value }) - - let missingArguments = requiredArguments.subtracting(providedArguments) - if !missingArguments.isEmpty { - context.report(error: GraphQLError( - message: missingArgumentsMessage( - fieldName: field.name, - type: type.name, - missingArguments: Array(missingArguments) - ), - nodes: [node] - )) - } +struct ProvidedNonNullArgumentsRule: ValidationRule { + let context: ValidationContext + func leave(field: Field, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { + if let fieldDef = context.fieldDef, let type = context.parentType { + let requiredArguments = Set( + fieldDef + .args + .filter { $0.type is GraphQLNonNull && $0.defaultValue == nil } + .map { $0.name } + ) + + let providedArguments = Set(field.arguments.map { $0.name.value }) + + let missingArguments = requiredArguments.subtracting(providedArguments) + if !missingArguments.isEmpty { + context.report(error: GraphQLError( + message: missingArgumentsMessage( + fieldName: fieldDef.name, + type: type.name, + missingArguments: Array(missingArguments) + ), + nodes: [field] + )) } - - return .continue } - ) + + return .continue + } } diff --git a/Sources/GraphQL/Validation/Rules/ScalarLeafsRule.swift b/Sources/GraphQL/Validation/Rules/ScalarLeafsRule.swift index 2379d7a8..9de69a96 100644 --- a/Sources/GraphQL/Validation/Rules/ScalarLeafsRule.swift +++ b/Sources/GraphQL/Validation/Rules/ScalarLeafsRule.swift @@ -1,9 +1,9 @@ -func noSubselectionAllowedMessage(fieldName: String, type: GraphQLType) -> String { +func noSubselectionAllowedMessage(fieldName: String, type: any GraphQLType) -> String { return "Field \"\(fieldName)\" must not have a selection since " + "type \"\(type)\" has no subfields." } -func requiredSubselectionMessage(fieldName: String, type: GraphQLType) -> String { +func requiredSubselectionMessage(fieldName: String, type: any GraphQLType) -> String { return "Field \"\(fieldName)\" of type \"\(type)\" must have a " + "selection of subfields. Did you mean \"\(fieldName) { ... }\"?" } @@ -14,30 +14,26 @@ func requiredSubselectionMessage(fieldName: String, type: GraphQLType) -> String * A GraphQL document is valid only if all leaf fields (fields without * sub selections) are of scalar or enum types. */ -func ScalarLeafsRule(context: ValidationContext) -> Visitor { - return Visitor( - enter: { node, key, parent, path, ancestors in - if let node = node as? Field { - if let type = context.type { - if isLeafType(type: getNamedType(type: type)) { - if let selectionSet = node.selectionSet { - let error = GraphQLError( - message: noSubselectionAllowedMessage(fieldName: node.name.value, type: type), - nodes: [selectionSet] - ) - context.report(error: error) - } - } else if node.selectionSet == nil { - let error = GraphQLError( - message: requiredSubselectionMessage(fieldName: node.name.value, type: type), - nodes: [node] - ) - context.report(error: error) - } +struct ScalarLeafsRule: ValidationRule { + let context: ValidationContext + func enter(field: Field, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { + if let type = context.type { + if isLeafType(type: getNamedType(type: type)) { + if let selectionSet = field.selectionSet { + let error = GraphQLError( + message: noSubselectionAllowedMessage(fieldName: field.name.value, type: type), + nodes: [selectionSet] + ) + context.report(error: error) } + } else if field.selectionSet == nil { + let error = GraphQLError( + message: requiredSubselectionMessage(fieldName: field.name.value, type: type), + nodes: [field] + ) + context.report(error: error) } - - return .continue } - ) + return .continue + } } diff --git a/Sources/GraphQL/Validation/Rules/VariablesAreInputTypesRule.swift b/Sources/GraphQL/Validation/Rules/VariablesAreInputTypesRule.swift new file mode 100644 index 00000000..9ffcbd5b --- /dev/null +++ b/Sources/GraphQL/Validation/Rules/VariablesAreInputTypesRule.swift @@ -0,0 +1,21 @@ +/** + * Variables are input types + * + * A GraphQL operation is only valid if all the variables it defines are of + * input types (scalar, enum, or input object). + * + * See https://spec.graphql.org/draft/#sec-Variables-Are-Input-Types + */ +struct VariablesAreInputTypesRule: ValidationRule { + let context: ValidationContext + func enter(variableDefinition: VariableDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { + if context.inputType == nil { + let error = GraphQLError( + message: "Variable \"$\(variableDefinition.variable.name.value)\" cannot be non-input type \"\(variableDefinition.type.printed)\".", + nodes: [variableDefinition.type] + ) + context.report(error: error) + } + return .continue + } +} diff --git a/Sources/GraphQL/Validation/SpecifiedRules.swift b/Sources/GraphQL/Validation/SpecifiedRules.swift index 74a521c9..e20e05d0 100644 --- a/Sources/GraphQL/Validation/SpecifiedRules.swift +++ b/Sources/GraphQL/Validation/SpecifiedRules.swift @@ -1,29 +1,30 @@ /** * This set includes all validation rules defined by the GraphQL spec. */ -let specifiedRules: [(ValidationContext) -> Visitor] = [ -// UniqueOperationNames, -// LoneAnonymousOperation, -// KnownTypeNames, -// FragmentsOnCompositeTypes, -// VariablesAreInputTypes, - ScalarLeafsRule, - FieldsOnCorrectTypeRule, -// UniqueFragmentNames, -// KnownFragmentNames, -// NoUnusedFragments, - PossibleFragmentSpreadsRule, -// NoFragmentCycles, -// UniqueVariableNames, -// NoUndefinedVariables, - NoUnusedVariablesRule, -// KnownDirectives, - KnownArgumentNamesRule, -// UniqueArgumentNames, -// ArgumentsOfCorrectType, - ProvidedNonNullArgumentsRule, -// DefaultValuesOfCorrectType, -// VariablesInAllowedPosition, -// OverlappingFieldsCanBeMerged, -// UniqueInputFieldNames, +let specifiedRules: [ValidationRule.Type] = [ + // UniqueOperationNames, + // LoneAnonymousOperation, + // KnownTypeNames, + // FragmentsOnCompositeTypes, + // VariablesAreInputTypes, + ScalarLeafsRule.self, + FieldsOnCorrectTypeRule.self, + // UniqueFragmentNames, + KnownFragmentNamesRule.self, + // NoUnusedFragments, + PossibleFragmentSpreadsRule.self, + // NoFragmentCycles, + // UniqueVariableNames, + // NoUndefinedVariables, + NoUnusedVariablesRule.self, + // KnownDirectives, + KnownArgumentNamesRule.self, + // UniqueArgumentNames, + // ArgumentsOfCorrectType, + ProvidedNonNullArgumentsRule.self, + // DefaultValuesOfCorrectType, + // VariablesInAllowedPosition, + // OverlappingFieldsCanBeMerged, + // UniqueInputFieldNames, + VariablesAreInputTypesRule.self ] diff --git a/Sources/GraphQL/Validation/Validate.swift b/Sources/GraphQL/Validation/Validate.swift index baa342fa..e28e14bf 100644 --- a/Sources/GraphQL/Validation/Validate.swift +++ b/Sources/GraphQL/Validation/Validate.swift @@ -1,21 +1,3 @@ -/// Implements the "Validation" section of the spec. -/// -/// Validation runs synchronously, returning an array of encountered errors, or -/// an empty array if no errors were encountered and the document is valid. -/// -/// - Parameters: -/// - instrumentation: The instrumentation implementation to call during the parsing, validating, execution, and field resolution stages. -/// - schema: The GraphQL type system to use when validating and executing a query. -/// - ast: A GraphQL document representing the requested operation. -/// - Returns: zero or more errors -public func validate( - instrumentation: Instrumentation = NoOpInstrumentation, - schema: GraphQLSchema, - ast: Document -) -> [GraphQLError] { - return validate(instrumentation: instrumentation, schema: schema, ast: ast, rules: []) -} - /** * Implements the "Validation" section of the spec. * @@ -28,21 +10,44 @@ public func validate( * Each validation rules is a function which returns a visitor * (see the language/visitor API). Visitor methods are expected to return * GraphQLErrors, or Arrays of GraphQLErrors when invalid. + * + * - Parameters: + * - instrumentation: The instrumentation implementation to call during the parsing, validating, execution, and field resolution stages. + * - schema: The GraphQL type system to use when validating and executing a query. + * - ast: A GraphQL document representing the requested operation. + */ + +public func validate( + instrumentation: Instrumentation = NoOpInstrumentation, + schema: GraphQLSchema, + ast: Document +) -> [GraphQLError] { + validate(instrumentation: instrumentation, schema: schema, ast: ast, rules: specifiedRules) +} + +/** + * An internal version of `validate` that lets you specify custom validation rules. + * + * - Parameters: + * - rules: A list of specific validation rules. If not provided, the default list of rules defined by the GraphQL specification will be used. */ func validate( instrumentation: Instrumentation = NoOpInstrumentation, schema: GraphQLSchema, ast: Document, - rules: [(ValidationContext) -> Visitor] + rules: [ValidationRule.Type] ) -> [GraphQLError] { let started = instrumentation.now let typeInfo = TypeInfo(schema: schema) - let rules = rules.isEmpty ? specifiedRules : rules let errors = visit(usingRules: rules, schema: schema, typeInfo: typeInfo, documentAST: ast) instrumentation.queryValidation(processId: processId(), threadId: threadId(), started: started, finished: instrumentation.now, schema: schema, document: ast, errors: errors) return errors } +protocol ValidationRule: Visitor { + init(context: ValidationContext) +} + /** * This uses a specialized visitor which runs multiple visitors in parallel, * while maintaining the visitor skip and break API. @@ -50,55 +55,30 @@ func validate( * @internal */ func visit( - usingRules rules: [(ValidationContext) -> Visitor], + usingRules rules: [ValidationRule.Type], schema: GraphQLSchema, typeInfo: TypeInfo, documentAST: Document ) -> [GraphQLError] { let context = ValidationContext(schema: schema, ast: documentAST, typeInfo: typeInfo) - let visitors = rules.map({ rule in rule(context) }) + let visitors = rules.map({ rule in rule.init(context: context) }) // Visit the whole document with each instance of all provided rules. - visit(root: documentAST, visitor: visitWithTypeInfo(typeInfo: typeInfo, visitor: visitInParallel(visitors: visitors))) + visit(root: documentAST, visitor: VisitorWithTypeInfo( + visitor: ParallelVisitor(visitors: visitors), + typeInfo: typeInfo + )) return context.errors } -enum HasSelectionSet { - case operation(OperationDefinition) - case fragment(FragmentDefinition) - - var node: Node { - switch self { - case .operation(let operation): - return operation - case .fragment(let fragment): - return fragment - } - } +protocol HasSelectionSet: Node { + var selectionSet: SelectionSet { get } } -extension HasSelectionSet : Hashable { - func hash(into hasher: inout Hasher) { - switch self { - case .operation(let operation): - return hasher.combine(operation.hashValue) - case .fragment(let fragment): - return hasher.combine(fragment.hashValue) - } - } +extension OperationDefinition: HasSelectionSet { } +extension FragmentDefinition: HasSelectionSet { } - static func == (lhs: HasSelectionSet, rhs: HasSelectionSet) -> Bool { - switch (lhs, rhs) { - case (.operation(let l), .operation(let r)): - return l == r - case (.fragment(let l), .fragment(let r)): - return l == r - default: - return false - } - } -} -typealias VariableUsage = (node: Variable, type: GraphQLInputType?) +typealias VariableUsage = (node: Variable, type: (any GraphQLInputType)?) /** * An instance of this class is passed as the "this" context to all validators, @@ -111,10 +91,11 @@ final class ValidationContext { let typeInfo: TypeInfo var errors: [GraphQLError] var fragments: [String: FragmentDefinition] - var fragmentSpreads: [SelectionSet: [FragmentSpread]] - var recursivelyReferencedFragments: [OperationDefinition: [FragmentDefinition]] - var variableUsages: [HasSelectionSet: [VariableUsage]] - var recursiveVariableUsages: [OperationDefinition: [VariableUsage]] + // TODO: memoise all these caches +// var fragmentSpreads: [SelectionSet: [FragmentSpread]] +// var recursivelyReferencedFragments: [OperationDefinition: [FragmentDefinition]] +// var variableUsages: [HasSelectionSet: [VariableUsage]] +// var recursiveVariableUsages: [OperationDefinition: [VariableUsage]] init(schema: GraphQLSchema, ast: Document, typeInfo: TypeInfo) { self.schema = schema @@ -122,10 +103,10 @@ final class ValidationContext { self.typeInfo = typeInfo self.errors = [] self.fragments = [:] - self.fragmentSpreads = [:] - self.recursivelyReferencedFragments = [:] - self.variableUsages = [:] - self.recursiveVariableUsages = [:] +// self.fragmentSpreads = [:] +// self.recursivelyReferencedFragments = [:] +// self.variableUsages = [:] +// self.recursiveVariableUsages = [:] } func report(error: GraphQLError) { @@ -139,7 +120,7 @@ final class ValidationContext { fragments = ast.definitions.reduce([:]) { frags, statement in var frags = frags - if let statement = statement as? FragmentDefinition { + if case let .executableDefinition(.fragment(statement)) = statement { frags[statement.name.value] = statement } @@ -153,116 +134,88 @@ final class ValidationContext { } func getFragmentSpreads(node: SelectionSet) -> [FragmentSpread] { - var spreads = fragmentSpreads[node] - - if spreads == nil { - spreads = [] - var setsToVisit: [SelectionSet] = [node] - - while let set = setsToVisit.popLast() { - for selection in set.selections { - if let selection = selection as? FragmentSpread { - spreads!.append(selection) - } - - if let selection = selection as? InlineFragment { - setsToVisit.append(selection.selectionSet) - } - - if let selection = selection as? Field, let selectionSet = selection.selectionSet { + var spreads: [FragmentSpread] = [] + var setsToVisit: [SelectionSet] = [node] + + while let set = setsToVisit.popLast() { + for selection in set.selections { + switch selection { + case let .fragmentSpread(fragmentSpread): + spreads.append(fragmentSpread) + case let .inlineFragment(inlineFragment): + setsToVisit.append(inlineFragment.selectionSet) + case let .field(field): + if let selectionSet = field.selectionSet { setsToVisit.append(selectionSet) } } } - - fragmentSpreads[node] = spreads } - - return spreads! + return spreads } func getRecursivelyReferencedFragments(operation: OperationDefinition) -> [FragmentDefinition] { - var fragments = recursivelyReferencedFragments[operation] - - if fragments == nil { - fragments = [] - var collectedNames: [String: Bool] = [:] - var nodesToVisit: [SelectionSet] = [operation.selectionSet] - - while let node = nodesToVisit.popLast() { - let spreads = getFragmentSpreads(node: node) - - for spread in spreads { - let fragName = spread.name.value - if collectedNames[fragName] != true { - collectedNames[fragName] = true - if let fragment = getFragment(name: fragName) { - fragments!.append(fragment) - nodesToVisit.append(fragment.selectionSet) - } + var fragments: [FragmentDefinition] = [] + var collectedNames: [String: Bool] = [:] + var nodesToVisit: [SelectionSet] = [operation.selectionSet] + + while let node = nodesToVisit.popLast() { + let spreads = getFragmentSpreads(node: node) + + for spread in spreads { + let fragName = spread.name.value + if collectedNames[fragName] != true { + collectedNames[fragName] = true + if let fragment = getFragment(name: fragName) { + fragments.append(fragment) + nodesToVisit.append(fragment.selectionSet) } } } - - recursivelyReferencedFragments[operation] = fragments } - - return fragments! - } - - func getVariableUsages(node: HasSelectionSet) -> [VariableUsage] { - var usages = variableUsages[node] - - if usages == nil { - var newUsages: [VariableUsage] = [] - let typeInfo = TypeInfo(schema: schema) - - visit(root: node.node, visitor: visitWithTypeInfo(typeInfo: typeInfo, visitor: Visitor(enter: { node, _, _, _, _ in - if node is VariableDefinition { - return .skip - } - - if let variable = node as? Variable { - newUsages.append(VariableUsage(node: variable, type: typeInfo.inputType)) - } - - return .continue - }))) - - usages = newUsages - variableUsages[node] = usages + return fragments + } + + class VariableUsageFinder: Visitor { + var newUsages: [VariableUsage] = [] + let typeInfo: TypeInfo + init(typeInfo: TypeInfo) { self.typeInfo = typeInfo } + func enter(variableDefinition: VariableDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { + .skip + } + func enter(variable: Variable, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { + newUsages.append(VariableUsage(node: variable, type: typeInfo.inputType)) + return .continue } + } - return usages! + func getVariableUsages(node: T) -> [VariableUsage] { + let typeInfo = TypeInfo(schema: schema) + let visitor = VariableUsageFinder(typeInfo: typeInfo) + visit(root: node, visitor: VisitorWithTypeInfo(visitor: visitor, typeInfo: typeInfo)) + return visitor.newUsages } func getRecursiveVariableUsages(operation: OperationDefinition) -> [VariableUsage] { - var usages = recursiveVariableUsages[operation] - - if usages == nil { - usages = getVariableUsages(node: .operation(operation)) - let fragments = getRecursivelyReferencedFragments(operation: operation) - - for fragment in fragments { - let newUsages = getVariableUsages(node: .fragment(fragment)) - usages!.append(contentsOf: newUsages) - } - - recursiveVariableUsages[operation] = usages - } + var usages = getVariableUsages(node: operation) + let fragments = getRecursivelyReferencedFragments(operation: operation) - return usages! + for fragment in fragments { + let newUsages = getVariableUsages(node: fragment) + usages.append(contentsOf: newUsages) + } + return usages } - var type: GraphQLOutputType? { + var type: (any GraphQLOutputType)? { return typeInfo.type } - var parentType: GraphQLCompositeType? { + var parentType: (any GraphQLCompositeType)? { return typeInfo.parentType } - var inputType: GraphQLInputType? { + var inputType: (any GraphQLInputType)? { return typeInfo.inputType } diff --git a/Tests/GraphQLTests/LanguageTests/ParserTests.swift b/Tests/GraphQLTests/LanguageTests/ParserTests.swift index 49260e86..c327f6e3 100644 --- a/Tests/GraphQLTests/LanguageTests/ParserTests.swift +++ b/Tests/GraphQLTests/LanguageTests/ParserTests.swift @@ -1,459 +1,459 @@ -import XCTest -@testable import GraphQL - -class ParserTests : XCTestCase { - func testErrorMessages() throws { - var source: String - - XCTAssertThrowsError(try parse(source: "{")) { error in - guard let error = error as? GraphQLError else { - return XCTFail() - } - - XCTAssertEqual(error.message, - "Syntax Error GraphQL (1:2) Expected Name, found \n\n" + - " 1: {\n" + - " ^\n" - ) - - XCTAssertEqual(error.positions, [1]) - XCTAssertEqual(error.locations[0].line, 1) - XCTAssertEqual(error.locations[0].column, 2) - } - - XCTAssertThrowsError(try parse(source: "{ ...MissingOn }\nfragment MissingOn Type\n")) { error in - guard let error = error as? GraphQLError else { - return XCTFail() - } - - XCTAssert(error.message.contains( - "Syntax Error GraphQL (2:20) Expected \"on\", found Name \"Type\"" - )) - } - - XCTAssertThrowsError(try parse(source: "{ field: {} }")) { error in - guard let error = error as? GraphQLError else { - return XCTFail() - } - - XCTAssert(error.message.contains( - "Syntax Error GraphQL (1:10) Expected Name, found {" - )) - } - - XCTAssertThrowsError(try parse(source: "notanoperation Foo { field }")) { error in - guard let error = error as? GraphQLError else { - return XCTFail() - } - - XCTAssert(error.message.contains( - "Syntax Error GraphQL (1:1) Unexpected Name \"notanoperation\"" - )) - } - - XCTAssertThrowsError(try parse(source: "...")) { error in - guard let error = error as? GraphQLError else { - return XCTFail() - } - - XCTAssert(error.message.contains( - "Syntax Error GraphQL (1:1) Unexpected ..." - )) - } - - XCTAssertThrowsError(try parse(source: Source(body: "query", name: "MyQuery.graphql"))) { error in - guard let error = error as? GraphQLError else { - return XCTFail() - } - - XCTAssert(error.message.contains( - "Syntax Error MyQuery.graphql (1:6) Expected {, found " - )) - } - - source = "query Foo($x: Complex = { a: { b: [ $var ] } }) { field }" - - XCTAssertThrowsError(try parse(source: source)) { error in - guard let error = error as? GraphQLError else { - return XCTFail() - } - - XCTAssert(error.message.contains( - "Syntax Error GraphQL (1:37) Unexpected $" - )) - } - - XCTAssertThrowsError(try parse(source: "fragment on on on { on }")) { error in - guard let error = error as? GraphQLError else { - return XCTFail() - } - - XCTAssert(error.message.contains( - "Syntax Error GraphQL (1:10) Unexpected Name \"on\"" - )) - } - - XCTAssertThrowsError(try parse(source: "{ ...on }")) { error in - guard let error = error as? GraphQLError else { - return XCTFail() - } - - XCTAssert(error.message.contains( - "Syntax Error GraphQL (1:9) Expected Name, found }" - )) - } - - } - - func testVariableInlineValues() throws { - _ = try parse(source: "{ field(complex: { a: { b: [ $var ] } }) }") - } - - func testFieldWithArguments() throws { - let query = """ - { - stringArgField(stringArg: "Hello World") - intArgField(intArg: 1) - floatArgField(floatArg: 3.14) - falseArgField(boolArg: false) - trueArgField(boolArg: true) - nullArgField(value: null) - enumArgField(enumArg: VALUE) - multipleArgs(arg1: 1, arg2: false, arg3: THIRD) - } - """ - - let expected = Document( - definitions: [ - OperationDefinition( - operation: .query, - selectionSet: SelectionSet( - selections: [ - Field( - name: Name(value: "stringArgField"), - arguments: [ - Argument( - name: Name(value: "stringArg"), - value: StringValue(value: "Hello World", block: false) - ) - ] - ), - Field( - name: Name(value: "intArgField"), - arguments: [ - Argument( - name: Name(value: "intArg"), - value: IntValue(value: "1") - ) - ] - ), - Field( - name: Name(value: "floatArgField"), - arguments: [ - Argument( - name: Name(value: "floatArg"), - value: FloatValue(value: "3.14") - ) - ] - ), - Field( - name: Name(value: "falseArgField"), - arguments: [ - Argument( - name: Name(value: "boolArg"), - value: BooleanValue(value: false) - ) - ] - ), - Field( - name: Name(value: "trueArgField"), - arguments: [ - Argument( - name: Name(value: "boolArg"), - value: BooleanValue(value: true) - ) - ] - ), - Field( - name: Name(value: "nullArgField"), - arguments: [ - Argument( - name: Name(value: "value"), - value: NullValue() - ) - ] - ), - Field( - name: Name(value: "enumArgField"), - arguments: [ - Argument( - name: Name(value: "enumArg"), - value: EnumValue(value: "VALUE") - ) - ] - ), - Field( - name: Name(value: "multipleArgs"), - arguments: [ - Argument( - name: Name(value: "arg1"), - value: IntValue(value: "1") - ), - Argument( - name: Name(value: "arg2"), - value: BooleanValue(value: false) - ), - Argument( - name: Name(value: "arg3"), - value: EnumValue(value: "THIRD") - ), - ] - ), - ] - ) - ) - ] - ) - - let document = try parse(source: query) - XCTAssert(document == expected) - } - -// it('parses multi-byte characters', async () => { -// // Note: \u0A0A could be naively interpretted as two line-feed chars. -// expect( -// parse(` -// # This comment has a \u0A0A multi-byte character. -// { field(arg: "Has a \u0A0A multi-byte character.") } -// `) -// ).to.containSubset({ -// definitions: [ { -// selectionSet: { -// selections: [ { -// arguments: [ { -// value: { -// kind: Kind.STRING, -// value: 'Has a \u0A0A multi-byte character.' -// } -// } ] -// } ] +//import XCTest +//@testable import GraphQL +// +//class ParserTests : XCTestCase { +// func testErrorMessages() throws { +// var source: String +// +// XCTAssertThrowsError(try parse(source: "{")) { error in +// guard let error = error as? GraphQLError else { +// return XCTFail() +// } +// +// XCTAssertEqual(error.message, +// "Syntax Error GraphQL (1:2) Expected Name, found \n\n" + +// " 1: {\n" + +// " ^\n" +// ) +// +// XCTAssertEqual(error.positions, [1]) +// XCTAssertEqual(error.locations[0].line, 1) +// XCTAssertEqual(error.locations[0].column, 2) // } -// } ] -// }); -// }); - - func testKitchenSink() throws { -// let path = "/Users/paulofaria/Development/Zewo/GraphQL/Tests/GraphQLTests/LanguageTests/kitchen-sink.graphql" -// let kitchenSink = try NSString(contentsOfFile: path, encoding: String.Encoding.utf8.rawValue) -// _ = try parse(source: kitchenSink as String) - } - - func testNonKeywordAsName() throws { - let nonKeywords = [ - "on", - "fragment", - "query", - "mutation", - "subscription", - "true", - "false" - ] - - for nonKeyword in nonKeywords { - var fragmentName = nonKeyword - // You can't define or reference a fragment named `on`. - if nonKeyword == "on" { - fragmentName = "a" - } - - _ = try parse(source: "query \(nonKeyword) {" + - "... \(fragmentName)" + - "... on \(nonKeyword) { field }" + - "}" + - "fragment \(fragmentName) on Type {" + - "\(nonKeyword)(\(nonKeyword): $\(nonKeyword)) @\(nonKeyword)(\(nonKeyword): \(nonKeyword))" + - "}" - ) - } - } - - func testAnonymousMutationOperation() throws { - _ = try parse(source: "mutation {" + - " mutationField" + - "}" - ) - } - - func testAnonymousSubscriptionOperation() throws { - _ = try parse(source: "subscription {" + - " subscriptionField" + - "}" - ) - } - - func testNamedMutationOperation() throws { - _ = try parse(source: "mutation Foo {" + - " mutationField" + - "}" - ) - } - - func testNamedSubscriptionOperation() throws { - _ = try parse(source: "subscription Foo {" + - " subscriptionField" + - "}" - ) - } - - func testCreateAST() throws { - let query = "{" + - " node(id: 4) {" + - " id," + - " name" + - " }" + - "}" - - let expected = Document( - definitions: [ - OperationDefinition( - operation: .query, - selectionSet: SelectionSet( - selections: [ - Field( - name: Name(value: "node"), - arguments: [ - Argument( - name: Name(value: "id"), - value: IntValue(value: "4") - ) - ], - selectionSet: SelectionSet( - selections: [ - Field(name: Name(value: "id")), - Field(name: Name(value: "name")) - ] - ) - ) - ] - ) - ) - ] - ) - - XCTAssert(try parse(source: query) == expected) - } - - func testNoLocation() throws { - let result = try parse(source: "{ id }", noLocation: true) - XCTAssertNil(result.loc) - } - - func testLocationSource() throws { - let source = Source(body: "{ id }") - let result = try parse(source: source) - XCTAssertEqual(result.loc?.source, source) - } - - func testLocationTokens() throws { - let source = Source(body: "{ id }") - let result = try parse(source: source) - XCTAssertEqual(result.loc?.startToken.kind, .sof) - XCTAssertEqual(result.loc?.endToken.kind, .eof) - } - - func testParseValue() throws { - let source = "[123 \"abc\"]" - - let expected: Value = ListValue( - values: [ - IntValue(value: "123"), - StringValue(value: "abc", block: false) - ] - ) - - XCTAssert(try parseValue(source: source) == expected) - } - - func testParseType() throws { - var source: String - var expected: Type - - source = "String" - - expected = NamedType( - name: Name(value: "String") - ) - - XCTAssert(try parseType(source: source) == expected) - - source = "MyType" - - expected = NamedType( - name: Name(value: "MyType") - ) - - XCTAssert(try parseType(source: source) == expected) - - source = "[MyType]" - - expected = ListType( - type: NamedType( - name: Name(value: "MyType") - ) - ) - - XCTAssert(try parseType(source: source) == expected) - - source = "MyType!" - - expected = NonNullType( - type: NamedType( - name: Name(value: "MyType") - ) - ) - - XCTAssert(try parseType(source: source) == expected) - - source = "[MyType!]" - - expected = ListType( - type: NonNullType( - type: NamedType( - name: Name(value: "MyType") - ) - ) - ) - - XCTAssert(try parseType(source: source) == expected) - } - - func testParseDirective() throws { - let source = #""" - directive @restricted( - """The reason for this restriction""" - reason: String = null - ) on FIELD_DEFINITION - """# - - let expected = Document(definitions: [ - DirectiveDefinition( - description: nil, - name: Name(value: "restricted"), - arguments: [ - InputValueDefinition( - description: StringValue(value: "The reason for this restriction", block: true), - name: Name(value: "reason"), - type: NamedType(name: Name(value: "String")), - defaultValue: NullValue() - ) - ], - locations: [ - Name(value: "FIELD_DEFINITION") - ] - ) - ]) - - let document = try parse(source: source) - XCTAssert(document == expected) - } -} +// +// XCTAssertThrowsError(try parse(source: "{ ...MissingOn }\nfragment MissingOn Type\n")) { error in +// guard let error = error as? GraphQLError else { +// return XCTFail() +// } +// +// XCTAssert(error.message.contains( +// "Syntax Error GraphQL (2:20) Expected \"on\", found Name \"Type\"" +// )) +// } +// +// XCTAssertThrowsError(try parse(source: "{ field: {} }")) { error in +// guard let error = error as? GraphQLError else { +// return XCTFail() +// } +// +// XCTAssert(error.message.contains( +// "Syntax Error GraphQL (1:10) Expected Name, found {" +// )) +// } +// +// XCTAssertThrowsError(try parse(source: "notanoperation Foo { field }")) { error in +// guard let error = error as? GraphQLError else { +// return XCTFail() +// } +// +// XCTAssert(error.message.contains( +// "Syntax Error GraphQL (1:1) Unexpected Name \"notanoperation\"" +// )) +// } +// +// XCTAssertThrowsError(try parse(source: "...")) { error in +// guard let error = error as? GraphQLError else { +// return XCTFail() +// } +// +// XCTAssert(error.message.contains( +// "Syntax Error GraphQL (1:1) Unexpected ..." +// )) +// } +// +// XCTAssertThrowsError(try parse(source: Source(body: "query", name: "MyQuery.graphql"))) { error in +// guard let error = error as? GraphQLError else { +// return XCTFail() +// } +// +// XCTAssert(error.message.contains( +// "Syntax Error MyQuery.graphql (1:6) Expected {, found " +// )) +// } +// +// source = "query Foo($x: Complex = { a: { b: [ $var ] } }) { field }" +// +// XCTAssertThrowsError(try parse(source: source)) { error in +// guard let error = error as? GraphQLError else { +// return XCTFail() +// } +// +// XCTAssert(error.message.contains( +// "Syntax Error GraphQL (1:37) Unexpected $" +// )) +// } +// +// XCTAssertThrowsError(try parse(source: "fragment on on on { on }")) { error in +// guard let error = error as? GraphQLError else { +// return XCTFail() +// } +// +// XCTAssert(error.message.contains( +// "Syntax Error GraphQL (1:10) Unexpected Name \"on\"" +// )) +// } +// +// XCTAssertThrowsError(try parse(source: "{ ...on }")) { error in +// guard let error = error as? GraphQLError else { +// return XCTFail() +// } +// +// XCTAssert(error.message.contains( +// "Syntax Error GraphQL (1:9) Expected Name, found }" +// )) +// } +// +// } +// +// func testVariableInlineValues() throws { +// _ = try parse(source: "{ field(complex: { a: { b: [ $var ] } }) }") +// } +// +// func testFieldWithArguments() throws { +// let query = """ +// { +// stringArgField(stringArg: "Hello World") +// intArgField(intArg: 1) +// floatArgField(floatArg: 3.14) +// falseArgField(boolArg: false) +// trueArgField(boolArg: true) +// nullArgField(value: null) +// enumArgField(enumArg: VALUE) +// multipleArgs(arg1: 1, arg2: false, arg3: THIRD) +// } +// """ +// +// let expected = Document( +// definitions: [ +// OperationDefinition( +// operation: .query, +// selectionSet: SelectionSet( +// selections: [ +// Field( +// name: Name(value: "stringArgField"), +// arguments: [ +// Argument( +// name: Name(value: "stringArg"), +// value: StringValue(value: "Hello World", block: false) +// ) +// ] +// ), +// Field( +// name: Name(value: "intArgField"), +// arguments: [ +// Argument( +// name: Name(value: "intArg"), +// value: IntValue(value: "1") +// ) +// ] +// ), +// Field( +// name: Name(value: "floatArgField"), +// arguments: [ +// Argument( +// name: Name(value: "floatArg"), +// value: FloatValue(value: "3.14") +// ) +// ] +// ), +// Field( +// name: Name(value: "falseArgField"), +// arguments: [ +// Argument( +// name: Name(value: "boolArg"), +// value: BooleanValue(value: false) +// ) +// ] +// ), +// Field( +// name: Name(value: "trueArgField"), +// arguments: [ +// Argument( +// name: Name(value: "boolArg"), +// value: BooleanValue(value: true) +// ) +// ] +// ), +// Field( +// name: Name(value: "nullArgField"), +// arguments: [ +// Argument( +// name: Name(value: "value"), +// value: NullValue() +// ) +// ] +// ), +// Field( +// name: Name(value: "enumArgField"), +// arguments: [ +// Argument( +// name: Name(value: "enumArg"), +// value: EnumValue(value: "VALUE") +// ) +// ] +// ), +// Field( +// name: Name(value: "multipleArgs"), +// arguments: [ +// Argument( +// name: Name(value: "arg1"), +// value: IntValue(value: "1") +// ), +// Argument( +// name: Name(value: "arg2"), +// value: BooleanValue(value: false) +// ), +// Argument( +// name: Name(value: "arg3"), +// value: EnumValue(value: "THIRD") +// ), +// ] +// ), +// ] +// ) +// ) +// ] +// ) +// +// let document = try parse(source: query) +// XCTAssert(document == expected) +// } +// +//// it('parses multi-byte characters', async () => { +//// // Note: \u0A0A could be naively interpretted as two line-feed chars. +//// expect( +//// parse(` +//// # This comment has a \u0A0A multi-byte character. +//// { field(arg: "Has a \u0A0A multi-byte character.") } +//// `) +//// ).to.containSubset({ +//// definitions: [ { +//// selectionSet: { +//// selections: [ { +//// arguments: [ { +//// value: { +//// kind: Kind.STRING, +//// value: 'Has a \u0A0A multi-byte character.' +//// } +//// } ] +//// } ] +//// } +//// } ] +//// }); +//// }); +// +// func testKitchenSink() throws { +//// let path = "/Users/paulofaria/Development/Zewo/GraphQL/Tests/GraphQLTests/LanguageTests/kitchen-sink.graphql" +//// let kitchenSink = try NSString(contentsOfFile: path, encoding: String.Encoding.utf8.rawValue) +//// _ = try parse(source: kitchenSink as String) +// } +// +// func testNonKeywordAsName() throws { +// let nonKeywords = [ +// "on", +// "fragment", +// "query", +// "mutation", +// "subscription", +// "true", +// "false" +// ] +// +// for nonKeyword in nonKeywords { +// var fragmentName = nonKeyword +// // You can't define or reference a fragment named `on`. +// if nonKeyword == "on" { +// fragmentName = "a" +// } +// +// _ = try parse(source: "query \(nonKeyword) {" + +// "... \(fragmentName)" + +// "... on \(nonKeyword) { field }" + +// "}" + +// "fragment \(fragmentName) on Type {" + +// "\(nonKeyword)(\(nonKeyword): $\(nonKeyword)) @\(nonKeyword)(\(nonKeyword): \(nonKeyword))" + +// "}" +// ) +// } +// } +// +// func testAnonymousMutationOperation() throws { +// _ = try parse(source: "mutation {" + +// " mutationField" + +// "}" +// ) +// } +// +// func testAnonymousSubscriptionOperation() throws { +// _ = try parse(source: "subscription {" + +// " subscriptionField" + +// "}" +// ) +// } +// +// func testNamedMutationOperation() throws { +// _ = try parse(source: "mutation Foo {" + +// " mutationField" + +// "}" +// ) +// } +// +// func testNamedSubscriptionOperation() throws { +// _ = try parse(source: "subscription Foo {" + +// " subscriptionField" + +// "}" +// ) +// } +// +// func testCreateAST() throws { +// let query = "{" + +// " node(id: 4) {" + +// " id," + +// " name" + +// " }" + +// "}" +// +// let expected = Document( +// definitions: [ +// OperationDefinition( +// operation: .query, +// selectionSet: SelectionSet( +// selections: [ +// Field( +// name: Name(value: "node"), +// arguments: [ +// Argument( +// name: Name(value: "id"), +// value: IntValue(value: "4") +// ) +// ], +// selectionSet: SelectionSet( +// selections: [ +// Field(name: Name(value: "id")), +// Field(name: Name(value: "name")) +// ] +// ) +// ) +// ] +// ) +// ) +// ] +// ) +// +// XCTAssert(try parse(source: query) == expected) +// } +// +// func testNoLocation() throws { +// let result = try parse(source: "{ id }", noLocation: true) +// XCTAssertNil(result.loc) +// } +// +// func testLocationSource() throws { +// let source = Source(body: "{ id }") +// let result = try parse(source: source) +// XCTAssertEqual(result.loc?.source, source) +// } +// +// func testLocationTokens() throws { +// let source = Source(body: "{ id }") +// let result = try parse(source: source) +// XCTAssertEqual(result.loc?.startToken.kind, .sof) +// XCTAssertEqual(result.loc?.endToken.kind, .eof) +// } +// +// func testParseValue() throws { +// let source = "[123 \"abc\"]" +// +// let expected: Value = ListValue( +// values: [ +// IntValue(value: "123"), +// StringValue(value: "abc", block: false) +// ] +// ) +// +// XCTAssert(try parseValue(source: source) == expected) +// } +// +// func testParseType() throws { +// var source: String +// var expected: Type +// +// source = "String" +// +// expected = NamedType( +// name: Name(value: "String") +// ) +// +// XCTAssert(try parseType(source: source) == expected) +// +// source = "MyType" +// +// expected = NamedType( +// name: Name(value: "MyType") +// ) +// +// XCTAssert(try parseType(source: source) == expected) +// +// source = "[MyType]" +// +// expected = ListType( +// type: NamedType( +// name: Name(value: "MyType") +// ) +// ) +// +// XCTAssert(try parseType(source: source) == expected) +// +// source = "MyType!" +// +// expected = NonNullType( +// type: NamedType( +// name: Name(value: "MyType") +// ) +// ) +// +// XCTAssert(try parseType(source: source) == expected) +// +// source = "[MyType!]" +// +// expected = ListType( +// type: NonNullType( +// type: NamedType( +// name: Name(value: "MyType") +// ) +// ) +// ) +// +// XCTAssert(try parseType(source: source) == expected) +// } +// +// func testParseDirective() throws { +// let source = #""" +// directive @restricted( +// """The reason for this restriction""" +// reason: String = null +// ) on FIELD_DEFINITION +// """# +// +// let expected = Document(definitions: [ +// DirectiveDefinition( +// description: nil, +// name: Name(value: "restricted"), +// arguments: [ +// InputValueDefinition( +// description: StringValue(value: "The reason for this restriction", block: true), +// name: Name(value: "reason"), +// type: NamedType(name: Name(value: "String")), +// defaultValue: NullValue() +// ) +// ], +// locations: [ +// Name(value: "FIELD_DEFINITION") +// ] +// ) +// ]) +// +// let document = try parse(source: source) +// XCTAssert(document == expected) +// } +//} diff --git a/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift b/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift index fa249e4b..2d76c062 100644 --- a/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift +++ b/Tests/GraphQLTests/LanguageTests/SchemaParserTests.swift @@ -1,736 +1,736 @@ -import XCTest -@testable import GraphQL - -func nameNode(_ name: String) -> Name { - return Name(value: name) -} - -func fieldNode(_ name: Name, _ type: Type) -> FieldDefinition { - return FieldDefinition(name: name, type: type) -} - -func fieldNodeWithDescription(_ description: StringValue? = nil, _ name: Name, _ type: Type) -> FieldDefinition { - return FieldDefinition(description: description, name: name, type: type) -} - -func typeNode(_ name: String) -> NamedType { - return NamedType(name: nameNode(name)) -} - -func enumValueNode(_ name: String) -> EnumValueDefinition { - return EnumValueDefinition(name: nameNode(name)) -} - -func enumValueWithDescriptionNode(_ description: StringValue?, _ name: String) -> EnumValueDefinition { - return EnumValueDefinition(description: description, name: nameNode(name)) -} - -func fieldNodeWithArgs(_ name: Name, _ type: Type, _ args: [InputValueDefinition]) -> FieldDefinition { - return FieldDefinition(name: name, arguments: args, type: type) -} - -func inputValueNode(_ name: Name, _ type: Type, _ defaultValue: Value? = nil) -> InputValueDefinition { - return InputValueDefinition(name: name, type: type, defaultValue: defaultValue) -} - -func inputValueWithDescriptionNode(_ description: StringValue?, - _ name: Name, - _ type: Type, - _ defaultValue: Value? = nil) -> InputValueDefinition { - return InputValueDefinition(description: description, name: name, type: type, defaultValue: defaultValue) -} - -func namedTypeNode(_ name: String ) -> NamedType { - return NamedType(name: nameNode(name)) -} - -class SchemaParserTests : XCTestCase { - func testSimpleType() throws { - let source = "type Hello { world: String }" - - let expected = Document( - definitions: [ - ObjectTypeDefinition( - name: nameNode("Hello"), - fields: [ - fieldNode( - nameNode("world"), - typeNode("String") - ) - ] - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - func testSimpleExtension() throws { - let source = "extend type Hello { world: String }" - - let expected = Document( - definitions: [ - TypeExtensionDefinition( - definition: ObjectTypeDefinition( - name: nameNode("Hello"), - fields: [ - fieldNode( - nameNode("world"), - typeNode("String") - ) - ] - ) - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - func testSimpleNonNullType() throws { - let source = "type Hello { world: String! }" - - let expected = Document( - definitions: [ - ObjectTypeDefinition( - name: nameNode("Hello"), - fields: [ - fieldNode( - nameNode("world"), - NonNullType( - type: typeNode("String") - ) - ) - ] - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - func testSimpleTypeInheritingInterface() throws { - let source = "type Hello implements World { }" - - let expected = Document( - definitions: [ - ObjectTypeDefinition( - name: nameNode("Hello"), - interfaces: [typeNode("World")] - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - func testSimpleTypeInheritingMultipleInterfaces() throws { - let source = "type Hello implements Wo, rld { }" - - let expected = Document( - definitions: [ - ObjectTypeDefinition( - name: nameNode("Hello"), - interfaces: [ - typeNode("Wo"), - typeNode("rld"), - ] - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - func testSingleValueEnum() throws { - let source = "enum Hello { WORLD }" - - let expected = Document( - definitions: [ - EnumTypeDefinition( - name: nameNode("Hello"), - values: [ - enumValueNode("WORLD"), - ] - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - func testDoubleValueEnum() throws { - let source = "enum Hello { WO, RLD }" - - let expected = Document( - definitions: [ - EnumTypeDefinition( - name: nameNode("Hello"), - values: [ - enumValueNode("WO"), - enumValueNode("RLD"), - ] - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - func testSimpleInterface() throws { - let source = "interface Hello { world: String }" - - let expected = Document( - definitions: [ - InterfaceTypeDefinition( - name: nameNode("Hello"), - fields: [ - fieldNode( - nameNode("world"), - typeNode("String") - ) - ] - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - func testSimpleFieldWithArg() throws { - let source = "type Hello { world(flag: Boolean): String }" - - let expected = Document( - definitions: [ - ObjectTypeDefinition( - name: nameNode("Hello"), - fields: [ - fieldNodeWithArgs( - nameNode("world"), - typeNode("String"), - [ - inputValueNode( - nameNode("flag"), - typeNode("Boolean") - ) - ] - ) - ] - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - func testSimpleFieldWithArgDefaultValue() throws { - let source = "type Hello { world(flag: Boolean = true): String }" - - let expected = Document( - definitions: [ - ObjectTypeDefinition( - name: nameNode("Hello"), - fields: [ - fieldNodeWithArgs( - nameNode("world"), - typeNode("String"), - [ - inputValueNode( - nameNode("flag"), - typeNode("Boolean"), - BooleanValue(value: true) - ) - ] - ) - ] - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - func testSimpleFieldWithListArg() throws { - let source = "type Hello { world(things: [String]): String }" - - let expected = Document( - definitions: [ - ObjectTypeDefinition( - name: nameNode("Hello"), - fields: [ - fieldNodeWithArgs( - nameNode("world"), - typeNode("String"), - [ - inputValueNode( - nameNode("things"), - ListType(type: typeNode("String")) - ) - ] - ) - ] - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - func testSimpleFieldWithTwoArgs() throws { - let source = "type Hello { world(argOne: Boolean, argTwo: Int): String }" - - let expected = Document( - definitions: [ - ObjectTypeDefinition( - name: nameNode("Hello"), - fields: [ - fieldNodeWithArgs( - nameNode("world"), - typeNode("String"), - [ - inputValueNode( - nameNode("argOne"), - typeNode("Boolean") - ), - inputValueNode( - nameNode("argTwo"), - typeNode("Int") - ) - ] - ) - ] - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - func testSimpleUnion() throws { - let source = "union Hello = World" - - let expected = Document( - definitions: [ - UnionTypeDefinition( - name: nameNode("Hello"), - types: [ - typeNode("World"), - ] - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - func testUnionTwoTypes() throws { - let source = "union Hello = Wo | Rld" - - let expected = Document( - definitions: [ - UnionTypeDefinition( - name: nameNode("Hello"), - types: [ - typeNode("Wo"), - typeNode("Rld"), - ] - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - func testScalar() throws { - let source = "scalar Hello" - - let expected = Document( - definitions: [ - ScalarTypeDefinition( - name: nameNode("Hello") - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - func testSimpleInputObject() throws { - let source = "input Hello { world: String }" - - let expected = Document( - definitions: [ - InputObjectTypeDefinition( - name: nameNode("Hello"), - fields: [ - inputValueNode( - nameNode("world"), - typeNode("String") - ) - ] - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - func testSimpleInputObjectWithArgs() throws { - let source = "input Hello { world(foo: Int): String }" - XCTAssertThrowsError(try parse(source: source)) - } - - func testSimpleSchema() throws { - let source = "schema { query: Hello }" - let expected = SchemaDefinition( - directives: [], - operationTypes: [ - OperationTypeDefinition( - operation: .query, - type: namedTypeNode("Hello") - ) - ] - ) - let result = try parse(source: source) - XCTAssert(result.definitions[0] == expected) - } - - // Description tests - - func testTypeWithDescription() throws { - let source = #""The Hello type" type Hello { world: String }"# - - let expected = ObjectTypeDefinition( - description: StringValue(value: "The Hello type", block: false), - name: nameNode("Hello"), - fields: [ - fieldNode( - nameNode("world"), - typeNode("String") - ) - ] - ) - - let result = try parse(source: source) - XCTAssertEqual(result.definitions[0] as! ObjectTypeDefinition, expected, "\n\(dump(result.definitions[0]))\n\(dump(expected))\n") - } - - func testTypeWitMultilinehDescription() throws { - let source = #""" - """ - The Hello type. - Multi-line description - """ - type Hello { - world: String - } - """# - - let expected = Document( - definitions: [ - ObjectTypeDefinition( - description: StringValue(value:"The Hello type.\nMulti-line description", block: true), - name: nameNode("Hello"), - fields: [ - fieldNode( - nameNode("world"), - typeNode("String") - ) - ] - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - func testDirectiveDesciption() throws { - let source = #""directive description" directive @Test(a: String = "hello") on FIELD"# - - let expected: Document = Document( - definitions: [ - DirectiveDefinition(loc: nil, - description: StringValue(value: "directive description", block: false), - name: nameNode("Test"), - arguments: [ - inputValueNode( - nameNode("a"), - typeNode("String"), - StringValue(value: "hello", block: false) - ) - ], - locations: [ - nameNode("FIELD") - ]) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - func testDirectiveMultilineDesciption() throws { - let source = #""" - """ - directive description - """ - directive @Test(a: String = "hello") on FIELD - """# - let expected: Document = Document( - definitions: [ - DirectiveDefinition(loc: nil, - description: StringValue(value: "directive description", block: true), - name: nameNode("Test"), - arguments: [ - inputValueNode( - nameNode("a"), - typeNode("String"), - StringValue(value: "hello", block: false) - ) - ], - locations: [ - nameNode("FIELD") - ]) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - func testSimpleSchemaWithDescription() throws { - let source = #""Hello Schema" schema { query: Hello } "# - - let expected = SchemaDefinition( - description: StringValue(value: "Hello Schema", block: false), - directives: [], - operationTypes: [ - OperationTypeDefinition( - operation: .query, - type: namedTypeNode("Hello") - ) - ] - ) - let result = try parse(source: source) - XCTAssert(result.definitions[0] == expected) - } - - func testScalarWithDescription() throws { - let source = #""Hello Scaler Test" scalar Hello"# - - let expected = Document( - definitions: [ - ScalarTypeDefinition( - description: StringValue(value: "Hello Scaler Test", block: false), - name: nameNode("Hello") - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - func testSimpleInterfaceWithDescription() throws { - let source = #""Hello World Interface" interface Hello { world: String } "# - - let expected = Document( - definitions: [ - InterfaceTypeDefinition( - description: StringValue(value: "Hello World Interface", block: false), - name: nameNode("Hello"), - fields: [ - fieldNode( - nameNode("world"), - typeNode("String") - ) - ] - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - func testSimpleUnionWithDescription() throws { - let source = #""Hello World Union!" union Hello = World "# - - let expected = Document( - definitions: [ - UnionTypeDefinition( - description: StringValue(value: "Hello World Union!", block: false), - name: nameNode("Hello"), - types: [ - typeNode("World"), - ] - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - func testSingleValueEnumDescription() throws { - let source = #""Hello World Enum..." enum Hello { WORLD } "# - - let expected = Document( - definitions: [ - EnumTypeDefinition( - description: StringValue(value: "Hello World Enum...", block: false), - name: nameNode("Hello"), - values: [ - enumValueNode("WORLD"), - ] - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - func testSimpleInputObjectWithDescription() throws { - let source = #""Hello Input Object" input Hello { world: String }"# - - let expected = Document( - definitions: [ - InputObjectTypeDefinition( - description: StringValue(value: "Hello Input Object", block: false), - name: nameNode("Hello"), - fields: [ - inputValueNode( - nameNode("world"), - typeNode("String") - ) - ] - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - // Test Fields and values with optional descriptions - - func testSingleValueEnumWithDescription() throws { - let source = """ - enum Hello { - "world description" - WORLD - "Hello there" - HELLO - } - """ - - let expected = Document( - definitions: [ - EnumTypeDefinition( - name: nameNode("Hello"), - values: [ - enumValueWithDescriptionNode(StringValue(value: "world description", block: false), "WORLD"), - enumValueWithDescriptionNode(StringValue(value: "Hello there", block: false), "HELLO") - ] - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - func testTypeFieldWithDescription() throws { - let source = #"type Hello { "The world field." world: String } "# - - let expected = Document( - definitions: [ - ObjectTypeDefinition( - name: nameNode("Hello"), - fields: [ - fieldNodeWithDescription( - StringValue(value: "The world field.", block: false), - nameNode("world"), - typeNode("String") - ) - ] - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - - func testTypeFieldWithMultilineDescription() throws { - let source = #""" - type Hello { - """ - The world - field. - """ - world: String - } - """# - - let expected = Document( - definitions: [ - ObjectTypeDefinition( - name: nameNode("Hello"), - fields: [ - fieldNodeWithDescription( - StringValue(value: "The world\nfield.", block: true), - nameNode("world"), - typeNode("String") - ) - ] - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected, "\(dump(result)) \n\n\(dump(expected))") - } - - - func testSimpleInputObjectFieldWithDescription() throws { - let source = #"input Hello { "World field" world: String }"# - - let expected = Document( - definitions: [ - InputObjectTypeDefinition( - name: nameNode("Hello"), - fields: [ - inputValueWithDescriptionNode( - StringValue(value: "World field", block: false), - nameNode("world"), - typeNode("String") - ) - ] - ) - ] - ) - - let result = try parse(source: source) - XCTAssert(result == expected) - } - -} +//import XCTest +//@testable import GraphQL +// +//func nameNode(_ name: String) -> Name { +// return Name(value: name) +//} +// +//func fieldNode(_ name: Name, _ type: Type) -> FieldDefinition { +// return FieldDefinition(name: name, type: type) +//} +// +//func fieldNodeWithDescription(_ description: StringValue? = nil, _ name: Name, _ type: Type) -> FieldDefinition { +// return FieldDefinition(description: description, name: name, type: type) +//} +// +//func typeNode(_ name: String) -> NamedType { +// return NamedType(name: nameNode(name)) +//} +// +//func enumValueNode(_ name: String) -> EnumValueDefinition { +// return EnumValueDefinition(name: nameNode(name)) +//} +// +//func enumValueWithDescriptionNode(_ description: StringValue?, _ name: String) -> EnumValueDefinition { +// return EnumValueDefinition(description: description, name: nameNode(name)) +//} +// +//func fieldNodeWithArgs(_ name: Name, _ type: Type, _ args: [InputValueDefinition]) -> FieldDefinition { +// return FieldDefinition(name: name, arguments: args, type: type) +//} +// +//func inputValueNode(_ name: Name, _ type: Type, _ defaultValue: Value? = nil) -> InputValueDefinition { +// return InputValueDefinition(name: name, type: type, defaultValue: defaultValue) +//} +// +//func inputValueWithDescriptionNode(_ description: StringValue?, +// _ name: Name, +// _ type: Type, +// _ defaultValue: Value? = nil) -> InputValueDefinition { +// return InputValueDefinition(description: description, name: name, type: type, defaultValue: defaultValue) +//} +// +//func namedTypeNode(_ name: String ) -> NamedType { +// return NamedType(name: nameNode(name)) +//} +// +//class SchemaParserTests : XCTestCase { +// func testSimpleType() throws { +// let source = "type Hello { world: String }" +// +// let expected = Document( +// definitions: [ +// ObjectTypeDefinition( +// name: nameNode("Hello"), +// fields: [ +// fieldNode( +// nameNode("world"), +// typeNode("String") +// ) +// ] +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// func testSimpleExtension() throws { +// let source = "extend type Hello { world: String }" +// +// let expected = Document( +// definitions: [ +// TypeExtensionDefinition( +// definition: ObjectTypeDefinition( +// name: nameNode("Hello"), +// fields: [ +// fieldNode( +// nameNode("world"), +// typeNode("String") +// ) +// ] +// ) +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// func testSimpleNonNullType() throws { +// let source = "type Hello { world: String! }" +// +// let expected = Document( +// definitions: [ +// ObjectTypeDefinition( +// name: nameNode("Hello"), +// fields: [ +// fieldNode( +// nameNode("world"), +// NonNullType( +// type: typeNode("String") +// ) +// ) +// ] +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// func testSimpleTypeInheritingInterface() throws { +// let source = "type Hello implements World { }" +// +// let expected = Document( +// definitions: [ +// ObjectTypeDefinition( +// name: nameNode("Hello"), +// interfaces: [typeNode("World")] +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// func testSimpleTypeInheritingMultipleInterfaces() throws { +// let source = "type Hello implements Wo, rld { }" +// +// let expected = Document( +// definitions: [ +// ObjectTypeDefinition( +// name: nameNode("Hello"), +// interfaces: [ +// typeNode("Wo"), +// typeNode("rld"), +// ] +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// func testSingleValueEnum() throws { +// let source = "enum Hello { WORLD }" +// +// let expected = Document( +// definitions: [ +// EnumTypeDefinition( +// name: nameNode("Hello"), +// values: [ +// enumValueNode("WORLD"), +// ] +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// func testDoubleValueEnum() throws { +// let source = "enum Hello { WO, RLD }" +// +// let expected = Document( +// definitions: [ +// EnumTypeDefinition( +// name: nameNode("Hello"), +// values: [ +// enumValueNode("WO"), +// enumValueNode("RLD"), +// ] +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// func testSimpleInterface() throws { +// let source = "interface Hello { world: String }" +// +// let expected = Document( +// definitions: [ +// InterfaceTypeDefinition( +// name: nameNode("Hello"), +// fields: [ +// fieldNode( +// nameNode("world"), +// typeNode("String") +// ) +// ] +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// func testSimpleFieldWithArg() throws { +// let source = "type Hello { world(flag: Boolean): String }" +// +// let expected = Document( +// definitions: [ +// ObjectTypeDefinition( +// name: nameNode("Hello"), +// fields: [ +// fieldNodeWithArgs( +// nameNode("world"), +// typeNode("String"), +// [ +// inputValueNode( +// nameNode("flag"), +// typeNode("Boolean") +// ) +// ] +// ) +// ] +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// func testSimpleFieldWithArgDefaultValue() throws { +// let source = "type Hello { world(flag: Boolean = true): String }" +// +// let expected = Document( +// definitions: [ +// ObjectTypeDefinition( +// name: nameNode("Hello"), +// fields: [ +// fieldNodeWithArgs( +// nameNode("world"), +// typeNode("String"), +// [ +// inputValueNode( +// nameNode("flag"), +// typeNode("Boolean"), +// BooleanValue(value: true) +// ) +// ] +// ) +// ] +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// func testSimpleFieldWithListArg() throws { +// let source = "type Hello { world(things: [String]): String }" +// +// let expected = Document( +// definitions: [ +// ObjectTypeDefinition( +// name: nameNode("Hello"), +// fields: [ +// fieldNodeWithArgs( +// nameNode("world"), +// typeNode("String"), +// [ +// inputValueNode( +// nameNode("things"), +// ListType(type: typeNode("String")) +// ) +// ] +// ) +// ] +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// func testSimpleFieldWithTwoArgs() throws { +// let source = "type Hello { world(argOne: Boolean, argTwo: Int): String }" +// +// let expected = Document( +// definitions: [ +// ObjectTypeDefinition( +// name: nameNode("Hello"), +// fields: [ +// fieldNodeWithArgs( +// nameNode("world"), +// typeNode("String"), +// [ +// inputValueNode( +// nameNode("argOne"), +// typeNode("Boolean") +// ), +// inputValueNode( +// nameNode("argTwo"), +// typeNode("Int") +// ) +// ] +// ) +// ] +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// func testSimpleUnion() throws { +// let source = "union Hello = World" +// +// let expected = Document( +// definitions: [ +// UnionTypeDefinition( +// name: nameNode("Hello"), +// types: [ +// typeNode("World"), +// ] +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// func testUnionTwoTypes() throws { +// let source = "union Hello = Wo | Rld" +// +// let expected = Document( +// definitions: [ +// UnionTypeDefinition( +// name: nameNode("Hello"), +// types: [ +// typeNode("Wo"), +// typeNode("Rld"), +// ] +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// func testScalar() throws { +// let source = "scalar Hello" +// +// let expected = Document( +// definitions: [ +// ScalarTypeDefinition( +// name: nameNode("Hello") +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// func testSimpleInputObject() throws { +// let source = "input Hello { world: String }" +// +// let expected = Document( +// definitions: [ +// InputObjectTypeDefinition( +// name: nameNode("Hello"), +// fields: [ +// inputValueNode( +// nameNode("world"), +// typeNode("String") +// ) +// ] +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// func testSimpleInputObjectWithArgs() throws { +// let source = "input Hello { world(foo: Int): String }" +// XCTAssertThrowsError(try parse(source: source)) +// } +// +// func testSimpleSchema() throws { +// let source = "schema { query: Hello }" +// let expected = SchemaDefinition( +// directives: [], +// operationTypes: [ +// OperationTypeDefinition( +// operation: .query, +// type: namedTypeNode("Hello") +// ) +// ] +// ) +// let result = try parse(source: source) +// XCTAssert(result.definitions[0] == expected) +// } +// +// // Description tests +// +// func testTypeWithDescription() throws { +// let source = #""The Hello type" type Hello { world: String }"# +// +// let expected = ObjectTypeDefinition( +// description: StringValue(value: "The Hello type", block: false), +// name: nameNode("Hello"), +// fields: [ +// fieldNode( +// nameNode("world"), +// typeNode("String") +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssertEqual(result.definitions[0] as! ObjectTypeDefinition, expected, "\n\(dump(result.definitions[0]))\n\(dump(expected))\n") +// } +// +// func testTypeWitMultilinehDescription() throws { +// let source = #""" +// """ +// The Hello type. +// Multi-line description +// """ +// type Hello { +// world: String +// } +// """# +// +// let expected = Document( +// definitions: [ +// ObjectTypeDefinition( +// description: StringValue(value:"The Hello type.\nMulti-line description", block: true), +// name: nameNode("Hello"), +// fields: [ +// fieldNode( +// nameNode("world"), +// typeNode("String") +// ) +// ] +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// func testDirectiveDesciption() throws { +// let source = #""directive description" directive @Test(a: String = "hello") on FIELD"# +// +// let expected: Document = Document( +// definitions: [ +// DirectiveDefinition(loc: nil, +// description: StringValue(value: "directive description", block: false), +// name: nameNode("Test"), +// arguments: [ +// inputValueNode( +// nameNode("a"), +// typeNode("String"), +// StringValue(value: "hello", block: false) +// ) +// ], +// locations: [ +// nameNode("FIELD") +// ]) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// func testDirectiveMultilineDesciption() throws { +// let source = #""" +// """ +// directive description +// """ +// directive @Test(a: String = "hello") on FIELD +// """# +// let expected: Document = Document( +// definitions: [ +// DirectiveDefinition(loc: nil, +// description: StringValue(value: "directive description", block: true), +// name: nameNode("Test"), +// arguments: [ +// inputValueNode( +// nameNode("a"), +// typeNode("String"), +// StringValue(value: "hello", block: false) +// ) +// ], +// locations: [ +// nameNode("FIELD") +// ]) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// func testSimpleSchemaWithDescription() throws { +// let source = #""Hello Schema" schema { query: Hello } "# +// +// let expected = SchemaDefinition( +// description: StringValue(value: "Hello Schema", block: false), +// directives: [], +// operationTypes: [ +// OperationTypeDefinition( +// operation: .query, +// type: namedTypeNode("Hello") +// ) +// ] +// ) +// let result = try parse(source: source) +// XCTAssert(result.definitions[0] == expected) +// } +// +// func testScalarWithDescription() throws { +// let source = #""Hello Scaler Test" scalar Hello"# +// +// let expected = Document( +// definitions: [ +// ScalarTypeDefinition( +// description: StringValue(value: "Hello Scaler Test", block: false), +// name: nameNode("Hello") +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// func testSimpleInterfaceWithDescription() throws { +// let source = #""Hello World Interface" interface Hello { world: String } "# +// +// let expected = Document( +// definitions: [ +// InterfaceTypeDefinition( +// description: StringValue(value: "Hello World Interface", block: false), +// name: nameNode("Hello"), +// fields: [ +// fieldNode( +// nameNode("world"), +// typeNode("String") +// ) +// ] +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// func testSimpleUnionWithDescription() throws { +// let source = #""Hello World Union!" union Hello = World "# +// +// let expected = Document( +// definitions: [ +// UnionTypeDefinition( +// description: StringValue(value: "Hello World Union!", block: false), +// name: nameNode("Hello"), +// types: [ +// typeNode("World"), +// ] +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// func testSingleValueEnumDescription() throws { +// let source = #""Hello World Enum..." enum Hello { WORLD } "# +// +// let expected = Document( +// definitions: [ +// EnumTypeDefinition( +// description: StringValue(value: "Hello World Enum...", block: false), +// name: nameNode("Hello"), +// values: [ +// enumValueNode("WORLD"), +// ] +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// func testSimpleInputObjectWithDescription() throws { +// let source = #""Hello Input Object" input Hello { world: String }"# +// +// let expected = Document( +// definitions: [ +// InputObjectTypeDefinition( +// description: StringValue(value: "Hello Input Object", block: false), +// name: nameNode("Hello"), +// fields: [ +// inputValueNode( +// nameNode("world"), +// typeNode("String") +// ) +// ] +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// // Test Fields and values with optional descriptions +// +// func testSingleValueEnumWithDescription() throws { +// let source = """ +// enum Hello { +// "world description" +// WORLD +// "Hello there" +// HELLO +// } +// """ +// +// let expected = Document( +// definitions: [ +// EnumTypeDefinition( +// name: nameNode("Hello"), +// values: [ +// enumValueWithDescriptionNode(StringValue(value: "world description", block: false), "WORLD"), +// enumValueWithDescriptionNode(StringValue(value: "Hello there", block: false), "HELLO") +// ] +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// func testTypeFieldWithDescription() throws { +// let source = #"type Hello { "The world field." world: String } "# +// +// let expected = Document( +// definitions: [ +// ObjectTypeDefinition( +// name: nameNode("Hello"), +// fields: [ +// fieldNodeWithDescription( +// StringValue(value: "The world field.", block: false), +// nameNode("world"), +// typeNode("String") +// ) +// ] +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +// func testTypeFieldWithMultilineDescription() throws { +// let source = #""" +// type Hello { +// """ +// The world +// field. +// """ +// world: String +// } +// """# +// +// let expected = Document( +// definitions: [ +// ObjectTypeDefinition( +// name: nameNode("Hello"), +// fields: [ +// fieldNodeWithDescription( +// StringValue(value: "The world\nfield.", block: true), +// nameNode("world"), +// typeNode("String") +// ) +// ] +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected, "\(dump(result)) \n\n\(dump(expected))") +// } +// +// +// func testSimpleInputObjectFieldWithDescription() throws { +// let source = #"input Hello { "World field" world: String }"# +// +// let expected = Document( +// definitions: [ +// InputObjectTypeDefinition( +// name: nameNode("Hello"), +// fields: [ +// inputValueWithDescriptionNode( +// StringValue(value: "World field", block: false), +// nameNode("world"), +// typeNode("String") +// ) +// ] +// ) +// ] +// ) +// +// let result = try parse(source: source) +// XCTAssert(result == expected) +// } +// +//} diff --git a/Tests/GraphQLTests/LanguageTests/VisitorTests.swift b/Tests/GraphQLTests/LanguageTests/VisitorTests.swift index 198d11d9..d078265e 100644 --- a/Tests/GraphQLTests/LanguageTests/VisitorTests.swift +++ b/Tests/GraphQLTests/LanguageTests/VisitorTests.swift @@ -2,6 +2,124 @@ import XCTest @testable import GraphQL class VisitorTests : XCTestCase { - func test() throws { + func testVisitsField() throws { + class FieldVisitor: Visitor { + var visited = false + func enter(field: Field, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { + visited = true + return .continue + } + } + let document = try! parse(source: """ + query foo { + bar + } + """) + let visitor = FieldVisitor() + visit(root: document, visitor: visitor) + XCTAssert(visitor.visited) + } + + func testVisitorCanEdit() throws { + struct FieldVisitor: Visitor { + func enter(field: Field, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { + guard case let .node(queryParent) = parent, queryParent is Selection else { + XCTFail("Unexpected parent") + return .continue + } + var newField = field + newField.name = Name(loc: nil, value: "baz") + return .node(newField) + } + } + let document = try! parse(source: """ + query foo { + bar + } + """) + let visitor = FieldVisitor() + let newDocument = visit(root: document, visitor: visitor) + guard case let .executableDefinition(.operation(opDef)) = newDocument.definitions.first else { + XCTFail("Unexpected definition") + return + } + guard case let .field(field) = opDef.selectionSet.selections.first else { + XCTFail("Unexpected selection") + return + } + XCTAssertEqual("baz", field.name.value) + } + + + func testVisitorCanEditArray() throws { + struct IntIncrementer: Visitor { + func enter(intValue: IntValue, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { + let newVal = Int(intValue.value)! + 1 + return .node(IntValue(loc: nil, value: String(newVal))) + } + + func leave(argument: Argument, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { + if argument.value == .intValue(IntValue(value: "2")) { + return .node(nil) + } + return .continue + } + } + let document = try! parse(source: """ + query foo { + bar(a: 1, b: 2, c: 3) + } + """) + let visitor = IntIncrementer() + let newDocument = visit(root: document, visitor: visitor) + guard case let .executableDefinition(.operation(opDef)) = newDocument.definitions.first else { + XCTFail("Unexpected definition") + return + } + guard case let .field(field) = opDef.selectionSet.selections.first else { + XCTFail("Unexpected selection") + return + } + let expectedInts = [3,4] + for (argument, expected) in zip(field.arguments, expectedInts) { + switch argument.value { + case .intValue(let intVal) where Int(intVal.value) == expected: + break + default: + XCTFail("Unexpected value") + return + } + } + } + + func testVisitorBreaks() { + class FieldVisitor: Visitor { + var visited = false + func enter(field: Field, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { + if (visited) { + XCTFail("Visited the nested field and didn't break") + } + visited = true + return .break + } + func leave(field: Field, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { + XCTFail("Left the field and didn't break") + return .continue + } + func leave(operationDefinition: OperationDefinition, key: AnyKeyPath?, parent: VisitorParent?, ancestors: [VisitorParent]) -> VisitResult { + XCTFail("Left the operation definition and didn't break") + return .continue + } + } + let document = try! parse(source: """ + { + bar { + baz + } + } + """) + let visitor = FieldVisitor() + visit(root: document, visitor: visitor) + XCTAssert(visitor.visited) } } diff --git a/Tests/GraphQLTests/TypeTests/GraphQLSchemaTests.swift b/Tests/GraphQLTests/TypeTests/GraphQLSchemaTests.swift index 7a77dcf2..ec075a4b 100644 --- a/Tests/GraphQLTests/TypeTests/GraphQLSchemaTests.swift +++ b/Tests/GraphQLTests/TypeTests/GraphQLSchemaTests.swift @@ -151,4 +151,14 @@ class GraphQLSchemaTests: XCTestCase { XCTAssertEqual(graphQLError.message, "Object.fieldWithoutArg includes required argument (addedRequiredArg:) that is missing from the Interface field Interface.fieldWithoutArg.") } } + + func testSubtypingIsReflexive() throws { + let object = try GraphQLObjectType( + name: "Object", + fields: ["foo": GraphQLField(type: GraphQLInt)], + interfaces: [] + ) + let schema = try GraphQLSchema(query: object, types: [object]) + XCTAssert(schema.isSubType(abstractType: object, maybeSubType: object)) + } } diff --git a/Tests/GraphQLTests/ValidationTests/ExampleSchema.swift b/Tests/GraphQLTests/ValidationTests/ExampleSchema.swift index e507be6e..2a2c2e0a 100644 --- a/Tests/GraphQLTests/ValidationTests/ExampleSchema.swift +++ b/Tests/GraphQLTests/ValidationTests/ExampleSchema.swift @@ -377,6 +377,18 @@ let ValidationExampleHumanOrAlien = try! GraphQLUnionType( types: [ValidationExampleHuman, ValidationExampleAlien] ) +// input Treat { +// name: String! +// amount: Int! +// } +let ValidationExampleTreat = try! GraphQLInputObjectType( + name: "Treat", + fields: [ + "name": InputObjectField(type: GraphQLNonNull(GraphQLString)), + "amount": InputObjectField(type: GraphQLNonNull(GraphQLInt)) + ] +) + // type QueryRoot { // dog: Dog // } @@ -402,5 +414,6 @@ let ValidationExampleSchema = try! GraphQLSchema( ValidationExampleDog, ValidationExampleHuman, ValidationExampleAlien, + ValidationExampleTreat ] ) diff --git a/Tests/GraphQLTests/ValidationTests/FieldsOnCorrectTypeTests.swift b/Tests/GraphQLTests/ValidationTests/FieldsOnCorrectTypeTests.swift index 1ff35730..e4e55c78 100644 --- a/Tests/GraphQLTests/ValidationTests/FieldsOnCorrectTypeTests.swift +++ b/Tests/GraphQLTests/ValidationTests/FieldsOnCorrectTypeTests.swift @@ -3,7 +3,7 @@ import XCTest class FieldsOnCorrectTypeTests : ValidationTestCase { override func setUp() { - rule = FieldsOnCorrectTypeRule + rule = FieldsOnCorrectTypeRule.self } func testValidWithObjectFieldSelection() throws { diff --git a/Tests/GraphQLTests/ValidationTests/KnownArgumentNamesTests.swift b/Tests/GraphQLTests/ValidationTests/KnownArgumentNamesTests.swift index 1120c373..f3a5262a 100644 --- a/Tests/GraphQLTests/ValidationTests/KnownArgumentNamesTests.swift +++ b/Tests/GraphQLTests/ValidationTests/KnownArgumentNamesTests.swift @@ -3,7 +3,7 @@ import XCTest class KnownArgumentNamesTests : ValidationTestCase { override func setUp() { - rule = KnownArgumentNamesRule + rule = KnownArgumentNamesRule.self } func testValidWithObjectWithoutArguments() throws { diff --git a/Tests/GraphQLTests/ValidationTests/KnownFragmentNamesRuleTests.swift b/Tests/GraphQLTests/ValidationTests/KnownFragmentNamesRuleTests.swift new file mode 100644 index 00000000..2069cd24 --- /dev/null +++ b/Tests/GraphQLTests/ValidationTests/KnownFragmentNamesRuleTests.swift @@ -0,0 +1,22 @@ +@testable import GraphQL +import XCTest + +class KnownFragmentNamesRuleTests : ValidationTestCase { + override func setUp() { + rule = KnownFragmentNamesRule.self + } + + func testValidWithKnownFragmentName() throws { + try assertValid(""" + fragment f on Dog { name } + query { dog { ...f } } + """) + } + + func testInvalidWithUnknownFragmentName() throws { + try assertInvalid( + errorCount: 1, + query: "{ dog { ...f } }" + ) + } +} diff --git a/Tests/GraphQLTests/ValidationTests/NoUnusedVariablesRuleTests.swift b/Tests/GraphQLTests/ValidationTests/NoUnusedVariablesRuleTests.swift index 27e62505..e36a644f 100644 --- a/Tests/GraphQLTests/ValidationTests/NoUnusedVariablesRuleTests.swift +++ b/Tests/GraphQLTests/ValidationTests/NoUnusedVariablesRuleTests.swift @@ -3,7 +3,7 @@ import XCTest class NoUnusedVariablesRuleTests : ValidationTestCase { override func setUp() { - rule = NoUnusedVariablesRule + rule = NoUnusedVariablesRule.self } func testUsesAllVariables() throws { diff --git a/Tests/GraphQLTests/ValidationTests/PossibleFragmentSpreadsRuleRuleTests.swift b/Tests/GraphQLTests/ValidationTests/PossibleFragmentSpreadsRuleRuleTests.swift index 8f998865..78a024fd 100644 --- a/Tests/GraphQLTests/ValidationTests/PossibleFragmentSpreadsRuleRuleTests.swift +++ b/Tests/GraphQLTests/ValidationTests/PossibleFragmentSpreadsRuleRuleTests.swift @@ -3,7 +3,7 @@ import XCTest class PossibleFragmentSpreadsRuleRuleTests : ValidationTestCase { override func setUp() { - rule = PossibleFragmentSpreadsRule + rule = PossibleFragmentSpreadsRule.self } func testUsesAllVariables() throws { diff --git a/Tests/GraphQLTests/ValidationTests/ProvidedNonNullArgumentsTests.swift b/Tests/GraphQLTests/ValidationTests/ProvidedNonNullArgumentsTests.swift index 35521a88..115d014c 100644 --- a/Tests/GraphQLTests/ValidationTests/ProvidedNonNullArgumentsTests.swift +++ b/Tests/GraphQLTests/ValidationTests/ProvidedNonNullArgumentsTests.swift @@ -3,7 +3,7 @@ import XCTest class ProvidedNonNullArgumentsTests : ValidationTestCase { override func setUp() { - rule = ProvidedNonNullArgumentsRule + rule = ProvidedNonNullArgumentsRule.self } func testValidWithObjectWithoutArguments() throws { diff --git a/Tests/GraphQLTests/ValidationTests/ValidationTests.swift b/Tests/GraphQLTests/ValidationTests/ValidationTests.swift index c319e17b..574e8c96 100644 --- a/Tests/GraphQLTests/ValidationTests/ValidationTests.swift +++ b/Tests/GraphQLTests/ValidationTests/ValidationTests.swift @@ -2,7 +2,7 @@ import XCTest class ValidationTestCase : XCTestCase { - typealias Rule = (ValidationContext) -> Visitor + typealias Rule = ValidationRule.Type var rule: Rule! @@ -51,5 +51,3 @@ class ValidationTestCase : XCTestCase { } } - - diff --git a/Tests/GraphQLTests/ValidationTests/VariablesAreInputTypesRuleTests.swift b/Tests/GraphQLTests/ValidationTests/VariablesAreInputTypesRuleTests.swift new file mode 100644 index 00000000..f58b5589 --- /dev/null +++ b/Tests/GraphQLTests/ValidationTests/VariablesAreInputTypesRuleTests.swift @@ -0,0 +1,20 @@ +@testable import GraphQL +import XCTest + +class VariablesAreInputTypesRuleTests : ValidationTestCase { + override func setUp() { + rule = VariablesAreInputTypesRule.self + } + + func testValidWithInputObject() throws { + try assertValid( + "query ($treat: Treat) { dog { __typename } } " + ) + } + + func testInvalidWithObject() throws { + try assertInvalid(errorCount: 1, query: + "query ($dog: Dog) { dog { __typename } } " + ) + } +}