Skip to content

Commit

Permalink
Merge pull request #135 from NeedleInAJayStack/feature/validation
Browse files Browse the repository at this point in the history
Adds Validation Rules
  • Loading branch information
NeedleInAJayStack committed Nov 28, 2023
2 parents db0d3fe + a4063a1 commit ca8b57f
Show file tree
Hide file tree
Showing 57 changed files with 6,392 additions and 172 deletions.
30 changes: 27 additions & 3 deletions Sources/GraphQL/Language/AST.swift
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ public final class OperationDefinition {
}
return .array(variableDefinitions)
case "directives":
guard !variableDefinitions.isEmpty else {
guard !directives.isEmpty else {
return nil
}
return .array(directives)
Expand Down Expand Up @@ -475,12 +475,20 @@ public final class VariableDefinition {
public private(set) var variable: Variable
public private(set) var type: Type
public private(set) var defaultValue: Value?
public private(set) var directives: [Directive]

init(loc: Location? = nil, variable: Variable, type: Type, defaultValue: Value? = nil) {
init(
loc: Location? = nil,
variable: Variable,
type: Type,
defaultValue: Value? = nil,
directives: [Directive] = []
) {
self.loc = loc
self.variable = variable
self.type = type
self.defaultValue = defaultValue
self.directives = directives
}

public func get(key: String) -> NodeResult? {
Expand All @@ -491,6 +499,11 @@ public final class VariableDefinition {
return .node(type)
case "defaultValue":
return defaultValue.map { .node($0) }
case "directives":
guard !directives.isEmpty else {
return nil
}
return .array(directives)
default:
return nil
}
Expand Down Expand Up @@ -525,6 +538,14 @@ public final class VariableDefinition {
return
}
self.defaultValue = defaultValue
case "directives":
guard
case let .array(values) = value,
let directives = values as? [Directive]
else {
return
}
self.directives = directives
default:
return
}
Expand Down Expand Up @@ -1748,7 +1769,10 @@ extension OperationTypeDefinition: Equatable {
}
}

public protocol TypeDefinition: TypeSystemDefinition {}
public protocol TypeDefinition: TypeSystemDefinition {
var name: Name { get }
}

extension ScalarTypeDefinition: TypeDefinition {}
extension ObjectTypeDefinition: TypeDefinition {}
extension InterfaceTypeDefinition: TypeDefinition {}
Expand Down
3 changes: 2 additions & 1 deletion Sources/GraphQL/Language/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,8 @@ func parseVariableDefinition(lexer: Lexer) throws -> VariableDefinition {
variable: parseVariable(lexer: lexer),
type: (expect(lexer: lexer, kind: .colon), parseTypeReference(lexer: lexer)).1,
defaultValue: skip(lexer: lexer, kind: .equals) ?
parseValueLiteral(lexer: lexer, isConst: true) : nil
parseValueLiteral(lexer: lexer, isConst: true) : nil,
directives: parseDirectives(lexer: lexer)
)
}

Expand Down
3 changes: 1 addition & 2 deletions Sources/GraphQL/Language/Printer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@ extension OperationDefinition: Printable {
extension VariableDefinition: Printable {
var printed: String {
variable + ": " + type.printed + wrap(" = ", defaultValue?.printed)
// + wrap(" ", join(directives, " "))
// TODO: variable directives are currently not supported
+ wrap(" ", join(directives, " "))
}
}

Expand Down
13 changes: 9 additions & 4 deletions Sources/GraphQL/Language/Visitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ let QueryDocumentKeys: [Kind: [String]] = [

.document: ["definitions"],
.operationDefinition: ["name", "variableDefinitions", "directives", "selectionSet"],
.variableDefinition: ["variable", "type", "defaultValue"],
.variableDefinition: ["variable", "type", "defaultValue", "directives"],
.variable: ["name"],
.selectionSet: ["selections"],
.field: ["alias", "name", "arguments", "directives", "selectionSet"],
Expand Down Expand Up @@ -304,9 +304,14 @@ func visitInParallel(visitors: [Visitor]) -> Visitor {
} else if case .node = result {
return result
}
} // else if case let .node(skippedNode) = skipping[i], skippedNode == node {
// skipping[i] = nil
// }
} else if
case let .node(skippedNodeValue) = skipping[i],
let skippedNode = skippedNodeValue,
skippedNode.kind == node.kind,
skippedNode.loc == node.loc
{
skipping[i] = nil
}
}

