UNPKG

@theguild/federation-composition

Version:

Open Source Composition library for Apollo Federation

263 lines (262 loc) 11.2 kB
import { Kind, specifiedScalarTypes, } from 'graphql'; import { createSpecSchema } from '../../specifications/federation.js'; import { stripTypeModifiers } from '../../utils/state.js'; import { TypeKind } from '../state.js'; export function createSimpleValidationContext(typeDefs, typeNodeInfo) { let reportedErrors = []; const directiveDefinitionMap = new Map(); const typeDefinitionMap = new Map(); for (const definition of typeDefs.definitions) { if (definition.kind === Kind.DIRECTIVE_DEFINITION) { directiveDefinitionMap.set(definition.name.value, definition); } else if ('name' in definition && definition.name && definition.name.kind === Kind.NAME) { typeDefinitionMap.set(definition.name.value, { name: definition.name, kind: definition.kind, }); } } return { getDocument() { return typeDefs; }, getKnownDirectiveDefinition(name) { return directiveDefinitionMap.get(name); }, getKnownTypeDefinition(name) { return typeDefinitionMap.get(name); }, getSchemaCoordinate(ancestors) { let coordinate = ''; for (let i = 0; i < ancestors.length; i++) { const ancestor = ancestors[i]; if ('kind' in ancestor && ancestor.kind !== Kind.DOCUMENT) { const name = ancestor.kind === Kind.SCHEMA_DEFINITION || ancestor.kind === Kind.SCHEMA_EXTENSION ? 'schema' : 'name' in ancestor && ancestor.name ? ancestor.name.value : ''; if (coordinate.length > 0) { coordinate = coordinate + '.' + name; } else { coordinate = name; } } } return coordinate; }, reportError(error) { reportedErrors.push(error); }, collectReportedErrors() { const errors = reportedErrors; reportedErrors = []; return errors; }, }; } export function createSubgraphValidationContext(subgraph, federation, typeNodeInfo, stateBuilder) { const { version, imports } = federation; const availableSpec = createSpecSchema(version, imports); const knownSpec = createSpecSchema(version); const knownSubgraphEntities = new Map(subgraph.typeDefs.definitions.filter(def => def.kind === Kind.OBJECT_TYPE_DEFINITION || def.kind === Kind.OBJECT_TYPE_EXTENSION || def.kind === Kind.INTERFACE_TYPE_DEFINITION || def.kind === Kind.INTERFACE_TYPE_EXTENSION).map(def => [def.name.value, def])); const knownSubgraphDirectiveDefinitions = new Map(subgraph.typeDefs.definitions.filter(def => def.kind === Kind.DIRECTIVE_DEFINITION).map(def => [def.name.value, def])); const leafTypeNames = new Set(specifiedScalarTypes.map(type => type.name)); for (const def of subgraph.typeDefs.definitions) { if (def.kind === Kind.SCALAR_TYPE_DEFINITION || def.kind === Kind.SCALAR_TYPE_EXTENSION || def.kind === Kind.ENUM_TYPE_DEFINITION || def.kind === Kind.ENUM_TYPE_EXTENSION) { leafTypeNames.add(def.name.value); } } let reportedErrors = []; const markedAsExternal = new Set(); const markedAsUsed = new Set(); const markedAsKeyField = new Set(); const overwrittenFederationDefinitionNames = new Set(); const directiveAlternativeNamesMap = new Map(); for (const specDirective of availableSpec.directives) { const isFederationPrefixed = specDirective.name.value.startsWith('federation__'); if (isFederationPrefixed) { const normalizedName = specDirective.name.value.replace('federation__', ''); const setOfNames = directiveAlternativeNamesMap.get(normalizedName); if (!setOfNames) { directiveAlternativeNamesMap.set(normalizedName, new Set([specDirective.name.value])); } } else { const { alias } = imports.find(i => i.name.replace(/^@/, '') === specDirective.name.value) ?? { alias: undefined, }; let setOfNames = directiveAlternativeNamesMap.get(specDirective.name.value); if (!setOfNames) { directiveAlternativeNamesMap.set(specDirective.name.value, new Set()); setOfNames = directiveAlternativeNamesMap.get(specDirective.name.value); } setOfNames.add(alias ? alias.replace(/^@/, '') : specDirective.name.value); } } const typeAlternativeNamesMap = new Map(); for (const specType of availableSpec.types) { const isFederationPrefixed = specType.name.value.startsWith('federation__'); if (isFederationPrefixed) { const normalizedName = specType.name.value.replace('federation__', ''); const setOfNames = typeAlternativeNamesMap.get(normalizedName); if (!setOfNames) { typeAlternativeNamesMap.set(normalizedName, new Set([specType.name.value])); } } else { const { alias } = imports.find(i => i.name === specType.name.value) ?? { alias: undefined, }; let setOfNames = typeAlternativeNamesMap.get(specType.name.value); if (!setOfNames) { typeAlternativeNamesMap.set(specType.name.value, new Set()); setOfNames = typeAlternativeNamesMap.get(specType.name.value); } setOfNames.add(alias ? alias : specType.name.value); } } const importedTypesSet = new Set(availableSpec.types.map(t => t.name.value)); if (importedTypesSet.size) { subgraph.typeDefs.definitions.forEach(def => { if ('name' in def && def.name && importedTypesSet.has(def.name.value)) { overwrittenFederationDefinitionNames.add(def.name.value); } }); } return { stateBuilder, isAvailableFederationType(name) { const alternativeNames = typeAlternativeNamesMap.get(name); if (alternativeNames) { return alternativeNames.has(name); } return false; }, isAvailableFederationDirective(specDirectiveName, directiveNode) { const alternativeNames = directiveAlternativeNamesMap.get(specDirectiveName); if (alternativeNames) { return alternativeNames.has(typeof directiveNode.name === 'string' ? directiveNode.name : directiveNode.name.value); } return false; }, satisfiesVersionRange(range) { const [sign, ver] = range.split(' '); const versionInRange = parseFloat(ver.replace('v', '')); const detectedVersion = parseFloat(version.replace('v', '')); if (sign === '<') { return detectedVersion < versionInRange; } if (sign === '>') { return detectedVersion > versionInRange; } return detectedVersion >= versionInRange; }, getKnownFederationDirectives() { return knownSpec.directives; }, getAvailableFederationDirectives() { return availableSpec.directives; }, isLeafType(typeName) { return leafTypeNames.has(typeName); }, getSubgraphObjectOrInterfaceTypes() { return knownSubgraphEntities; }, getSubgraphDirectiveDefinitions() { return knownSubgraphDirectiveDefinitions; }, getAvailableFederationTypeAndDirectiveDefinitions() { return [].concat(availableSpec.directives.map(d => { const alias = imports.find(i => i.name.replace(/^@/, '') === d.name.value)?.alias; if (alias) { d.name.value = alias.replace(/^@/, ''); } return d; }), availableSpec.types.map(t => { const alias = imports.find(i => i.name === t.name.value)?.alias; if (alias) { t.name.value = alias; } return t; })); }, typeNodeInfo, getDocument() { return subgraph.typeDefs; }, getSubgraphName() { return subgraph.name; }, getSubgraphId() { return subgraph.id; }, markAsExternal(coordinate) { markedAsExternal.add(coordinate); }, markAsUsed(reason, kind, typeName, fieldName) { if (!fieldName.startsWith('__') && !typeName.startsWith('__') && reason === 'fields') { switch (kind) { case Kind.OBJECT_TYPE_DEFINITION: case Kind.OBJECT_TYPE_EXTENSION: { stateBuilder.objectType.field.setUsed(typeName, fieldName); break; } case Kind.INTERFACE_TYPE_DEFINITION: case Kind.INTERFACE_TYPE_EXTENSION: { stateBuilder.interfaceType.field.setUsed(typeName, fieldName); break; } } } markedAsUsed.add(`${typeName}.${fieldName}`); }, markAsKeyField(coordinate) { markedAsKeyField.add(coordinate); }, markAsFederationDefinitionReplacement(name) { overwrittenFederationDefinitionNames.add(name); }, collectFederationDefinitionReplacements() { return overwrittenFederationDefinitionNames; }, collectUnusedExternal() { if (version === 'v1.0') { return Array.from(markedAsExternal).filter(c => !markedAsUsed.has(c) && markedAsKeyField.has(c)); } const unused = Array.from(markedAsExternal).filter(c => !markedAsUsed.has(c)); return unused.filter(coordinate => { const [typeName, fieldName] = coordinate.split('.'); const typeDef = stateBuilder.state.types.get(typeName); if (typeDef && typeDef.kind === TypeKind.OBJECT) { const fieldDef = typeDef.fields.get(fieldName); if (fieldDef) { const outputTypeName = stripTypeModifiers(fieldDef.type); const outputType = stateBuilder.state.types.get(outputTypeName); if (outputType?.kind === TypeKind.OBJECT && outputType.shareable) { return false; } } } return true; }); }, reportError(error) { reportedErrors.push(error); }, collectReportedErrors() { const errors = reportedErrors; reportedErrors = []; return errors; }, }; }