Initial Sample.

This commit is contained in:
2024-06-03 20:23:50 +05:30
parent ef2b65f673
commit 5269ec3c66
2575 changed files with 282312 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
import type { ASTVisitor } from '../../language/visitor';
import type { ASTValidationContext } from '../ValidationContext';
/**
* Executable definitions
*
* A GraphQL document is only valid for execution if all definitions are either
* operation or fragment definitions.
*
* See https://spec.graphql.org/draft/#sec-Executable-Definitions
*/
export declare function ExecutableDefinitionsRule(
context: ASTValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,46 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.ExecutableDefinitionsRule = ExecutableDefinitionsRule;
var _GraphQLError = require('../../error/GraphQLError.js');
var _kinds = require('../../language/kinds.js');
var _predicates = require('../../language/predicates.js');
/**
* Executable definitions
*
* A GraphQL document is only valid for execution if all definitions are either
* operation or fragment definitions.
*
* See https://spec.graphql.org/draft/#sec-Executable-Definitions
*/
function ExecutableDefinitionsRule(context) {
return {
Document(node) {
for (const definition of node.definitions) {
if (!(0, _predicates.isExecutableDefinitionNode)(definition)) {
const defName =
definition.kind === _kinds.Kind.SCHEMA_DEFINITION ||
definition.kind === _kinds.Kind.SCHEMA_EXTENSION
? 'schema'
: '"' + definition.name.value + '"';
context.reportError(
new _GraphQLError.GraphQLError(
`The ${defName} definition is not executable.`,
{
nodes: definition,
},
),
);
}
}
return false;
},
};
}

View File

@@ -0,0 +1,34 @@
import { GraphQLError } from '../../error/GraphQLError.mjs';
import { Kind } from '../../language/kinds.mjs';
import { isExecutableDefinitionNode } from '../../language/predicates.mjs';
/**
* Executable definitions
*
* A GraphQL document is only valid for execution if all definitions are either
* operation or fragment definitions.
*
* See https://spec.graphql.org/draft/#sec-Executable-Definitions
*/
export function ExecutableDefinitionsRule(context) {
return {
Document(node) {
for (const definition of node.definitions) {
if (!isExecutableDefinitionNode(definition)) {
const defName =
definition.kind === Kind.SCHEMA_DEFINITION ||
definition.kind === Kind.SCHEMA_EXTENSION
? 'schema'
: '"' + definition.name.value + '"';
context.reportError(
new GraphQLError(`The ${defName} definition is not executable.`, {
nodes: definition,
}),
);
}
}
return false;
},
};
}

View File

@@ -0,0 +1,13 @@
import type { ASTVisitor } from '../../language/visitor';
import type { ValidationContext } from '../ValidationContext';
/**
* Fields on correct type
*
* A GraphQL document is only valid if all fields selected are defined by the
* parent type, or are an allowed meta field such as __typename.
*
* See https://spec.graphql.org/draft/#sec-Field-Selections
*/
export declare function FieldsOnCorrectTypeRule(
context: ValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,145 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.FieldsOnCorrectTypeRule = FieldsOnCorrectTypeRule;
var _didYouMean = require('../../jsutils/didYouMean.js');
var _naturalCompare = require('../../jsutils/naturalCompare.js');
var _suggestionList = require('../../jsutils/suggestionList.js');
var _GraphQLError = require('../../error/GraphQLError.js');
var _definition = require('../../type/definition.js');
/**
* Fields on correct type
*
* A GraphQL document is only valid if all fields selected are defined by the
* parent type, or are an allowed meta field such as __typename.
*
* See https://spec.graphql.org/draft/#sec-Field-Selections
*/
function FieldsOnCorrectTypeRule(context) {
return {
Field(node) {
const type = context.getParentType();
if (type) {
const fieldDef = context.getFieldDef();
if (!fieldDef) {
// This field doesn't exist, lets look for suggestions.
const schema = context.getSchema();
const fieldName = node.name.value; // First determine if there are any suggested types to condition on.
let suggestion = (0, _didYouMean.didYouMean)(
'to use an inline fragment on',
getSuggestedTypeNames(schema, type, fieldName),
); // If there are no suggested types, then perhaps this was a typo?
if (suggestion === '') {
suggestion = (0, _didYouMean.didYouMean)(
getSuggestedFieldNames(type, fieldName),
);
} // Report an error, including helpful suggestions.
context.reportError(
new _GraphQLError.GraphQLError(
`Cannot query field "${fieldName}" on type "${type.name}".` +
suggestion,
{
nodes: node,
},
),
);
}
}
},
};
}
/**
* Go through all of the implementations of type, as well as the interfaces that
* they implement. If any of those types include the provided field, suggest them,
* sorted by how often the type is referenced.
*/
function getSuggestedTypeNames(schema, type, fieldName) {
if (!(0, _definition.isAbstractType)(type)) {
// Must be an Object type, which does not have possible fields.
return [];
}
const suggestedTypes = new Set();
const usageCount = Object.create(null);
for (const possibleType of schema.getPossibleTypes(type)) {
if (!possibleType.getFields()[fieldName]) {
continue;
} // This object type defines this field.
suggestedTypes.add(possibleType);
usageCount[possibleType.name] = 1;
for (const possibleInterface of possibleType.getInterfaces()) {
var _usageCount$possibleI;
if (!possibleInterface.getFields()[fieldName]) {
continue;
} // This interface type defines this field.
suggestedTypes.add(possibleInterface);
usageCount[possibleInterface.name] =
((_usageCount$possibleI = usageCount[possibleInterface.name]) !==
null && _usageCount$possibleI !== void 0
? _usageCount$possibleI
: 0) + 1;
}
}
return [...suggestedTypes]
.sort((typeA, typeB) => {
// Suggest both interface and object types based on how common they are.
const usageCountDiff = usageCount[typeB.name] - usageCount[typeA.name];
if (usageCountDiff !== 0) {
return usageCountDiff;
} // Suggest super types first followed by subtypes
if (
(0, _definition.isInterfaceType)(typeA) &&
schema.isSubType(typeA, typeB)
) {
return -1;
}
if (
(0, _definition.isInterfaceType)(typeB) &&
schema.isSubType(typeB, typeA)
) {
return 1;
}
return (0, _naturalCompare.naturalCompare)(typeA.name, typeB.name);
})
.map((x) => x.name);
}
/**
* For the field name provided, determine if there are any similar field names
* that may be the result of a typo.
*/
function getSuggestedFieldNames(type, fieldName) {
if (
(0, _definition.isObjectType)(type) ||
(0, _definition.isInterfaceType)(type)
) {
const possibleFieldNames = Object.keys(type.getFields());
return (0, _suggestionList.suggestionList)(fieldName, possibleFieldNames);
} // Otherwise, must be a Union type, which does not define fields.
return [];
}

View File

@@ -0,0 +1,127 @@
import { didYouMean } from '../../jsutils/didYouMean.mjs';
import { naturalCompare } from '../../jsutils/naturalCompare.mjs';
import { suggestionList } from '../../jsutils/suggestionList.mjs';
import { GraphQLError } from '../../error/GraphQLError.mjs';
import {
isAbstractType,
isInterfaceType,
isObjectType,
} from '../../type/definition.mjs';
/**
* Fields on correct type
*
* A GraphQL document is only valid if all fields selected are defined by the
* parent type, or are an allowed meta field such as __typename.
*
* See https://spec.graphql.org/draft/#sec-Field-Selections
*/
export function FieldsOnCorrectTypeRule(context) {
return {
Field(node) {
const type = context.getParentType();
if (type) {
const fieldDef = context.getFieldDef();
if (!fieldDef) {
// This field doesn't exist, lets look for suggestions.
const schema = context.getSchema();
const fieldName = node.name.value; // First determine if there are any suggested types to condition on.
let suggestion = didYouMean(
'to use an inline fragment on',
getSuggestedTypeNames(schema, type, fieldName),
); // If there are no suggested types, then perhaps this was a typo?
if (suggestion === '') {
suggestion = didYouMean(getSuggestedFieldNames(type, fieldName));
} // Report an error, including helpful suggestions.
context.reportError(
new GraphQLError(
`Cannot query field "${fieldName}" on type "${type.name}".` +
suggestion,
{
nodes: node,
},
),
);
}
}
},
};
}
/**
* Go through all of the implementations of type, as well as the interfaces that
* they implement. If any of those types include the provided field, suggest them,
* sorted by how often the type is referenced.
*/
function getSuggestedTypeNames(schema, type, fieldName) {
if (!isAbstractType(type)) {
// Must be an Object type, which does not have possible fields.
return [];
}
const suggestedTypes = new Set();
const usageCount = Object.create(null);
for (const possibleType of schema.getPossibleTypes(type)) {
if (!possibleType.getFields()[fieldName]) {
continue;
} // This object type defines this field.
suggestedTypes.add(possibleType);
usageCount[possibleType.name] = 1;
for (const possibleInterface of possibleType.getInterfaces()) {
var _usageCount$possibleI;
if (!possibleInterface.getFields()[fieldName]) {
continue;
} // This interface type defines this field.
suggestedTypes.add(possibleInterface);
usageCount[possibleInterface.name] =
((_usageCount$possibleI = usageCount[possibleInterface.name]) !==
null && _usageCount$possibleI !== void 0
? _usageCount$possibleI
: 0) + 1;
}
}
return [...suggestedTypes]
.sort((typeA, typeB) => {
// Suggest both interface and object types based on how common they are.
const usageCountDiff = usageCount[typeB.name] - usageCount[typeA.name];
if (usageCountDiff !== 0) {
return usageCountDiff;
} // Suggest super types first followed by subtypes
if (isInterfaceType(typeA) && schema.isSubType(typeA, typeB)) {
return -1;
}
if (isInterfaceType(typeB) && schema.isSubType(typeB, typeA)) {
return 1;
}
return naturalCompare(typeA.name, typeB.name);
})
.map((x) => x.name);
}
/**
* For the field name provided, determine if there are any similar field names
* that may be the result of a typo.
*/
function getSuggestedFieldNames(type, fieldName) {
if (isObjectType(type) || isInterfaceType(type)) {
const possibleFieldNames = Object.keys(type.getFields());
return suggestionList(fieldName, possibleFieldNames);
} // Otherwise, must be a Union type, which does not define fields.
return [];
}

View File

@@ -0,0 +1,14 @@
import type { ASTVisitor } from '../../language/visitor';
import type { ValidationContext } from '../ValidationContext';
/**
* Fragments on composite type
*
* Fragments use a type condition to determine if they apply, since fragments
* can only be spread into a composite type (object, interface, or union), the
* type condition must also be a composite type.
*
* See https://spec.graphql.org/draft/#sec-Fragments-On-Composite-Types
*/
export declare function FragmentsOnCompositeTypesRule(
context: ValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,69 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.FragmentsOnCompositeTypesRule = FragmentsOnCompositeTypesRule;
var _GraphQLError = require('../../error/GraphQLError.js');
var _printer = require('../../language/printer.js');
var _definition = require('../../type/definition.js');
var _typeFromAST = require('../../utilities/typeFromAST.js');
/**
* Fragments on composite type
*
* Fragments use a type condition to determine if they apply, since fragments
* can only be spread into a composite type (object, interface, or union), the
* type condition must also be a composite type.
*
* See https://spec.graphql.org/draft/#sec-Fragments-On-Composite-Types
*/
function FragmentsOnCompositeTypesRule(context) {
return {
InlineFragment(node) {
const typeCondition = node.typeCondition;
if (typeCondition) {
const type = (0, _typeFromAST.typeFromAST)(
context.getSchema(),
typeCondition,
);
if (type && !(0, _definition.isCompositeType)(type)) {
const typeStr = (0, _printer.print)(typeCondition);
context.reportError(
new _GraphQLError.GraphQLError(
`Fragment cannot condition on non composite type "${typeStr}".`,
{
nodes: typeCondition,
},
),
);
}
}
},
FragmentDefinition(node) {
const type = (0, _typeFromAST.typeFromAST)(
context.getSchema(),
node.typeCondition,
);
if (type && !(0, _definition.isCompositeType)(type)) {
const typeStr = (0, _printer.print)(node.typeCondition);
context.reportError(
new _GraphQLError.GraphQLError(
`Fragment "${node.name.value}" cannot condition on non composite type "${typeStr}".`,
{
nodes: node.typeCondition,
},
),
);
}
},
};
}

View File

@@ -0,0 +1,53 @@
import { GraphQLError } from '../../error/GraphQLError.mjs';
import { print } from '../../language/printer.mjs';
import { isCompositeType } from '../../type/definition.mjs';
import { typeFromAST } from '../../utilities/typeFromAST.mjs';
/**
* Fragments on composite type
*
* Fragments use a type condition to determine if they apply, since fragments
* can only be spread into a composite type (object, interface, or union), the
* type condition must also be a composite type.
*
* See https://spec.graphql.org/draft/#sec-Fragments-On-Composite-Types
*/
export function FragmentsOnCompositeTypesRule(context) {
return {
InlineFragment(node) {
const typeCondition = node.typeCondition;
if (typeCondition) {
const type = typeFromAST(context.getSchema(), typeCondition);
if (type && !isCompositeType(type)) {
const typeStr = print(typeCondition);
context.reportError(
new GraphQLError(
`Fragment cannot condition on non composite type "${typeStr}".`,
{
nodes: typeCondition,
},
),
);
}
}
},
FragmentDefinition(node) {
const type = typeFromAST(context.getSchema(), node.typeCondition);
if (type && !isCompositeType(type)) {
const typeStr = print(node.typeCondition);
context.reportError(
new GraphQLError(
`Fragment "${node.name.value}" cannot condition on non composite type "${typeStr}".`,
{
nodes: node.typeCondition,
},
),
);
}
},
};
}

View File

@@ -0,0 +1,23 @@
import type { ASTVisitor } from '../../language/visitor';
import type {
SDLValidationContext,
ValidationContext,
} from '../ValidationContext';
/**
* Known argument names
*
* A GraphQL field is only valid if all supplied arguments are defined by
* that field.
*
* See https://spec.graphql.org/draft/#sec-Argument-Names
* See https://spec.graphql.org/draft/#sec-Directives-Are-In-Valid-Locations
*/
export declare function KnownArgumentNamesRule(
context: ValidationContext,
): ASTVisitor;
/**
* @internal
*/
export declare function KnownArgumentNamesOnDirectivesRule(
context: ValidationContext | SDLValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,120 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.KnownArgumentNamesOnDirectivesRule = KnownArgumentNamesOnDirectivesRule;
exports.KnownArgumentNamesRule = KnownArgumentNamesRule;
var _didYouMean = require('../../jsutils/didYouMean.js');
var _suggestionList = require('../../jsutils/suggestionList.js');
var _GraphQLError = require('../../error/GraphQLError.js');
var _kinds = require('../../language/kinds.js');
var _directives = require('../../type/directives.js');
/**
* Known argument names
*
* A GraphQL field is only valid if all supplied arguments are defined by
* that field.
*
* See https://spec.graphql.org/draft/#sec-Argument-Names
* See https://spec.graphql.org/draft/#sec-Directives-Are-In-Valid-Locations
*/
function KnownArgumentNamesRule(context) {
return {
// eslint-disable-next-line new-cap
...KnownArgumentNamesOnDirectivesRule(context),
Argument(argNode) {
const argDef = context.getArgument();
const fieldDef = context.getFieldDef();
const parentType = context.getParentType();
if (!argDef && fieldDef && parentType) {
const argName = argNode.name.value;
const knownArgsNames = fieldDef.args.map((arg) => arg.name);
const suggestions = (0, _suggestionList.suggestionList)(
argName,
knownArgsNames,
);
context.reportError(
new _GraphQLError.GraphQLError(
`Unknown argument "${argName}" on field "${parentType.name}.${fieldDef.name}".` +
(0, _didYouMean.didYouMean)(suggestions),
{
nodes: argNode,
},
),
);
}
},
};
}
/**
* @internal
*/
function KnownArgumentNamesOnDirectivesRule(context) {
const directiveArgs = Object.create(null);
const schema = context.getSchema();
const definedDirectives = schema
? schema.getDirectives()
: _directives.specifiedDirectives;
for (const directive of definedDirectives) {
directiveArgs[directive.name] = directive.args.map((arg) => arg.name);
}
const astDefinitions = context.getDocument().definitions;
for (const def of astDefinitions) {
if (def.kind === _kinds.Kind.DIRECTIVE_DEFINITION) {
var _def$arguments;
// FIXME: https://github.com/graphql/graphql-js/issues/2203
/* c8 ignore next */
const argsNodes =
(_def$arguments = def.arguments) !== null && _def$arguments !== void 0
? _def$arguments
: [];
directiveArgs[def.name.value] = argsNodes.map((arg) => arg.name.value);
}
}
return {
Directive(directiveNode) {
const directiveName = directiveNode.name.value;
const knownArgs = directiveArgs[directiveName];
if (directiveNode.arguments && knownArgs) {
for (const argNode of directiveNode.arguments) {
const argName = argNode.name.value;
if (!knownArgs.includes(argName)) {
const suggestions = (0, _suggestionList.suggestionList)(
argName,
knownArgs,
);
context.reportError(
new _GraphQLError.GraphQLError(
`Unknown argument "${argName}" on directive "@${directiveName}".` +
(0, _didYouMean.didYouMean)(suggestions),
{
nodes: argNode,
},
),
);
}
}
}
return false;
},
};
}

View File

@@ -0,0 +1,102 @@
import { didYouMean } from '../../jsutils/didYouMean.mjs';
import { suggestionList } from '../../jsutils/suggestionList.mjs';
import { GraphQLError } from '../../error/GraphQLError.mjs';
import { Kind } from '../../language/kinds.mjs';
import { specifiedDirectives } from '../../type/directives.mjs';
/**
* Known argument names
*
* A GraphQL field is only valid if all supplied arguments are defined by
* that field.
*
* See https://spec.graphql.org/draft/#sec-Argument-Names
* See https://spec.graphql.org/draft/#sec-Directives-Are-In-Valid-Locations
*/
export function KnownArgumentNamesRule(context) {
return {
// eslint-disable-next-line new-cap
...KnownArgumentNamesOnDirectivesRule(context),
Argument(argNode) {
const argDef = context.getArgument();
const fieldDef = context.getFieldDef();
const parentType = context.getParentType();
if (!argDef && fieldDef && parentType) {
const argName = argNode.name.value;
const knownArgsNames = fieldDef.args.map((arg) => arg.name);
const suggestions = suggestionList(argName, knownArgsNames);
context.reportError(
new GraphQLError(
`Unknown argument "${argName}" on field "${parentType.name}.${fieldDef.name}".` +
didYouMean(suggestions),
{
nodes: argNode,
},
),
);
}
},
};
}
/**
* @internal
*/
export function KnownArgumentNamesOnDirectivesRule(context) {
const directiveArgs = Object.create(null);
const schema = context.getSchema();
const definedDirectives = schema
? schema.getDirectives()
: specifiedDirectives;
for (const directive of definedDirectives) {
directiveArgs[directive.name] = directive.args.map((arg) => arg.name);
}
const astDefinitions = context.getDocument().definitions;
for (const def of astDefinitions) {
if (def.kind === Kind.DIRECTIVE_DEFINITION) {
var _def$arguments;
// FIXME: https://github.com/graphql/graphql-js/issues/2203
/* c8 ignore next */
const argsNodes =
(_def$arguments = def.arguments) !== null && _def$arguments !== void 0
? _def$arguments
: [];
directiveArgs[def.name.value] = argsNodes.map((arg) => arg.name.value);
}
}
return {
Directive(directiveNode) {
const directiveName = directiveNode.name.value;
const knownArgs = directiveArgs[directiveName];
if (directiveNode.arguments && knownArgs) {
for (const argNode of directiveNode.arguments) {
const argName = argNode.name.value;
if (!knownArgs.includes(argName)) {
const suggestions = suggestionList(argName, knownArgs);
context.reportError(
new GraphQLError(
`Unknown argument "${argName}" on directive "@${directiveName}".` +
didYouMean(suggestions),
{
nodes: argNode,
},
),
);
}
}
}
return false;
},
};
}

View File

@@ -0,0 +1,16 @@
import type { ASTVisitor } from '../../language/visitor';
import type {
SDLValidationContext,
ValidationContext,
} from '../ValidationContext';
/**
* Known directives
*
* A GraphQL document is only valid if all `@directives` are known by the
* schema and legally positioned.
*
* See https://spec.graphql.org/draft/#sec-Directives-Are-Defined
*/
export declare function KnownDirectivesRule(
context: ValidationContext | SDLValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,167 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.KnownDirectivesRule = KnownDirectivesRule;
var _inspect = require('../../jsutils/inspect.js');
var _invariant = require('../../jsutils/invariant.js');
var _GraphQLError = require('../../error/GraphQLError.js');
var _ast = require('../../language/ast.js');
var _directiveLocation = require('../../language/directiveLocation.js');
var _kinds = require('../../language/kinds.js');
var _directives = require('../../type/directives.js');
/**
* Known directives
*
* A GraphQL document is only valid if all `@directives` are known by the
* schema and legally positioned.
*
* See https://spec.graphql.org/draft/#sec-Directives-Are-Defined
*/
function KnownDirectivesRule(context) {
const locationsMap = Object.create(null);
const schema = context.getSchema();
const definedDirectives = schema
? schema.getDirectives()
: _directives.specifiedDirectives;
for (const directive of definedDirectives) {
locationsMap[directive.name] = directive.locations;
}
const astDefinitions = context.getDocument().definitions;
for (const def of astDefinitions) {
if (def.kind === _kinds.Kind.DIRECTIVE_DEFINITION) {
locationsMap[def.name.value] = def.locations.map((name) => name.value);
}
}
return {
Directive(node, _key, _parent, _path, ancestors) {
const name = node.name.value;
const locations = locationsMap[name];
if (!locations) {
context.reportError(
new _GraphQLError.GraphQLError(`Unknown directive "@${name}".`, {
nodes: node,
}),
);
return;
}
const candidateLocation = getDirectiveLocationForASTPath(ancestors);
if (candidateLocation && !locations.includes(candidateLocation)) {
context.reportError(
new _GraphQLError.GraphQLError(
`Directive "@${name}" may not be used on ${candidateLocation}.`,
{
nodes: node,
},
),
);
}
},
};
}
function getDirectiveLocationForASTPath(ancestors) {
const appliedTo = ancestors[ancestors.length - 1];
'kind' in appliedTo || (0, _invariant.invariant)(false);
switch (appliedTo.kind) {
case _kinds.Kind.OPERATION_DEFINITION:
return getDirectiveLocationForOperation(appliedTo.operation);
case _kinds.Kind.FIELD:
return _directiveLocation.DirectiveLocation.FIELD;
case _kinds.Kind.FRAGMENT_SPREAD:
return _directiveLocation.DirectiveLocation.FRAGMENT_SPREAD;
case _kinds.Kind.INLINE_FRAGMENT:
return _directiveLocation.DirectiveLocation.INLINE_FRAGMENT;
case _kinds.Kind.FRAGMENT_DEFINITION:
return _directiveLocation.DirectiveLocation.FRAGMENT_DEFINITION;
case _kinds.Kind.VARIABLE_DEFINITION:
return _directiveLocation.DirectiveLocation.VARIABLE_DEFINITION;
case _kinds.Kind.SCHEMA_DEFINITION:
case _kinds.Kind.SCHEMA_EXTENSION:
return _directiveLocation.DirectiveLocation.SCHEMA;
case _kinds.Kind.SCALAR_TYPE_DEFINITION:
case _kinds.Kind.SCALAR_TYPE_EXTENSION:
return _directiveLocation.DirectiveLocation.SCALAR;
case _kinds.Kind.OBJECT_TYPE_DEFINITION:
case _kinds.Kind.OBJECT_TYPE_EXTENSION:
return _directiveLocation.DirectiveLocation.OBJECT;
case _kinds.Kind.FIELD_DEFINITION:
return _directiveLocation.DirectiveLocation.FIELD_DEFINITION;
case _kinds.Kind.INTERFACE_TYPE_DEFINITION:
case _kinds.Kind.INTERFACE_TYPE_EXTENSION:
return _directiveLocation.DirectiveLocation.INTERFACE;
case _kinds.Kind.UNION_TYPE_DEFINITION:
case _kinds.Kind.UNION_TYPE_EXTENSION:
return _directiveLocation.DirectiveLocation.UNION;
case _kinds.Kind.ENUM_TYPE_DEFINITION:
case _kinds.Kind.ENUM_TYPE_EXTENSION:
return _directiveLocation.DirectiveLocation.ENUM;
case _kinds.Kind.ENUM_VALUE_DEFINITION:
return _directiveLocation.DirectiveLocation.ENUM_VALUE;
case _kinds.Kind.INPUT_OBJECT_TYPE_DEFINITION:
case _kinds.Kind.INPUT_OBJECT_TYPE_EXTENSION:
return _directiveLocation.DirectiveLocation.INPUT_OBJECT;
case _kinds.Kind.INPUT_VALUE_DEFINITION: {
const parentNode = ancestors[ancestors.length - 3];
'kind' in parentNode || (0, _invariant.invariant)(false);
return parentNode.kind === _kinds.Kind.INPUT_OBJECT_TYPE_DEFINITION
? _directiveLocation.DirectiveLocation.INPUT_FIELD_DEFINITION
: _directiveLocation.DirectiveLocation.ARGUMENT_DEFINITION;
}
// Not reachable, all possible types have been considered.
/* c8 ignore next */
default:
false ||
(0, _invariant.invariant)(
false,
'Unexpected kind: ' + (0, _inspect.inspect)(appliedTo.kind),
);
}
}
function getDirectiveLocationForOperation(operation) {
switch (operation) {
case _ast.OperationTypeNode.QUERY:
return _directiveLocation.DirectiveLocation.QUERY;
case _ast.OperationTypeNode.MUTATION:
return _directiveLocation.DirectiveLocation.MUTATION;
case _ast.OperationTypeNode.SUBSCRIPTION:
return _directiveLocation.DirectiveLocation.SUBSCRIPTION;
}
}

View File

@@ -0,0 +1,150 @@
import { inspect } from '../../jsutils/inspect.mjs';
import { invariant } from '../../jsutils/invariant.mjs';
import { GraphQLError } from '../../error/GraphQLError.mjs';
import { OperationTypeNode } from '../../language/ast.mjs';
import { DirectiveLocation } from '../../language/directiveLocation.mjs';
import { Kind } from '../../language/kinds.mjs';
import { specifiedDirectives } from '../../type/directives.mjs';
/**
* Known directives
*
* A GraphQL document is only valid if all `@directives` are known by the
* schema and legally positioned.
*
* See https://spec.graphql.org/draft/#sec-Directives-Are-Defined
*/
export function KnownDirectivesRule(context) {
const locationsMap = Object.create(null);
const schema = context.getSchema();
const definedDirectives = schema
? schema.getDirectives()
: specifiedDirectives;
for (const directive of definedDirectives) {
locationsMap[directive.name] = directive.locations;
}
const astDefinitions = context.getDocument().definitions;
for (const def of astDefinitions) {
if (def.kind === Kind.DIRECTIVE_DEFINITION) {
locationsMap[def.name.value] = def.locations.map((name) => name.value);
}
}
return {
Directive(node, _key, _parent, _path, ancestors) {
const name = node.name.value;
const locations = locationsMap[name];
if (!locations) {
context.reportError(
new GraphQLError(`Unknown directive "@${name}".`, {
nodes: node,
}),
);
return;
}
const candidateLocation = getDirectiveLocationForASTPath(ancestors);
if (candidateLocation && !locations.includes(candidateLocation)) {
context.reportError(
new GraphQLError(
`Directive "@${name}" may not be used on ${candidateLocation}.`,
{
nodes: node,
},
),
);
}
},
};
}
function getDirectiveLocationForASTPath(ancestors) {
const appliedTo = ancestors[ancestors.length - 1];
'kind' in appliedTo || invariant(false);
switch (appliedTo.kind) {
case Kind.OPERATION_DEFINITION:
return getDirectiveLocationForOperation(appliedTo.operation);
case Kind.FIELD:
return DirectiveLocation.FIELD;
case Kind.FRAGMENT_SPREAD:
return DirectiveLocation.FRAGMENT_SPREAD;
case Kind.INLINE_FRAGMENT:
return DirectiveLocation.INLINE_FRAGMENT;
case Kind.FRAGMENT_DEFINITION:
return DirectiveLocation.FRAGMENT_DEFINITION;
case Kind.VARIABLE_DEFINITION:
return DirectiveLocation.VARIABLE_DEFINITION;
case Kind.SCHEMA_DEFINITION:
case Kind.SCHEMA_EXTENSION:
return DirectiveLocation.SCHEMA;
case Kind.SCALAR_TYPE_DEFINITION:
case Kind.SCALAR_TYPE_EXTENSION:
return DirectiveLocation.SCALAR;
case Kind.OBJECT_TYPE_DEFINITION:
case Kind.OBJECT_TYPE_EXTENSION:
return DirectiveLocation.OBJECT;
case Kind.FIELD_DEFINITION:
return DirectiveLocation.FIELD_DEFINITION;
case Kind.INTERFACE_TYPE_DEFINITION:
case Kind.INTERFACE_TYPE_EXTENSION:
return DirectiveLocation.INTERFACE;
case Kind.UNION_TYPE_DEFINITION:
case Kind.UNION_TYPE_EXTENSION:
return DirectiveLocation.UNION;
case Kind.ENUM_TYPE_DEFINITION:
case Kind.ENUM_TYPE_EXTENSION:
return DirectiveLocation.ENUM;
case Kind.ENUM_VALUE_DEFINITION:
return DirectiveLocation.ENUM_VALUE;
case Kind.INPUT_OBJECT_TYPE_DEFINITION:
case Kind.INPUT_OBJECT_TYPE_EXTENSION:
return DirectiveLocation.INPUT_OBJECT;
case Kind.INPUT_VALUE_DEFINITION: {
const parentNode = ancestors[ancestors.length - 3];
'kind' in parentNode || invariant(false);
return parentNode.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION
? DirectiveLocation.INPUT_FIELD_DEFINITION
: DirectiveLocation.ARGUMENT_DEFINITION;
}
// Not reachable, all possible types have been considered.
/* c8 ignore next */
default:
false || invariant(false, 'Unexpected kind: ' + inspect(appliedTo.kind));
}
}
function getDirectiveLocationForOperation(operation) {
switch (operation) {
case OperationTypeNode.QUERY:
return DirectiveLocation.QUERY;
case OperationTypeNode.MUTATION:
return DirectiveLocation.MUTATION;
case OperationTypeNode.SUBSCRIPTION:
return DirectiveLocation.SUBSCRIPTION;
}
}

View File

@@ -0,0 +1,13 @@
import type { ASTVisitor } from '../../language/visitor';
import type { ValidationContext } from '../ValidationContext';
/**
* Known fragment names
*
* A GraphQL document is only valid if all `...Fragment` fragment spreads refer
* to fragments defined in the same document.
*
* See https://spec.graphql.org/draft/#sec-Fragment-spread-target-defined
*/
export declare function KnownFragmentNamesRule(
context: ValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,36 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.KnownFragmentNamesRule = KnownFragmentNamesRule;
var _GraphQLError = require('../../error/GraphQLError.js');
/**
* Known fragment names
*
* A GraphQL document is only valid if all `...Fragment` fragment spreads refer
* to fragments defined in the same document.
*
* See https://spec.graphql.org/draft/#sec-Fragment-spread-target-defined
*/
function KnownFragmentNamesRule(context) {
return {
FragmentSpread(node) {
const fragmentName = node.name.value;
const fragment = context.getFragment(fragmentName);
if (!fragment) {
context.reportError(
new _GraphQLError.GraphQLError(
`Unknown fragment "${fragmentName}".`,
{
nodes: node.name,
},
),
);
}
},
};
}

View File

@@ -0,0 +1,26 @@
import { GraphQLError } from '../../error/GraphQLError.mjs';
/**
* Known fragment names
*
* A GraphQL document is only valid if all `...Fragment` fragment spreads refer
* to fragments defined in the same document.
*
* See https://spec.graphql.org/draft/#sec-Fragment-spread-target-defined
*/
export function KnownFragmentNamesRule(context) {
return {
FragmentSpread(node) {
const fragmentName = node.name.value;
const fragment = context.getFragment(fragmentName);
if (!fragment) {
context.reportError(
new GraphQLError(`Unknown fragment "${fragmentName}".`, {
nodes: node.name,
}),
);
}
},
};
}

View File

@@ -0,0 +1,16 @@
import type { ASTVisitor } from '../../language/visitor';
import type {
SDLValidationContext,
ValidationContext,
} from '../ValidationContext';
/**
* Known type names
*
* A GraphQL document is only valid if referenced types (specifically
* variable definitions and fragment conditions) are defined by the type schema.
*
* See https://spec.graphql.org/draft/#sec-Fragment-Spread-Type-Existence
*/
export declare function KnownTypeNamesRule(
context: ValidationContext | SDLValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,89 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.KnownTypeNamesRule = KnownTypeNamesRule;
var _didYouMean = require('../../jsutils/didYouMean.js');
var _suggestionList = require('../../jsutils/suggestionList.js');
var _GraphQLError = require('../../error/GraphQLError.js');
var _predicates = require('../../language/predicates.js');
var _introspection = require('../../type/introspection.js');
var _scalars = require('../../type/scalars.js');
/**
* Known type names
*
* A GraphQL document is only valid if referenced types (specifically
* variable definitions and fragment conditions) are defined by the type schema.
*
* See https://spec.graphql.org/draft/#sec-Fragment-Spread-Type-Existence
*/
function KnownTypeNamesRule(context) {
const schema = context.getSchema();
const existingTypesMap = schema ? schema.getTypeMap() : Object.create(null);
const definedTypes = Object.create(null);
for (const def of context.getDocument().definitions) {
if ((0, _predicates.isTypeDefinitionNode)(def)) {
definedTypes[def.name.value] = true;
}
}
const typeNames = [
...Object.keys(existingTypesMap),
...Object.keys(definedTypes),
];
return {
NamedType(node, _1, parent, _2, ancestors) {
const typeName = node.name.value;
if (!existingTypesMap[typeName] && !definedTypes[typeName]) {
var _ancestors$;
const definitionNode =
(_ancestors$ = ancestors[2]) !== null && _ancestors$ !== void 0
? _ancestors$
: parent;
const isSDL = definitionNode != null && isSDLNode(definitionNode);
if (isSDL && standardTypeNames.includes(typeName)) {
return;
}
const suggestedTypes = (0, _suggestionList.suggestionList)(
typeName,
isSDL ? standardTypeNames.concat(typeNames) : typeNames,
);
context.reportError(
new _GraphQLError.GraphQLError(
`Unknown type "${typeName}".` +
(0, _didYouMean.didYouMean)(suggestedTypes),
{
nodes: node,
},
),
);
}
},
};
}
const standardTypeNames = [
..._scalars.specifiedScalarTypes,
..._introspection.introspectionTypes,
].map((type) => type.name);
function isSDLNode(value) {
return (
'kind' in value &&
((0, _predicates.isTypeSystemDefinitionNode)(value) ||
(0, _predicates.isTypeSystemExtensionNode)(value))
);
}

View File

@@ -0,0 +1,77 @@
import { didYouMean } from '../../jsutils/didYouMean.mjs';
import { suggestionList } from '../../jsutils/suggestionList.mjs';
import { GraphQLError } from '../../error/GraphQLError.mjs';
import {
isTypeDefinitionNode,
isTypeSystemDefinitionNode,
isTypeSystemExtensionNode,
} from '../../language/predicates.mjs';
import { introspectionTypes } from '../../type/introspection.mjs';
import { specifiedScalarTypes } from '../../type/scalars.mjs';
/**
* Known type names
*
* A GraphQL document is only valid if referenced types (specifically
* variable definitions and fragment conditions) are defined by the type schema.
*
* See https://spec.graphql.org/draft/#sec-Fragment-Spread-Type-Existence
*/
export function KnownTypeNamesRule(context) {
const schema = context.getSchema();
const existingTypesMap = schema ? schema.getTypeMap() : Object.create(null);
const definedTypes = Object.create(null);
for (const def of context.getDocument().definitions) {
if (isTypeDefinitionNode(def)) {
definedTypes[def.name.value] = true;
}
}
const typeNames = [
...Object.keys(existingTypesMap),
...Object.keys(definedTypes),
];
return {
NamedType(node, _1, parent, _2, ancestors) {
const typeName = node.name.value;
if (!existingTypesMap[typeName] && !definedTypes[typeName]) {
var _ancestors$;
const definitionNode =
(_ancestors$ = ancestors[2]) !== null && _ancestors$ !== void 0
? _ancestors$
: parent;
const isSDL = definitionNode != null && isSDLNode(definitionNode);
if (isSDL && standardTypeNames.includes(typeName)) {
return;
}
const suggestedTypes = suggestionList(
typeName,
isSDL ? standardTypeNames.concat(typeNames) : typeNames,
);
context.reportError(
new GraphQLError(
`Unknown type "${typeName}".` + didYouMean(suggestedTypes),
{
nodes: node,
},
),
);
}
},
};
}
const standardTypeNames = [...specifiedScalarTypes, ...introspectionTypes].map(
(type) => type.name,
);
function isSDLNode(value) {
return (
'kind' in value &&
(isTypeSystemDefinitionNode(value) || isTypeSystemExtensionNode(value))
);
}

View File

@@ -0,0 +1,13 @@
import type { ASTVisitor } from '../../language/visitor';
import type { ASTValidationContext } from '../ValidationContext';
/**
* Lone anonymous operation
*
* A GraphQL document is only valid if when it contains an anonymous operation
* (the query short-hand) that it contains only that one operation definition.
*
* See https://spec.graphql.org/draft/#sec-Lone-Anonymous-Operation
*/
export declare function LoneAnonymousOperationRule(
context: ASTValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,42 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.LoneAnonymousOperationRule = LoneAnonymousOperationRule;
var _GraphQLError = require('../../error/GraphQLError.js');
var _kinds = require('../../language/kinds.js');
/**
* Lone anonymous operation
*
* A GraphQL document is only valid if when it contains an anonymous operation
* (the query short-hand) that it contains only that one operation definition.
*
* See https://spec.graphql.org/draft/#sec-Lone-Anonymous-Operation
*/
function LoneAnonymousOperationRule(context) {
let operationCount = 0;
return {
Document(node) {
operationCount = node.definitions.filter(
(definition) => definition.kind === _kinds.Kind.OPERATION_DEFINITION,
).length;
},
OperationDefinition(node) {
if (!node.name && operationCount > 1) {
context.reportError(
new _GraphQLError.GraphQLError(
'This anonymous operation must be the only defined operation.',
{
nodes: node,
},
),
);
}
},
};
}

View File

@@ -0,0 +1,34 @@
import { GraphQLError } from '../../error/GraphQLError.mjs';
import { Kind } from '../../language/kinds.mjs';
/**
* Lone anonymous operation
*
* A GraphQL document is only valid if when it contains an anonymous operation
* (the query short-hand) that it contains only that one operation definition.
*
* See https://spec.graphql.org/draft/#sec-Lone-Anonymous-Operation
*/
export function LoneAnonymousOperationRule(context) {
let operationCount = 0;
return {
Document(node) {
operationCount = node.definitions.filter(
(definition) => definition.kind === Kind.OPERATION_DEFINITION,
).length;
},
OperationDefinition(node) {
if (!node.name && operationCount > 1) {
context.reportError(
new GraphQLError(
'This anonymous operation must be the only defined operation.',
{
nodes: node,
},
),
);
}
},
};
}

View File

@@ -0,0 +1,10 @@
import type { ASTVisitor } from '../../language/visitor';
import type { SDLValidationContext } from '../ValidationContext';
/**
* Lone Schema definition
*
* A GraphQL document is only valid if it contains only one schema definition.
*/
export declare function LoneSchemaDefinitionRule(
context: SDLValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,67 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.LoneSchemaDefinitionRule = LoneSchemaDefinitionRule;
var _GraphQLError = require('../../error/GraphQLError.js');
/**
* Lone Schema definition
*
* A GraphQL document is only valid if it contains only one schema definition.
*/
function LoneSchemaDefinitionRule(context) {
var _ref, _ref2, _oldSchema$astNode;
const oldSchema = context.getSchema();
const alreadyDefined =
(_ref =
(_ref2 =
(_oldSchema$astNode =
oldSchema === null || oldSchema === void 0
? void 0
: oldSchema.astNode) !== null && _oldSchema$astNode !== void 0
? _oldSchema$astNode
: oldSchema === null || oldSchema === void 0
? void 0
: oldSchema.getQueryType()) !== null && _ref2 !== void 0
? _ref2
: oldSchema === null || oldSchema === void 0
? void 0
: oldSchema.getMutationType()) !== null && _ref !== void 0
? _ref
: oldSchema === null || oldSchema === void 0
? void 0
: oldSchema.getSubscriptionType();
let schemaDefinitionsCount = 0;
return {
SchemaDefinition(node) {
if (alreadyDefined) {
context.reportError(
new _GraphQLError.GraphQLError(
'Cannot define a new schema within a schema extension.',
{
nodes: node,
},
),
);
return;
}
if (schemaDefinitionsCount > 0) {
context.reportError(
new _GraphQLError.GraphQLError(
'Must provide only one schema definition.',
{
nodes: node,
},
),
);
}
++schemaDefinitionsCount;
},
};
}

View File

@@ -0,0 +1,57 @@
import { GraphQLError } from '../../error/GraphQLError.mjs';
/**
* Lone Schema definition
*
* A GraphQL document is only valid if it contains only one schema definition.
*/
export function LoneSchemaDefinitionRule(context) {
var _ref, _ref2, _oldSchema$astNode;
const oldSchema = context.getSchema();
const alreadyDefined =
(_ref =
(_ref2 =
(_oldSchema$astNode =
oldSchema === null || oldSchema === void 0
? void 0
: oldSchema.astNode) !== null && _oldSchema$astNode !== void 0
? _oldSchema$astNode
: oldSchema === null || oldSchema === void 0
? void 0
: oldSchema.getQueryType()) !== null && _ref2 !== void 0
? _ref2
: oldSchema === null || oldSchema === void 0
? void 0
: oldSchema.getMutationType()) !== null && _ref !== void 0
? _ref
: oldSchema === null || oldSchema === void 0
? void 0
: oldSchema.getSubscriptionType();
let schemaDefinitionsCount = 0;
return {
SchemaDefinition(node) {
if (alreadyDefined) {
context.reportError(
new GraphQLError(
'Cannot define a new schema within a schema extension.',
{
nodes: node,
},
),
);
return;
}
if (schemaDefinitionsCount > 0) {
context.reportError(
new GraphQLError('Must provide only one schema definition.', {
nodes: node,
}),
);
}
++schemaDefinitionsCount;
},
};
}

View File

@@ -0,0 +1,13 @@
import type { ASTVisitor } from '../../language/visitor';
import type { ASTValidationContext } from '../ValidationContext';
/**
* No fragment cycles
*
* The graph of fragment spreads must not form any cycles including spreading itself.
* Otherwise an operation could infinitely spread or infinitely execute on cycles in the underlying data.
*
* See https://spec.graphql.org/draft/#sec-Fragment-spreads-must-not-form-cycles
*/
export declare function NoFragmentCyclesRule(
context: ASTValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,85 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.NoFragmentCyclesRule = NoFragmentCyclesRule;
var _GraphQLError = require('../../error/GraphQLError.js');
/**
* No fragment cycles
*
* The graph of fragment spreads must not form any cycles including spreading itself.
* Otherwise an operation could infinitely spread or infinitely execute on cycles in the underlying data.
*
* See https://spec.graphql.org/draft/#sec-Fragment-spreads-must-not-form-cycles
*/
function NoFragmentCyclesRule(context) {
// Tracks already visited fragments to maintain O(N) and to ensure that cycles
// are not redundantly reported.
const visitedFrags = Object.create(null); // Array of AST nodes used to produce meaningful errors
const spreadPath = []; // Position in the spread path
const spreadPathIndexByName = Object.create(null);
return {
OperationDefinition: () => false,
FragmentDefinition(node) {
detectCycleRecursive(node);
return false;
},
}; // This does a straight-forward DFS to find cycles.
// It does not terminate when a cycle was found but continues to explore
// the graph to find all possible cycles.
function detectCycleRecursive(fragment) {
if (visitedFrags[fragment.name.value]) {
return;
}
const fragmentName = fragment.name.value;
visitedFrags[fragmentName] = true;
const spreadNodes = context.getFragmentSpreads(fragment.selectionSet);
if (spreadNodes.length === 0) {
return;
}
spreadPathIndexByName[fragmentName] = spreadPath.length;
for (const spreadNode of spreadNodes) {
const spreadName = spreadNode.name.value;
const cycleIndex = spreadPathIndexByName[spreadName];
spreadPath.push(spreadNode);
if (cycleIndex === undefined) {
const spreadFragment = context.getFragment(spreadName);
if (spreadFragment) {
detectCycleRecursive(spreadFragment);
}
} else {
const cyclePath = spreadPath.slice(cycleIndex);
const viaPath = cyclePath
.slice(0, -1)
.map((s) => '"' + s.name.value + '"')
.join(', ');
context.reportError(
new _GraphQLError.GraphQLError(
`Cannot spread fragment "${spreadName}" within itself` +
(viaPath !== '' ? ` via ${viaPath}.` : '.'),
{
nodes: cyclePath,
},
),
);
}
spreadPath.pop();
}
spreadPathIndexByName[fragmentName] = undefined;
}
}

View File

@@ -0,0 +1,78 @@
import { GraphQLError } from '../../error/GraphQLError.mjs';
/**
* No fragment cycles
*
* The graph of fragment spreads must not form any cycles including spreading itself.
* Otherwise an operation could infinitely spread or infinitely execute on cycles in the underlying data.
*
* See https://spec.graphql.org/draft/#sec-Fragment-spreads-must-not-form-cycles
*/
export function NoFragmentCyclesRule(context) {
// Tracks already visited fragments to maintain O(N) and to ensure that cycles
// are not redundantly reported.
const visitedFrags = Object.create(null); // Array of AST nodes used to produce meaningful errors
const spreadPath = []; // Position in the spread path
const spreadPathIndexByName = Object.create(null);
return {
OperationDefinition: () => false,
FragmentDefinition(node) {
detectCycleRecursive(node);
return false;
},
}; // This does a straight-forward DFS to find cycles.
// It does not terminate when a cycle was found but continues to explore
// the graph to find all possible cycles.
function detectCycleRecursive(fragment) {
if (visitedFrags[fragment.name.value]) {
return;
}
const fragmentName = fragment.name.value;
visitedFrags[fragmentName] = true;
const spreadNodes = context.getFragmentSpreads(fragment.selectionSet);
if (spreadNodes.length === 0) {
return;
}
spreadPathIndexByName[fragmentName] = spreadPath.length;
for (const spreadNode of spreadNodes) {
const spreadName = spreadNode.name.value;
const cycleIndex = spreadPathIndexByName[spreadName];
spreadPath.push(spreadNode);
if (cycleIndex === undefined) {
const spreadFragment = context.getFragment(spreadName);
if (spreadFragment) {
detectCycleRecursive(spreadFragment);
}
} else {
const cyclePath = spreadPath.slice(cycleIndex);
const viaPath = cyclePath
.slice(0, -1)
.map((s) => '"' + s.name.value + '"')
.join(', ');
context.reportError(
new GraphQLError(
`Cannot spread fragment "${spreadName}" within itself` +
(viaPath !== '' ? ` via ${viaPath}.` : '.'),
{
nodes: cyclePath,
},
),
);
}
spreadPath.pop();
}
spreadPathIndexByName[fragmentName] = undefined;
}
}

View File

@@ -0,0 +1,13 @@
import type { ASTVisitor } from '../../language/visitor';
import type { ValidationContext } from '../ValidationContext';
/**
* No undefined variables
*
* A GraphQL operation is only valid if all variables encountered, both directly
* and via fragment spreads, are defined by that operation.
*
* See https://spec.graphql.org/draft/#sec-All-Variable-Uses-Defined
*/
export declare function NoUndefinedVariablesRule(
context: ValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,52 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.NoUndefinedVariablesRule = NoUndefinedVariablesRule;
var _GraphQLError = require('../../error/GraphQLError.js');
/**
* No undefined variables
*
* A GraphQL operation is only valid if all variables encountered, both directly
* and via fragment spreads, are defined by that operation.
*
* See https://spec.graphql.org/draft/#sec-All-Variable-Uses-Defined
*/
function NoUndefinedVariablesRule(context) {
let variableNameDefined = Object.create(null);
return {
OperationDefinition: {
enter() {
variableNameDefined = Object.create(null);
},
leave(operation) {
const usages = context.getRecursiveVariableUsages(operation);
for (const { node } of usages) {
const varName = node.name.value;
if (variableNameDefined[varName] !== true) {
context.reportError(
new _GraphQLError.GraphQLError(
operation.name
? `Variable "$${varName}" is not defined by operation "${operation.name.value}".`
: `Variable "$${varName}" is not defined.`,
{
nodes: [node, operation],
},
),
);
}
}
},
},
VariableDefinition(node) {
variableNameDefined[node.variable.name.value] = true;
},
};
}

View File

@@ -0,0 +1,45 @@
import { GraphQLError } from '../../error/GraphQLError.mjs';
/**
* No undefined variables
*
* A GraphQL operation is only valid if all variables encountered, both directly
* and via fragment spreads, are defined by that operation.
*
* See https://spec.graphql.org/draft/#sec-All-Variable-Uses-Defined
*/
export function NoUndefinedVariablesRule(context) {
let variableNameDefined = Object.create(null);
return {
OperationDefinition: {
enter() {
variableNameDefined = Object.create(null);
},
leave(operation) {
const usages = context.getRecursiveVariableUsages(operation);
for (const { node } of usages) {
const varName = node.name.value;
if (variableNameDefined[varName] !== true) {
context.reportError(
new GraphQLError(
operation.name
? `Variable "$${varName}" is not defined by operation "${operation.name.value}".`
: `Variable "$${varName}" is not defined.`,
{
nodes: [node, operation],
},
),
);
}
}
},
},
VariableDefinition(node) {
variableNameDefined[node.variable.name.value] = true;
},
};
}

View File

@@ -0,0 +1,13 @@
import type { ASTVisitor } from '../../language/visitor';
import type { ASTValidationContext } from '../ValidationContext';
/**
* No unused fragments
*
* A GraphQL document is only valid if all fragment definitions are spread
* within operations, or spread within other fragments spread within operations.
*
* See https://spec.graphql.org/draft/#sec-Fragments-Must-Be-Used
*/
export declare function NoUnusedFragmentsRule(
context: ASTValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,61 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.NoUnusedFragmentsRule = NoUnusedFragmentsRule;
var _GraphQLError = require('../../error/GraphQLError.js');
/**
* No unused fragments
*
* A GraphQL document is only valid if all fragment definitions are spread
* within operations, or spread within other fragments spread within operations.
*
* See https://spec.graphql.org/draft/#sec-Fragments-Must-Be-Used
*/
function NoUnusedFragmentsRule(context) {
const operationDefs = [];
const fragmentDefs = [];
return {
OperationDefinition(node) {
operationDefs.push(node);
return false;
},
FragmentDefinition(node) {
fragmentDefs.push(node);
return false;
},
Document: {
leave() {
const fragmentNameUsed = Object.create(null);
for (const operation of operationDefs) {
for (const fragment of context.getRecursivelyReferencedFragments(
operation,
)) {
fragmentNameUsed[fragment.name.value] = true;
}
}
for (const fragmentDef of fragmentDefs) {
const fragName = fragmentDef.name.value;
if (fragmentNameUsed[fragName] !== true) {
context.reportError(
new _GraphQLError.GraphQLError(
`Fragment "${fragName}" is never used.`,
{
nodes: fragmentDef,
},
),
);
}
}
},
},
};
}

View File

@@ -0,0 +1,51 @@
import { GraphQLError } from '../../error/GraphQLError.mjs';
/**
* No unused fragments
*
* A GraphQL document is only valid if all fragment definitions are spread
* within operations, or spread within other fragments spread within operations.
*
* See https://spec.graphql.org/draft/#sec-Fragments-Must-Be-Used
*/
export function NoUnusedFragmentsRule(context) {
const operationDefs = [];
const fragmentDefs = [];
return {
OperationDefinition(node) {
operationDefs.push(node);
return false;
},
FragmentDefinition(node) {
fragmentDefs.push(node);
return false;
},
Document: {
leave() {
const fragmentNameUsed = Object.create(null);
for (const operation of operationDefs) {
for (const fragment of context.getRecursivelyReferencedFragments(
operation,
)) {
fragmentNameUsed[fragment.name.value] = true;
}
}
for (const fragmentDef of fragmentDefs) {
const fragName = fragmentDef.name.value;
if (fragmentNameUsed[fragName] !== true) {
context.reportError(
new GraphQLError(`Fragment "${fragName}" is never used.`, {
nodes: fragmentDef,
}),
);
}
}
},
},
};
}

View File

@@ -0,0 +1,13 @@
import type { ASTVisitor } from '../../language/visitor';
import type { ValidationContext } from '../ValidationContext';
/**
* No unused variables
*
* A GraphQL operation is only valid if all variables defined by an operation
* are used, either directly or within a spread fragment.
*
* See https://spec.graphql.org/draft/#sec-All-Variables-Used
*/
export declare function NoUnusedVariablesRule(
context: ValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,57 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.NoUnusedVariablesRule = NoUnusedVariablesRule;
var _GraphQLError = require('../../error/GraphQLError.js');
/**
* No unused variables
*
* A GraphQL operation is only valid if all variables defined by an operation
* are used, either directly or within a spread fragment.
*
* See https://spec.graphql.org/draft/#sec-All-Variables-Used
*/
function NoUnusedVariablesRule(context) {
let variableDefs = [];
return {
OperationDefinition: {
enter() {
variableDefs = [];
},
leave(operation) {
const variableNameUsed = Object.create(null);
const usages = context.getRecursiveVariableUsages(operation);
for (const { node } of usages) {
variableNameUsed[node.name.value] = true;
}
for (const variableDef of variableDefs) {
const variableName = variableDef.variable.name.value;
if (variableNameUsed[variableName] !== true) {
context.reportError(
new _GraphQLError.GraphQLError(
operation.name
? `Variable "$${variableName}" is never used in operation "${operation.name.value}".`
: `Variable "$${variableName}" is never used.`,
{
nodes: variableDef,
},
),
);
}
}
},
},
VariableDefinition(def) {
variableDefs.push(def);
},
};
}

View File

@@ -0,0 +1,50 @@
import { GraphQLError } from '../../error/GraphQLError.mjs';
/**
* No unused variables
*
* A GraphQL operation is only valid if all variables defined by an operation
* are used, either directly or within a spread fragment.
*
* See https://spec.graphql.org/draft/#sec-All-Variables-Used
*/
export function NoUnusedVariablesRule(context) {
let variableDefs = [];
return {
OperationDefinition: {
enter() {
variableDefs = [];
},
leave(operation) {
const variableNameUsed = Object.create(null);
const usages = context.getRecursiveVariableUsages(operation);
for (const { node } of usages) {
variableNameUsed[node.name.value] = true;
}
for (const variableDef of variableDefs) {
const variableName = variableDef.variable.name.value;
if (variableNameUsed[variableName] !== true) {
context.reportError(
new GraphQLError(
operation.name
? `Variable "$${variableName}" is never used in operation "${operation.name.value}".`
: `Variable "$${variableName}" is never used.`,
{
nodes: variableDef,
},
),
);
}
}
},
},
VariableDefinition(def) {
variableDefs.push(def);
},
};
}

View File

@@ -0,0 +1,14 @@
import type { ASTVisitor } from '../../language/visitor';
import type { ValidationContext } from '../ValidationContext';
/**
* Overlapping fields can be merged
*
* A selection set is only valid if all fields (including spreading any
* fragments) either correspond to distinct response names or can be merged
* without ambiguity.
*
* See https://spec.graphql.org/draft/#sec-Field-Selection-Merging
*/
export declare function OverlappingFieldsCanBeMergedRule(
context: ValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,820 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.OverlappingFieldsCanBeMergedRule = OverlappingFieldsCanBeMergedRule;
var _inspect = require('../../jsutils/inspect.js');
var _GraphQLError = require('../../error/GraphQLError.js');
var _kinds = require('../../language/kinds.js');
var _printer = require('../../language/printer.js');
var _definition = require('../../type/definition.js');
var _sortValueNode = require('../../utilities/sortValueNode.js');
var _typeFromAST = require('../../utilities/typeFromAST.js');
function reasonMessage(reason) {
if (Array.isArray(reason)) {
return reason
.map(
([responseName, subReason]) =>
`subfields "${responseName}" conflict because ` +
reasonMessage(subReason),
)
.join(' and ');
}
return reason;
}
/**
* Overlapping fields can be merged
*
* A selection set is only valid if all fields (including spreading any
* fragments) either correspond to distinct response names or can be merged
* without ambiguity.
*
* See https://spec.graphql.org/draft/#sec-Field-Selection-Merging
*/
function OverlappingFieldsCanBeMergedRule(context) {
// A memoization for when two fragments are compared "between" each other for
// conflicts. Two fragments may be compared many times, so memoizing this can
// dramatically improve the performance of this validator.
const comparedFragmentPairs = new PairSet(); // A cache for the "field map" and list of fragment names found in any given
// selection set. Selection sets may be asked for this information multiple
// times, so this improves the performance of this validator.
const cachedFieldsAndFragmentNames = new Map();
return {
SelectionSet(selectionSet) {
const conflicts = findConflictsWithinSelectionSet(
context,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
context.getParentType(),
selectionSet,
);
for (const [[responseName, reason], fields1, fields2] of conflicts) {
const reasonMsg = reasonMessage(reason);
context.reportError(
new _GraphQLError.GraphQLError(
`Fields "${responseName}" conflict because ${reasonMsg}. Use different aliases on the fields to fetch both if this was intentional.`,
{
nodes: fields1.concat(fields2),
},
),
);
}
},
};
}
/**
* Algorithm:
*
* Conflicts occur when two fields exist in a query which will produce the same
* response name, but represent differing values, thus creating a conflict.
* The algorithm below finds all conflicts via making a series of comparisons
* between fields. In order to compare as few fields as possible, this makes
* a series of comparisons "within" sets of fields and "between" sets of fields.
*
* Given any selection set, a collection produces both a set of fields by
* also including all inline fragments, as well as a list of fragments
* referenced by fragment spreads.
*
* A) Each selection set represented in the document first compares "within" its
* collected set of fields, finding any conflicts between every pair of
* overlapping fields.
* Note: This is the *only time* that a the fields "within" a set are compared
* to each other. After this only fields "between" sets are compared.
*
* B) Also, if any fragment is referenced in a selection set, then a
* comparison is made "between" the original set of fields and the
* referenced fragment.
*
* C) Also, if multiple fragments are referenced, then comparisons
* are made "between" each referenced fragment.
*
* D) When comparing "between" a set of fields and a referenced fragment, first
* a comparison is made between each field in the original set of fields and
* each field in the the referenced set of fields.
*
* E) Also, if any fragment is referenced in the referenced selection set,
* then a comparison is made "between" the original set of fields and the
* referenced fragment (recursively referring to step D).
*
* F) When comparing "between" two fragments, first a comparison is made between
* each field in the first referenced set of fields and each field in the the
* second referenced set of fields.
*
* G) Also, any fragments referenced by the first must be compared to the
* second, and any fragments referenced by the second must be compared to the
* first (recursively referring to step F).
*
* H) When comparing two fields, if both have selection sets, then a comparison
* is made "between" both selection sets, first comparing the set of fields in
* the first selection set with the set of fields in the second.
*
* I) Also, if any fragment is referenced in either selection set, then a
* comparison is made "between" the other set of fields and the
* referenced fragment.
*
* J) Also, if two fragments are referenced in both selection sets, then a
* comparison is made "between" the two fragments.
*
*/
// Find all conflicts found "within" a selection set, including those found
// via spreading in fragments. Called when visiting each SelectionSet in the
// GraphQL Document.
function findConflictsWithinSelectionSet(
context,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
parentType,
selectionSet,
) {
const conflicts = [];
const [fieldMap, fragmentNames] = getFieldsAndFragmentNames(
context,
cachedFieldsAndFragmentNames,
parentType,
selectionSet,
); // (A) Find find all conflicts "within" the fields of this selection set.
// Note: this is the *only place* `collectConflictsWithin` is called.
collectConflictsWithin(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
fieldMap,
);
if (fragmentNames.length !== 0) {
// (B) Then collect conflicts between these fields and those represented by
// each spread fragment name found.
for (let i = 0; i < fragmentNames.length; i++) {
collectConflictsBetweenFieldsAndFragment(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
false,
fieldMap,
fragmentNames[i],
); // (C) Then compare this fragment with all other fragments found in this
// selection set to collect conflicts between fragments spread together.
// This compares each item in the list of fragment names to every other
// item in that same list (except for itself).
for (let j = i + 1; j < fragmentNames.length; j++) {
collectConflictsBetweenFragments(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
false,
fragmentNames[i],
fragmentNames[j],
);
}
}
}
return conflicts;
} // Collect all conflicts found between a set of fields and a fragment reference
// including via spreading in any nested fragments.
function collectConflictsBetweenFieldsAndFragment(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
fieldMap,
fragmentName,
) {
const fragment = context.getFragment(fragmentName);
if (!fragment) {
return;
}
const [fieldMap2, referencedFragmentNames] =
getReferencedFieldsAndFragmentNames(
context,
cachedFieldsAndFragmentNames,
fragment,
); // Do not compare a fragment's fieldMap to itself.
if (fieldMap === fieldMap2) {
return;
} // (D) First collect any conflicts between the provided collection of fields
// and the collection of fields represented by the given fragment.
collectConflictsBetween(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
fieldMap,
fieldMap2,
); // (E) Then collect any conflicts between the provided collection of fields
// and any fragment names found in the given fragment.
for (const referencedFragmentName of referencedFragmentNames) {
// Memoize so two fragments are not compared for conflicts more than once.
if (
comparedFragmentPairs.has(
referencedFragmentName,
fragmentName,
areMutuallyExclusive,
)
) {
continue;
}
comparedFragmentPairs.add(
referencedFragmentName,
fragmentName,
areMutuallyExclusive,
);
collectConflictsBetweenFieldsAndFragment(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
fieldMap,
referencedFragmentName,
);
}
} // Collect all conflicts found between two fragments, including via spreading in
// any nested fragments.
function collectConflictsBetweenFragments(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
fragmentName1,
fragmentName2,
) {
// No need to compare a fragment to itself.
if (fragmentName1 === fragmentName2) {
return;
} // Memoize so two fragments are not compared for conflicts more than once.
if (
comparedFragmentPairs.has(
fragmentName1,
fragmentName2,
areMutuallyExclusive,
)
) {
return;
}
comparedFragmentPairs.add(fragmentName1, fragmentName2, areMutuallyExclusive);
const fragment1 = context.getFragment(fragmentName1);
const fragment2 = context.getFragment(fragmentName2);
if (!fragment1 || !fragment2) {
return;
}
const [fieldMap1, referencedFragmentNames1] =
getReferencedFieldsAndFragmentNames(
context,
cachedFieldsAndFragmentNames,
fragment1,
);
const [fieldMap2, referencedFragmentNames2] =
getReferencedFieldsAndFragmentNames(
context,
cachedFieldsAndFragmentNames,
fragment2,
); // (F) First, collect all conflicts between these two collections of fields
// (not including any nested fragments).
collectConflictsBetween(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
fieldMap1,
fieldMap2,
); // (G) Then collect conflicts between the first fragment and any nested
// fragments spread in the second fragment.
for (const referencedFragmentName2 of referencedFragmentNames2) {
collectConflictsBetweenFragments(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
fragmentName1,
referencedFragmentName2,
);
} // (G) Then collect conflicts between the second fragment and any nested
// fragments spread in the first fragment.
for (const referencedFragmentName1 of referencedFragmentNames1) {
collectConflictsBetweenFragments(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
referencedFragmentName1,
fragmentName2,
);
}
} // Find all conflicts found between two selection sets, including those found
// via spreading in fragments. Called when determining if conflicts exist
// between the sub-fields of two overlapping fields.
function findConflictsBetweenSubSelectionSets(
context,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
parentType1,
selectionSet1,
parentType2,
selectionSet2,
) {
const conflicts = [];
const [fieldMap1, fragmentNames1] = getFieldsAndFragmentNames(
context,
cachedFieldsAndFragmentNames,
parentType1,
selectionSet1,
);
const [fieldMap2, fragmentNames2] = getFieldsAndFragmentNames(
context,
cachedFieldsAndFragmentNames,
parentType2,
selectionSet2,
); // (H) First, collect all conflicts between these two collections of field.
collectConflictsBetween(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
fieldMap1,
fieldMap2,
); // (I) Then collect conflicts between the first collection of fields and
// those referenced by each fragment name associated with the second.
for (const fragmentName2 of fragmentNames2) {
collectConflictsBetweenFieldsAndFragment(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
fieldMap1,
fragmentName2,
);
} // (I) Then collect conflicts between the second collection of fields and
// those referenced by each fragment name associated with the first.
for (const fragmentName1 of fragmentNames1) {
collectConflictsBetweenFieldsAndFragment(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
fieldMap2,
fragmentName1,
);
} // (J) Also collect conflicts between any fragment names by the first and
// fragment names by the second. This compares each item in the first set of
// names to each item in the second set of names.
for (const fragmentName1 of fragmentNames1) {
for (const fragmentName2 of fragmentNames2) {
collectConflictsBetweenFragments(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
fragmentName1,
fragmentName2,
);
}
}
return conflicts;
} // Collect all Conflicts "within" one collection of fields.
function collectConflictsWithin(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
fieldMap,
) {
// A field map is a keyed collection, where each key represents a response
// name and the value at that key is a list of all fields which provide that
// response name. For every response name, if there are multiple fields, they
// must be compared to find a potential conflict.
for (const [responseName, fields] of Object.entries(fieldMap)) {
// This compares every field in the list to every other field in this list
// (except to itself). If the list only has one item, nothing needs to
// be compared.
if (fields.length > 1) {
for (let i = 0; i < fields.length; i++) {
for (let j = i + 1; j < fields.length; j++) {
const conflict = findConflict(
context,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
false, // within one collection is never mutually exclusive
responseName,
fields[i],
fields[j],
);
if (conflict) {
conflicts.push(conflict);
}
}
}
}
}
} // Collect all Conflicts between two collections of fields. This is similar to,
// but different from the `collectConflictsWithin` function above. This check
// assumes that `collectConflictsWithin` has already been called on each
// provided collection of fields. This is true because this validator traverses
// each individual selection set.
function collectConflictsBetween(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
parentFieldsAreMutuallyExclusive,
fieldMap1,
fieldMap2,
) {
// A field map is a keyed collection, where each key represents a response
// name and the value at that key is a list of all fields which provide that
// response name. For any response name which appears in both provided field
// maps, each field from the first field map must be compared to every field
// in the second field map to find potential conflicts.
for (const [responseName, fields1] of Object.entries(fieldMap1)) {
const fields2 = fieldMap2[responseName];
if (fields2) {
for (const field1 of fields1) {
for (const field2 of fields2) {
const conflict = findConflict(
context,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
parentFieldsAreMutuallyExclusive,
responseName,
field1,
field2,
);
if (conflict) {
conflicts.push(conflict);
}
}
}
}
}
} // Determines if there is a conflict between two particular fields, including
// comparing their sub-fields.
function findConflict(
context,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
parentFieldsAreMutuallyExclusive,
responseName,
field1,
field2,
) {
const [parentType1, node1, def1] = field1;
const [parentType2, node2, def2] = field2; // If it is known that two fields could not possibly apply at the same
// time, due to the parent types, then it is safe to permit them to diverge
// in aliased field or arguments used as they will not present any ambiguity
// by differing.
// It is known that two parent types could never overlap if they are
// different Object types. Interface or Union types might overlap - if not
// in the current state of the schema, then perhaps in some future version,
// thus may not safely diverge.
const areMutuallyExclusive =
parentFieldsAreMutuallyExclusive ||
(parentType1 !== parentType2 &&
(0, _definition.isObjectType)(parentType1) &&
(0, _definition.isObjectType)(parentType2));
if (!areMutuallyExclusive) {
// Two aliases must refer to the same field.
const name1 = node1.name.value;
const name2 = node2.name.value;
if (name1 !== name2) {
return [
[responseName, `"${name1}" and "${name2}" are different fields`],
[node1],
[node2],
];
} // Two field calls must have the same arguments.
if (!sameArguments(node1, node2)) {
return [
[responseName, 'they have differing arguments'],
[node1],
[node2],
];
}
} // The return type for each field.
const type1 = def1 === null || def1 === void 0 ? void 0 : def1.type;
const type2 = def2 === null || def2 === void 0 ? void 0 : def2.type;
if (type1 && type2 && doTypesConflict(type1, type2)) {
return [
[
responseName,
`they return conflicting types "${(0, _inspect.inspect)(
type1,
)}" and "${(0, _inspect.inspect)(type2)}"`,
],
[node1],
[node2],
];
} // Collect and compare sub-fields. Use the same "visited fragment names" list
// for both collections so fields in a fragment reference are never
// compared to themselves.
const selectionSet1 = node1.selectionSet;
const selectionSet2 = node2.selectionSet;
if (selectionSet1 && selectionSet2) {
const conflicts = findConflictsBetweenSubSelectionSets(
context,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
(0, _definition.getNamedType)(type1),
selectionSet1,
(0, _definition.getNamedType)(type2),
selectionSet2,
);
return subfieldConflicts(conflicts, responseName, node1, node2);
}
}
function sameArguments(node1, node2) {
const args1 = node1.arguments;
const args2 = node2.arguments;
if (args1 === undefined || args1.length === 0) {
return args2 === undefined || args2.length === 0;
}
if (args2 === undefined || args2.length === 0) {
return false;
}
/* c8 ignore next */
if (args1.length !== args2.length) {
/* c8 ignore next */
return false;
/* c8 ignore next */
}
const values2 = new Map(args2.map(({ name, value }) => [name.value, value]));
return args1.every((arg1) => {
const value1 = arg1.value;
const value2 = values2.get(arg1.name.value);
if (value2 === undefined) {
return false;
}
return stringifyValue(value1) === stringifyValue(value2);
});
}
function stringifyValue(value) {
return (0, _printer.print)((0, _sortValueNode.sortValueNode)(value));
} // Two types conflict if both types could not apply to a value simultaneously.
// Composite types are ignored as their individual field types will be compared
// later recursively. However List and Non-Null types must match.
function doTypesConflict(type1, type2) {
if ((0, _definition.isListType)(type1)) {
return (0, _definition.isListType)(type2)
? doTypesConflict(type1.ofType, type2.ofType)
: true;
}
if ((0, _definition.isListType)(type2)) {
return true;
}
if ((0, _definition.isNonNullType)(type1)) {
return (0, _definition.isNonNullType)(type2)
? doTypesConflict(type1.ofType, type2.ofType)
: true;
}
if ((0, _definition.isNonNullType)(type2)) {
return true;
}
if (
(0, _definition.isLeafType)(type1) ||
(0, _definition.isLeafType)(type2)
) {
return type1 !== type2;
}
return false;
} // Given a selection set, return the collection of fields (a mapping of response
// name to field nodes and definitions) as well as a list of fragment names
// referenced via fragment spreads.
function getFieldsAndFragmentNames(
context,
cachedFieldsAndFragmentNames,
parentType,
selectionSet,
) {
const cached = cachedFieldsAndFragmentNames.get(selectionSet);
if (cached) {
return cached;
}
const nodeAndDefs = Object.create(null);
const fragmentNames = Object.create(null);
_collectFieldsAndFragmentNames(
context,
parentType,
selectionSet,
nodeAndDefs,
fragmentNames,
);
const result = [nodeAndDefs, Object.keys(fragmentNames)];
cachedFieldsAndFragmentNames.set(selectionSet, result);
return result;
} // Given a reference to a fragment, return the represented collection of fields
// as well as a list of nested fragment names referenced via fragment spreads.
function getReferencedFieldsAndFragmentNames(
context,
cachedFieldsAndFragmentNames,
fragment,
) {
// Short-circuit building a type from the node if possible.
const cached = cachedFieldsAndFragmentNames.get(fragment.selectionSet);
if (cached) {
return cached;
}
const fragmentType = (0, _typeFromAST.typeFromAST)(
context.getSchema(),
fragment.typeCondition,
);
return getFieldsAndFragmentNames(
context,
cachedFieldsAndFragmentNames,
fragmentType,
fragment.selectionSet,
);
}
function _collectFieldsAndFragmentNames(
context,
parentType,
selectionSet,
nodeAndDefs,
fragmentNames,
) {
for (const selection of selectionSet.selections) {
switch (selection.kind) {
case _kinds.Kind.FIELD: {
const fieldName = selection.name.value;
let fieldDef;
if (
(0, _definition.isObjectType)(parentType) ||
(0, _definition.isInterfaceType)(parentType)
) {
fieldDef = parentType.getFields()[fieldName];
}
const responseName = selection.alias
? selection.alias.value
: fieldName;
if (!nodeAndDefs[responseName]) {
nodeAndDefs[responseName] = [];
}
nodeAndDefs[responseName].push([parentType, selection, fieldDef]);
break;
}
case _kinds.Kind.FRAGMENT_SPREAD:
fragmentNames[selection.name.value] = true;
break;
case _kinds.Kind.INLINE_FRAGMENT: {
const typeCondition = selection.typeCondition;
const inlineFragmentType = typeCondition
? (0, _typeFromAST.typeFromAST)(context.getSchema(), typeCondition)
: parentType;
_collectFieldsAndFragmentNames(
context,
inlineFragmentType,
selection.selectionSet,
nodeAndDefs,
fragmentNames,
);
break;
}
}
}
} // Given a series of Conflicts which occurred between two sub-fields, generate
// a single Conflict.
function subfieldConflicts(conflicts, responseName, node1, node2) {
if (conflicts.length > 0) {
return [
[responseName, conflicts.map(([reason]) => reason)],
[node1, ...conflicts.map(([, fields1]) => fields1).flat()],
[node2, ...conflicts.map(([, , fields2]) => fields2).flat()],
];
}
}
/**
* A way to keep track of pairs of things when the ordering of the pair does not matter.
*/
class PairSet {
constructor() {
this._data = new Map();
}
has(a, b, areMutuallyExclusive) {
var _this$_data$get;
const [key1, key2] = a < b ? [a, b] : [b, a];
const result =
(_this$_data$get = this._data.get(key1)) === null ||
_this$_data$get === void 0
? void 0
: _this$_data$get.get(key2);
if (result === undefined) {
return false;
} // areMutuallyExclusive being false is a superset of being true, hence if
// we want to know if this PairSet "has" these two with no exclusivity,
// we have to ensure it was added as such.
return areMutuallyExclusive ? true : areMutuallyExclusive === result;
}
add(a, b, areMutuallyExclusive) {
const [key1, key2] = a < b ? [a, b] : [b, a];
const map = this._data.get(key1);
if (map === undefined) {
this._data.set(key1, new Map([[key2, areMutuallyExclusive]]));
} else {
map.set(key2, areMutuallyExclusive);
}
}
}

View File

@@ -0,0 +1,805 @@
import { inspect } from '../../jsutils/inspect.mjs';
import { GraphQLError } from '../../error/GraphQLError.mjs';
import { Kind } from '../../language/kinds.mjs';
import { print } from '../../language/printer.mjs';
import {
getNamedType,
isInterfaceType,
isLeafType,
isListType,
isNonNullType,
isObjectType,
} from '../../type/definition.mjs';
import { sortValueNode } from '../../utilities/sortValueNode.mjs';
import { typeFromAST } from '../../utilities/typeFromAST.mjs';
function reasonMessage(reason) {
if (Array.isArray(reason)) {
return reason
.map(
([responseName, subReason]) =>
`subfields "${responseName}" conflict because ` +
reasonMessage(subReason),
)
.join(' and ');
}
return reason;
}
/**
* Overlapping fields can be merged
*
* A selection set is only valid if all fields (including spreading any
* fragments) either correspond to distinct response names or can be merged
* without ambiguity.
*
* See https://spec.graphql.org/draft/#sec-Field-Selection-Merging
*/
export function OverlappingFieldsCanBeMergedRule(context) {
// A memoization for when two fragments are compared "between" each other for
// conflicts. Two fragments may be compared many times, so memoizing this can
// dramatically improve the performance of this validator.
const comparedFragmentPairs = new PairSet(); // A cache for the "field map" and list of fragment names found in any given
// selection set. Selection sets may be asked for this information multiple
// times, so this improves the performance of this validator.
const cachedFieldsAndFragmentNames = new Map();
return {
SelectionSet(selectionSet) {
const conflicts = findConflictsWithinSelectionSet(
context,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
context.getParentType(),
selectionSet,
);
for (const [[responseName, reason], fields1, fields2] of conflicts) {
const reasonMsg = reasonMessage(reason);
context.reportError(
new GraphQLError(
`Fields "${responseName}" conflict because ${reasonMsg}. Use different aliases on the fields to fetch both if this was intentional.`,
{
nodes: fields1.concat(fields2),
},
),
);
}
},
};
}
/**
* Algorithm:
*
* Conflicts occur when two fields exist in a query which will produce the same
* response name, but represent differing values, thus creating a conflict.
* The algorithm below finds all conflicts via making a series of comparisons
* between fields. In order to compare as few fields as possible, this makes
* a series of comparisons "within" sets of fields and "between" sets of fields.
*
* Given any selection set, a collection produces both a set of fields by
* also including all inline fragments, as well as a list of fragments
* referenced by fragment spreads.
*
* A) Each selection set represented in the document first compares "within" its
* collected set of fields, finding any conflicts between every pair of
* overlapping fields.
* Note: This is the *only time* that a the fields "within" a set are compared
* to each other. After this only fields "between" sets are compared.
*
* B) Also, if any fragment is referenced in a selection set, then a
* comparison is made "between" the original set of fields and the
* referenced fragment.
*
* C) Also, if multiple fragments are referenced, then comparisons
* are made "between" each referenced fragment.
*
* D) When comparing "between" a set of fields and a referenced fragment, first
* a comparison is made between each field in the original set of fields and
* each field in the the referenced set of fields.
*
* E) Also, if any fragment is referenced in the referenced selection set,
* then a comparison is made "between" the original set of fields and the
* referenced fragment (recursively referring to step D).
*
* F) When comparing "between" two fragments, first a comparison is made between
* each field in the first referenced set of fields and each field in the the
* second referenced set of fields.
*
* G) Also, any fragments referenced by the first must be compared to the
* second, and any fragments referenced by the second must be compared to the
* first (recursively referring to step F).
*
* H) When comparing two fields, if both have selection sets, then a comparison
* is made "between" both selection sets, first comparing the set of fields in
* the first selection set with the set of fields in the second.
*
* I) Also, if any fragment is referenced in either selection set, then a
* comparison is made "between" the other set of fields and the
* referenced fragment.
*
* J) Also, if two fragments are referenced in both selection sets, then a
* comparison is made "between" the two fragments.
*
*/
// Find all conflicts found "within" a selection set, including those found
// via spreading in fragments. Called when visiting each SelectionSet in the
// GraphQL Document.
function findConflictsWithinSelectionSet(
context,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
parentType,
selectionSet,
) {
const conflicts = [];
const [fieldMap, fragmentNames] = getFieldsAndFragmentNames(
context,
cachedFieldsAndFragmentNames,
parentType,
selectionSet,
); // (A) Find find all conflicts "within" the fields of this selection set.
// Note: this is the *only place* `collectConflictsWithin` is called.
collectConflictsWithin(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
fieldMap,
);
if (fragmentNames.length !== 0) {
// (B) Then collect conflicts between these fields and those represented by
// each spread fragment name found.
for (let i = 0; i < fragmentNames.length; i++) {
collectConflictsBetweenFieldsAndFragment(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
false,
fieldMap,
fragmentNames[i],
); // (C) Then compare this fragment with all other fragments found in this
// selection set to collect conflicts between fragments spread together.
// This compares each item in the list of fragment names to every other
// item in that same list (except for itself).
for (let j = i + 1; j < fragmentNames.length; j++) {
collectConflictsBetweenFragments(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
false,
fragmentNames[i],
fragmentNames[j],
);
}
}
}
return conflicts;
} // Collect all conflicts found between a set of fields and a fragment reference
// including via spreading in any nested fragments.
function collectConflictsBetweenFieldsAndFragment(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
fieldMap,
fragmentName,
) {
const fragment = context.getFragment(fragmentName);
if (!fragment) {
return;
}
const [fieldMap2, referencedFragmentNames] =
getReferencedFieldsAndFragmentNames(
context,
cachedFieldsAndFragmentNames,
fragment,
); // Do not compare a fragment's fieldMap to itself.
if (fieldMap === fieldMap2) {
return;
} // (D) First collect any conflicts between the provided collection of fields
// and the collection of fields represented by the given fragment.
collectConflictsBetween(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
fieldMap,
fieldMap2,
); // (E) Then collect any conflicts between the provided collection of fields
// and any fragment names found in the given fragment.
for (const referencedFragmentName of referencedFragmentNames) {
// Memoize so two fragments are not compared for conflicts more than once.
if (
comparedFragmentPairs.has(
referencedFragmentName,
fragmentName,
areMutuallyExclusive,
)
) {
continue;
}
comparedFragmentPairs.add(
referencedFragmentName,
fragmentName,
areMutuallyExclusive,
);
collectConflictsBetweenFieldsAndFragment(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
fieldMap,
referencedFragmentName,
);
}
} // Collect all conflicts found between two fragments, including via spreading in
// any nested fragments.
function collectConflictsBetweenFragments(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
fragmentName1,
fragmentName2,
) {
// No need to compare a fragment to itself.
if (fragmentName1 === fragmentName2) {
return;
} // Memoize so two fragments are not compared for conflicts more than once.
if (
comparedFragmentPairs.has(
fragmentName1,
fragmentName2,
areMutuallyExclusive,
)
) {
return;
}
comparedFragmentPairs.add(fragmentName1, fragmentName2, areMutuallyExclusive);
const fragment1 = context.getFragment(fragmentName1);
const fragment2 = context.getFragment(fragmentName2);
if (!fragment1 || !fragment2) {
return;
}
const [fieldMap1, referencedFragmentNames1] =
getReferencedFieldsAndFragmentNames(
context,
cachedFieldsAndFragmentNames,
fragment1,
);
const [fieldMap2, referencedFragmentNames2] =
getReferencedFieldsAndFragmentNames(
context,
cachedFieldsAndFragmentNames,
fragment2,
); // (F) First, collect all conflicts between these two collections of fields
// (not including any nested fragments).
collectConflictsBetween(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
fieldMap1,
fieldMap2,
); // (G) Then collect conflicts between the first fragment and any nested
// fragments spread in the second fragment.
for (const referencedFragmentName2 of referencedFragmentNames2) {
collectConflictsBetweenFragments(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
fragmentName1,
referencedFragmentName2,
);
} // (G) Then collect conflicts between the second fragment and any nested
// fragments spread in the first fragment.
for (const referencedFragmentName1 of referencedFragmentNames1) {
collectConflictsBetweenFragments(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
referencedFragmentName1,
fragmentName2,
);
}
} // Find all conflicts found between two selection sets, including those found
// via spreading in fragments. Called when determining if conflicts exist
// between the sub-fields of two overlapping fields.
function findConflictsBetweenSubSelectionSets(
context,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
parentType1,
selectionSet1,
parentType2,
selectionSet2,
) {
const conflicts = [];
const [fieldMap1, fragmentNames1] = getFieldsAndFragmentNames(
context,
cachedFieldsAndFragmentNames,
parentType1,
selectionSet1,
);
const [fieldMap2, fragmentNames2] = getFieldsAndFragmentNames(
context,
cachedFieldsAndFragmentNames,
parentType2,
selectionSet2,
); // (H) First, collect all conflicts between these two collections of field.
collectConflictsBetween(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
fieldMap1,
fieldMap2,
); // (I) Then collect conflicts between the first collection of fields and
// those referenced by each fragment name associated with the second.
for (const fragmentName2 of fragmentNames2) {
collectConflictsBetweenFieldsAndFragment(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
fieldMap1,
fragmentName2,
);
} // (I) Then collect conflicts between the second collection of fields and
// those referenced by each fragment name associated with the first.
for (const fragmentName1 of fragmentNames1) {
collectConflictsBetweenFieldsAndFragment(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
fieldMap2,
fragmentName1,
);
} // (J) Also collect conflicts between any fragment names by the first and
// fragment names by the second. This compares each item in the first set of
// names to each item in the second set of names.
for (const fragmentName1 of fragmentNames1) {
for (const fragmentName2 of fragmentNames2) {
collectConflictsBetweenFragments(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
fragmentName1,
fragmentName2,
);
}
}
return conflicts;
} // Collect all Conflicts "within" one collection of fields.
function collectConflictsWithin(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
fieldMap,
) {
// A field map is a keyed collection, where each key represents a response
// name and the value at that key is a list of all fields which provide that
// response name. For every response name, if there are multiple fields, they
// must be compared to find a potential conflict.
for (const [responseName, fields] of Object.entries(fieldMap)) {
// This compares every field in the list to every other field in this list
// (except to itself). If the list only has one item, nothing needs to
// be compared.
if (fields.length > 1) {
for (let i = 0; i < fields.length; i++) {
for (let j = i + 1; j < fields.length; j++) {
const conflict = findConflict(
context,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
false, // within one collection is never mutually exclusive
responseName,
fields[i],
fields[j],
);
if (conflict) {
conflicts.push(conflict);
}
}
}
}
}
} // Collect all Conflicts between two collections of fields. This is similar to,
// but different from the `collectConflictsWithin` function above. This check
// assumes that `collectConflictsWithin` has already been called on each
// provided collection of fields. This is true because this validator traverses
// each individual selection set.
function collectConflictsBetween(
context,
conflicts,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
parentFieldsAreMutuallyExclusive,
fieldMap1,
fieldMap2,
) {
// A field map is a keyed collection, where each key represents a response
// name and the value at that key is a list of all fields which provide that
// response name. For any response name which appears in both provided field
// maps, each field from the first field map must be compared to every field
// in the second field map to find potential conflicts.
for (const [responseName, fields1] of Object.entries(fieldMap1)) {
const fields2 = fieldMap2[responseName];
if (fields2) {
for (const field1 of fields1) {
for (const field2 of fields2) {
const conflict = findConflict(
context,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
parentFieldsAreMutuallyExclusive,
responseName,
field1,
field2,
);
if (conflict) {
conflicts.push(conflict);
}
}
}
}
}
} // Determines if there is a conflict between two particular fields, including
// comparing their sub-fields.
function findConflict(
context,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
parentFieldsAreMutuallyExclusive,
responseName,
field1,
field2,
) {
const [parentType1, node1, def1] = field1;
const [parentType2, node2, def2] = field2; // If it is known that two fields could not possibly apply at the same
// time, due to the parent types, then it is safe to permit them to diverge
// in aliased field or arguments used as they will not present any ambiguity
// by differing.
// It is known that two parent types could never overlap if they are
// different Object types. Interface or Union types might overlap - if not
// in the current state of the schema, then perhaps in some future version,
// thus may not safely diverge.
const areMutuallyExclusive =
parentFieldsAreMutuallyExclusive ||
(parentType1 !== parentType2 &&
isObjectType(parentType1) &&
isObjectType(parentType2));
if (!areMutuallyExclusive) {
// Two aliases must refer to the same field.
const name1 = node1.name.value;
const name2 = node2.name.value;
if (name1 !== name2) {
return [
[responseName, `"${name1}" and "${name2}" are different fields`],
[node1],
[node2],
];
} // Two field calls must have the same arguments.
if (!sameArguments(node1, node2)) {
return [
[responseName, 'they have differing arguments'],
[node1],
[node2],
];
}
} // The return type for each field.
const type1 = def1 === null || def1 === void 0 ? void 0 : def1.type;
const type2 = def2 === null || def2 === void 0 ? void 0 : def2.type;
if (type1 && type2 && doTypesConflict(type1, type2)) {
return [
[
responseName,
`they return conflicting types "${inspect(type1)}" and "${inspect(
type2,
)}"`,
],
[node1],
[node2],
];
} // Collect and compare sub-fields. Use the same "visited fragment names" list
// for both collections so fields in a fragment reference are never
// compared to themselves.
const selectionSet1 = node1.selectionSet;
const selectionSet2 = node2.selectionSet;
if (selectionSet1 && selectionSet2) {
const conflicts = findConflictsBetweenSubSelectionSets(
context,
cachedFieldsAndFragmentNames,
comparedFragmentPairs,
areMutuallyExclusive,
getNamedType(type1),
selectionSet1,
getNamedType(type2),
selectionSet2,
);
return subfieldConflicts(conflicts, responseName, node1, node2);
}
}
function sameArguments(node1, node2) {
const args1 = node1.arguments;
const args2 = node2.arguments;
if (args1 === undefined || args1.length === 0) {
return args2 === undefined || args2.length === 0;
}
if (args2 === undefined || args2.length === 0) {
return false;
}
/* c8 ignore next */
if (args1.length !== args2.length) {
/* c8 ignore next */
return false;
/* c8 ignore next */
}
const values2 = new Map(args2.map(({ name, value }) => [name.value, value]));
return args1.every((arg1) => {
const value1 = arg1.value;
const value2 = values2.get(arg1.name.value);
if (value2 === undefined) {
return false;
}
return stringifyValue(value1) === stringifyValue(value2);
});
}
function stringifyValue(value) {
return print(sortValueNode(value));
} // Two types conflict if both types could not apply to a value simultaneously.
// Composite types are ignored as their individual field types will be compared
// later recursively. However List and Non-Null types must match.
function doTypesConflict(type1, type2) {
if (isListType(type1)) {
return isListType(type2)
? doTypesConflict(type1.ofType, type2.ofType)
: true;
}
if (isListType(type2)) {
return true;
}
if (isNonNullType(type1)) {
return isNonNullType(type2)
? doTypesConflict(type1.ofType, type2.ofType)
: true;
}
if (isNonNullType(type2)) {
return true;
}
if (isLeafType(type1) || isLeafType(type2)) {
return type1 !== type2;
}
return false;
} // Given a selection set, return the collection of fields (a mapping of response
// name to field nodes and definitions) as well as a list of fragment names
// referenced via fragment spreads.
function getFieldsAndFragmentNames(
context,
cachedFieldsAndFragmentNames,
parentType,
selectionSet,
) {
const cached = cachedFieldsAndFragmentNames.get(selectionSet);
if (cached) {
return cached;
}
const nodeAndDefs = Object.create(null);
const fragmentNames = Object.create(null);
_collectFieldsAndFragmentNames(
context,
parentType,
selectionSet,
nodeAndDefs,
fragmentNames,
);
const result = [nodeAndDefs, Object.keys(fragmentNames)];
cachedFieldsAndFragmentNames.set(selectionSet, result);
return result;
} // Given a reference to a fragment, return the represented collection of fields
// as well as a list of nested fragment names referenced via fragment spreads.
function getReferencedFieldsAndFragmentNames(
context,
cachedFieldsAndFragmentNames,
fragment,
) {
// Short-circuit building a type from the node if possible.
const cached = cachedFieldsAndFragmentNames.get(fragment.selectionSet);
if (cached) {
return cached;
}
const fragmentType = typeFromAST(context.getSchema(), fragment.typeCondition);
return getFieldsAndFragmentNames(
context,
cachedFieldsAndFragmentNames,
fragmentType,
fragment.selectionSet,
);
}
function _collectFieldsAndFragmentNames(
context,
parentType,
selectionSet,
nodeAndDefs,
fragmentNames,
) {
for (const selection of selectionSet.selections) {
switch (selection.kind) {
case Kind.FIELD: {
const fieldName = selection.name.value;
let fieldDef;
if (isObjectType(parentType) || isInterfaceType(parentType)) {
fieldDef = parentType.getFields()[fieldName];
}
const responseName = selection.alias
? selection.alias.value
: fieldName;
if (!nodeAndDefs[responseName]) {
nodeAndDefs[responseName] = [];
}
nodeAndDefs[responseName].push([parentType, selection, fieldDef]);
break;
}
case Kind.FRAGMENT_SPREAD:
fragmentNames[selection.name.value] = true;
break;
case Kind.INLINE_FRAGMENT: {
const typeCondition = selection.typeCondition;
const inlineFragmentType = typeCondition
? typeFromAST(context.getSchema(), typeCondition)
: parentType;
_collectFieldsAndFragmentNames(
context,
inlineFragmentType,
selection.selectionSet,
nodeAndDefs,
fragmentNames,
);
break;
}
}
}
} // Given a series of Conflicts which occurred between two sub-fields, generate
// a single Conflict.
function subfieldConflicts(conflicts, responseName, node1, node2) {
if (conflicts.length > 0) {
return [
[responseName, conflicts.map(([reason]) => reason)],
[node1, ...conflicts.map(([, fields1]) => fields1).flat()],
[node2, ...conflicts.map(([, , fields2]) => fields2).flat()],
];
}
}
/**
* A way to keep track of pairs of things when the ordering of the pair does not matter.
*/
class PairSet {
constructor() {
this._data = new Map();
}
has(a, b, areMutuallyExclusive) {
var _this$_data$get;
const [key1, key2] = a < b ? [a, b] : [b, a];
const result =
(_this$_data$get = this._data.get(key1)) === null ||
_this$_data$get === void 0
? void 0
: _this$_data$get.get(key2);
if (result === undefined) {
return false;
} // areMutuallyExclusive being false is a superset of being true, hence if
// we want to know if this PairSet "has" these two with no exclusivity,
// we have to ensure it was added as such.
return areMutuallyExclusive ? true : areMutuallyExclusive === result;
}
add(a, b, areMutuallyExclusive) {
const [key1, key2] = a < b ? [a, b] : [b, a];
const map = this._data.get(key1);
if (map === undefined) {
this._data.set(key1, new Map([[key2, areMutuallyExclusive]]));
} else {
map.set(key2, areMutuallyExclusive);
}
}
}

View File

@@ -0,0 +1,12 @@
import type { ASTVisitor } from '../../language/visitor';
import type { ValidationContext } from '../ValidationContext';
/**
* Possible fragment spread
*
* A fragment spread is only valid if the type condition could ever possibly
* be true: if there is a non-empty intersection of the possible parent types,
* and possible types which pass the type condition.
*/
export declare function PossibleFragmentSpreadsRule(
context: ValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,95 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.PossibleFragmentSpreadsRule = PossibleFragmentSpreadsRule;
var _inspect = require('../../jsutils/inspect.js');
var _GraphQLError = require('../../error/GraphQLError.js');
var _definition = require('../../type/definition.js');
var _typeComparators = require('../../utilities/typeComparators.js');
var _typeFromAST = require('../../utilities/typeFromAST.js');
/**
* Possible fragment spread
*
* A fragment spread is only valid if the type condition could ever possibly
* be true: if there is a non-empty intersection of the possible parent types,
* and possible types which pass the type condition.
*/
function PossibleFragmentSpreadsRule(context) {
return {
InlineFragment(node) {
const fragType = context.getType();
const parentType = context.getParentType();
if (
(0, _definition.isCompositeType)(fragType) &&
(0, _definition.isCompositeType)(parentType) &&
!(0, _typeComparators.doTypesOverlap)(
context.getSchema(),
fragType,
parentType,
)
) {
const parentTypeStr = (0, _inspect.inspect)(parentType);
const fragTypeStr = (0, _inspect.inspect)(fragType);
context.reportError(
new _GraphQLError.GraphQLError(
`Fragment cannot be spread here as objects of type "${parentTypeStr}" can never be of type "${fragTypeStr}".`,
{
nodes: node,
},
),
);
}
},
FragmentSpread(node) {
const fragName = node.name.value;
const fragType = getFragmentType(context, fragName);
const parentType = context.getParentType();
if (
fragType &&
parentType &&
!(0, _typeComparators.doTypesOverlap)(
context.getSchema(),
fragType,
parentType,
)
) {
const parentTypeStr = (0, _inspect.inspect)(parentType);
const fragTypeStr = (0, _inspect.inspect)(fragType);
context.reportError(
new _GraphQLError.GraphQLError(
`Fragment "${fragName}" cannot be spread here as objects of type "${parentTypeStr}" can never be of type "${fragTypeStr}".`,
{
nodes: node,
},
),
);
}
},
};
}
function getFragmentType(context, name) {
const frag = context.getFragment(name);
if (frag) {
const type = (0, _typeFromAST.typeFromAST)(
context.getSchema(),
frag.typeCondition,
);
if ((0, _definition.isCompositeType)(type)) {
return type;
}
}
}

View File

@@ -0,0 +1,73 @@
import { inspect } from '../../jsutils/inspect.mjs';
import { GraphQLError } from '../../error/GraphQLError.mjs';
import { isCompositeType } from '../../type/definition.mjs';
import { doTypesOverlap } from '../../utilities/typeComparators.mjs';
import { typeFromAST } from '../../utilities/typeFromAST.mjs';
/**
* Possible fragment spread
*
* A fragment spread is only valid if the type condition could ever possibly
* be true: if there is a non-empty intersection of the possible parent types,
* and possible types which pass the type condition.
*/
export function PossibleFragmentSpreadsRule(context) {
return {
InlineFragment(node) {
const fragType = context.getType();
const parentType = context.getParentType();
if (
isCompositeType(fragType) &&
isCompositeType(parentType) &&
!doTypesOverlap(context.getSchema(), fragType, parentType)
) {
const parentTypeStr = inspect(parentType);
const fragTypeStr = inspect(fragType);
context.reportError(
new GraphQLError(
`Fragment cannot be spread here as objects of type "${parentTypeStr}" can never be of type "${fragTypeStr}".`,
{
nodes: node,
},
),
);
}
},
FragmentSpread(node) {
const fragName = node.name.value;
const fragType = getFragmentType(context, fragName);
const parentType = context.getParentType();
if (
fragType &&
parentType &&
!doTypesOverlap(context.getSchema(), fragType, parentType)
) {
const parentTypeStr = inspect(parentType);
const fragTypeStr = inspect(fragType);
context.reportError(
new GraphQLError(
`Fragment "${fragName}" cannot be spread here as objects of type "${parentTypeStr}" can never be of type "${fragTypeStr}".`,
{
nodes: node,
},
),
);
}
},
};
}
function getFragmentType(context, name) {
const frag = context.getFragment(name);
if (frag) {
const type = typeFromAST(context.getSchema(), frag.typeCondition);
if (isCompositeType(type)) {
return type;
}
}
}

View File

@@ -0,0 +1,10 @@
import type { ASTVisitor } from '../../language/visitor';
import type { SDLValidationContext } from '../ValidationContext';
/**
* Possible type extension
*
* A type extension is only valid if the type is defined and has the same kind.
*/
export declare function PossibleTypeExtensionsRule(
context: SDLValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,171 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.PossibleTypeExtensionsRule = PossibleTypeExtensionsRule;
var _didYouMean = require('../../jsutils/didYouMean.js');
var _inspect = require('../../jsutils/inspect.js');
var _invariant = require('../../jsutils/invariant.js');
var _suggestionList = require('../../jsutils/suggestionList.js');
var _GraphQLError = require('../../error/GraphQLError.js');
var _kinds = require('../../language/kinds.js');
var _predicates = require('../../language/predicates.js');
var _definition = require('../../type/definition.js');
/**
* Possible type extension
*
* A type extension is only valid if the type is defined and has the same kind.
*/
function PossibleTypeExtensionsRule(context) {
const schema = context.getSchema();
const definedTypes = Object.create(null);
for (const def of context.getDocument().definitions) {
if ((0, _predicates.isTypeDefinitionNode)(def)) {
definedTypes[def.name.value] = def;
}
}
return {
ScalarTypeExtension: checkExtension,
ObjectTypeExtension: checkExtension,
InterfaceTypeExtension: checkExtension,
UnionTypeExtension: checkExtension,
EnumTypeExtension: checkExtension,
InputObjectTypeExtension: checkExtension,
};
function checkExtension(node) {
const typeName = node.name.value;
const defNode = definedTypes[typeName];
const existingType =
schema === null || schema === void 0 ? void 0 : schema.getType(typeName);
let expectedKind;
if (defNode) {
expectedKind = defKindToExtKind[defNode.kind];
} else if (existingType) {
expectedKind = typeToExtKind(existingType);
}
if (expectedKind) {
if (expectedKind !== node.kind) {
const kindStr = extensionKindToTypeName(node.kind);
context.reportError(
new _GraphQLError.GraphQLError(
`Cannot extend non-${kindStr} type "${typeName}".`,
{
nodes: defNode ? [defNode, node] : node,
},
),
);
}
} else {
const allTypeNames = Object.keys({
...definedTypes,
...(schema === null || schema === void 0
? void 0
: schema.getTypeMap()),
});
const suggestedTypes = (0, _suggestionList.suggestionList)(
typeName,
allTypeNames,
);
context.reportError(
new _GraphQLError.GraphQLError(
`Cannot extend type "${typeName}" because it is not defined.` +
(0, _didYouMean.didYouMean)(suggestedTypes),
{
nodes: node.name,
},
),
);
}
}
}
const defKindToExtKind = {
[_kinds.Kind.SCALAR_TYPE_DEFINITION]: _kinds.Kind.SCALAR_TYPE_EXTENSION,
[_kinds.Kind.OBJECT_TYPE_DEFINITION]: _kinds.Kind.OBJECT_TYPE_EXTENSION,
[_kinds.Kind.INTERFACE_TYPE_DEFINITION]: _kinds.Kind.INTERFACE_TYPE_EXTENSION,
[_kinds.Kind.UNION_TYPE_DEFINITION]: _kinds.Kind.UNION_TYPE_EXTENSION,
[_kinds.Kind.ENUM_TYPE_DEFINITION]: _kinds.Kind.ENUM_TYPE_EXTENSION,
[_kinds.Kind.INPUT_OBJECT_TYPE_DEFINITION]:
_kinds.Kind.INPUT_OBJECT_TYPE_EXTENSION,
};
function typeToExtKind(type) {
if ((0, _definition.isScalarType)(type)) {
return _kinds.Kind.SCALAR_TYPE_EXTENSION;
}
if ((0, _definition.isObjectType)(type)) {
return _kinds.Kind.OBJECT_TYPE_EXTENSION;
}
if ((0, _definition.isInterfaceType)(type)) {
return _kinds.Kind.INTERFACE_TYPE_EXTENSION;
}
if ((0, _definition.isUnionType)(type)) {
return _kinds.Kind.UNION_TYPE_EXTENSION;
}
if ((0, _definition.isEnumType)(type)) {
return _kinds.Kind.ENUM_TYPE_EXTENSION;
}
if ((0, _definition.isInputObjectType)(type)) {
return _kinds.Kind.INPUT_OBJECT_TYPE_EXTENSION;
}
/* c8 ignore next 3 */
// Not reachable. All possible types have been considered
false ||
(0, _invariant.invariant)(
false,
'Unexpected type: ' + (0, _inspect.inspect)(type),
);
}
function extensionKindToTypeName(kind) {
switch (kind) {
case _kinds.Kind.SCALAR_TYPE_EXTENSION:
return 'scalar';
case _kinds.Kind.OBJECT_TYPE_EXTENSION:
return 'object';
case _kinds.Kind.INTERFACE_TYPE_EXTENSION:
return 'interface';
case _kinds.Kind.UNION_TYPE_EXTENSION:
return 'union';
case _kinds.Kind.ENUM_TYPE_EXTENSION:
return 'enum';
case _kinds.Kind.INPUT_OBJECT_TYPE_EXTENSION:
return 'input object';
// Not reachable. All possible types have been considered
/* c8 ignore next */
default:
false ||
(0, _invariant.invariant)(
false,
'Unexpected kind: ' + (0, _inspect.inspect)(kind),
);
}
}

View File

@@ -0,0 +1,148 @@
import { didYouMean } from '../../jsutils/didYouMean.mjs';
import { inspect } from '../../jsutils/inspect.mjs';
import { invariant } from '../../jsutils/invariant.mjs';
import { suggestionList } from '../../jsutils/suggestionList.mjs';
import { GraphQLError } from '../../error/GraphQLError.mjs';
import { Kind } from '../../language/kinds.mjs';
import { isTypeDefinitionNode } from '../../language/predicates.mjs';
import {
isEnumType,
isInputObjectType,
isInterfaceType,
isObjectType,
isScalarType,
isUnionType,
} from '../../type/definition.mjs';
/**
* Possible type extension
*
* A type extension is only valid if the type is defined and has the same kind.
*/
export function PossibleTypeExtensionsRule(context) {
const schema = context.getSchema();
const definedTypes = Object.create(null);
for (const def of context.getDocument().definitions) {
if (isTypeDefinitionNode(def)) {
definedTypes[def.name.value] = def;
}
}
return {
ScalarTypeExtension: checkExtension,
ObjectTypeExtension: checkExtension,
InterfaceTypeExtension: checkExtension,
UnionTypeExtension: checkExtension,
EnumTypeExtension: checkExtension,
InputObjectTypeExtension: checkExtension,
};
function checkExtension(node) {
const typeName = node.name.value;
const defNode = definedTypes[typeName];
const existingType =
schema === null || schema === void 0 ? void 0 : schema.getType(typeName);
let expectedKind;
if (defNode) {
expectedKind = defKindToExtKind[defNode.kind];
} else if (existingType) {
expectedKind = typeToExtKind(existingType);
}
if (expectedKind) {
if (expectedKind !== node.kind) {
const kindStr = extensionKindToTypeName(node.kind);
context.reportError(
new GraphQLError(`Cannot extend non-${kindStr} type "${typeName}".`, {
nodes: defNode ? [defNode, node] : node,
}),
);
}
} else {
const allTypeNames = Object.keys({
...definedTypes,
...(schema === null || schema === void 0
? void 0
: schema.getTypeMap()),
});
const suggestedTypes = suggestionList(typeName, allTypeNames);
context.reportError(
new GraphQLError(
`Cannot extend type "${typeName}" because it is not defined.` +
didYouMean(suggestedTypes),
{
nodes: node.name,
},
),
);
}
}
}
const defKindToExtKind = {
[Kind.SCALAR_TYPE_DEFINITION]: Kind.SCALAR_TYPE_EXTENSION,
[Kind.OBJECT_TYPE_DEFINITION]: Kind.OBJECT_TYPE_EXTENSION,
[Kind.INTERFACE_TYPE_DEFINITION]: Kind.INTERFACE_TYPE_EXTENSION,
[Kind.UNION_TYPE_DEFINITION]: Kind.UNION_TYPE_EXTENSION,
[Kind.ENUM_TYPE_DEFINITION]: Kind.ENUM_TYPE_EXTENSION,
[Kind.INPUT_OBJECT_TYPE_DEFINITION]: Kind.INPUT_OBJECT_TYPE_EXTENSION,
};
function typeToExtKind(type) {
if (isScalarType(type)) {
return Kind.SCALAR_TYPE_EXTENSION;
}
if (isObjectType(type)) {
return Kind.OBJECT_TYPE_EXTENSION;
}
if (isInterfaceType(type)) {
return Kind.INTERFACE_TYPE_EXTENSION;
}
if (isUnionType(type)) {
return Kind.UNION_TYPE_EXTENSION;
}
if (isEnumType(type)) {
return Kind.ENUM_TYPE_EXTENSION;
}
if (isInputObjectType(type)) {
return Kind.INPUT_OBJECT_TYPE_EXTENSION;
}
/* c8 ignore next 3 */
// Not reachable. All possible types have been considered
false || invariant(false, 'Unexpected type: ' + inspect(type));
}
function extensionKindToTypeName(kind) {
switch (kind) {
case Kind.SCALAR_TYPE_EXTENSION:
return 'scalar';
case Kind.OBJECT_TYPE_EXTENSION:
return 'object';
case Kind.INTERFACE_TYPE_EXTENSION:
return 'interface';
case Kind.UNION_TYPE_EXTENSION:
return 'union';
case Kind.ENUM_TYPE_EXTENSION:
return 'enum';
case Kind.INPUT_OBJECT_TYPE_EXTENSION:
return 'input object';
// Not reachable. All possible types have been considered
/* c8 ignore next */
default:
false || invariant(false, 'Unexpected kind: ' + inspect(kind));
}
}

View File

@@ -0,0 +1,20 @@
import type { ASTVisitor } from '../../language/visitor';
import type {
SDLValidationContext,
ValidationContext,
} from '../ValidationContext';
/**
* Provided required arguments
*
* A field or directive is only valid if all required (non-null without a
* default value) field arguments have been provided.
*/
export declare function ProvidedRequiredArgumentsRule(
context: ValidationContext,
): ASTVisitor;
/**
* @internal
*/
export declare function ProvidedRequiredArgumentsOnDirectivesRule(
context: ValidationContext | SDLValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,162 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.ProvidedRequiredArgumentsOnDirectivesRule =
ProvidedRequiredArgumentsOnDirectivesRule;
exports.ProvidedRequiredArgumentsRule = ProvidedRequiredArgumentsRule;
var _inspect = require('../../jsutils/inspect.js');
var _keyMap = require('../../jsutils/keyMap.js');
var _GraphQLError = require('../../error/GraphQLError.js');
var _kinds = require('../../language/kinds.js');
var _printer = require('../../language/printer.js');
var _definition = require('../../type/definition.js');
var _directives = require('../../type/directives.js');
/**
* Provided required arguments
*
* A field or directive is only valid if all required (non-null without a
* default value) field arguments have been provided.
*/
function ProvidedRequiredArgumentsRule(context) {
return {
// eslint-disable-next-line new-cap
...ProvidedRequiredArgumentsOnDirectivesRule(context),
Field: {
// Validate on leave to allow for deeper errors to appear first.
leave(fieldNode) {
var _fieldNode$arguments;
const fieldDef = context.getFieldDef();
if (!fieldDef) {
return false;
}
const providedArgs = new Set( // FIXME: https://github.com/graphql/graphql-js/issues/2203
/* c8 ignore next */
(_fieldNode$arguments = fieldNode.arguments) === null ||
_fieldNode$arguments === void 0
? void 0
: _fieldNode$arguments.map((arg) => arg.name.value),
);
for (const argDef of fieldDef.args) {
if (
!providedArgs.has(argDef.name) &&
(0, _definition.isRequiredArgument)(argDef)
) {
const argTypeStr = (0, _inspect.inspect)(argDef.type);
context.reportError(
new _GraphQLError.GraphQLError(
`Field "${fieldDef.name}" argument "${argDef.name}" of type "${argTypeStr}" is required, but it was not provided.`,
{
nodes: fieldNode,
},
),
);
}
}
},
},
};
}
/**
* @internal
*/
function ProvidedRequiredArgumentsOnDirectivesRule(context) {
var _schema$getDirectives;
const requiredArgsMap = Object.create(null);
const schema = context.getSchema();
const definedDirectives =
(_schema$getDirectives =
schema === null || schema === void 0
? void 0
: schema.getDirectives()) !== null && _schema$getDirectives !== void 0
? _schema$getDirectives
: _directives.specifiedDirectives;
for (const directive of definedDirectives) {
requiredArgsMap[directive.name] = (0, _keyMap.keyMap)(
directive.args.filter(_definition.isRequiredArgument),
(arg) => arg.name,
);
}
const astDefinitions = context.getDocument().definitions;
for (const def of astDefinitions) {
if (def.kind === _kinds.Kind.DIRECTIVE_DEFINITION) {
var _def$arguments;
// FIXME: https://github.com/graphql/graphql-js/issues/2203
/* c8 ignore next */
const argNodes =
(_def$arguments = def.arguments) !== null && _def$arguments !== void 0
? _def$arguments
: [];
requiredArgsMap[def.name.value] = (0, _keyMap.keyMap)(
argNodes.filter(isRequiredArgumentNode),
(arg) => arg.name.value,
);
}
}
return {
Directive: {
// Validate on leave to allow for deeper errors to appear first.
leave(directiveNode) {
const directiveName = directiveNode.name.value;
const requiredArgs = requiredArgsMap[directiveName];
if (requiredArgs) {
var _directiveNode$argume;
// FIXME: https://github.com/graphql/graphql-js/issues/2203
/* c8 ignore next */
const argNodes =
(_directiveNode$argume = directiveNode.arguments) !== null &&
_directiveNode$argume !== void 0
? _directiveNode$argume
: [];
const argNodeMap = new Set(argNodes.map((arg) => arg.name.value));
for (const [argName, argDef] of Object.entries(requiredArgs)) {
if (!argNodeMap.has(argName)) {
const argType = (0, _definition.isType)(argDef.type)
? (0, _inspect.inspect)(argDef.type)
: (0, _printer.print)(argDef.type);
context.reportError(
new _GraphQLError.GraphQLError(
`Directive "@${directiveName}" argument "${argName}" of type "${argType}" is required, but it was not provided.`,
{
nodes: directiveNode,
},
),
);
}
}
}
},
},
};
}
function isRequiredArgumentNode(arg) {
return (
arg.type.kind === _kinds.Kind.NON_NULL_TYPE && arg.defaultValue == null
);
}

View File

@@ -0,0 +1,142 @@
import { inspect } from '../../jsutils/inspect.mjs';
import { keyMap } from '../../jsutils/keyMap.mjs';
import { GraphQLError } from '../../error/GraphQLError.mjs';
import { Kind } from '../../language/kinds.mjs';
import { print } from '../../language/printer.mjs';
import { isRequiredArgument, isType } from '../../type/definition.mjs';
import { specifiedDirectives } from '../../type/directives.mjs';
/**
* Provided required arguments
*
* A field or directive is only valid if all required (non-null without a
* default value) field arguments have been provided.
*/
export function ProvidedRequiredArgumentsRule(context) {
return {
// eslint-disable-next-line new-cap
...ProvidedRequiredArgumentsOnDirectivesRule(context),
Field: {
// Validate on leave to allow for deeper errors to appear first.
leave(fieldNode) {
var _fieldNode$arguments;
const fieldDef = context.getFieldDef();
if (!fieldDef) {
return false;
}
const providedArgs = new Set( // FIXME: https://github.com/graphql/graphql-js/issues/2203
/* c8 ignore next */
(_fieldNode$arguments = fieldNode.arguments) === null ||
_fieldNode$arguments === void 0
? void 0
: _fieldNode$arguments.map((arg) => arg.name.value),
);
for (const argDef of fieldDef.args) {
if (!providedArgs.has(argDef.name) && isRequiredArgument(argDef)) {
const argTypeStr = inspect(argDef.type);
context.reportError(
new GraphQLError(
`Field "${fieldDef.name}" argument "${argDef.name}" of type "${argTypeStr}" is required, but it was not provided.`,
{
nodes: fieldNode,
},
),
);
}
}
},
},
};
}
/**
* @internal
*/
export function ProvidedRequiredArgumentsOnDirectivesRule(context) {
var _schema$getDirectives;
const requiredArgsMap = Object.create(null);
const schema = context.getSchema();
const definedDirectives =
(_schema$getDirectives =
schema === null || schema === void 0
? void 0
: schema.getDirectives()) !== null && _schema$getDirectives !== void 0
? _schema$getDirectives
: specifiedDirectives;
for (const directive of definedDirectives) {
requiredArgsMap[directive.name] = keyMap(
directive.args.filter(isRequiredArgument),
(arg) => arg.name,
);
}
const astDefinitions = context.getDocument().definitions;
for (const def of astDefinitions) {
if (def.kind === Kind.DIRECTIVE_DEFINITION) {
var _def$arguments;
// FIXME: https://github.com/graphql/graphql-js/issues/2203
/* c8 ignore next */
const argNodes =
(_def$arguments = def.arguments) !== null && _def$arguments !== void 0
? _def$arguments
: [];
requiredArgsMap[def.name.value] = keyMap(
argNodes.filter(isRequiredArgumentNode),
(arg) => arg.name.value,
);
}
}
return {
Directive: {
// Validate on leave to allow for deeper errors to appear first.
leave(directiveNode) {
const directiveName = directiveNode.name.value;
const requiredArgs = requiredArgsMap[directiveName];
if (requiredArgs) {
var _directiveNode$argume;
// FIXME: https://github.com/graphql/graphql-js/issues/2203
/* c8 ignore next */
const argNodes =
(_directiveNode$argume = directiveNode.arguments) !== null &&
_directiveNode$argume !== void 0
? _directiveNode$argume
: [];
const argNodeMap = new Set(argNodes.map((arg) => arg.name.value));
for (const [argName, argDef] of Object.entries(requiredArgs)) {
if (!argNodeMap.has(argName)) {
const argType = isType(argDef.type)
? inspect(argDef.type)
: print(argDef.type);
context.reportError(
new GraphQLError(
`Directive "@${directiveName}" argument "${argName}" of type "${argType}" is required, but it was not provided.`,
{
nodes: directiveNode,
},
),
);
}
}
}
},
},
};
}
function isRequiredArgumentNode(arg) {
return arg.type.kind === Kind.NON_NULL_TYPE && arg.defaultValue == null;
}

View File

@@ -0,0 +1,9 @@
import type { ASTVisitor } from '../../language/visitor';
import type { ValidationContext } from '../ValidationContext';
/**
* Scalar leafs
*
* A GraphQL document is valid only if all leaf fields (fields without
* sub selections) are of scalar or enum types.
*/
export declare function ScalarLeafsRule(context: ValidationContext): ASTVisitor;

View File

@@ -0,0 +1,55 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.ScalarLeafsRule = ScalarLeafsRule;
var _inspect = require('../../jsutils/inspect.js');
var _GraphQLError = require('../../error/GraphQLError.js');
var _definition = require('../../type/definition.js');
/**
* Scalar leafs
*
* A GraphQL document is valid only if all leaf fields (fields without
* sub selections) are of scalar or enum types.
*/
function ScalarLeafsRule(context) {
return {
Field(node) {
const type = context.getType();
const selectionSet = node.selectionSet;
if (type) {
if ((0, _definition.isLeafType)((0, _definition.getNamedType)(type))) {
if (selectionSet) {
const fieldName = node.name.value;
const typeStr = (0, _inspect.inspect)(type);
context.reportError(
new _GraphQLError.GraphQLError(
`Field "${fieldName}" must not have a selection since type "${typeStr}" has no subfields.`,
{
nodes: selectionSet,
},
),
);
}
} else if (!selectionSet) {
const fieldName = node.name.value;
const typeStr = (0, _inspect.inspect)(type);
context.reportError(
new _GraphQLError.GraphQLError(
`Field "${fieldName}" of type "${typeStr}" must have a selection of subfields. Did you mean "${fieldName} { ... }"?`,
{
nodes: node,
},
),
);
}
}
},
};
}

View File

@@ -0,0 +1,46 @@
import { inspect } from '../../jsutils/inspect.mjs';
import { GraphQLError } from '../../error/GraphQLError.mjs';
import { getNamedType, isLeafType } from '../../type/definition.mjs';
/**
* Scalar leafs
*
* A GraphQL document is valid only if all leaf fields (fields without
* sub selections) are of scalar or enum types.
*/
export function ScalarLeafsRule(context) {
return {
Field(node) {
const type = context.getType();
const selectionSet = node.selectionSet;
if (type) {
if (isLeafType(getNamedType(type))) {
if (selectionSet) {
const fieldName = node.name.value;
const typeStr = inspect(type);
context.reportError(
new GraphQLError(
`Field "${fieldName}" must not have a selection since type "${typeStr}" has no subfields.`,
{
nodes: selectionSet,
},
),
);
}
} else if (!selectionSet) {
const fieldName = node.name.value;
const typeStr = inspect(type);
context.reportError(
new GraphQLError(
`Field "${fieldName}" of type "${typeStr}" must have a selection of subfields. Did you mean "${fieldName} { ... }"?`,
{
nodes: node,
},
),
);
}
}
},
};
}

View File

@@ -0,0 +1,13 @@
import type { ASTVisitor } from '../../language/visitor';
import type { ValidationContext } from '../ValidationContext';
/**
* Subscriptions must only include a non-introspection field.
*
* A GraphQL subscription is valid only if it contains a single root field and
* that root field is not an introspection field.
*
* See https://spec.graphql.org/draft/#sec-Single-root-field
*/
export declare function SingleFieldSubscriptionsRule(
context: ValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,86 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.SingleFieldSubscriptionsRule = SingleFieldSubscriptionsRule;
var _GraphQLError = require('../../error/GraphQLError.js');
var _kinds = require('../../language/kinds.js');
var _collectFields = require('../../execution/collectFields.js');
/**
* Subscriptions must only include a non-introspection field.
*
* A GraphQL subscription is valid only if it contains a single root field and
* that root field is not an introspection field.
*
* See https://spec.graphql.org/draft/#sec-Single-root-field
*/
function SingleFieldSubscriptionsRule(context) {
return {
OperationDefinition(node) {
if (node.operation === 'subscription') {
const schema = context.getSchema();
const subscriptionType = schema.getSubscriptionType();
if (subscriptionType) {
const operationName = node.name ? node.name.value : null;
const variableValues = Object.create(null);
const document = context.getDocument();
const fragments = Object.create(null);
for (const definition of document.definitions) {
if (definition.kind === _kinds.Kind.FRAGMENT_DEFINITION) {
fragments[definition.name.value] = definition;
}
}
const fields = (0, _collectFields.collectFields)(
schema,
fragments,
variableValues,
subscriptionType,
node.selectionSet,
);
if (fields.size > 1) {
const fieldSelectionLists = [...fields.values()];
const extraFieldSelectionLists = fieldSelectionLists.slice(1);
const extraFieldSelections = extraFieldSelectionLists.flat();
context.reportError(
new _GraphQLError.GraphQLError(
operationName != null
? `Subscription "${operationName}" must select only one top level field.`
: 'Anonymous Subscription must select only one top level field.',
{
nodes: extraFieldSelections,
},
),
);
}
for (const fieldNodes of fields.values()) {
const field = fieldNodes[0];
const fieldName = field.name.value;
if (fieldName.startsWith('__')) {
context.reportError(
new _GraphQLError.GraphQLError(
operationName != null
? `Subscription "${operationName}" must not select an introspection top level field.`
: 'Anonymous Subscription must not select an introspection top level field.',
{
nodes: fieldNodes,
},
),
);
}
}
}
}
},
};
}

View File

@@ -0,0 +1,77 @@
import { GraphQLError } from '../../error/GraphQLError.mjs';
import { Kind } from '../../language/kinds.mjs';
import { collectFields } from '../../execution/collectFields.mjs';
/**
* Subscriptions must only include a non-introspection field.
*
* A GraphQL subscription is valid only if it contains a single root field and
* that root field is not an introspection field.
*
* See https://spec.graphql.org/draft/#sec-Single-root-field
*/
export function SingleFieldSubscriptionsRule(context) {
return {
OperationDefinition(node) {
if (node.operation === 'subscription') {
const schema = context.getSchema();
const subscriptionType = schema.getSubscriptionType();
if (subscriptionType) {
const operationName = node.name ? node.name.value : null;
const variableValues = Object.create(null);
const document = context.getDocument();
const fragments = Object.create(null);
for (const definition of document.definitions) {
if (definition.kind === Kind.FRAGMENT_DEFINITION) {
fragments[definition.name.value] = definition;
}
}
const fields = collectFields(
schema,
fragments,
variableValues,
subscriptionType,
node.selectionSet,
);
if (fields.size > 1) {
const fieldSelectionLists = [...fields.values()];
const extraFieldSelectionLists = fieldSelectionLists.slice(1);
const extraFieldSelections = extraFieldSelectionLists.flat();
context.reportError(
new GraphQLError(
operationName != null
? `Subscription "${operationName}" must select only one top level field.`
: 'Anonymous Subscription must select only one top level field.',
{
nodes: extraFieldSelections,
},
),
);
}
for (const fieldNodes of fields.values()) {
const field = fieldNodes[0];
const fieldName = field.name.value;
if (fieldName.startsWith('__')) {
context.reportError(
new GraphQLError(
operationName != null
? `Subscription "${operationName}" must not select an introspection top level field.`
: 'Anonymous Subscription must not select an introspection top level field.',
{
nodes: fieldNodes,
},
),
);
}
}
}
}
},
};
}

View File

@@ -0,0 +1,11 @@
import type { ASTVisitor } from '../../language/visitor';
import type { SDLValidationContext } from '../ValidationContext';
/**
* Unique argument definition names
*
* A GraphQL Object or Interface type is only valid if all its fields have uniquely named arguments.
* A GraphQL Directive is only valid if all its arguments are uniquely named.
*/
export declare function UniqueArgumentDefinitionNamesRule(
context: SDLValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,92 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.UniqueArgumentDefinitionNamesRule = UniqueArgumentDefinitionNamesRule;
var _groupBy = require('../../jsutils/groupBy.js');
var _GraphQLError = require('../../error/GraphQLError.js');
/**
* Unique argument definition names
*
* A GraphQL Object or Interface type is only valid if all its fields have uniquely named arguments.
* A GraphQL Directive is only valid if all its arguments are uniquely named.
*/
function UniqueArgumentDefinitionNamesRule(context) {
return {
DirectiveDefinition(directiveNode) {
var _directiveNode$argume;
// FIXME: https://github.com/graphql/graphql-js/issues/2203
/* c8 ignore next */
const argumentNodes =
(_directiveNode$argume = directiveNode.arguments) !== null &&
_directiveNode$argume !== void 0
? _directiveNode$argume
: [];
return checkArgUniqueness(`@${directiveNode.name.value}`, argumentNodes);
},
InterfaceTypeDefinition: checkArgUniquenessPerField,
InterfaceTypeExtension: checkArgUniquenessPerField,
ObjectTypeDefinition: checkArgUniquenessPerField,
ObjectTypeExtension: checkArgUniquenessPerField,
};
function checkArgUniquenessPerField(typeNode) {
var _typeNode$fields;
const typeName = typeNode.name.value; // FIXME: https://github.com/graphql/graphql-js/issues/2203
/* c8 ignore next */
const fieldNodes =
(_typeNode$fields = typeNode.fields) !== null &&
_typeNode$fields !== void 0
? _typeNode$fields
: [];
for (const fieldDef of fieldNodes) {
var _fieldDef$arguments;
const fieldName = fieldDef.name.value; // FIXME: https://github.com/graphql/graphql-js/issues/2203
/* c8 ignore next */
const argumentNodes =
(_fieldDef$arguments = fieldDef.arguments) !== null &&
_fieldDef$arguments !== void 0
? _fieldDef$arguments
: [];
checkArgUniqueness(`${typeName}.${fieldName}`, argumentNodes);
}
return false;
}
function checkArgUniqueness(parentName, argumentNodes) {
const seenArgs = (0, _groupBy.groupBy)(
argumentNodes,
(arg) => arg.name.value,
);
for (const [argName, argNodes] of seenArgs) {
if (argNodes.length > 1) {
context.reportError(
new _GraphQLError.GraphQLError(
`Argument "${parentName}(${argName}:)" can only be defined once.`,
{
nodes: argNodes.map((node) => node.name),
},
),
);
}
}
return false;
}
}

View File

@@ -0,0 +1,81 @@
import { groupBy } from '../../jsutils/groupBy.mjs';
import { GraphQLError } from '../../error/GraphQLError.mjs';
/**
* Unique argument definition names
*
* A GraphQL Object or Interface type is only valid if all its fields have uniquely named arguments.
* A GraphQL Directive is only valid if all its arguments are uniquely named.
*/
export function UniqueArgumentDefinitionNamesRule(context) {
return {
DirectiveDefinition(directiveNode) {
var _directiveNode$argume;
// FIXME: https://github.com/graphql/graphql-js/issues/2203
/* c8 ignore next */
const argumentNodes =
(_directiveNode$argume = directiveNode.arguments) !== null &&
_directiveNode$argume !== void 0
? _directiveNode$argume
: [];
return checkArgUniqueness(`@${directiveNode.name.value}`, argumentNodes);
},
InterfaceTypeDefinition: checkArgUniquenessPerField,
InterfaceTypeExtension: checkArgUniquenessPerField,
ObjectTypeDefinition: checkArgUniquenessPerField,
ObjectTypeExtension: checkArgUniquenessPerField,
};
function checkArgUniquenessPerField(typeNode) {
var _typeNode$fields;
const typeName = typeNode.name.value; // FIXME: https://github.com/graphql/graphql-js/issues/2203
/* c8 ignore next */
const fieldNodes =
(_typeNode$fields = typeNode.fields) !== null &&
_typeNode$fields !== void 0
? _typeNode$fields
: [];
for (const fieldDef of fieldNodes) {
var _fieldDef$arguments;
const fieldName = fieldDef.name.value; // FIXME: https://github.com/graphql/graphql-js/issues/2203
/* c8 ignore next */
const argumentNodes =
(_fieldDef$arguments = fieldDef.arguments) !== null &&
_fieldDef$arguments !== void 0
? _fieldDef$arguments
: [];
checkArgUniqueness(`${typeName}.${fieldName}`, argumentNodes);
}
return false;
}
function checkArgUniqueness(parentName, argumentNodes) {
const seenArgs = groupBy(argumentNodes, (arg) => arg.name.value);
for (const [argName, argNodes] of seenArgs) {
if (argNodes.length > 1) {
context.reportError(
new GraphQLError(
`Argument "${parentName}(${argName}:)" can only be defined once.`,
{
nodes: argNodes.map((node) => node.name),
},
),
);
}
}
return false;
}
}

View File

@@ -0,0 +1,13 @@
import type { ASTVisitor } from '../../language/visitor';
import type { ASTValidationContext } from '../ValidationContext';
/**
* Unique argument names
*
* A GraphQL field or directive is only valid if all supplied arguments are
* uniquely named.
*
* See https://spec.graphql.org/draft/#sec-Argument-Names
*/
export declare function UniqueArgumentNamesRule(
context: ASTValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,55 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.UniqueArgumentNamesRule = UniqueArgumentNamesRule;
var _groupBy = require('../../jsutils/groupBy.js');
var _GraphQLError = require('../../error/GraphQLError.js');
/**
* Unique argument names
*
* A GraphQL field or directive is only valid if all supplied arguments are
* uniquely named.
*
* See https://spec.graphql.org/draft/#sec-Argument-Names
*/
function UniqueArgumentNamesRule(context) {
return {
Field: checkArgUniqueness,
Directive: checkArgUniqueness,
};
function checkArgUniqueness(parentNode) {
var _parentNode$arguments;
// FIXME: https://github.com/graphql/graphql-js/issues/2203
/* c8 ignore next */
const argumentNodes =
(_parentNode$arguments = parentNode.arguments) !== null &&
_parentNode$arguments !== void 0
? _parentNode$arguments
: [];
const seenArgs = (0, _groupBy.groupBy)(
argumentNodes,
(arg) => arg.name.value,
);
for (const [argName, argNodes] of seenArgs) {
if (argNodes.length > 1) {
context.reportError(
new _GraphQLError.GraphQLError(
`There can be only one argument named "${argName}".`,
{
nodes: argNodes.map((node) => node.name),
},
),
);
}
}
}
}

View File

@@ -0,0 +1,44 @@
import { groupBy } from '../../jsutils/groupBy.mjs';
import { GraphQLError } from '../../error/GraphQLError.mjs';
/**
* Unique argument names
*
* A GraphQL field or directive is only valid if all supplied arguments are
* uniquely named.
*
* See https://spec.graphql.org/draft/#sec-Argument-Names
*/
export function UniqueArgumentNamesRule(context) {
return {
Field: checkArgUniqueness,
Directive: checkArgUniqueness,
};
function checkArgUniqueness(parentNode) {
var _parentNode$arguments;
// FIXME: https://github.com/graphql/graphql-js/issues/2203
/* c8 ignore next */
const argumentNodes =
(_parentNode$arguments = parentNode.arguments) !== null &&
_parentNode$arguments !== void 0
? _parentNode$arguments
: [];
const seenArgs = groupBy(argumentNodes, (arg) => arg.name.value);
for (const [argName, argNodes] of seenArgs) {
if (argNodes.length > 1) {
context.reportError(
new GraphQLError(
`There can be only one argument named "${argName}".`,
{
nodes: argNodes.map((node) => node.name),
},
),
);
}
}
}
}

View File

@@ -0,0 +1,10 @@
import type { ASTVisitor } from '../../language/visitor';
import type { SDLValidationContext } from '../ValidationContext';
/**
* Unique directive names
*
* A GraphQL document is only valid if all defined directives have unique names.
*/
export declare function UniqueDirectiveNamesRule(
context: SDLValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,54 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.UniqueDirectiveNamesRule = UniqueDirectiveNamesRule;
var _GraphQLError = require('../../error/GraphQLError.js');
/**
* Unique directive names
*
* A GraphQL document is only valid if all defined directives have unique names.
*/
function UniqueDirectiveNamesRule(context) {
const knownDirectiveNames = Object.create(null);
const schema = context.getSchema();
return {
DirectiveDefinition(node) {
const directiveName = node.name.value;
if (
schema !== null &&
schema !== void 0 &&
schema.getDirective(directiveName)
) {
context.reportError(
new _GraphQLError.GraphQLError(
`Directive "@${directiveName}" already exists in the schema. It cannot be redefined.`,
{
nodes: node.name,
},
),
);
return;
}
if (knownDirectiveNames[directiveName]) {
context.reportError(
new _GraphQLError.GraphQLError(
`There can be only one directive named "@${directiveName}".`,
{
nodes: [knownDirectiveNames[directiveName], node.name],
},
),
);
} else {
knownDirectiveNames[directiveName] = node.name;
}
return false;
},
};
}

View File

@@ -0,0 +1,47 @@
import { GraphQLError } from '../../error/GraphQLError.mjs';
/**
* Unique directive names
*
* A GraphQL document is only valid if all defined directives have unique names.
*/
export function UniqueDirectiveNamesRule(context) {
const knownDirectiveNames = Object.create(null);
const schema = context.getSchema();
return {
DirectiveDefinition(node) {
const directiveName = node.name.value;
if (
schema !== null &&
schema !== void 0 &&
schema.getDirective(directiveName)
) {
context.reportError(
new GraphQLError(
`Directive "@${directiveName}" already exists in the schema. It cannot be redefined.`,
{
nodes: node.name,
},
),
);
return;
}
if (knownDirectiveNames[directiveName]) {
context.reportError(
new GraphQLError(
`There can be only one directive named "@${directiveName}".`,
{
nodes: [knownDirectiveNames[directiveName], node.name],
},
),
);
} else {
knownDirectiveNames[directiveName] = node.name;
}
return false;
},
};
}

View File

@@ -0,0 +1,16 @@
import type { ASTVisitor } from '../../language/visitor';
import type {
SDLValidationContext,
ValidationContext,
} from '../ValidationContext';
/**
* Unique directive names per location
*
* A GraphQL document is only valid if all non-repeatable directives at
* a given location are uniquely named.
*
* See https://spec.graphql.org/draft/#sec-Directives-Are-Unique-Per-Location
*/
export declare function UniqueDirectivesPerLocationRule(
context: ValidationContext | SDLValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,95 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.UniqueDirectivesPerLocationRule = UniqueDirectivesPerLocationRule;
var _GraphQLError = require('../../error/GraphQLError.js');
var _kinds = require('../../language/kinds.js');
var _predicates = require('../../language/predicates.js');
var _directives = require('../../type/directives.js');
/**
* Unique directive names per location
*
* A GraphQL document is only valid if all non-repeatable directives at
* a given location are uniquely named.
*
* See https://spec.graphql.org/draft/#sec-Directives-Are-Unique-Per-Location
*/
function UniqueDirectivesPerLocationRule(context) {
const uniqueDirectiveMap = Object.create(null);
const schema = context.getSchema();
const definedDirectives = schema
? schema.getDirectives()
: _directives.specifiedDirectives;
for (const directive of definedDirectives) {
uniqueDirectiveMap[directive.name] = !directive.isRepeatable;
}
const astDefinitions = context.getDocument().definitions;
for (const def of astDefinitions) {
if (def.kind === _kinds.Kind.DIRECTIVE_DEFINITION) {
uniqueDirectiveMap[def.name.value] = !def.repeatable;
}
}
const schemaDirectives = Object.create(null);
const typeDirectivesMap = Object.create(null);
return {
// Many different AST nodes may contain directives. Rather than listing
// them all, just listen for entering any node, and check to see if it
// defines any directives.
enter(node) {
if (!('directives' in node) || !node.directives) {
return;
}
let seenDirectives;
if (
node.kind === _kinds.Kind.SCHEMA_DEFINITION ||
node.kind === _kinds.Kind.SCHEMA_EXTENSION
) {
seenDirectives = schemaDirectives;
} else if (
(0, _predicates.isTypeDefinitionNode)(node) ||
(0, _predicates.isTypeExtensionNode)(node)
) {
const typeName = node.name.value;
seenDirectives = typeDirectivesMap[typeName];
if (seenDirectives === undefined) {
typeDirectivesMap[typeName] = seenDirectives = Object.create(null);
}
} else {
seenDirectives = Object.create(null);
}
for (const directive of node.directives) {
const directiveName = directive.name.value;
if (uniqueDirectiveMap[directiveName]) {
if (seenDirectives[directiveName]) {
context.reportError(
new _GraphQLError.GraphQLError(
`The directive "@${directiveName}" can only be used once at this location.`,
{
nodes: [seenDirectives[directiveName], directive],
},
),
);
} else {
seenDirectives[directiveName] = directive;
}
}
}
},
};
}

View File

@@ -0,0 +1,85 @@
import { GraphQLError } from '../../error/GraphQLError.mjs';
import { Kind } from '../../language/kinds.mjs';
import {
isTypeDefinitionNode,
isTypeExtensionNode,
} from '../../language/predicates.mjs';
import { specifiedDirectives } from '../../type/directives.mjs';
/**
* Unique directive names per location
*
* A GraphQL document is only valid if all non-repeatable directives at
* a given location are uniquely named.
*
* See https://spec.graphql.org/draft/#sec-Directives-Are-Unique-Per-Location
*/
export function UniqueDirectivesPerLocationRule(context) {
const uniqueDirectiveMap = Object.create(null);
const schema = context.getSchema();
const definedDirectives = schema
? schema.getDirectives()
: specifiedDirectives;
for (const directive of definedDirectives) {
uniqueDirectiveMap[directive.name] = !directive.isRepeatable;
}
const astDefinitions = context.getDocument().definitions;
for (const def of astDefinitions) {
if (def.kind === Kind.DIRECTIVE_DEFINITION) {
uniqueDirectiveMap[def.name.value] = !def.repeatable;
}
}
const schemaDirectives = Object.create(null);
const typeDirectivesMap = Object.create(null);
return {
// Many different AST nodes may contain directives. Rather than listing
// them all, just listen for entering any node, and check to see if it
// defines any directives.
enter(node) {
if (!('directives' in node) || !node.directives) {
return;
}
let seenDirectives;
if (
node.kind === Kind.SCHEMA_DEFINITION ||
node.kind === Kind.SCHEMA_EXTENSION
) {
seenDirectives = schemaDirectives;
} else if (isTypeDefinitionNode(node) || isTypeExtensionNode(node)) {
const typeName = node.name.value;
seenDirectives = typeDirectivesMap[typeName];
if (seenDirectives === undefined) {
typeDirectivesMap[typeName] = seenDirectives = Object.create(null);
}
} else {
seenDirectives = Object.create(null);
}
for (const directive of node.directives) {
const directiveName = directive.name.value;
if (uniqueDirectiveMap[directiveName]) {
if (seenDirectives[directiveName]) {
context.reportError(
new GraphQLError(
`The directive "@${directiveName}" can only be used once at this location.`,
{
nodes: [seenDirectives[directiveName], directive],
},
),
);
} else {
seenDirectives[directiveName] = directive;
}
}
}
},
};
}

View File

@@ -0,0 +1,10 @@
import type { ASTVisitor } from '../../language/visitor';
import type { SDLValidationContext } from '../ValidationContext';
/**
* Unique enum value names
*
* A GraphQL enum type is only valid if all its values are uniquely named.
*/
export declare function UniqueEnumValueNamesRule(
context: SDLValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,75 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.UniqueEnumValueNamesRule = UniqueEnumValueNamesRule;
var _GraphQLError = require('../../error/GraphQLError.js');
var _definition = require('../../type/definition.js');
/**
* Unique enum value names
*
* A GraphQL enum type is only valid if all its values are uniquely named.
*/
function UniqueEnumValueNamesRule(context) {
const schema = context.getSchema();
const existingTypeMap = schema ? schema.getTypeMap() : Object.create(null);
const knownValueNames = Object.create(null);
return {
EnumTypeDefinition: checkValueUniqueness,
EnumTypeExtension: checkValueUniqueness,
};
function checkValueUniqueness(node) {
var _node$values;
const typeName = node.name.value;
if (!knownValueNames[typeName]) {
knownValueNames[typeName] = Object.create(null);
} // FIXME: https://github.com/graphql/graphql-js/issues/2203
/* c8 ignore next */
const valueNodes =
(_node$values = node.values) !== null && _node$values !== void 0
? _node$values
: [];
const valueNames = knownValueNames[typeName];
for (const valueDef of valueNodes) {
const valueName = valueDef.name.value;
const existingType = existingTypeMap[typeName];
if (
(0, _definition.isEnumType)(existingType) &&
existingType.getValue(valueName)
) {
context.reportError(
new _GraphQLError.GraphQLError(
`Enum value "${typeName}.${valueName}" already exists in the schema. It cannot also be defined in this type extension.`,
{
nodes: valueDef.name,
},
),
);
} else if (valueNames[valueName]) {
context.reportError(
new _GraphQLError.GraphQLError(
`Enum value "${typeName}.${valueName}" can only be defined once.`,
{
nodes: [valueNames[valueName], valueDef.name],
},
),
);
} else {
valueNames[valueName] = valueDef.name;
}
}
return false;
}
}

View File

@@ -0,0 +1,64 @@
import { GraphQLError } from '../../error/GraphQLError.mjs';
import { isEnumType } from '../../type/definition.mjs';
/**
* Unique enum value names
*
* A GraphQL enum type is only valid if all its values are uniquely named.
*/
export function UniqueEnumValueNamesRule(context) {
const schema = context.getSchema();
const existingTypeMap = schema ? schema.getTypeMap() : Object.create(null);
const knownValueNames = Object.create(null);
return {
EnumTypeDefinition: checkValueUniqueness,
EnumTypeExtension: checkValueUniqueness,
};
function checkValueUniqueness(node) {
var _node$values;
const typeName = node.name.value;
if (!knownValueNames[typeName]) {
knownValueNames[typeName] = Object.create(null);
} // FIXME: https://github.com/graphql/graphql-js/issues/2203
/* c8 ignore next */
const valueNodes =
(_node$values = node.values) !== null && _node$values !== void 0
? _node$values
: [];
const valueNames = knownValueNames[typeName];
for (const valueDef of valueNodes) {
const valueName = valueDef.name.value;
const existingType = existingTypeMap[typeName];
if (isEnumType(existingType) && existingType.getValue(valueName)) {
context.reportError(
new GraphQLError(
`Enum value "${typeName}.${valueName}" already exists in the schema. It cannot also be defined in this type extension.`,
{
nodes: valueDef.name,
},
),
);
} else if (valueNames[valueName]) {
context.reportError(
new GraphQLError(
`Enum value "${typeName}.${valueName}" can only be defined once.`,
{
nodes: [valueNames[valueName], valueDef.name],
},
),
);
} else {
valueNames[valueName] = valueDef.name;
}
}
return false;
}
}

View File

@@ -0,0 +1,10 @@
import type { ASTVisitor } from '../../language/visitor';
import type { SDLValidationContext } from '../ValidationContext';
/**
* Unique field definition names
*
* A GraphQL complex type is only valid if all its fields are uniquely named.
*/
export declare function UniqueFieldDefinitionNamesRule(
context: SDLValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,87 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.UniqueFieldDefinitionNamesRule = UniqueFieldDefinitionNamesRule;
var _GraphQLError = require('../../error/GraphQLError.js');
var _definition = require('../../type/definition.js');
/**
* Unique field definition names
*
* A GraphQL complex type is only valid if all its fields are uniquely named.
*/
function UniqueFieldDefinitionNamesRule(context) {
const schema = context.getSchema();
const existingTypeMap = schema ? schema.getTypeMap() : Object.create(null);
const knownFieldNames = Object.create(null);
return {
InputObjectTypeDefinition: checkFieldUniqueness,
InputObjectTypeExtension: checkFieldUniqueness,
InterfaceTypeDefinition: checkFieldUniqueness,
InterfaceTypeExtension: checkFieldUniqueness,
ObjectTypeDefinition: checkFieldUniqueness,
ObjectTypeExtension: checkFieldUniqueness,
};
function checkFieldUniqueness(node) {
var _node$fields;
const typeName = node.name.value;
if (!knownFieldNames[typeName]) {
knownFieldNames[typeName] = Object.create(null);
} // FIXME: https://github.com/graphql/graphql-js/issues/2203
/* c8 ignore next */
const fieldNodes =
(_node$fields = node.fields) !== null && _node$fields !== void 0
? _node$fields
: [];
const fieldNames = knownFieldNames[typeName];
for (const fieldDef of fieldNodes) {
const fieldName = fieldDef.name.value;
if (hasField(existingTypeMap[typeName], fieldName)) {
context.reportError(
new _GraphQLError.GraphQLError(
`Field "${typeName}.${fieldName}" already exists in the schema. It cannot also be defined in this type extension.`,
{
nodes: fieldDef.name,
},
),
);
} else if (fieldNames[fieldName]) {
context.reportError(
new _GraphQLError.GraphQLError(
`Field "${typeName}.${fieldName}" can only be defined once.`,
{
nodes: [fieldNames[fieldName], fieldDef.name],
},
),
);
} else {
fieldNames[fieldName] = fieldDef.name;
}
}
return false;
}
}
function hasField(type, fieldName) {
if (
(0, _definition.isObjectType)(type) ||
(0, _definition.isInterfaceType)(type) ||
(0, _definition.isInputObjectType)(type)
) {
return type.getFields()[fieldName] != null;
}
return false;
}

View File

@@ -0,0 +1,79 @@
import { GraphQLError } from '../../error/GraphQLError.mjs';
import {
isInputObjectType,
isInterfaceType,
isObjectType,
} from '../../type/definition.mjs';
/**
* Unique field definition names
*
* A GraphQL complex type is only valid if all its fields are uniquely named.
*/
export function UniqueFieldDefinitionNamesRule(context) {
const schema = context.getSchema();
const existingTypeMap = schema ? schema.getTypeMap() : Object.create(null);
const knownFieldNames = Object.create(null);
return {
InputObjectTypeDefinition: checkFieldUniqueness,
InputObjectTypeExtension: checkFieldUniqueness,
InterfaceTypeDefinition: checkFieldUniqueness,
InterfaceTypeExtension: checkFieldUniqueness,
ObjectTypeDefinition: checkFieldUniqueness,
ObjectTypeExtension: checkFieldUniqueness,
};
function checkFieldUniqueness(node) {
var _node$fields;
const typeName = node.name.value;
if (!knownFieldNames[typeName]) {
knownFieldNames[typeName] = Object.create(null);
} // FIXME: https://github.com/graphql/graphql-js/issues/2203
/* c8 ignore next */
const fieldNodes =
(_node$fields = node.fields) !== null && _node$fields !== void 0
? _node$fields
: [];
const fieldNames = knownFieldNames[typeName];
for (const fieldDef of fieldNodes) {
const fieldName = fieldDef.name.value;
if (hasField(existingTypeMap[typeName], fieldName)) {
context.reportError(
new GraphQLError(
`Field "${typeName}.${fieldName}" already exists in the schema. It cannot also be defined in this type extension.`,
{
nodes: fieldDef.name,
},
),
);
} else if (fieldNames[fieldName]) {
context.reportError(
new GraphQLError(
`Field "${typeName}.${fieldName}" can only be defined once.`,
{
nodes: [fieldNames[fieldName], fieldDef.name],
},
),
);
} else {
fieldNames[fieldName] = fieldDef.name;
}
}
return false;
}
}
function hasField(type, fieldName) {
if (isObjectType(type) || isInterfaceType(type) || isInputObjectType(type)) {
return type.getFields()[fieldName] != null;
}
return false;
}

View File

@@ -0,0 +1,12 @@
import type { ASTVisitor } from '../../language/visitor';
import type { ASTValidationContext } from '../ValidationContext';
/**
* Unique fragment names
*
* A GraphQL document is only valid if all defined fragments have unique names.
*
* See https://spec.graphql.org/draft/#sec-Fragment-Name-Uniqueness
*/
export declare function UniqueFragmentNamesRule(
context: ASTValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,41 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.UniqueFragmentNamesRule = UniqueFragmentNamesRule;
var _GraphQLError = require('../../error/GraphQLError.js');
/**
* Unique fragment names
*
* A GraphQL document is only valid if all defined fragments have unique names.
*
* See https://spec.graphql.org/draft/#sec-Fragment-Name-Uniqueness
*/
function UniqueFragmentNamesRule(context) {
const knownFragmentNames = Object.create(null);
return {
OperationDefinition: () => false,
FragmentDefinition(node) {
const fragmentName = node.name.value;
if (knownFragmentNames[fragmentName]) {
context.reportError(
new _GraphQLError.GraphQLError(
`There can be only one fragment named "${fragmentName}".`,
{
nodes: [knownFragmentNames[fragmentName], node.name],
},
),
);
} else {
knownFragmentNames[fragmentName] = node.name;
}
return false;
},
};
}

View File

@@ -0,0 +1,34 @@
import { GraphQLError } from '../../error/GraphQLError.mjs';
/**
* Unique fragment names
*
* A GraphQL document is only valid if all defined fragments have unique names.
*
* See https://spec.graphql.org/draft/#sec-Fragment-Name-Uniqueness
*/
export function UniqueFragmentNamesRule(context) {
const knownFragmentNames = Object.create(null);
return {
OperationDefinition: () => false,
FragmentDefinition(node) {
const fragmentName = node.name.value;
if (knownFragmentNames[fragmentName]) {
context.reportError(
new GraphQLError(
`There can be only one fragment named "${fragmentName}".`,
{
nodes: [knownFragmentNames[fragmentName], node.name],
},
),
);
} else {
knownFragmentNames[fragmentName] = node.name;
}
return false;
},
};
}

View File

@@ -0,0 +1,13 @@
import type { ASTVisitor } from '../../language/visitor';
import type { ASTValidationContext } from '../ValidationContext';
/**
* Unique input field names
*
* A GraphQL input object value is only valid if all supplied fields are
* uniquely named.
*
* See https://spec.graphql.org/draft/#sec-Input-Object-Field-Uniqueness
*/
export declare function UniqueInputFieldNamesRule(
context: ASTValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,54 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.UniqueInputFieldNamesRule = UniqueInputFieldNamesRule;
var _invariant = require('../../jsutils/invariant.js');
var _GraphQLError = require('../../error/GraphQLError.js');
/**
* Unique input field names
*
* A GraphQL input object value is only valid if all supplied fields are
* uniquely named.
*
* See https://spec.graphql.org/draft/#sec-Input-Object-Field-Uniqueness
*/
function UniqueInputFieldNamesRule(context) {
const knownNameStack = [];
let knownNames = Object.create(null);
return {
ObjectValue: {
enter() {
knownNameStack.push(knownNames);
knownNames = Object.create(null);
},
leave() {
const prevKnownNames = knownNameStack.pop();
prevKnownNames || (0, _invariant.invariant)(false);
knownNames = prevKnownNames;
},
},
ObjectField(node) {
const fieldName = node.name.value;
if (knownNames[fieldName]) {
context.reportError(
new _GraphQLError.GraphQLError(
`There can be only one input field named "${fieldName}".`,
{
nodes: [knownNames[fieldName], node.name],
},
),
);
} else {
knownNames[fieldName] = node.name;
}
},
};
}

View File

@@ -0,0 +1,46 @@
import { invariant } from '../../jsutils/invariant.mjs';
import { GraphQLError } from '../../error/GraphQLError.mjs';
/**
* Unique input field names
*
* A GraphQL input object value is only valid if all supplied fields are
* uniquely named.
*
* See https://spec.graphql.org/draft/#sec-Input-Object-Field-Uniqueness
*/
export function UniqueInputFieldNamesRule(context) {
const knownNameStack = [];
let knownNames = Object.create(null);
return {
ObjectValue: {
enter() {
knownNameStack.push(knownNames);
knownNames = Object.create(null);
},
leave() {
const prevKnownNames = knownNameStack.pop();
prevKnownNames || invariant(false);
knownNames = prevKnownNames;
},
},
ObjectField(node) {
const fieldName = node.name.value;
if (knownNames[fieldName]) {
context.reportError(
new GraphQLError(
`There can be only one input field named "${fieldName}".`,
{
nodes: [knownNames[fieldName], node.name],
},
),
);
} else {
knownNames[fieldName] = node.name;
}
},
};
}

View File

@@ -0,0 +1,12 @@
import type { ASTVisitor } from '../../language/visitor';
import type { ASTValidationContext } from '../ValidationContext';
/**
* Unique operation names
*
* A GraphQL document is only valid if all defined operations have unique names.
*
* See https://spec.graphql.org/draft/#sec-Operation-Name-Uniqueness
*/
export declare function UniqueOperationNamesRule(
context: ASTValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,46 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.UniqueOperationNamesRule = UniqueOperationNamesRule;
var _GraphQLError = require('../../error/GraphQLError.js');
/**
* Unique operation names
*
* A GraphQL document is only valid if all defined operations have unique names.
*
* See https://spec.graphql.org/draft/#sec-Operation-Name-Uniqueness
*/
function UniqueOperationNamesRule(context) {
const knownOperationNames = Object.create(null);
return {
OperationDefinition(node) {
const operationName = node.name;
if (operationName) {
if (knownOperationNames[operationName.value]) {
context.reportError(
new _GraphQLError.GraphQLError(
`There can be only one operation named "${operationName.value}".`,
{
nodes: [
knownOperationNames[operationName.value],
operationName,
],
},
),
);
} else {
knownOperationNames[operationName.value] = operationName;
}
}
return false;
},
FragmentDefinition: () => false,
};
}

View File

@@ -0,0 +1,39 @@
import { GraphQLError } from '../../error/GraphQLError.mjs';
/**
* Unique operation names
*
* A GraphQL document is only valid if all defined operations have unique names.
*
* See https://spec.graphql.org/draft/#sec-Operation-Name-Uniqueness
*/
export function UniqueOperationNamesRule(context) {
const knownOperationNames = Object.create(null);
return {
OperationDefinition(node) {
const operationName = node.name;
if (operationName) {
if (knownOperationNames[operationName.value]) {
context.reportError(
new GraphQLError(
`There can be only one operation named "${operationName.value}".`,
{
nodes: [
knownOperationNames[operationName.value],
operationName,
],
},
),
);
} else {
knownOperationNames[operationName.value] = operationName;
}
}
return false;
},
FragmentDefinition: () => false,
};
}

View File

@@ -0,0 +1,10 @@
import type { ASTVisitor } from '../../language/visitor';
import type { SDLValidationContext } from '../ValidationContext';
/**
* Unique operation types
*
* A GraphQL document is only valid if it has only one type per operation.
*/
export declare function UniqueOperationTypesRule(
context: SDLValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,71 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.UniqueOperationTypesRule = UniqueOperationTypesRule;
var _GraphQLError = require('../../error/GraphQLError.js');
/**
* Unique operation types
*
* A GraphQL document is only valid if it has only one type per operation.
*/
function UniqueOperationTypesRule(context) {
const schema = context.getSchema();
const definedOperationTypes = Object.create(null);
const existingOperationTypes = schema
? {
query: schema.getQueryType(),
mutation: schema.getMutationType(),
subscription: schema.getSubscriptionType(),
}
: {};
return {
SchemaDefinition: checkOperationTypes,
SchemaExtension: checkOperationTypes,
};
function checkOperationTypes(node) {
var _node$operationTypes;
// See: https://github.com/graphql/graphql-js/issues/2203
/* c8 ignore next */
const operationTypesNodes =
(_node$operationTypes = node.operationTypes) !== null &&
_node$operationTypes !== void 0
? _node$operationTypes
: [];
for (const operationType of operationTypesNodes) {
const operation = operationType.operation;
const alreadyDefinedOperationType = definedOperationTypes[operation];
if (existingOperationTypes[operation]) {
context.reportError(
new _GraphQLError.GraphQLError(
`Type for ${operation} already defined in the schema. It cannot be redefined.`,
{
nodes: operationType,
},
),
);
} else if (alreadyDefinedOperationType) {
context.reportError(
new _GraphQLError.GraphQLError(
`There can be only one ${operation} type in schema.`,
{
nodes: [alreadyDefinedOperationType, operationType],
},
),
);
} else {
definedOperationTypes[operation] = operationType;
}
}
return false;
}
}

View File

@@ -0,0 +1,64 @@
import { GraphQLError } from '../../error/GraphQLError.mjs';
/**
* Unique operation types
*
* A GraphQL document is only valid if it has only one type per operation.
*/
export function UniqueOperationTypesRule(context) {
const schema = context.getSchema();
const definedOperationTypes = Object.create(null);
const existingOperationTypes = schema
? {
query: schema.getQueryType(),
mutation: schema.getMutationType(),
subscription: schema.getSubscriptionType(),
}
: {};
return {
SchemaDefinition: checkOperationTypes,
SchemaExtension: checkOperationTypes,
};
function checkOperationTypes(node) {
var _node$operationTypes;
// See: https://github.com/graphql/graphql-js/issues/2203
/* c8 ignore next */
const operationTypesNodes =
(_node$operationTypes = node.operationTypes) !== null &&
_node$operationTypes !== void 0
? _node$operationTypes
: [];
for (const operationType of operationTypesNodes) {
const operation = operationType.operation;
const alreadyDefinedOperationType = definedOperationTypes[operation];
if (existingOperationTypes[operation]) {
context.reportError(
new GraphQLError(
`Type for ${operation} already defined in the schema. It cannot be redefined.`,
{
nodes: operationType,
},
),
);
} else if (alreadyDefinedOperationType) {
context.reportError(
new GraphQLError(
`There can be only one ${operation} type in schema.`,
{
nodes: [alreadyDefinedOperationType, operationType],
},
),
);
} else {
definedOperationTypes[operation] = operationType;
}
}
return false;
}
}

View File

@@ -0,0 +1,10 @@
import type { ASTVisitor } from '../../language/visitor';
import type { SDLValidationContext } from '../ValidationContext';
/**
* Unique type names
*
* A GraphQL document is only valid if all defined types have unique names.
*/
export declare function UniqueTypeNamesRule(
context: SDLValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,57 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.UniqueTypeNamesRule = UniqueTypeNamesRule;
var _GraphQLError = require('../../error/GraphQLError.js');
/**
* Unique type names
*
* A GraphQL document is only valid if all defined types have unique names.
*/
function UniqueTypeNamesRule(context) {
const knownTypeNames = Object.create(null);
const schema = context.getSchema();
return {
ScalarTypeDefinition: checkTypeName,
ObjectTypeDefinition: checkTypeName,
InterfaceTypeDefinition: checkTypeName,
UnionTypeDefinition: checkTypeName,
EnumTypeDefinition: checkTypeName,
InputObjectTypeDefinition: checkTypeName,
};
function checkTypeName(node) {
const typeName = node.name.value;
if (schema !== null && schema !== void 0 && schema.getType(typeName)) {
context.reportError(
new _GraphQLError.GraphQLError(
`Type "${typeName}" already exists in the schema. It cannot also be defined in this type definition.`,
{
nodes: node.name,
},
),
);
return;
}
if (knownTypeNames[typeName]) {
context.reportError(
new _GraphQLError.GraphQLError(
`There can be only one type named "${typeName}".`,
{
nodes: [knownTypeNames[typeName], node.name],
},
),
);
} else {
knownTypeNames[typeName] = node.name;
}
return false;
}
}

View File

@@ -0,0 +1,47 @@
import { GraphQLError } from '../../error/GraphQLError.mjs';
/**
* Unique type names
*
* A GraphQL document is only valid if all defined types have unique names.
*/
export function UniqueTypeNamesRule(context) {
const knownTypeNames = Object.create(null);
const schema = context.getSchema();
return {
ScalarTypeDefinition: checkTypeName,
ObjectTypeDefinition: checkTypeName,
InterfaceTypeDefinition: checkTypeName,
UnionTypeDefinition: checkTypeName,
EnumTypeDefinition: checkTypeName,
InputObjectTypeDefinition: checkTypeName,
};
function checkTypeName(node) {
const typeName = node.name.value;
if (schema !== null && schema !== void 0 && schema.getType(typeName)) {
context.reportError(
new GraphQLError(
`Type "${typeName}" already exists in the schema. It cannot also be defined in this type definition.`,
{
nodes: node.name,
},
),
);
return;
}
if (knownTypeNames[typeName]) {
context.reportError(
new GraphQLError(`There can be only one type named "${typeName}".`, {
nodes: [knownTypeNames[typeName], node.name],
}),
);
} else {
knownTypeNames[typeName] = node.name;
}
return false;
}
}

View File

@@ -0,0 +1,10 @@
import type { ASTVisitor } from '../../language/visitor';
import type { ASTValidationContext } from '../ValidationContext';
/**
* Unique variable names
*
* A GraphQL operation is only valid if all its variables are uniquely named.
*/
export declare function UniqueVariableNamesRule(
context: ASTValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,49 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.UniqueVariableNamesRule = UniqueVariableNamesRule;
var _groupBy = require('../../jsutils/groupBy.js');
var _GraphQLError = require('../../error/GraphQLError.js');
/**
* Unique variable names
*
* A GraphQL operation is only valid if all its variables are uniquely named.
*/
function UniqueVariableNamesRule(context) {
return {
OperationDefinition(operationNode) {
var _operationNode$variab;
// See: https://github.com/graphql/graphql-js/issues/2203
/* c8 ignore next */
const variableDefinitions =
(_operationNode$variab = operationNode.variableDefinitions) !== null &&
_operationNode$variab !== void 0
? _operationNode$variab
: [];
const seenVariableDefinitions = (0, _groupBy.groupBy)(
variableDefinitions,
(node) => node.variable.name.value,
);
for (const [variableName, variableNodes] of seenVariableDefinitions) {
if (variableNodes.length > 1) {
context.reportError(
new _GraphQLError.GraphQLError(
`There can be only one variable named "$${variableName}".`,
{
nodes: variableNodes.map((node) => node.variable.name),
},
),
);
}
}
},
};
}

View File

@@ -0,0 +1,41 @@
import { groupBy } from '../../jsutils/groupBy.mjs';
import { GraphQLError } from '../../error/GraphQLError.mjs';
/**
* Unique variable names
*
* A GraphQL operation is only valid if all its variables are uniquely named.
*/
export function UniqueVariableNamesRule(context) {
return {
OperationDefinition(operationNode) {
var _operationNode$variab;
// See: https://github.com/graphql/graphql-js/issues/2203
/* c8 ignore next */
const variableDefinitions =
(_operationNode$variab = operationNode.variableDefinitions) !== null &&
_operationNode$variab !== void 0
? _operationNode$variab
: [];
const seenVariableDefinitions = groupBy(
variableDefinitions,
(node) => node.variable.name.value,
);
for (const [variableName, variableNodes] of seenVariableDefinitions) {
if (variableNodes.length > 1) {
context.reportError(
new GraphQLError(
`There can be only one variable named "$${variableName}".`,
{
nodes: variableNodes.map((node) => node.variable.name),
},
),
);
}
}
},
};
}

View File

@@ -0,0 +1,13 @@
import type { ASTVisitor } from '../../language/visitor';
import type { ValidationContext } from '../ValidationContext';
/**
* Value literals of correct type
*
* A GraphQL document is only valid if all value literals are of the type
* expected at their position.
*
* See https://spec.graphql.org/draft/#sec-Values-of-Correct-Type
*/
export declare function ValuesOfCorrectTypeRule(
context: ValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,192 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.ValuesOfCorrectTypeRule = ValuesOfCorrectTypeRule;
var _didYouMean = require('../../jsutils/didYouMean.js');
var _inspect = require('../../jsutils/inspect.js');
var _keyMap = require('../../jsutils/keyMap.js');
var _suggestionList = require('../../jsutils/suggestionList.js');
var _GraphQLError = require('../../error/GraphQLError.js');
var _printer = require('../../language/printer.js');
var _definition = require('../../type/definition.js');
/**
* Value literals of correct type
*
* A GraphQL document is only valid if all value literals are of the type
* expected at their position.
*
* See https://spec.graphql.org/draft/#sec-Values-of-Correct-Type
*/
function ValuesOfCorrectTypeRule(context) {
return {
ListValue(node) {
// Note: TypeInfo will traverse into a list's item type, so look to the
// parent input type to check if it is a list.
const type = (0, _definition.getNullableType)(
context.getParentInputType(),
);
if (!(0, _definition.isListType)(type)) {
isValidValueNode(context, node);
return false; // Don't traverse further.
}
},
ObjectValue(node) {
const type = (0, _definition.getNamedType)(context.getInputType());
if (!(0, _definition.isInputObjectType)(type)) {
isValidValueNode(context, node);
return false; // Don't traverse further.
} // Ensure every required field exists.
const fieldNodeMap = (0, _keyMap.keyMap)(
node.fields,
(field) => field.name.value,
);
for (const fieldDef of Object.values(type.getFields())) {
const fieldNode = fieldNodeMap[fieldDef.name];
if (!fieldNode && (0, _definition.isRequiredInputField)(fieldDef)) {
const typeStr = (0, _inspect.inspect)(fieldDef.type);
context.reportError(
new _GraphQLError.GraphQLError(
`Field "${type.name}.${fieldDef.name}" of required type "${typeStr}" was not provided.`,
{
nodes: node,
},
),
);
}
}
},
ObjectField(node) {
const parentType = (0, _definition.getNamedType)(
context.getParentInputType(),
);
const fieldType = context.getInputType();
if (!fieldType && (0, _definition.isInputObjectType)(parentType)) {
const suggestions = (0, _suggestionList.suggestionList)(
node.name.value,
Object.keys(parentType.getFields()),
);
context.reportError(
new _GraphQLError.GraphQLError(
`Field "${node.name.value}" is not defined by type "${parentType.name}".` +
(0, _didYouMean.didYouMean)(suggestions),
{
nodes: node,
},
),
);
}
},
NullValue(node) {
const type = context.getInputType();
if ((0, _definition.isNonNullType)(type)) {
context.reportError(
new _GraphQLError.GraphQLError(
`Expected value of type "${(0, _inspect.inspect)(
type,
)}", found ${(0, _printer.print)(node)}.`,
{
nodes: node,
},
),
);
}
},
EnumValue: (node) => isValidValueNode(context, node),
IntValue: (node) => isValidValueNode(context, node),
FloatValue: (node) => isValidValueNode(context, node),
StringValue: (node) => isValidValueNode(context, node),
BooleanValue: (node) => isValidValueNode(context, node),
};
}
/**
* Any value literal may be a valid representation of a Scalar, depending on
* that scalar type.
*/
function isValidValueNode(context, node) {
// Report any error at the full type expected by the location.
const locationType = context.getInputType();
if (!locationType) {
return;
}
const type = (0, _definition.getNamedType)(locationType);
if (!(0, _definition.isLeafType)(type)) {
const typeStr = (0, _inspect.inspect)(locationType);
context.reportError(
new _GraphQLError.GraphQLError(
`Expected value of type "${typeStr}", found ${(0, _printer.print)(
node,
)}.`,
{
nodes: node,
},
),
);
return;
} // Scalars and Enums determine if a literal value is valid via parseLiteral(),
// which may throw or return an invalid value to indicate failure.
try {
const parseResult = type.parseLiteral(
node,
undefined,
/* variables */
);
if (parseResult === undefined) {
const typeStr = (0, _inspect.inspect)(locationType);
context.reportError(
new _GraphQLError.GraphQLError(
`Expected value of type "${typeStr}", found ${(0, _printer.print)(
node,
)}.`,
{
nodes: node,
},
),
);
}
} catch (error) {
const typeStr = (0, _inspect.inspect)(locationType);
if (error instanceof _GraphQLError.GraphQLError) {
context.reportError(error);
} else {
context.reportError(
new _GraphQLError.GraphQLError(
`Expected value of type "${typeStr}", found ${(0, _printer.print)(
node,
)}; ` + error.message,
{
nodes: node,
originalError: error,
},
),
);
}
}
}

View File

@@ -0,0 +1,173 @@
import { didYouMean } from '../../jsutils/didYouMean.mjs';
import { inspect } from '../../jsutils/inspect.mjs';
import { keyMap } from '../../jsutils/keyMap.mjs';
import { suggestionList } from '../../jsutils/suggestionList.mjs';
import { GraphQLError } from '../../error/GraphQLError.mjs';
import { print } from '../../language/printer.mjs';
import {
getNamedType,
getNullableType,
isInputObjectType,
isLeafType,
isListType,
isNonNullType,
isRequiredInputField,
} from '../../type/definition.mjs';
/**
* Value literals of correct type
*
* A GraphQL document is only valid if all value literals are of the type
* expected at their position.
*
* See https://spec.graphql.org/draft/#sec-Values-of-Correct-Type
*/
export function ValuesOfCorrectTypeRule(context) {
return {
ListValue(node) {
// Note: TypeInfo will traverse into a list's item type, so look to the
// parent input type to check if it is a list.
const type = getNullableType(context.getParentInputType());
if (!isListType(type)) {
isValidValueNode(context, node);
return false; // Don't traverse further.
}
},
ObjectValue(node) {
const type = getNamedType(context.getInputType());
if (!isInputObjectType(type)) {
isValidValueNode(context, node);
return false; // Don't traverse further.
} // Ensure every required field exists.
const fieldNodeMap = keyMap(node.fields, (field) => field.name.value);
for (const fieldDef of Object.values(type.getFields())) {
const fieldNode = fieldNodeMap[fieldDef.name];
if (!fieldNode && isRequiredInputField(fieldDef)) {
const typeStr = inspect(fieldDef.type);
context.reportError(
new GraphQLError(
`Field "${type.name}.${fieldDef.name}" of required type "${typeStr}" was not provided.`,
{
nodes: node,
},
),
);
}
}
},
ObjectField(node) {
const parentType = getNamedType(context.getParentInputType());
const fieldType = context.getInputType();
if (!fieldType && isInputObjectType(parentType)) {
const suggestions = suggestionList(
node.name.value,
Object.keys(parentType.getFields()),
);
context.reportError(
new GraphQLError(
`Field "${node.name.value}" is not defined by type "${parentType.name}".` +
didYouMean(suggestions),
{
nodes: node,
},
),
);
}
},
NullValue(node) {
const type = context.getInputType();
if (isNonNullType(type)) {
context.reportError(
new GraphQLError(
`Expected value of type "${inspect(type)}", found ${print(node)}.`,
{
nodes: node,
},
),
);
}
},
EnumValue: (node) => isValidValueNode(context, node),
IntValue: (node) => isValidValueNode(context, node),
FloatValue: (node) => isValidValueNode(context, node),
StringValue: (node) => isValidValueNode(context, node),
BooleanValue: (node) => isValidValueNode(context, node),
};
}
/**
* Any value literal may be a valid representation of a Scalar, depending on
* that scalar type.
*/
function isValidValueNode(context, node) {
// Report any error at the full type expected by the location.
const locationType = context.getInputType();
if (!locationType) {
return;
}
const type = getNamedType(locationType);
if (!isLeafType(type)) {
const typeStr = inspect(locationType);
context.reportError(
new GraphQLError(
`Expected value of type "${typeStr}", found ${print(node)}.`,
{
nodes: node,
},
),
);
return;
} // Scalars and Enums determine if a literal value is valid via parseLiteral(),
// which may throw or return an invalid value to indicate failure.
try {
const parseResult = type.parseLiteral(
node,
undefined,
/* variables */
);
if (parseResult === undefined) {
const typeStr = inspect(locationType);
context.reportError(
new GraphQLError(
`Expected value of type "${typeStr}", found ${print(node)}.`,
{
nodes: node,
},
),
);
}
} catch (error) {
const typeStr = inspect(locationType);
if (error instanceof GraphQLError) {
context.reportError(error);
} else {
context.reportError(
new GraphQLError(
`Expected value of type "${typeStr}", found ${print(node)}; ` +
error.message,
{
nodes: node,
originalError: error,
},
),
);
}
}
}

View File

@@ -0,0 +1,13 @@
import type { ASTVisitor } from '../../language/visitor';
import type { ValidationContext } from '../ValidationContext';
/**
* Variables are input types
*
* A GraphQL operation is only valid if all the variables it defines are of
* input types (scalar, enum, or input object).
*
* See https://spec.graphql.org/draft/#sec-Variables-Are-Input-Types
*/
export declare function VariablesAreInputTypesRule(
context: ValidationContext,
): ASTVisitor;

View File

@@ -0,0 +1,46 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.VariablesAreInputTypesRule = VariablesAreInputTypesRule;
var _GraphQLError = require('../../error/GraphQLError.js');
var _printer = require('../../language/printer.js');
var _definition = require('../../type/definition.js');
var _typeFromAST = require('../../utilities/typeFromAST.js');
/**
* Variables are input types
*
* A GraphQL operation is only valid if all the variables it defines are of
* input types (scalar, enum, or input object).
*
* See https://spec.graphql.org/draft/#sec-Variables-Are-Input-Types
*/
function VariablesAreInputTypesRule(context) {
return {
VariableDefinition(node) {
const type = (0, _typeFromAST.typeFromAST)(
context.getSchema(),
node.type,
);
if (type !== undefined && !(0, _definition.isInputType)(type)) {
const variableName = node.variable.name.value;
const typeName = (0, _printer.print)(node.type);
context.reportError(
new _GraphQLError.GraphQLError(
`Variable "$${variableName}" cannot be non-input type "${typeName}".`,
{
nodes: node.type,
},
),
);
}
},
};
}

View File

@@ -0,0 +1,33 @@
import { GraphQLError } from '../../error/GraphQLError.mjs';
import { print } from '../../language/printer.mjs';
import { isInputType } from '../../type/definition.mjs';
import { typeFromAST } from '../../utilities/typeFromAST.mjs';
/**
* Variables are input types
*
* A GraphQL operation is only valid if all the variables it defines are of
* input types (scalar, enum, or input object).
*
* See https://spec.graphql.org/draft/#sec-Variables-Are-Input-Types
*/
export function VariablesAreInputTypesRule(context) {
return {
VariableDefinition(node) {
const type = typeFromAST(context.getSchema(), node.type);
if (type !== undefined && !isInputType(type)) {
const variableName = node.variable.name.value;
const typeName = print(node.type);
context.reportError(
new GraphQLError(
`Variable "$${variableName}" cannot be non-input type "${typeName}".`,
{
nodes: node.type,
},
),
);
}
},
};
}

View File

@@ -0,0 +1,12 @@
import type { ASTVisitor } from '../../language/visitor';
import type { ValidationContext } from '../ValidationContext';
/**
* Variables in allowed position
*
* Variable usages must be compatible with the arguments they are passed to.
*
* See https://spec.graphql.org/draft/#sec-All-Variable-Usages-are-Allowed
*/
export declare function VariablesInAllowedPositionRule(
context: ValidationContext,
): ASTVisitor;

Some files were not shown because too many files have changed in this diff Show More