Skip to content

Commit 75cfce2

Browse files
Merge pull request #136 from NeedleInAJayStack/feature/custom-validations
Adds Custom Validation Rules
2 parents ca8b57f + 436998c commit 75cfce2

File tree

6 files changed

+512
-6
lines changed

6 files changed

+512
-6
lines changed

Sources/GraphQL/Type/Definition.swift

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,8 @@ func defineArgumentMap(args: GraphQLArgumentConfigMap) throws -> [GraphQLArgumen
414414
name: name,
415415
type: config.type,
416416
defaultValue: config.defaultValue,
417-
description: config.description
417+
description: config.description,
418+
deprecationReason: config.deprecationReason
418419
)
419420
arguments.append(argument)
420421
}
@@ -661,15 +662,18 @@ public struct GraphQLArgument {
661662
public let type: GraphQLInputType
662663
public let description: String?
663664
public let defaultValue: Map?
665+
public let deprecationReason: String?
664666

665667
public init(
666668
type: GraphQLInputType,
667669
description: String? = nil,
668-
defaultValue: Map? = nil
670+
defaultValue: Map? = nil,
671+
deprecationReason: String? = nil
669672
) {
670673
self.type = type
671674
self.description = description
672675
self.defaultValue = defaultValue
676+
self.deprecationReason = deprecationReason
673677
}
674678
}
675679

@@ -678,17 +682,20 @@ public struct GraphQLArgumentDefinition {
678682
public let type: GraphQLInputType
679683
public let defaultValue: Map?
680684
public let description: String?
685+
public let deprecationReason: String?
681686

682687
init(
683688
name: String,
684689
type: GraphQLInputType,
685690
defaultValue: Map? = nil,
686-
description: String? = nil
691+
description: String? = nil,
692+
deprecationReason: String? = nil
687693
) {
688694
self.name = name
689695
self.type = type
690696
self.defaultValue = defaultValue
691697
self.description = description
698+
self.deprecationReason = deprecationReason
692699
}
693700
}
694701

@@ -702,6 +709,7 @@ extension GraphQLArgumentDefinition: Encodable {
702709
case description
703710
case type
704711
case defaultValue
712+
case deprecationReason
705713
}
706714

707715
public func encode(to encoder: Encoder) throws {
@@ -710,6 +718,7 @@ extension GraphQLArgumentDefinition: Encodable {
710718
try container.encode(description, forKey: .description)
711719
try container.encode(AnyEncodable(type), forKey: .type)
712720
try container.encode(defaultValue, forKey: .defaultValue)
721+
try container.encode(deprecationReason, forKey: .deprecationReason)
713722
}
714723
}
715724

@@ -724,6 +733,8 @@ extension GraphQLArgumentDefinition: KeySubscriptable {
724733
return type
725734
case CodingKeys.defaultValue.rawValue:
726735
return defaultValue
736+
case CodingKeys.deprecationReason.rawValue:
737+
return deprecationReason
727738
default:
728739
return nil
729740
}
@@ -1292,7 +1303,8 @@ func defineInputObjectFieldMap(
12921303
name: name,
12931304
type: field.type,
12941305
description: field.description,
1295-
defaultValue: field.defaultValue
1306+
defaultValue: field.defaultValue,
1307+
deprecationReason: field.deprecationReason
12961308
)
12971309

