UNPKG

@graphql-inspector/ci

Version:

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

132 lines (131 loc) 6.57 kB
import { Kind, parseConstValue, parseType, print, } from 'graphql'; import { AddedAttributeCoordinateNotFoundError, AddedCoordinateAlreadyExistsError, ChangedAncestorCoordinateNotFoundError, ChangedCoordinateKindMismatchError, ChangePathMissingError, DeletedAncestorCoordinateNotFoundError, DeletedCoordinateNotFound, ValueMismatchError, } from '../errors.js'; import { nameNode, stringNode } from '../node-templates.js'; import { assertValueMatch, getChangedNodeOfKind, getDeletedNodeOfKind, parentPath, } from '../utils.js'; export function inputFieldAdded(change, nodeByPath, config, _context) { if (!change.path) { config.onError(new ChangePathMissingError(change), change); return; } const existingNode = nodeByPath.get(change.path); if (existingNode) { if (existingNode.kind === Kind.INPUT_VALUE_DEFINITION) { config.onError(new AddedCoordinateAlreadyExistsError(change.path, change.type), change); } else { config.onError(new ChangedCoordinateKindMismatchError(Kind.INPUT_VALUE_DEFINITION, existingNode.kind), change); } return; } const typeNode = nodeByPath.get(parentPath(change.path)); if (!typeNode) { config.onError(new AddedAttributeCoordinateNotFoundError(change.path, change.type, change.meta.addedInputFieldName), change); return; } if (typeNode.kind !== Kind.INPUT_OBJECT_TYPE_DEFINITION) { config.onError(new ChangedCoordinateKindMismatchError(Kind.INPUT_OBJECT_TYPE_DEFINITION, typeNode.kind), change); return; } const node = { kind: Kind.INPUT_VALUE_DEFINITION, name: nameNode(change.meta.addedInputFieldName), type: parseType(change.meta.addedInputFieldType), // description: change.meta.addedInputFieldDescription // ? stringNode(change.meta.addedInputFieldDescription) // : undefined, }; typeNode.fields = [...(typeNode.fields ?? []), node]; // add new field to the node set nodeByPath.set(change.path, node); } export function inputFieldRemoved(change, nodeByPath, config, _context) { if (!change.path) { config.onError(new ChangePathMissingError(change), change); return; } const existingNode = nodeByPath.get(change.path); if (!existingNode) { config.onError(new DeletedCoordinateNotFound(change.path, change.type), change); return; } const typeNode = nodeByPath.get(parentPath(change.path)); if (!typeNode) { config.onError(new DeletedAncestorCoordinateNotFoundError(change.path, change.type, change.meta.removedFieldName), change); return; } if (typeNode.kind !== Kind.INPUT_OBJECT_TYPE_DEFINITION) { config.onError(new ChangedCoordinateKindMismatchError(Kind.INPUT_OBJECT_TYPE_DEFINITION, typeNode.kind), change); return; } typeNode.fields = typeNode.fields?.filter(f => f.name.value !== change.meta.removedFieldName); // add new field to the node set nodeByPath.delete(change.path); } export function inputFieldDescriptionAdded(change, nodeByPath, config, _context) { if (!change.path) { config.onError(new ChangePathMissingError(change), change); return; } const existingNode = nodeByPath.get(change.path); if (!existingNode) { config.onError(new DeletedAncestorCoordinateNotFoundError(change.path, change.type, change.meta.addedInputFieldDescription), change); return; } if (existingNode.kind !== Kind.INPUT_VALUE_DEFINITION) { config.onError(new ChangedCoordinateKindMismatchError(Kind.INPUT_VALUE_DEFINITION, existingNode.kind), change); return; } existingNode.description = stringNode(change.meta.addedInputFieldDescription); } export function inputFieldTypeChanged(change, nodeByPath, config, _context) { const inputFieldNode = getChangedNodeOfKind(change, nodeByPath, Kind.INPUT_VALUE_DEFINITION, config); if (inputFieldNode) { assertValueMatch(change, Kind.INPUT_VALUE_DEFINITION, change.meta.oldInputFieldType, print(inputFieldNode.type), config); inputFieldNode.type = parseType(change.meta.newInputFieldType); } } export function inputFieldDefaultValueChanged(change, nodeByPath, config, _context) { if (!change.path) { config.onError(new ChangePathMissingError(change), change); return; } const existingNode = nodeByPath.get(change.path); if (!existingNode) { config.onError(new ChangedAncestorCoordinateNotFoundError(change.path, change.type, change.meta.newDefaultValue ?? null), change); return; } if (existingNode.kind !== Kind.INPUT_VALUE_DEFINITION) { config.onError(new ChangedCoordinateKindMismatchError(Kind.INPUT_VALUE_DEFINITION, existingNode.kind), change); return; } const oldValueMatches = (existingNode.defaultValue && print(existingNode.defaultValue)) === change.meta.oldDefaultValue; if (!oldValueMatches) { config.onError(new ValueMismatchError(existingNode.defaultValue?.kind ?? Kind.INPUT_VALUE_DEFINITION, change.meta.oldDefaultValue, existingNode.defaultValue && print(existingNode.defaultValue)), change); } existingNode.defaultValue = change.meta.newDefaultValue ? parseConstValue(change.meta.newDefaultValue) : undefined; } export function inputFieldDescriptionChanged(change, nodeByPath, config, _context) { const existingNode = getChangedNodeOfKind(change, nodeByPath, Kind.INPUT_VALUE_DEFINITION, config); if (!existingNode) { return; } if (existingNode.description?.value !== change.meta.oldInputFieldDescription) { config.onError(new ValueMismatchError(Kind.STRING, change.meta.oldInputFieldDescription, existingNode.description?.value), change); } existingNode.description = stringNode(change.meta.newInputFieldDescription); } export function inputFieldDescriptionRemoved(change, nodeByPath, config, _context) { const existingNode = getDeletedNodeOfKind(change, nodeByPath, Kind.INPUT_VALUE_DEFINITION, config); if (!existingNode) { return; } if (existingNode.description === undefined) { console.warn(`Cannot remove a description at ${change.path} because no description is set.`); } else if (existingNode.description.value !== change.meta.removedDescription) { console.warn(`Description at ${change.path} does not match expected description, but proceeding with description removal anyways.`); } existingNode.description = undefined; }