@graphql-mesh/compose-cli
Version:
103 lines (102 loc) • 5.6 kB
JavaScript
import { buildSchema } from 'graphql';
import { suggestionList } from '@graphql-mesh/fusion-composition';
import { createGraphQLError, getDirectiveExtensions } from '@graphql-tools/utils';
export class FatalCompositionError extends Error {
constructor(message) {
super(`Fatal composition error: ${message}`);
this.name = 'FatalCompositionError';
Error.captureStackTrace(this, this.constructor);
}
}
export function validateSupergraphSdl(supergraphSdl, subgraphs) {
const schema = buildSchema(supergraphSdl, {
assumeValid: true,
assumeValidSDL: true,
noLocation: true,
});
const typeMap = schema.getTypeMap();
const errors = [];
for (const typeName in typeMap) {
const type = typeMap[typeName];
if (type) {
const directives = getDirectiveExtensions(type, schema);
if (directives?.merge) {
for (const mergeDirective of directives.merge) {
const subgraphName = mergeDirective.subgraph;
const subgraph = subgraphs.find(s => s.name === subgraphName);
if (!subgraphName) {
errors.push(createGraphQLError(`Expected @merge directive on type ${typeName} to have a subgraph argument`, {
nodes: type.astNode,
}));
continue;
}
else if (!subgraph) {
errors.push(createGraphQLError(`@merge directive on type ${typeName} references unknown subgraph ${subgraphName}`, {
nodes: type.astNode,
}));
}
continue;
}
}
if (directives?.resolveTo) {
for (const resolveToDirective of directives.resolveTo) {
const subgraphName = resolveToDirective.sourceName;
const subgraph = subgraphs.find(s => s.name === subgraphName);
if (!subgraphName) {
errors.push(createGraphQLError(`Expected @resolveTo directive on type ${typeName} to have a sourceName argument`, {
nodes: type.astNode,
}));
continue;
}
else if (!subgraph) {
errors.push(createGraphQLError(`@resolveTo directive on type ${typeName} references unknown subgraph ${subgraphName}`, {
nodes: type.astNode,
}));
continue;
}
if (resolveToDirective.sourceFieldName && resolveToDirective.sourceTypeName) {
const typeDef = subgraph.typeDefs.definitions.find(def => 'name' in def && def.name.value === resolveToDirective.sourceTypeName);
if (!typeDef) {
errors.push(createGraphQLError(`@resolveTo directive on type ${typeName} references unknown type ${resolveToDirective.sourceTypeName} in subgraph ${subgraphName}`, {
nodes: type.astNode,
}));
continue;
}
const fields = ('fields' in typeDef && typeDef.fields) || [];
const fieldName = resolveToDirective.sourceFieldName;
const fieldDef = fields.find(f => f.name.value === fieldName);
if (!fieldDef) {
const suggestions = suggestionList(fieldName, fields.map(f => f.name.value));
const suggestionStr = suggestions.length
? ` Did you mean "${suggestions.join(' or ')}"?`
: '';
errors.push(createGraphQLError(`@resolveTo directive on type ${typeName} references unknown field ${fieldName} in subgraph ${subgraphName} ${suggestionStr}`, {
nodes: type.astNode,
}));
continue;
}
// Args validation
if (resolveToDirective.sourceArgs) {
const argOfFields = 'arguments' in fieldDef && fieldDef.arguments
? fieldDef.arguments.map(arg => arg.name.value)
: [];
for (const argName in resolveToDirective.sourceArgs) {
const argNameInField = argOfFields.find(arg => arg === argName);
if (!argNameInField) {
const suggestions = suggestionList(argName, argOfFields);
const suggestionStr = suggestions.length
? ` Did you mean "${suggestions.join(' or ')}"?`
: '';
errors.push(createGraphQLError(`@resolveTo directive on type ${typeName} references unknown argument ${argName} in field ${fieldName} of subgraph ${subgraphName} ${suggestionStr}`, {
nodes: type.astNode,
}));
}
}
}
}
}
}
}
}
return errors;
}