UNPKG

@graphql-inspector/cli

Version:

Tooling for GraphQL. Compare GraphQL Schemas, check documents, find breaking changes, find similar types.

276 lines (275 loc) • 13.4 kB
import { Kind, parseConstValue, parseType, print, } from 'graphql'; import { AddedAttributeAlreadyExistsError, AddedAttributeCoordinateNotFoundError, AddedCoordinateAlreadyExistsError, ChangedAncestorCoordinateNotFoundError, ChangedCoordinateKindMismatchError, ChangePathMissingError, DeletedAncestorCoordinateNotFoundError, DeletedAttributeNotFoundError, ValueMismatchError, } from '../errors.js'; import { nameNode, stringNode } from '../node-templates.js'; import { deleteNamedNode, findNamedNode, getDeletedNodeOfKind, parentPath } from '../utils.js'; export function directiveAdded(change, nodeByPath, config, _context) { if (change.path === undefined) { config.onError(new ChangePathMissingError(change), change); return; } const changedNode = nodeByPath.get(change.path); if (!changedNode) { const node = { kind: Kind.DIRECTIVE_DEFINITION, name: nameNode(change.meta.addedDirectiveName), repeatable: change.meta.addedDirectiveRepeatable, locations: change.meta.addedDirectiveLocations.map(l => nameNode(l)), description: change.meta.addedDirectiveDescription ? stringNode(change.meta.addedDirectiveDescription) : undefined, }; nodeByPath.set(change.path, node); return; } if (changedNode.kind !== Kind.DIRECTIVE_DEFINITION) { config.onError(new ChangedCoordinateKindMismatchError(Kind.DIRECTIVE_DEFINITION, changedNode.kind), change); return; } // eslint-disable-next-line eqeqeq if (change.meta.addedDirectiveRepeatable != changedNode.repeatable) { config.onError(new ValueMismatchError(changedNode.kind, `repeatable=${change.meta.addedDirectiveRepeatable}`, `repeatable=${changedNode.repeatable}`), change); return; } if (change.meta.addedDirectiveLocations.join('|') !== changedNode.locations.map(l => l.value).join('|')) { config.onError(new ValueMismatchError(changedNode.kind, `locations=${change.meta.addedDirectiveLocations.join('|')}`, `locations=${changedNode.locations.map(l => l.value).join('|')}`), change); return; } config.onError(new AddedCoordinateAlreadyExistsError(change.path, change.type), change); } export function directiveRemoved(change, nodeByPath, config, _context) { const existing = getDeletedNodeOfKind(change, nodeByPath, Kind.DIRECTIVE_DEFINITION, config); if (existing) { nodeByPath.delete(change.path); } } export function directiveArgumentAdded(change, nodeByPath, config, _context) { if (!change.path) { config.onError(new ChangePathMissingError(change), change); return; } const directiveNode = nodeByPath.get(change.path); if (!directiveNode) { config.onError(new AddedAttributeCoordinateNotFoundError(change.path, change.type, change.meta.addedDirectiveArgumentName), change); return; } if (directiveNode.kind !== Kind.DIRECTIVE_DEFINITION) { config.onError(new ChangedCoordinateKindMismatchError(Kind.DIRECTIVE_DEFINITION, directiveNode.kind), change); return; } const existingArg = findNamedNode(directiveNode.arguments, change.meta.addedDirectiveArgumentName); if (!existingArg) { const node = { kind: Kind.INPUT_VALUE_DEFINITION, name: nameNode(change.meta.addedDirectiveArgumentName), type: parseType(change.meta.addedDirectiveArgumentType), }; directiveNode.arguments = [ ...(directiveNode.arguments ?? []), node, ]; nodeByPath.set(`${change.path}.${change.meta.addedDirectiveArgumentName}`, node); return; } const existingType = print(existingArg.type); if (existingType !== change.meta.addedDirectiveArgumentType) { config.onError(new ValueMismatchError(existingArg.kind, `type=${change.meta.addedDirectiveArgumentType}`, `type=${existingType}`), change); } const existingDefaultValue = existingArg.defaultValue ? print(existingArg.defaultValue) : undefined; if (change.meta.addedDirectiveDefaultValue !== existingDefaultValue) { config.onError(new ValueMismatchError(existingArg.kind, `defaultValue=${change.meta.addedDirectiveDefaultValue}`, `defaultValue=${existingDefaultValue}`), change); } config.onError(new AddedAttributeAlreadyExistsError(change.path, change.type, 'arguments', change.meta.addedDirectiveArgumentName), change); } export function directiveArgumentRemoved(change, nodeByPath, config, _context) { if (!change.path) { config.onError(new ChangePathMissingError(change), change); return; } const argNode = getDeletedNodeOfKind(change, nodeByPath, Kind.INPUT_VALUE_DEFINITION, config); if (argNode) { const directiveNode = nodeByPath.get(parentPath(change.path)); if (!directiveNode) { config.onError(new DeletedAncestorCoordinateNotFoundError(change.path, change.type, change.meta.removedDirectiveArgumentName), change); return; } if (directiveNode.kind !== Kind.DIRECTIVE_DEFINITION) { config.onError(new ChangedCoordinateKindMismatchError(Kind.DIRECTIVE_DEFINITION, directiveNode.kind), change); return; } directiveNode.arguments = deleteNamedNode(directiveNode.arguments, change.meta.removedDirectiveArgumentName); } } export function directiveLocationAdded(change, nodeByPath, config, _context) { if (!change.path) { config.onError(new ChangePathMissingError(change), change); return; } const changedNode = nodeByPath.get(change.path); if (!changedNode) { config.onError(new ChangedAncestorCoordinateNotFoundError(change.path, change.type, change.meta.addedDirectiveLocation), change); return; } if (changedNode.kind !== Kind.DIRECTIVE_DEFINITION) { config.onError(new ChangedCoordinateKindMismatchError(Kind.DIRECTIVE_DEFINITION, changedNode.kind), change); return; } if (changedNode.locations.some(l => l.value === change.meta.addedDirectiveLocation)) { config.onError(new AddedAttributeAlreadyExistsError(change.path, change.type, 'locations', change.meta.addedDirectiveLocation), change); return; } changedNode.locations = [ ...changedNode.locations, nameNode(change.meta.addedDirectiveLocation), ]; } export function directiveLocationRemoved(change, nodeByPath, config, _context) { if (!change.path) { config.onError(new ChangePathMissingError(change), change); return; } const changedNode = nodeByPath.get(change.path); if (!changedNode) { config.onError(new DeletedAncestorCoordinateNotFoundError(change.path, change.type, change.meta.removedDirectiveLocation), change); return; } if (changedNode.kind !== Kind.DIRECTIVE_DEFINITION) { config.onError(new ChangedCoordinateKindMismatchError(Kind.DIRECTIVE_DEFINITION, changedNode.kind), change); return; } const existing = changedNode.locations.findIndex(l => l.value === change.meta.removedDirectiveLocation); if (existing >= 0) { changedNode.locations = changedNode.locations.toSpliced(existing, 1); } else { config.onError(new DeletedAttributeNotFoundError(change.path, change.type, 'locations', change.meta.removedDirectiveLocation), change); } } export function directiveDescriptionChanged(change, nodeByPath, config, _context) { if (!change.path) { config.onError(new ChangePathMissingError(change), change); return; } const directiveNode = nodeByPath.get(change.path); if (!directiveNode) { config.onError(new ChangedAncestorCoordinateNotFoundError(change.path, change.type, change.meta.newDirectiveDescription), change); return; } if (directiveNode.kind !== Kind.DIRECTIVE_DEFINITION) { config.onError(new ChangedCoordinateKindMismatchError(Kind.DIRECTIVE_DEFINITION, directiveNode.kind), change); return; } if ((directiveNode.description?.value ?? null) !== change.meta.oldDirectiveDescription) { config.onError(new ValueMismatchError(Kind.STRING, change.meta.oldDirectiveDescription, directiveNode.description?.value), change); } directiveNode.description = change.meta.newDirectiveDescription ? stringNode(change.meta.newDirectiveDescription) : undefined; } export function directiveArgumentDefaultValueChanged(change, nodeByPath, config, _context) { if (!change.path) { config.onError(new ChangePathMissingError(change), change); return; } const argumentNode = nodeByPath.get(change.path); if (!argumentNode) { config.onError(new ChangedAncestorCoordinateNotFoundError(change.path, change.type, change.meta.newDirectiveArgumentDefaultValue ?? null), change); return; } if (argumentNode.kind !== Kind.INPUT_VALUE_DEFINITION) { config.onError(new ChangedCoordinateKindMismatchError(Kind.INPUT_VALUE_DEFINITION, argumentNode.kind), change); return; } if ((argumentNode.defaultValue && print(argumentNode.defaultValue)) === change.meta.oldDirectiveArgumentDefaultValue) { argumentNode.defaultValue = change.meta .newDirectiveArgumentDefaultValue ? parseConstValue(change.meta.newDirectiveArgumentDefaultValue) : undefined; } else { config.onError(new ValueMismatchError(Kind.INPUT_VALUE_DEFINITION, change.meta.oldDirectiveArgumentDefaultValue, argumentNode.defaultValue && print(argumentNode.defaultValue)), change); } } export function directiveArgumentDescriptionChanged(change, nodeByPath, config, _context) { if (!change.path) { config.onError(new ChangePathMissingError(change), change); return; } const argumentNode = nodeByPath.get(change.path); if (!argumentNode) { config.onError(new ChangedAncestorCoordinateNotFoundError(change.path, change.type, change.meta.newDirectiveArgumentDescription), change); return; } if (argumentNode.kind !== Kind.INPUT_VALUE_DEFINITION) { config.onError(new ChangedCoordinateKindMismatchError(Kind.INPUT_VALUE_DEFINITION, argumentNode.kind), change); return; } if ((argumentNode.description?.value ?? null) !== change.meta.oldDirectiveArgumentDescription) { config.onError(new ValueMismatchError(Kind.STRING, change.meta.oldDirectiveArgumentDescription ?? undefined, argumentNode.description?.value), change); } argumentNode.description = change.meta .newDirectiveArgumentDescription ? stringNode(change.meta.newDirectiveArgumentDescription) : undefined; } export function directiveArgumentTypeChanged(change, nodeByPath, config, _context) { if (!change.path) { config.onError(new ChangePathMissingError(change), change); return; } const argumentNode = nodeByPath.get(change.path); if (!argumentNode) { config.onError(new ChangedAncestorCoordinateNotFoundError(change.path, change.type, change.meta.newDirectiveArgumentType), change); return; } if (argumentNode.kind !== Kind.INPUT_VALUE_DEFINITION) { config.onError(new ChangedCoordinateKindMismatchError(Kind.INPUT_VALUE_DEFINITION, argumentNode.kind), change); return; } if (print(argumentNode.type) !== change.meta.oldDirectiveArgumentType) { config.onError(new ValueMismatchError(Kind.STRING, change.meta.oldDirectiveArgumentType, print(argumentNode.type)), change); } argumentNode.type = parseType(change.meta.newDirectiveArgumentType); } export function directiveRepeatableAdded(change, nodeByPath, config, _context) { if (!change.path) { config.onError(new ChangePathMissingError(change), change); return; } const directiveNode = nodeByPath.get(change.path); if (!directiveNode) { config.onError(new ChangedAncestorCoordinateNotFoundError(change.path, change.type, true), change); return; } if (directiveNode.kind !== Kind.DIRECTIVE_DEFINITION) { config.onError(new ChangedCoordinateKindMismatchError(Kind.DIRECTIVE_DEFINITION, directiveNode.kind), change); return; } if (directiveNode.repeatable !== false) { config.onError(new ValueMismatchError(Kind.BOOLEAN, String(directiveNode.repeatable), 'false'), change); } directiveNode.repeatable = true; } export function directiveRepeatableRemoved(change, nodeByPath, config, _context) { if (!change.path) { config.onError(new ChangePathMissingError(change), change); return; } const directiveNode = nodeByPath.get(change.path); if (!directiveNode) { config.onError(new DeletedAncestorCoordinateNotFoundError(change.path, change.type, true), change); return; } if (directiveNode.kind !== Kind.DIRECTIVE_DEFINITION) { config.onError(new ChangedCoordinateKindMismatchError(Kind.DIRECTIVE_DEFINITION, directiveNode.kind), change); return; } if (directiveNode.repeatable !== true) { config.onError(new ValueMismatchError(Kind.BOOLEAN, String(directiveNode.repeatable), 'true'), change); } directiveNode.repeatable = false; }