@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
JavaScript
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;
}