Skip to content

Commit 958cbea

Browse files
feature: Adds KnownDirectivesRule
1 parent 3fe1484 commit 958cbea

File tree

3 files changed

+396
-1
lines changed

3 files changed

+396
-1
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
2+
/**
3+
* Known directives
4+
*
5+
* A GraphQL document is only valid if all `@directives` are known by the
6+
* schema and legally positioned.
7+
*
8+
* See https://spec.graphql.org/draft/#sec-Directives-Are-Defined
9+
*/
10+
func KnownDirectivesRule(context: ValidationContext) -> Visitor {
11+
var locationsMap = [String: [String]]()
12+
13+
let schema = context.schema
14+
let definedDirectives = schema.directives
15+
for directive in definedDirectives {
16+
locationsMap[directive.name] = directive.locations.map { $0.rawValue }
17+
}
18+
19+
let astDefinitions = context.ast.definitions
20+
for def in astDefinitions {
21+
if let directive = def as? DirectiveDefinition {
22+
locationsMap[directive.name.value] = directive.locations.map { $0.value }
23+
}
24+
}
25+
26+
return Visitor(
27+
enter: { node, _, _, _, ancestors in
28+
if let node = node as? Directive {
29+
let name = node.name.value
30+
let locations = locationsMap[name]
31+
32+
guard let locations = locations else {
33+
context.report(
34+
error: GraphQLError(
35+
message: "Unknown directive \"@\(name)\".",
36+
nodes: [node]
37+
)
38+
)
39+
return .continue
40+
}
41+
42+
let candidateLocation = getDirectiveLocationForASTPath(ancestors)
43+
if
44+
let candidateLocation = candidateLocation,
45+
!locations.contains(candidateLocation.rawValue)
46+
{
47+
context.report(
48+
error: GraphQLError(
49+
message: "Directive \"@\(name)\" may not be used on \(candidateLocation.rawValue).",
50+
nodes: [node]
51+
)
52+
)
53+
}
54+
}
55+
return .continue
56+
}
57+
)
58+
}
59+
60+
func getDirectiveLocationForASTPath(_ ancestors: [NodeResult]) -> DirectiveLocation? {
61+
guard let last = ancestors.last, case let .node(appliedTo) = last else {
62+
return nil
63+
}
64+
65+
switch appliedTo {
66+
case let appliedTo as OperationDefinition:
67+
return getDirectiveLocationForOperation(appliedTo.operation)
68+
case is Field:
69+
return DirectiveLocation.field
70+
case is FragmentSpread:
71+
return DirectiveLocation.fragmentSpread
72+
case is InlineFragment:
73+
return DirectiveLocation.inlineFragment
74+
case is FragmentDefinition:
75+
return DirectiveLocation.fragmentDefinition
76+
case is VariableDefinition:
77+
return DirectiveLocation.variableDefinition
78+
case is SchemaDefinition:
79+
return DirectiveLocation.schema
80+
case is ScalarTypeDefinition, is ScalarExtensionDefinition:
81+
return DirectiveLocation.scalar
82+
case is ObjectTypeDefinition:
83+
return DirectiveLocation.object
84+
case is FieldDefinition:
85+
return DirectiveLocation.fieldDefinition
86+
case is InterfaceTypeDefinition, is InterfaceExtensionDefinition:
87+
return DirectiveLocation.interface
88+
case is UnionTypeDefinition, is UnionExtensionDefinition:
89+
return DirectiveLocation.union
90+
case is EnumTypeDefinition, is EnumExtensionDefinition:
91+
return DirectiveLocation.enum
92+
case is EnumValueDefinition:
93+
return DirectiveLocation.enumValue
94+
case is InputObjectTypeDefinition, is InputObjectExtensionDefinition:
95+
return DirectiveLocation.inputObject
96+
case is InputValueDefinition:
97+
guard ancestors.count >= 3 else {
98+
return nil
99+
}
100+
let parentNode = ancestors[ancestors.count - 3]
101+
guard case let .node(parentNode) = parentNode else {
102+
return nil
103+
}
104+
return parentNode.kind == .inputObjectTypeDefinition
105+
? DirectiveLocation.inputFieldDefinition
106+
: DirectiveLocation.argumentDefinition
107+
// Not reachable, all possible types have been considered.
108+
default:
109+
return nil
110+
}
111+
}
112+
113+
func getDirectiveLocationForOperation(_ operation: OperationType) -> DirectiveLocation {
114+
switch operation {
115+
case .query:
116+
return DirectiveLocation.query
117+
case .mutation:
118+
return DirectiveLocation.mutation
119+
case .subscription:
120+
return DirectiveLocation.subscription
121+
}
122+
}

Sources/GraphQL/Validation/SpecifiedRules.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public let specifiedRules: [(ValidationContext) -> Visitor] = [
1919
UniqueVariableNamesRule,
2020
NoUndefinedVariablesRule,
2121
NoUnusedVariablesRule,
22-
// KnownDirectivesRule,
22+
KnownDirectivesRule,
2323
// UniqueDirectivesPerLocationRule,
2424
// DeferStreamDirectiveOnRootFieldRule,
2525
// DeferStreamDirectiveOnValidOperationsRule,

0 commit comments

Comments
 (0)