Skip to content

Adds Custom Validation Rules #136

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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