Skip to content

Commit

Permalink
Merge pull request #136 from NeedleInAJayStack/feature/custom-validat…
Browse files Browse the repository at this point in the history
…ions

Adds Custom Validation Rules
  • Loading branch information
NeedleInAJayStack committed Dec 4, 2023
2 parents ca8b57f + 436998c commit 75cfce2
Show file tree
Hide file tree
Showing 6 changed files with 512 additions and 6 deletions.
38 changes: 32 additions & 6 deletions Sources/GraphQL/Type/Definition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,8 @@ func defineArgumentMap(args: GraphQLArgumentConfigMap) throws -> [GraphQLArgumen
name: name,
type: config.type,
defaultValue: config.defaultValue,
description: config.description
description: config.description,
deprecationReason: config.deprecationReason
)
arguments.append(argument)
}
Expand Down Expand Up @@ -661,15 +662,18 @@ public struct GraphQLArgument {
public let type: GraphQLInputType
public let description: String?
public let defaultValue: Map?
public let deprecationReason: String?

public init(
type: GraphQLInputType,
description: String? = nil,
defaultValue: Map? = nil
defaultValue: Map? = nil,
deprecationReason: String? = nil
) {
self.type = type
self.description = description
self.defaultValue = defaultValue
self.deprecationReason = deprecationReason
}
}

Expand All @@ -678,17 +682,20 @@ public struct GraphQLArgumentDefinition {
public let type: GraphQLInputType
public let defaultValue: Map?
public let description: String?
public let deprecationReason: String?

init(
name: String,
type: GraphQLInputType,
defaultValue: Map? = nil,
description: String? = nil
description: String? = nil,
deprecationReason: String? = nil
) {
self.name = name
self.type = type
self.defaultValue = defaultValue
self.description = description
self.deprecationReason = deprecationReason
}
}

Expand All @@ -702,6 +709,7 @@ extension GraphQLArgumentDefinition: Encodable {
case description
case type
case defaultValue
case deprecationReason
}

public func encode(to encoder: Encoder) throws {
Expand All @@ -710,6 +718,7 @@ extension GraphQLArgumentDefinition: Encodable {
try container.encode(description, forKey: .description)
try container.encode(AnyEncodable(type), forKey: .type)
try container.encode(defaultValue, forKey: .defaultValue)
try container.encode(deprecationReason, forKey: .deprecationReason)
}
}

Expand All @@ -724,6 +733,8 @@ extension GraphQLArgumentDefinition: KeySubscriptable {
return type
case CodingKeys.defaultValue.rawValue:
return defaultValue
case CodingKeys.deprecationReason.rawValue:
return deprecationReason
default:
return nil
}
Expand Down Expand Up @@ -1292,7 +1303,8 @@ func defineInputObjectFieldMap(
name: name,
type: field.type,
description: field.description,
defaultValue: field.defaultValue
defaultValue: field.defaultValue,
deprecationReason: field.deprecationReason
)

