@theguild/federation-composition
Version:
Open Source Composition library for Apollo Federation
130 lines (129 loc) • 6.83 kB
JavaScript
import { GraphQLError, Kind } from "graphql";
import { print } from "../../../../graphql/printer.js";
import { getFieldsArgument, parseFields, validateDirectiveAgainstOriginal, visitFields, } from "../../../helpers.js";
export function RequiresRules(context) {
return {
DirectiveDefinition(node) {
validateDirectiveAgainstOriginal(node, "requires", context);
},
Directive(directiveNode) {
if (!context.isAvailableFederationDirective("requires", directiveNode)) {
return;
}
const annotatedType = context.typeNodeInfo.getTypeDef();
const annotatedField = context.typeNodeInfo.getFieldDef();
if (!annotatedType || !annotatedField) {
return;
}
const fieldCoordinate = `${annotatedType.name.value}.${annotatedField.name.value}`;
const usedOnInterface = annotatedType.kind === Kind.INTERFACE_TYPE_DEFINITION ||
annotatedType?.kind === Kind.INTERFACE_TYPE_EXTENSION;
if (annotatedField && usedOnInterface) {
context.reportError(new GraphQLError(`Cannot use on field "${fieldCoordinate}" of parent type "${annotatedType.name.value}": is not yet supported within interfaces`, {
nodes: directiveNode,
extensions: { code: "REQUIRES_UNSUPPORTED_ON_INTERFACE" },
}));
return;
}
const fieldsArg = getFieldsArgument(directiveNode);
if (!fieldsArg) {
return;
}
const printedFieldsValue = print(fieldsArg.value);
if (fieldsArg.value.kind !== Kind.STRING &&
fieldsArg.value.kind !== Kind.ENUM) {
context.reportError(new GraphQLError(`On field "${fieldCoordinate}", for : Invalid value for argument "fields": must be a string.`, {
nodes: directiveNode,
extensions: {
code: "REQUIRES_INVALID_FIELDS_TYPE",
},
}));
return;
}
let selectionSet;
try {
selectionSet = parseFields(fieldsArg.value.value);
}
catch (error) {
if (error instanceof GraphQLError) {
context.reportError(new GraphQLError(`On field "${fieldCoordinate}", for : ${error.message}`, {
nodes: directiveNode,
extensions: {
code: "REQUIRES_INVALID_FIELDS",
},
}));
return;
}
throw error;
}
if (!selectionSet) {
return;
}
let isValid = true;
if (annotatedType.kind !== Kind.INTERFACE_TYPE_DEFINITION &&
annotatedType.kind !== Kind.INTERFACE_TYPE_EXTENSION &&
annotatedType.kind !== Kind.OBJECT_TYPE_DEFINITION &&
annotatedType.kind !== Kind.OBJECT_TYPE_EXTENSION) {
return;
}
const mergedTypeDef = context
.getSubgraphObjectOrInterfaceTypes()
.get(annotatedType.name.value);
if (!mergedTypeDef) {
throw new Error(`Could not find type "${annotatedType.name.value}"`);
}
visitFields({
context,
selectionSet,
typeDefinition: mergedTypeDef,
interceptField(info) {
if (info.typeDefinition.kind === Kind.OBJECT_TYPE_DEFINITION ||
info.typeDefinition.kind === Kind.OBJECT_TYPE_EXTENSION) {
if (info.fieldName !== "__typename") {
context.stateBuilder.objectType.field.markedAsRequired(info.typeDefinition.name.value, info.fieldName);
}
}
},
interceptUnknownField(info) {
isValid = false;
context.reportError(new GraphQLError(`On field "${fieldCoordinate}", for : Cannot query field "${info.fieldName}" on type "${info.typeDefinition.name.value}" (if the field is defined in another subgraph, you need to add it to this subgraph with ).`, {
nodes: directiveNode,
extensions: { code: "REQUIRES_INVALID_FIELDS" },
}));
},
interceptDirective(info) {
isValid = false;
if (info.isKnown) {
context.reportError(new GraphQLError(`On field "${fieldCoordinate}", for : cannot have directive applications in the argument but found @${info.directiveName}.`, {
nodes: directiveNode,
extensions: { code: "REQUIRES_DIRECTIVE_IN_FIELDS_ARG" },
}));
}
else {
context.reportError(new GraphQLError(`On field "${fieldCoordinate}", for : Unknown directive "@${info.directiveName}" in selection`, {
nodes: directiveNode,
extensions: { code: "REQUIRES_INVALID_FIELDS" },
}));
}
},
interceptNonExternalField(info) {
if (context.satisfiesVersionRange("> v1.0")) {
isValid = false;
context.reportError(new GraphQLError(`On field "${fieldCoordinate}", for : field "${info.typeDefinition.name.value}.${info.fieldName}" should not be part of a since it is already provided by this subgraph (it is not marked )`, {
extensions: {
code: "REQUIRES_FIELDS_MISSING_EXTERNAL",
},
}));
}
},
});
if (isValid) {
if (usedOnInterface) {
context.stateBuilder.interfaceType.field.setRequires(annotatedType.name.value, annotatedField.name.value, fieldsArg.value.value);
return;
}
context.stateBuilder.objectType.field.setRequires(annotatedType.name.value, annotatedField.name.value, fieldsArg.value.value);
}
},
};
}