UNPKG

@theguild/federation-composition

Version:
280 lines (279 loc) 14.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ListSizeRule = ListSizeRule; const graphql_1 = require("graphql"); const helpers_js_1 = require("../../../helpers.js"); function ListSizeRule(context) { return { DirectiveDefinition(node) { (0, helpers_js_1.validateDirectiveAgainstOriginal)(node, "listSize", context); }, Directive(node, _key, _parent, paths, ancestors) { if (!context.isAvailableFederationDirective("listSize", node)) { return; } let assumedSizeArg = null; let assumedSize = null; let slicingArgumentsArg = null; let slicingArguments = null; let sizedFieldsArg = null; let sizedFields = null; let requireOneSlicingArgumentArg = null; let requireOneSlicingArgument = true; for (const arg of node.arguments || []) { if (arg.name.value === "assumedSize") { assumedSizeArg = arg; } else if (arg.name.value === "slicingArguments") { slicingArgumentsArg = arg; } else if (arg.name.value === "sizedFields") { sizedFieldsArg = arg; } else if (arg.name.value === "requireOneSlicingArgument") { requireOneSlicingArgumentArg = arg; } } if (assumedSizeArg) { if (assumedSizeArg.value.kind !== graphql_1.Kind.INT) { context.reportError(new graphql_1.GraphQLError(`The value of @listSize(assumedSize:) must be an integer`, { extensions: { code: "LIST_SIZE_INVALID_ASSUMED_SIZE", }, })); return; } try { assumedSize = parseInt(assumedSizeArg.value.value, 10); } catch (error) { context.reportError(new graphql_1.GraphQLError(`The value of @listSize(assumedSize:) is not a valid integer`, { extensions: { code: "LIST_SIZE_INVALID_ASSUMED_SIZE", }, })); return; } } if (slicingArgumentsArg) { if (slicingArgumentsArg.value.kind !== graphql_1.Kind.LIST) { context.reportError(new graphql_1.GraphQLError(`The value of @listSize(slicingArguments:) must be a list of strings`, { extensions: { code: "LIST_SIZE_INVALID_SLICING_ARGUMENT", }, })); return; } const values = slicingArgumentsArg.value.values; if (values.some((val) => val.kind !== graphql_1.Kind.STRING)) { context.reportError(new graphql_1.GraphQLError(`The value of @listSize(slicingArguments:) must be a list of strings`, { extensions: { code: "LIST_SIZE_INVALID_SLICING_ARGUMENT", }, })); return; } slicingArguments = values.map((val) => { if (val.kind !== graphql_1.Kind.STRING) { throw new Error("Expected @listSize(slicingArguments:) to be a list of strings"); } return val.value; }); } if (sizedFieldsArg) { if (sizedFieldsArg.value.kind !== graphql_1.Kind.LIST) { context.reportError(new graphql_1.GraphQLError(`The value of @listSize(sizedFields:) must be a list of strings`, { extensions: { code: "LIST_SIZE_INVALID_SIZED_FIELD", }, })); return; } const values = sizedFieldsArg.value.values; if (values.some((val) => val.kind !== graphql_1.Kind.STRING)) { context.reportError(new graphql_1.GraphQLError(`The value of @listSize(sizedFields:) must be a list of strings`, { extensions: { code: "LIST_SIZE_INVALID_SIZED_FIELD", }, })); return; } sizedFields = values.map((val) => { if (val.kind !== graphql_1.Kind.STRING) { throw new Error("Expected @listSize(sizedFields:) to be a list of strings"); } return val.value; }); } if (requireOneSlicingArgumentArg) { if (requireOneSlicingArgumentArg.value.kind !== graphql_1.Kind.BOOLEAN) { context.reportError(new graphql_1.GraphQLError(`The value of @listSize(requireOneSlicingArgument:) must be a boolean when defined`, { extensions: { code: "LIST_SIZE_INVALID_REQUIRE_ONE_SLICING_ARGUMENT", }, })); return; } requireOneSlicingArgument = requireOneSlicingArgumentArg.value.value; } const directivesKeyAt = paths.findIndex((path) => path === "directives"); if (directivesKeyAt === -1) { throw new Error('Could not find "directives" key in ancestors'); } const parent = ancestors[directivesKeyAt]; if (!parent) { throw new Error("Could not find the node annotated with @listSize"); } if (Array.isArray(parent)) { throw new Error("Expected parent to be a single node"); } if (!("kind" in parent)) { throw new Error("Expected parent to be a node"); } context.stateBuilder.markCostSpecAsUsed("listSize", node.name.value); switch (parent.kind) { case graphql_1.Kind.FIELD_DEFINITION: { const typeDef = context.typeNodeInfo.getTypeDef(); const fieldDef = parent; if (!typeDef) { throw new Error("Could not find the parent type of the field annotated with @listSize"); } const coordinate = `"${typeDef.name.value}.${parent.name.value}"`; if (typeof assumedSize === "number" && assumedSize < 0) { context.reportError(new graphql_1.GraphQLError(`${coordinate} has negative @listSize(assumedSize:) value`, { extensions: { code: "LIST_SIZE_INVALID_ASSUMED_SIZE", }, })); return; } if (typeof assumedSize === "number" && assumedSize < 0) { context.reportError(new graphql_1.GraphQLError(`${coordinate} has negative @listSize(assumedSize:) value`, { extensions: { code: "LIST_SIZE_INVALID_ASSUMED_SIZE", }, })); return; } if ((sizedFields === null || sizedFields.length === 0) && !isListType(fieldDef.type)) { context.reportError(new graphql_1.GraphQLError(`${coordinate} is not a list. Try to add @listSize(sizedFields:) argument.`, { extensions: { code: "LIST_SIZE_INVALID_SIZED_FIELD", }, })); return; } if (sizedFields?.length) { const knownObjectsAndInterfaces = context.getSubgraphObjectOrInterfaceTypes(); const outputType = (0, helpers_js_1.namedTypeFromTypeNode)(fieldDef.type); const targetType = knownObjectsAndInterfaces.get(outputType.name.value); if (!targetType) { context.reportError(new graphql_1.GraphQLError(`${coordinate} has @listSize(sizedFields:) applied, but the output type is not an object`, { extensions: { code: "LIST_SIZE_INVALID_SIZED_FIELD", }, })); return; } const nonIntFields = []; const nonExistingFields = []; for (const sizedField of sizedFields) { const targetField = targetType.fields?.find((f) => f.name.value === sizedField); if (!targetField) { nonExistingFields.push(sizedField); } else if (!isIntTypeOrNullableIntType(targetField.type)) { nonIntFields.push(sizedField); } } if (nonIntFields.length || nonExistingFields.length) { nonIntFields.forEach((fieldName) => { context.reportError(new graphql_1.GraphQLError(`${coordinate} references "${fieldName}" field in @listSize(sizedFields:) argument that is not an integer.`, { extensions: { code: "LIST_SIZE_INVALID_SIZED_FIELD", }, })); }); nonExistingFields.forEach((fieldName) => { context.reportError(new graphql_1.GraphQLError(`${coordinate} references "${fieldName}" field in @listSize(sizedFields:) argument that does not exist.`, { extensions: { code: "LIST_SIZE_INVALID_SIZED_FIELD", }, })); }); return; } } if (slicingArguments?.length) { const nonIntArgument = []; const nonExistingArgument = []; for (const slicingArgument of slicingArguments) { const targetArgument = fieldDef.arguments?.find((a) => a.name.value === slicingArgument); if (!targetArgument) { nonExistingArgument.push(slicingArgument); } else if (!isIntTypeOrNullableIntType(targetArgument.type)) { nonIntArgument.push(slicingArgument); } } if (nonIntArgument.length || nonExistingArgument.length) { nonIntArgument.forEach((argumentName) => { context.reportError(new graphql_1.GraphQLError(`${coordinate} references "${argumentName}" argument in @listSize(slicingArguments:) that is not an integer.`, { extensions: { code: "LIST_SIZE_INVALID_SLICING_ARGUMENT", }, })); }); nonExistingArgument.forEach((argumentName) => { context.reportError(new graphql_1.GraphQLError(`${coordinate} references "${argumentName}" argument in @listSize(slicingArguments:) that does not exist.`, { extensions: { code: "LIST_SIZE_INVALID_SLICING_ARGUMENT", }, })); }); return; } } if (typeDef.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION || typeDef.kind === graphql_1.Kind.OBJECT_TYPE_EXTENSION) { context.stateBuilder.objectType.field.setListSize(typeDef.name.value, parent.name.value, { assumedSize, slicingArguments, sizedFields, requireOneSlicingArgument, }); } if (typeDef.kind === graphql_1.Kind.INTERFACE_TYPE_DEFINITION || typeDef.kind === graphql_1.Kind.INTERFACE_TYPE_EXTENSION) { context.stateBuilder.interfaceType.field.setListSize(typeDef.name.value, parent.name.value, { assumedSize, slicingArguments, sizedFields, requireOneSlicingArgument, }); } break; } } }, }; } function isListType(typeNode) { if (typeNode.kind === graphql_1.Kind.LIST_TYPE) { return true; } if (typeNode.kind === graphql_1.Kind.NAMED_TYPE) { return false; } return isListType(typeNode.type); } function isIntTypeOrNullableIntType(typeNode) { if (typeNode.kind === graphql_1.Kind.LIST_TYPE) { return false; } if (typeNode.kind === graphql_1.Kind.NAMED_TYPE) { return typeNode.name.value === "Int"; } return isIntTypeOrNullableIntType(typeNode.type); }