return .continue
Expand Down
7 changes: 6 additions & 1 deletion Sources/GraphQL/SwiftUtilities/SuggestionList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ func suggestionList(
}
return optionsByDistance.keys.sorted {
// Values are guaranteed non-nil since the keys come from the object itself
optionsByDistance[$0]! - optionsByDistance[$1]! != 0
let distanceDiff = optionsByDistance[$0]! - optionsByDistance[$1]!
if distanceDiff != 0 {
return distanceDiff < 0
} else {
return $0.lexicographicallyPrecedes($1)
}
}
}

Expand Down
69 changes: 36 additions & 33 deletions Sources/GraphQL/Type/Definition.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Foundation
import NIO
import OrderedCollections

/**
* These are all of the possible kinds of types.
Expand Down Expand Up @@ -171,35 +172,22 @@ public final class GraphQLScalarType {
public let kind: TypeKind = .scalar

let serialize: (Any) throws -> Map
let parseValue: ((Map) throws -> Map)?
let parseLiteral: ((Value) throws -> Map)?

public init(
name: String,
description: String? = nil,
serialize: @escaping (Any) throws -> Map
) throws {
try assertValid(name: name)
self.name = name
self.description = description
self.serialize = serialize
parseValue = nil
parseLiteral = nil
}
let parseValue: (Map) throws -> Map
let parseLiteral: (Value) throws -> Map

public init(
name: String,
description: String? = nil,
serialize: @escaping (Any) throws -> Map,
parseValue: @escaping (Map) throws -> Map,
parseLiteral: @escaping (Value) throws -> Map
parseValue: ((Map) throws -> Map)? = nil,
parseLiteral: ((Value) throws -> Map)? = nil
) throws {
try assertValid(name: name)
self.name = name
self.description = description
self.serialize = serialize
self.parseValue = parseValue
self.parseLiteral = parseLiteral
self.parseValue = parseValue ?? defaultParseValue
self.parseLiteral = parseLiteral ?? defaultParseLiteral
}

// Serializes an internal value to include in a response.
Expand All @@ -209,15 +197,23 @@ public final class GraphQLScalarType {

// Parses an externally provided value to use as an input.
public func parseValue(value: Map) throws -> Map {
return try parseValue?(value) ?? Map.null
return try parseValue(value)
}

// Parses an externally provided literal value to use as an input.
public func parseLiteral(valueAST: Value) throws -> Map {
return try parseLiteral?(valueAST) ?? Map.null
return try parseLiteral(valueAST)
}
}

let defaultParseValue: ((Map) throws -> Map) = { value in
value
}

let defaultParseLiteral: ((Value) throws -> Map) = { value in
try valueFromASTUntyped(valueAST: value)
}

extension GraphQLScalarType: Encodable {
private enum CodingKeys: String, CodingKey {
case name
Expand Down Expand Up @@ -513,7 +509,7 @@ public struct GraphQLResolveInfo {
public let variableValues: [String: Any]
}

public typealias GraphQLFieldMap = [String: GraphQLField]
public typealias GraphQLFieldMap = OrderedDictionary<String, GraphQLField>

public struct GraphQLField {
public let type: GraphQLOutputType
Expand Down Expand Up @@ -573,7 +569,7 @@ public struct GraphQLField {
}
}

public typealias GraphQLFieldDefinitionMap = [String: GraphQLFieldDefinition]
public typealias GraphQLFieldDefinitionMap = OrderedDictionary<String, GraphQLFieldDefinition>

public final class GraphQLFieldDefinition {
public let name: String
Expand Down Expand Up @@ -659,7 +655,7 @@ extension GraphQLFieldDefinition: KeySubscriptable {
}
}

public typealias GraphQLArgumentConfigMap = [String: GraphQLArgument]
public typealias GraphQLArgumentConfigMap = OrderedDictionary<String, GraphQLArgument>

public struct GraphQLArgument {
public let type: GraphQLInputType
Expand Down Expand Up @@ -1018,7 +1014,7 @@ public final class GraphQLEnumType {
let mapValue = try map(from: value)
guard let enumValue = valueLookup[mapValue] else {
throw GraphQLError(
message: "Enum '\(name)' cannot represent value '\(mapValue)'."
message: "Enum \"\(name)\" cannot represent value: \(mapValue)."
)
}
return .string(enumValue.name)
Expand All @@ -1027,13 +1023,13 @@ public final class GraphQLEnumType {
public func parseValue(value: Map) throws -> Map {
guard let valueStr = value.string else {
throw GraphQLError(
message: "Enum '\(name)' cannot represent non-string value '\(value)'." +
message: "Enum \"\(name)\" cannot represent non-string value: \(value)." +
didYouMeanEnumValue(unknownValueStr: value.description)
)
}
guard let enumValue = nameLookup[valueStr] else {
throw GraphQLError(
message: "Value '\(valueStr)' does not exist in '\(name)' enum." +
message: "Value \"\(valueStr)\" does not exist in \"\(name)\" enum." +
didYouMeanEnumValue(unknownValueStr: valueStr)
)
}
Expand All @@ -1043,14 +1039,14 @@ public final class GraphQLEnumType {
public func parseLiteral(valueAST: Value) throws -> Map {
guard let enumNode = valueAST as? EnumValue else {
throw GraphQLError(
message: "Enum '\(name)' cannot represent non-enum value '\(valueAST)'." +
didYouMeanEnumValue(unknownValueStr: "\(valueAST)"),
message: "Enum \"\(name)\" cannot represent non-enum value: \(print(ast: valueAST))." +
didYouMeanEnumValue(unknownValueStr: print(ast: valueAST)),
nodes: [valueAST]
)
}
guard let enumValue = nameLookup[enumNode.value] else {
throw GraphQLError(
message: "Value '\(enumNode)' does not exist in '\(name)' enum." +
message: "Value \"\(enumNode.value)\" does not exist in \"\(name)\" enum." +
didYouMeanEnumValue(unknownValueStr: enumNode.value),
nodes: [valueAST]
)
Expand Down Expand Up @@ -1136,7 +1132,7 @@ func defineEnumValues(
return definitions
}

public typealias GraphQLEnumValueMap = [String: GraphQLEnumValue]
public typealias GraphQLEnumValueMap = OrderedDictionary<String, GraphQLEnumValue>

public struct GraphQLEnumValue {
public let value: Map
Expand Down Expand Up @@ -1317,7 +1313,7 @@ public struct InputObjectField {
}
}

public typealias InputObjectFieldMap = [String: InputObjectField]
public typealias InputObjectFieldMap = OrderedDictionary<String, InputObjectField>

public final class InputObjectFieldDefinition {
public let name: String
Expand Down Expand Up @@ -1384,7 +1380,14 @@ extension InputObjectFieldDefinition: KeySubscriptable {
}
}

public typealias InputObjectFieldDefinitionMap = [String: InputObjectFieldDefinition]
public func isRequiredInputField(_ field: InputObjectFieldDefinition) -> Bool {
return field.type is GraphQLNonNull && field.defaultValue == nil
}

public typealias InputObjectFieldDefinitionMap = OrderedDictionary<
String,
InputObjectFieldDefinition
>

/**
* List Modifier
Expand Down
8 changes: 6 additions & 2 deletions Sources/GraphQL/Type/Directives.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public enum DirectiveLocation: String, Encodable {
case fragmentDefinition = "FRAGMENT_DEFINITION"
case fragmentSpread = "FRAGMENT_SPREAD"
case inlineFragment = "INLINE_FRAGMENT"
case variableDefinition = "VARIABLE_DEFINITION"
// Schema Definitions
case schema = "SCHEMA"
case scalar = "SCALAR"
Expand All @@ -30,18 +31,21 @@ public struct GraphQLDirective: Encodable {
public let description: String
public let locations: [DirectiveLocation]
public let args: [GraphQLArgumentDefinition]
public let isRepeatable: Bool

public init(
name: String,
description: String,
description: String = "",
locations: [DirectiveLocation],
args: GraphQLArgumentConfigMap = [:]
args: GraphQLArgumentConfigMap = [:],
isRepeatable: Bool = false
) throws {
try assertValid(name: name)
self.name = name
self.description = description
self.locations = locations
self.args = try defineArgumentMap(args: args)
self.isRepeatable = isRepeatable
}
}

Expand Down
1 change: 1 addition & 0 deletions Sources/GraphQL/Type/Introspection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ let __Directive = try! GraphQLObjectType(
fields: [
"name": GraphQLField(type: GraphQLNonNull(GraphQLString)),
"description": GraphQLField(type: GraphQLString),
"isRepeatable": GraphQLField(type: GraphQLNonNull(GraphQLBoolean)),
"locations": GraphQLField(type: GraphQLNonNull(GraphQLList(GraphQLNonNull(__DirectiveLocation)))),
"args": GraphQLField(
type: GraphQLNonNull(GraphQLList(GraphQLNonNull(__InputValue))),
Expand Down
Loading

0 comments on commit ca8b57f

Please sign in to comment.