1 | import { getNullableType, isAbstractType, isInterfaceType, isListType, isNamedType, isObjectType, isUnionType, parseValue, } from 'graphql';
|
2 | import { getDirective, getImplementingTypes, isSome, MapperKind, mapSchema, parseSelectionSet, } from '@graphql-tools/utils';
|
3 | import { defaultStitchingDirectiveOptions } from './defaultStitchingDirectiveOptions.js';
|
4 | import { parseMergeArgsExpr } from './parseMergeArgsExpr.js';
|
5 | const dottedNameRegEx = /^[_A-Za-z][_0-9A-Za-z]*(.[_A-Za-z][_0-9A-Za-z]*)*$/;
|
6 | export 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 |
|
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 |
|
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 |
|
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 |
|
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 | }
|