UNPKG

7.51 kBJavaScriptView Raw
1import { getNullableType, isAbstractType, isInterfaceType, isListType, isNamedType, isObjectType, isUnionType, parseValue, } from 'graphql';
2import { getDirective, getImplementingTypes, isSome, MapperKind, mapSchema, parseSelectionSet, } from '@graphql-tools/utils';
3import { defaultStitchingDirectiveOptions } from './defaultStitchingDirectiveOptions.js';
4import { parseMergeArgsExpr } from './parseMergeArgsExpr.js';
5const dottedNameRegEx = /^[_A-Za-z][_0-9A-Za-z]*(.[_A-Za-z][_0-9A-Za-z]*)*$/;
6export function stitchingDirectivesValidator(options = {}) {
7 const { keyDirectiveName, computedDirectiveName, mergeDirectiveName, pathToDirectivesInExtensions } = {
8 ...defaultStitchingDirectiveOptions,
9 ...options,
10 };
11 return (schema) => {
12 var _a;
13 const queryTypeName = (_a = schema.getQueryType()) === null || _a === void 0 ? void 0 : _a.name;
14 mapSchema(schema, {
15 [MapperKind.OBJECT_TYPE]: type => {
16 var _a;
17 const keyDirective = (_a = getDirective(schema, type, keyDirectiveName, pathToDirectivesInExtensions)) === null || _a === void 0 ? void 0 : _a[0];
18 if (keyDirective != null) {
19 parseSelectionSet(keyDirective['selectionSet']);
20 }
21 return undefined;
22 },
23 [MapperKind.OBJECT_FIELD]: (fieldConfig, _fieldName, typeName) => {
24 var _a, _b, _c;
25 const computedDirective = (_a = getDirective(schema, fieldConfig, computedDirectiveName, pathToDirectivesInExtensions)) === null || _a === void 0 ? void 0 : _a[0];
26 if (computedDirective != null) {
27 parseSelectionSet(computedDirective['selectionSet']);
28 }
29 const mergeDirective = (_b = getDirective(schema, fieldConfig, mergeDirectiveName, pathToDirectivesInExtensions)) === null || _b === void 0 ? void 0 : _b[0];
30 if (mergeDirective != null) {
31 if (typeName !== queryTypeName) {
32 throw new Error('@merge directive may be used only for root fields of the root Query type.');
33 }
34 let returnType = getNullableType(fieldConfig.type);
35 if (isListType(returnType)) {
36 returnType = getNullableType(returnType.ofType);
37 }
38 if (!isNamedType(returnType)) {
39 throw new Error('@merge directive must be used on a field that returns an object or a list of objects.');
40 }
41 const mergeArgsExpr = mergeDirective['argsExpr'];
42 if (mergeArgsExpr != null) {
43 parseMergeArgsExpr(mergeArgsExpr);
44 }
45 const args = Object.keys((_c = fieldConfig.args) !== null && _c !== void 0 ? _c : {});
46 const keyArg = mergeDirective['keyArg'];
47 if (keyArg == null) {
48 if (!mergeArgsExpr && args.length !== 1) {
49 throw new Error('Cannot use @merge directive without `keyArg` argument if resolver takes more than one argument.');
50 }
51 }
52 else if (!keyArg.match(dottedNameRegEx)) {
53 throw new Error('`keyArg` argument for @merge directive must be a set of valid GraphQL SDL names separated by periods.');
54 // TODO: ideally we should check that the arg exists for the resolver
55 }
56 const keyField = mergeDirective['keyField'];
57 if (keyField != null && !keyField.match(dottedNameRegEx)) {
58 throw new Error('`keyField` argument for @merge directive must be a set of valid GraphQL SDL names separated by periods.');
59 // TODO: ideally we should check that it is part of the key
60 }
61 const key = mergeDirective['key'];
62 if (key != null) {
63 if (keyField != null) {
64 throw new Error('Cannot use @merge directive with both `keyField` and `key` arguments.');
65 }
66 for (const keyDef of key) {
67 let [aliasOrKeyPath, keyPath] = keyDef.split(':');
68 let aliasPath;
69 if (keyPath == null) {
70 keyPath = aliasPath = aliasOrKeyPath;
71 }
72 else {
73 aliasPath = aliasOrKeyPath;
74 }
75 if (keyPath != null && !keyPath.match(dottedNameRegEx)) {
76 throw new Error('Each partial key within the `key` argument for @merge directive must be a set of valid GraphQL SDL names separated by periods.');
77 // TODO: ideally we should check that it is part of the key
78 }
79 if (aliasPath != null && !aliasOrKeyPath.match(dottedNameRegEx)) {
80 throw new Error('Each alias within the `key` argument for @merge directive must be a set of valid GraphQL SDL names separated by periods.');
81 // TODO: ideally we should check that the arg exists within the resolver
82 }
83 }
84 }
85 const additionalArgs = mergeDirective['additionalArgs'];
86 if (additionalArgs != null) {
87 parseValue(`{ ${additionalArgs} }`, { noLocation: true });
88 }
89 if (mergeArgsExpr != null && (keyArg != null || additionalArgs != null)) {
90 throw new Error('Cannot use @merge directive with both `argsExpr` argument and any additional argument.');
91 }
92 if (!isInterfaceType(returnType) && !isUnionType(returnType) && !isObjectType(returnType)) {
93 throw new Error('@merge directive may be used only with resolver that return an object, interface, or union.');
94 }
95 const typeNames = mergeDirective['types'];
96 if (typeNames != null) {
97 if (!isAbstractType(returnType)) {
98 throw new Error('Types argument can only be used with a field that returns an abstract type.');
99 }
100 const implementingTypes = isInterfaceType(returnType)
101 ? getImplementingTypes(returnType.name, schema).map(typeName => schema.getType(typeName))
102 : returnType.getTypes();
103 const implementingTypeNames = implementingTypes.map(type => type === null || type === void 0 ? void 0 : type.name).filter(isSome);
104 for (const typeName of typeNames) {
105 if (!implementingTypeNames.includes(typeName)) {
106 throw new Error(`Types argument can only include only type names that implement the field return type's abstract type.`);
107 }
108 }
109 }
110 }
111 return undefined;
112 },
113 });
114 return schema;
115 };
116}