UNPKG

@theguild/federation-composition

Version:
90 lines (89 loc) 5.22 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.OverrideRules = OverrideRules; const graphql_1 = require("graphql"); const helpers_js_1 = require("../../../helpers.js"); const printer_js_1 = require("../../../../graphql/printer.js"); const labelNonPercentRegex = /^[a-zA-Z][a-zA-Z0-9_\-:./]*$/; const labelPercentRegex = /^percent\(((\d{1,2}(\.\d{1,8})?)|100(\.[0]{1,8})?)\)$/; function OverrideRules(context) { return { DirectiveDefinition(node) { (0, helpers_js_1.validateDirectiveAgainstOriginal)(node, 'override', context); }, Directive(node) { if (!context.isAvailableFederationDirective('override', node)) { return; } const fieldDef = context.typeNodeInfo.getFieldDef(); const typeDef = context.typeNodeInfo.getTypeDef(); if (!fieldDef || !typeDef) { return; } if ((typeDef.kind === graphql_1.Kind.INTERFACE_TYPE_DEFINITION || typeDef.kind === graphql_1.Kind.INTERFACE_TYPE_EXTENSION) && context.satisfiesVersionRange('>= v2.3')) { context.reportError(new graphql_1.GraphQLError(`@override cannot be used on field "${typeDef.name.value}.${fieldDef.name.value}" on subgraph "${context.getSubgraphName()}": @override is not supported on interface type fields.`, { nodes: node, extensions: { code: 'OVERRIDE_ON_INTERFACE' } })); return; } const labelArg = node.arguments?.find(arg => arg.name.value === 'label'); if (labelArg && context.satisfiesVersionRange('>= v2.7')) { if (!isValidLabel(labelArg.value)) { context.reportError(new graphql_1.GraphQLError(invalidLabelValueError((0, printer_js_1.print)(labelArg.value), `${typeDef.name.value}.${fieldDef.name.value}`, context.getSubgraphName()), { extensions: { code: 'OVERRIDE_LABEL_INVALID' } })); return; } } const labelValue = labelArg?.value; if (labelValue && labelValue.kind !== graphql_1.Kind.STRING) { return; } const fromArg = node.arguments?.find(arg => arg.name.value === 'from'); if (!fromArg || fromArg.value.kind !== graphql_1.Kind.STRING) { return; } if (!typeDef) { throw new Error('Parent type not found but `@override` directive is present on a field.'); } const conflictingDirectives = fieldDef.directives?.filter(directive => context.isAvailableFederationDirective('external', directive)); if (conflictingDirectives?.length) { conflictingDirectives.forEach(directive => { context.reportError(new graphql_1.GraphQLError(`@override cannot be used on field "${typeDef.name.value}.${fieldDef.name.value}" on subgraph "${context.getSubgraphName()}" since "${typeDef.name.value}.${fieldDef.name.value}" on "${context.getSubgraphName()}" is marked with directive "@${directive.name.value}"`, { extensions: { code: 'OVERRIDE_COLLISION_WITH_ANOTHER_DIRECTIVE', }, })); }); } if (fromArg.value.value === context.getSubgraphName()) { context.reportError(new graphql_1.GraphQLError(`Source and destination subgraphs "${fromArg.value.value}" are the same for overridden field "${typeDef.name.value}.${fieldDef.name.value}"`, { nodes: node, extensions: { code: 'OVERRIDE_FROM_SELF_ERROR' } })); } if (typeDef.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION || typeDef.kind === graphql_1.Kind.OBJECT_TYPE_EXTENSION) { context.stateBuilder.objectType.field.setOverride(typeDef.name.value, fieldDef.name.value, fromArg.value.value, labelValue?.value ?? null); } else { context.stateBuilder.interfaceType.field.setOverride(typeDef.name.value, fieldDef.name.value, fromArg.value.value, labelValue?.value ?? null); } }, }; } function invalidLabelValueError(value, coordinate, subgraphName) { return `Invalid @override label ${value} on field "${coordinate}" on subgraph "${subgraphName}": labels must start with a letter and after that may contain alphanumerics, underscores, minuses, colons, periods, or slashes. Alternatively, labels may be of the form "percent(x)" where x is a float between 0-100 inclusive.`; } function isValidLabel(valueNode) { if (valueNode.kind !== graphql_1.Kind.STRING) { return false; } const value = valueNode.value; if (value.startsWith('percent(+') || value.startsWith('percent(-')) { return false; } if (value.startsWith('percent(') && value.endsWith(')')) { if (!labelPercentRegex.test(value)) { return false; } const percentage = parseFloat(value.replace('percent(', '').replace(')', '')); return percentage >= 0 && percentage <= 100; } return labelNonPercentRegex.test(value); }