12981310
definitionMap[name] = definition
@@ -1305,11 +1317,18 @@ public struct InputObjectField {
13051317
public let type: GraphQLInputType
13061318
public let defaultValue: Map?
13071319
public let description: String?
1320+
public let deprecationReason: String?
13081321

1309-
public init(type: GraphQLInputType, defaultValue: Map? = nil, description: String? = nil) {
1322+
public init(
1323+
type: GraphQLInputType,
1324+
defaultValue: Map? = nil,
1325+
description: String? = nil,
1326+
deprecationReason: String? = nil
1327+
) {
13101328
self.type = type
13111329
self.defaultValue = defaultValue
13121330
self.description = description
1331+
self.deprecationReason = deprecationReason
13131332
}
13141333
}
13151334

@@ -1320,17 +1339,20 @@ public final class InputObjectFieldDefinition {
13201339
public internal(set) var type: GraphQLInputType
13211340
public let description: String?
13221341
public let defaultValue: Map?
1342+
public let deprecationReason: String?
13231343

13241344
init(
13251345
name: String,
13261346
type: GraphQLInputType,
13271347
description: String? = nil,
1328-
defaultValue: Map? = nil
1348+
defaultValue: Map? = nil,
1349+
deprecationReason: String? = nil
13291350
) {
13301351
self.name = name
13311352
self.type = type
13321353
self.description = description
13331354
self.defaultValue = defaultValue
1355+
self.deprecationReason = deprecationReason
13341356
}
13351357

13361358
func replaceTypeReferences(typeMap: TypeMap) throws {
@@ -1352,6 +1374,7 @@ extension InputObjectFieldDefinition: Encodable {
13521374
case description
13531375
case type
13541376
case defaultValue
1377+
case deprecationReason
13551378
}
13561379

13571380
public func encode(to encoder: Encoder) throws {
@@ -1360,6 +1383,7 @@ extension InputObjectFieldDefinition: Encodable {
13601383
try container.encode(description, forKey: .description)
13611384
try container.encode(AnyEncodable(type), forKey: .type)
13621385
try container.encode(defaultValue, forKey: .defaultValue)
1386+
try container.encode(deprecationReason, forKey: .deprecationReason)
13631387
}
13641388
}
13651389

@@ -1374,6 +1398,8 @@ extension InputObjectFieldDefinition: KeySubscriptable {
13741398
return type
13751399
case CodingKeys.defaultValue.rawValue:
13761400
return defaultValue
1401+
case CodingKeys.deprecationReason.rawValue:
1402+
return deprecationReason
13771403
default:
13781404
return nil
13791405
}

Sources/GraphQL/Type/Directives.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ public let GraphQLDeprecatedDirective = try! GraphQLDirective(
105105
"Marks an element of a GraphQL schema as no longer supported.",
106106
locations: [
107107
.fieldDefinition,
108+
.argumentDefinition,
109+
.inputFieldDefinition,
108110
.enumValue,
109111
],
110112
args: [

Sources/GraphQL/Type/Introspection.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,3 +476,18 @@ let TypeNameMetaFieldDef = GraphQLFieldDefinition(
476476
eventLoopGroup.next().makeSucceededFuture(info.parentType.name)
477477
}
478478
)
479+
480+
let introspectionTypeNames = [
481+
__Schema.name,
482+
__Directive.name,
483+
__DirectiveLocation.name,
484+
__Type.name,
485+
__Field.name,
486+
__InputValue.name,
487+
__EnumValue.name,
488+
__TypeKind.name,
489+
]
490+
491+
func isIntrospectionType(type: GraphQLNamedType) -> Bool {
492+
return introspectionTypeNames.contains(type.name)
493+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
2+
/**
3+
* No deprecated
4+
*
5+
* A GraphQL document is only valid if all selected fields and all used enum values have not been
6+
* deprecated.
7+
*
8+
* Note: This rule is optional and is not part of the Validation section of the GraphQL
9+
* Specification. The main purpose of this rule is detection of deprecated usages and not
10+
* necessarily to forbid their use when querying a service.
11+
*/
12+
public func NoDeprecatedCustomRule(context: ValidationContext) -> Visitor {
13+
return Visitor(
14+
enter: { node, _, _, _, _ in
15+
if let node = node as? Field {
16+
if
17+
let fieldDef = context.fieldDef,
18+
let deprecationReason = fieldDef.deprecationReason,
19+
let parentType = context.parentType
20+
{
21+
context.report(
22+
error: GraphQLError(
23+
message: "The field \(parentType.name).\(fieldDef.name) is deprecated. \(deprecationReason)",
24+
nodes: [node]
25+
)
26+
)
27+
}
28+
}
29+
if let node = node as? Argument {
30+
if
31+
let argDef = context.argument,
32+
let deprecationReason = argDef.deprecationReason
33+
{
34+
if let directiveDef = context.typeInfo.directive {
35+
context.report(
36+
error: GraphQLError(
37+
message: "Directive \"@\(directiveDef.name)\" argument \"\(argDef.name)\" is deprecated. \(deprecationReason)",
38+
nodes: [node]
39+
)
40+
)
41+
} else if
42+
let fieldDef = context.fieldDef,
43+
let parentType = context.parentType
44+
{
45+
context.report(
46+
error: GraphQLError(
47+
message: "Field \"\(parentType.name).\(fieldDef.name)\" argument \"\(argDef.name)\" is deprecated. \(deprecationReason)",
48+
nodes: [node]
49+
)
50+
)
51+
}
52+
}
53+
}
54+
if let node = node as? ObjectField {
55+
let inputObjectDef = context.parentInputType as? GraphQLInputObjectType
56+
57+
if
58+
let inputObjectDef = context.parentInputType as? GraphQLInputObjectType,
59+
let inputFieldDef = inputObjectDef.fields[node.name.value],
60+
let deprecationReason = inputFieldDef.deprecationReason
61+
{
62+
context.report(
63+
error: GraphQLError(
64+
message: "The input field \(inputObjectDef.name).\(inputFieldDef.name) is deprecated. \(deprecationReason)",
65+
nodes: [node]
66+
)
67+
)
68+
}
69+
}
70+
if let node = node as? EnumValue {
71+
if
72+
let enumValueDef = context.typeInfo.enumValue,
73+
let deprecationReason = enumValueDef.deprecationReason,
74+
let enumTypeDef = getNamedType(type: context.inputType)
75+
{
76+
context.report(
77+
error: GraphQLError(
78+
message: "The enum value \"\(enumTypeDef.name).\(enumValueDef.name)\" is deprecated. \(deprecationReason)",
79+
nodes: [node]
80+
)
81+
)
82+
}
83+
}
84+
return .continue
85+
}
86+
)
87+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
/**
3+
* Prohibit introspection queries
4+
*
5+
* A GraphQL document is only valid if all fields selected are not fields that
6+
* return an introspection type.
7+
*
8+
* Note: This rule is optional and is not part of the Validation section of the
9+
* GraphQL Specification. This rule effectively disables introspection, which
10+
* does not reflect best practices and should only be done if absolutely necessary.
11+
*/
12+
public func NoSchemaIntrospectionCustomRule(context: ValidationContext) -> Visitor {
13+
return Visitor(
14+
enter: { node, _, _, _, _ in
15+
if let node = node as? Field {
16+
if
17+
let type = getNamedType(type: context.type),
18+
isIntrospectionType(type: type)
19+
{
20+
context.report(
21+
error: GraphQLError(
22+
message: "GraphQL introspection has been disabled, but the requested query contained the field \(node.name.value)",
23+
nodes: [node]
24+
)
25+
)
26+
}
27+
}
28+
return .continue
29+
}
30+
)
31+
}

0 commit comments

Comments
 (0)