definitionMap[name] = definition
Expand All @@ -1305,11 +1317,18 @@ public struct InputObjectField {
public let type: GraphQLInputType
public let defaultValue: Map?
public let description: String?
public let deprecationReason: String?

public init(type: GraphQLInputType, defaultValue: Map? = nil, description: String? = nil) {
public init(
type: GraphQLInputType,
defaultValue: Map? = nil,
description: String? = nil,
deprecationReason: String? = nil
) {
self.type = type
self.defaultValue = defaultValue
self.description = description
self.deprecationReason = deprecationReason
}
}

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

init(
name: String,
type: GraphQLInputType,
description: String? = nil,
defaultValue: Map? = nil
defaultValue: Map? = nil,
deprecationReason: String? = nil
) {
self.name = name
self.type = type
self.description = description
self.defaultValue = defaultValue
self.deprecationReason = deprecationReason
}

func replaceTypeReferences(typeMap: TypeMap) throws {
Expand All @@ -1352,6 +1374,7 @@ extension InputObjectFieldDefinition: Encodable {
case description
case type
case defaultValue
case deprecationReason
}

public func encode(to encoder: Encoder) throws {
Expand All @@ -1360,6 +1383,7 @@ extension InputObjectFieldDefinition: Encodable {
try container.encode(description, forKey: .description)
try container.encode(AnyEncodable(type), forKey: .type)
try container.encode(defaultValue, forKey: .defaultValue)
try container.encode(deprecationReason, forKey: .deprecationReason)
}
}

Expand All @@ -1374,6 +1398,8 @@ extension InputObjectFieldDefinition: KeySubscriptable {
return type
case CodingKeys.defaultValue.rawValue:
return defaultValue
case CodingKeys.deprecationReason.rawValue:
return deprecationReason
default:
return nil
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/GraphQL/Type/Directives.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ public let GraphQLDeprecatedDirective = try! GraphQLDirective(
"Marks an element of a GraphQL schema as no longer supported.",
locations: [
.fieldDefinition,
.argumentDefinition,
.inputFieldDefinition,
.enumValue,
],
args: [
Expand Down
15 changes: 15 additions & 0 deletions Sources/GraphQL/Type/Introspection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -476,3 +476,18 @@ let TypeNameMetaFieldDef = GraphQLFieldDefinition(
eventLoopGroup.next().makeSucceededFuture(info.parentType.name)
}
)

let introspectionTypeNames = [
__Schema.name,
__Directive.name,
__DirectiveLocation.name,
__Type.name,
__Field.name,
__InputValue.name,
__EnumValue.name,
__TypeKind.name,
]

func isIntrospectionType(type: GraphQLNamedType) -> Bool {
return introspectionTypeNames.contains(type.name)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@

/**
* No deprecated
*
* A GraphQL document is only valid if all selected fields and all used enum values have not been
* deprecated.
*
* Note: This rule is optional and is not part of the Validation section of the GraphQL
* Specification. The main purpose of this rule is detection of deprecated usages and not
* necessarily to forbid their use when querying a service.
*/
public func NoDeprecatedCustomRule(context: ValidationContext) -> Visitor {
return Visitor(
enter: { node, _, _, _, _ in
if let node = node as? Field {
if
let fieldDef = context.fieldDef,
let deprecationReason = fieldDef.deprecationReason,
let parentType = context.parentType
{
context.report(
error: GraphQLError(
message: "The field \(parentType.name).\(fieldDef.name) is deprecated. \(deprecationReason)",
nodes: [node]
)
)
}
}
if let node = node as? Argument {
if
let argDef = context.argument,
let deprecationReason = argDef.deprecationReason
{
if let directiveDef = context.typeInfo.directive {
context.report(
error: GraphQLError(
message: "Directive \"@\(directiveDef.name)\" argument \"\(argDef.name)\" is deprecated. \(deprecationReason)",
nodes: [node]
)
)
} else if
let fieldDef = context.fieldDef,
let parentType = context.parentType
{
context.report(
error: GraphQLError(
message: "Field \"\(parentType.name).\(fieldDef.name)\" argument \"\(argDef.name)\" is deprecated. \(deprecationReason)",
nodes: [node]
)
)
}
}
}
if let node = node as? ObjectField {
let inputObjectDef = context.parentInputType as? GraphQLInputObjectType

if
let inputObjectDef = context.parentInputType as? GraphQLInputObjectType,
let inputFieldDef = inputObjectDef.fields[node.name.value],
let deprecationReason = inputFieldDef.deprecationReason
{
context.report(
error: GraphQLError(
message: "The input field \(inputObjectDef.name).\(inputFieldDef.name) is deprecated. \(deprecationReason)",
nodes: [node]
)
)
}
}
if let node = node as? EnumValue {
if
let enumValueDef = context.typeInfo.enumValue,
let deprecationReason = enumValueDef.deprecationReason,
let enumTypeDef = getNamedType(type: context.inputType)
{
context.report(
error: GraphQLError(
message: "The enum value \"\(enumTypeDef.name).\(enumValueDef.name)\" is deprecated. \(deprecationReason)",
nodes: [node]
)
)
}
}
return .continue
}
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

/**
* Prohibit introspection queries
*
* A GraphQL document is only valid if all fields selected are not fields that
* return an introspection type.
*
* Note: This rule is optional and is not part of the Validation section of the
* GraphQL Specification. This rule effectively disables introspection, which
* does not reflect best practices and should only be done if absolutely necessary.
*/
public func NoSchemaIntrospectionCustomRule(context: ValidationContext) -> Visitor {
return Visitor(
enter: { node, _, _, _, _ in
if let node = node as? Field {
if
let type = getNamedType(type: context.type),
isIntrospectionType(type: type)
{
context.report(
error: GraphQLError(
message: "GraphQL introspection has been disabled, but the requested query contained the field \(node.name.value)",
nodes: [node]
)
)
}
}
return .continue
}
)
}
Loading

0 comments on commit 75cfce2

Please sign in to comment.