@apollo/gateway 2.3.0-beta.1 → 2.3.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,439 @@
1
+ import {
2
+ assert,
3
+ CompositeType,
4
+ Directive,
5
+ ERRORS,
6
+ FieldSelection,
7
+ isCompositeType,
8
+ isListType,
9
+ isNonNullType,
10
+ isObjectType,
11
+ isSubtype,
12
+ isValidLeafValue,
13
+ Operation,
14
+ OperationElement,
15
+ OutputType,
16
+ Schema,
17
+ SelectionSet,
18
+ Type,
19
+ typenameFieldName,
20
+ Variable
21
+ } from "@apollo/federation-internals";
22
+ import { ResponsePath } from "@apollo/query-planner";
23
+ import { GraphQLError } from "graphql";
24
+
25
+ /**
26
+ * Performs post-query plan execution processing of internally fetched data to produce the final query response.
27
+ *
28
+ * The reason for this post-processing are the following ones:
29
+ * 1. executing the query plan will usually query more fields that are strictly requested. That is because key, required
30
+ * and __typename fields must often be requested to subgraphs even when they are not part of the query. So this method
31
+ * will filter out anything that has been fetched but isn't part of the user query.
32
+ * 2. query plan execution does not guarantee that in the data fetched, the fields will respect the ordering that the
33
+ * GraphQL spec defines (for the query). The reason for that being that as we fetch data from multiple subgraphs, we
34
+ * have to "destroy" the ordering to an extend, and that order has to be re-establish as a post-fetches step, and it's
35
+ * a lot easier to do this as part of this final post-processing step.
36
+ * 3. query plan execution ignores schema introspection sub-parts as those are not something to be asked to subgraphs,
37
+ * and so any requested introspection needs to be added separatly from query plan execution. Note that this method
38
+ * does add introspection results to the final response returned, but it delegates the computation of introspect to
39
+ * its `introspectionHandling` callback.
40
+ * 4. query plans do not request the __typename of root types to subgraphs, even if those are queried. The reason is
41
+ * that subgraph are allowed to use non-standard root type names, but the supergraph always use the standard name,
42
+ * so asking the subgraph for __typename on those type may actually return an incorrect result. This method
43
+ * compelets those __typename of root types if necessary.
44
+ *
45
+ * @param operation - the query that was planned and for which we're post-processing the result.
46
+ * @param variables - the value for the variables in `operation`.
47
+ * @param input - the data fetched during query plan execution and that should be filtered/re-ordered.
48
+ * @param instrospectionHandling - a function that, given the selection of a schema introspection field (`__schema`)
49
+ * returns the introspection results for that field. This method does not handle introspection by itself, and
50
+ * so if some introspection is requested (`__schema` or `__type` only , `__typename` *is* handled by this method),
51
+ * this function is called to compute the proper result.
52
+ */
53
+ export function computeResponse({
54
+ operation,
55
+ variables,
56
+ input,
57
+ introspectionHandling,
58
+ }: {
59
+ operation: Operation,
60
+ variables?: Record<string, any>,
61
+ input: Record<string, any> | null | undefined,
62
+ introspectionHandling: (introspectionSelection: FieldSelection) => any,
63
+ }): {
64
+ data: Record<string, any> | null | undefined,
65
+ errors: GraphQLError[],
66
+ } {
67
+ if (!input) {
68
+ return { data: input, errors: [] };
69
+ }
70
+
71
+ const parameters = {
72
+ schema: operation.schema,
73
+ variables: variables ?? {},
74
+ errors: [],
75
+ introspectionHandling,
76
+ };
77
+
78
+ const data = Object.create(null);
79
+
80
+ const res = applySelectionSet({
81
+ input,
82
+ selectionSet: operation.selectionSet,
83
+ output: data,
84
+ parameters,
85
+ path: [],
86
+ parentType: operation.schema.schemaDefinition.rootType(operation.rootKind)!,
87
+ });
88
+
89
+ return {
90
+ data: res === ApplyResult.NULL_BUBBLE_UP ? null : data,
91
+ errors: parameters.errors,
92
+ };
93
+ }
94
+
95
+ type Parameters = {
96
+ schema: Schema,
97
+ variables: Record<string, any>,
98
+ errors: GraphQLError[],
99
+ introspectionHandling: (introspectionSelection: FieldSelection) => any,
100
+ }
101
+
102
+
103
+ function shouldSkip(element: OperationElement, parameters: Parameters): boolean {
104
+ const skipDirective = element.appliedDirectivesOf(parameters.schema.skipDirective())[0];
105
+ const includeDirective = element.appliedDirectivesOf(parameters.schema.includeDirective())[0];
106
+ return (skipDirective && ifValue(skipDirective, parameters.variables))
107
+ || (includeDirective && !ifValue(includeDirective, parameters.variables));
108
+ }
109
+
110
+ function ifValue(directive: Directive<any, { if: boolean | Variable }>, variables: Record<string, any>): boolean {
111
+ const ifArg = directive.arguments().if;
112
+ if (ifArg instanceof Variable) {
113
+ const value = variables[ifArg.name];
114
+ // If the query has been validated, which we assume, the value must exists and be a boolean
115
+ assert(value !== undefined && typeof value === 'boolean', () => `Unexpected value ${value} for variable ${ifArg} of ${directive}`);
116
+ return value;
117
+ } else {
118
+ return ifArg;
119
+ }
120
+ }
121
+
122
+ enum ApplyResult { OK, NULL_BUBBLE_UP };
123
+
124
+ function typeConditionApplies(
125
+ schema: Schema,
126
+ typeCondition: CompositeType | undefined,
127
+ typename: string | undefined,
128
+ parentType: CompositeType,
129
+ ): boolean {
130
+ if (!typeCondition) {
131
+ return true;
132
+ }
133
+
134
+ if (typename) {
135
+ const type = schema.type(typename);
136
+ return !!type && isSubtype(typeCondition, type);
137
+ } else {
138
+ // No __typename, just check that the condition matches the parent type (unsure this is necessary as the query wouldn't have
139
+ // been valid otherwise but ...).
140
+ return isSubtype(typeCondition, parentType);
141
+ }
142
+ }
143
+
144
+ function applySelectionSet({
145
+ input,
146
+ selectionSet,
147
+ output,
148
+ parameters,
149
+ path,
150
+ parentType,
151
+ }: {
152
+ input: Record<string, any>,
153
+ selectionSet: SelectionSet,
154
+ output: Record<string, any>,
155
+ parameters: Parameters,
156
+ path: ResponsePath,
157
+ parentType: CompositeType,
158
+ }): ApplyResult {
159
+ for (const selection of selectionSet.selections()) {
160
+ if (shouldSkip(selection.element(), parameters)) {
161
+ continue;
162
+ }
163
+
164
+ if (selection.kind === 'FieldSelection') {
165
+ const field = selection.element();
166
+ const fieldType = field.definition.type!;
167
+ const responseName = field.responseName();
168
+ const outputValue = output[responseName];
169
+
170
+ if (field.definition.isSchemaIntrospectionField()) {
171
+ if (outputValue === undefined) {
172
+ output[responseName] = parameters.introspectionHandling(selection);
173
+ }
174
+ continue;
175
+ }
176
+
177
+ let inputValue = input[responseName] ?? null;
178
+
179
+ // We handle __typename separately because there is some cases where the internal data may either not have
180
+ // the value (despite __typename always having a value), or an actually incorrect value that should not be
181
+ // returned. More specifically, this is due to 2 things at the moment:
182
+ // 1. it is allowed for subgraphs to use custom names for root types, and different subgraphs can even use
183
+ // different names, but the supergraph will always use the default names (`Mutation` or `Query`). But it
184
+ // means that if we ask a subgraph for the __typename of a root type, the returned value may well be
185
+ // incorrect. In fact, to work around this, the query planner does not query the __typename of root
186
+ // types from subgraphs, and this is why this needs to be handled now.
187
+ // 2. @interfaceObject makes it so that some subgraph may know what is an interface type in the supergraph
188
+ // as an object type locally. When __typename is used to such subgraph, it will thus return what is an
189
+ // interface type name for the supergraph and is thus invalid. Now, if __typename is explicitly queried
190
+ // (the case we're handling here) then the query planner will ensure in the query plan that after having
191
+ // queried a subgraph with @interfaceObject, we always follow that with a query to another subgraph
192
+ // having the type as an interface (and a @key on it), so as to "override" the __typename with the
193
+ // correct implementation type. _However_, that later fetch could fail, and when that is the case,
194
+ // the incorrect __typename will be incorrect. In that case, we must return it but instead should make
195
+ // the whole object null.
196
+ if (field.name === typenameFieldName) {
197
+ // If we've already set outputValue, we've already run this logic and are just dealing with a repeated
198
+ // fragments, so just continue with the rest of the selections.
199
+ if (outputValue === undefined) {
200
+ // Note that this could an aliasing of __typename. If so, we've checked the input for the alias, but
201
+ // if we found nothing it's worth double check if we don't have the __typename unaliased.
202
+ // Note(Sylvain): unsure if there is real situation where this would help but it's cheap to check
203
+ // and it's pretty logical to try so...
204
+ if (inputValue === null && responseName !== typenameFieldName) {
205
+ inputValue = input[typenameFieldName] ?? null;
206
+ }
207
+
208
+ // We're using the type pointed by our input value if there is one and it points to a genuine
209
+ // type of the schema. Otherwise, we default to our parent type.
210
+ const type = inputValue !== null && typeof inputValue !== 'string'
211
+ ? parameters.schema.type(inputValue) ?? parentType
212
+ : parentType;
213
+
214
+ // If if that type is not an object, then we cannot use it and our only option is to nullify
215
+ // the whole object.
216
+ if (!isObjectType(type)) {
217
+ return ApplyResult.NULL_BUBBLE_UP;
218
+ }
219
+ output[responseName] = type.name;
220
+ }
221
+ continue;
222
+ }
223
+
224
+ path.push(responseName);
225
+ const { updated, isInvalid } = updateOutputValue({
226
+ outputValue,
227
+ type: fieldType,
228
+ inputValue,
229
+ selectionSet: selection.selectionSet,
230
+ path,
231
+ parameters,
232
+ parentType,
233
+ });
234
+ output[responseName] = updated
235
+ path.pop();
236
+ if (isInvalid) {
237
+ return ApplyResult.NULL_BUBBLE_UP;
238
+ }
239
+ } else {
240
+ const fragment = selection.element();
241
+ const typename = input[typenameFieldName];
242
+ assert(!typename || typeof typename === 'string', () => `Got unexpected value for __typename: ${typename}`);
243
+ if (typeConditionApplies(parameters.schema, fragment.typeCondition, typename, parentType)) {
244
+ const res = applySelectionSet({
245
+ input,
246
+ selectionSet: selection.selectionSet,
247
+ output,
248
+ parameters,
249
+ path,
250
+ parentType: fragment.typeCondition ?? parentType,
251
+ });
252
+ if (res === ApplyResult.NULL_BUBBLE_UP) {
253
+ return ApplyResult.NULL_BUBBLE_UP;
254
+ }
255
+ }
256
+ }
257
+ }
258
+
259
+ return ApplyResult.OK;
260
+ }
261
+
262
+ function pathLastElementDescription(path: ResponsePath, currentType: Type, parentType: CompositeType): string {
263
+ const element = path[path.length - 1];
264
+ assert(element !== undefined, 'Should not have been called on an empty path');
265
+ return typeof element === 'string'
266
+ ? `field ${parentType}.${element}`
267
+ : `array element of type ${currentType} at index ${element}`;
268
+ }
269
+
270
+
271
+ /**
272
+ * Given some partially computed output value (`outputValue`, possibly `undefined`) for a given `type` and the
273
+ * corresponding input value (`inputValue`, which should never be `undefined` for this method, but can be `null`),
274
+ * computes an updated output value for applying the provided `selectionSet` as sub-selection.
275
+ */
276
+ function updateOutputValue({
277
+ outputValue,
278
+ type,
279
+ inputValue,
280
+ selectionSet,
281
+ path,
282
+ parameters,
283
+ parentType,
284
+ }: {
285
+ outputValue: any,
286
+ type: OutputType,
287
+ inputValue: any,
288
+ selectionSet: SelectionSet | undefined,
289
+ path: ResponsePath,
290
+ parameters: Parameters,
291
+ parentType: CompositeType,
292
+ }): {
293
+ // The updated version of `outputValue`. Never `undefined`, but can be `null`.
294
+ updated: any,
295
+ // Whether the returned value is "valid" for `type`. In other words, this is true only if both `updated` is `null`
296
+ // and `type` is non-nullable. This indicates that this is a `null` that needs to be "bubbled up".
297
+ isInvalid?: boolean,
298
+ // Whether errors have already been generated for the computation of the current value. This only exists for the sake
299
+ // of recursive calls to avoid generating multiple errors as we bubble up nulls.
300
+ hasErrors?: boolean,
301
+ } {
302
+ assert(inputValue !== undefined, 'Should not pass undefined for `inputValue` to this method');
303
+
304
+ if (outputValue === null || (outputValue !== undefined && !selectionSet)) {
305
+ // If we've already computed the value for a non-composite type (scalar or enum), then we're just
306
+ // running into a "duplicate" selection of this value but we have nothing more to do (the reason we
307
+ // have more to do for composites is that the sub-selection may differ from the previous we've seen).
308
+ // And if the value is null, even if the type is composite, then there is nothing more to be done (do
309
+ // not that if there was some bubbling up of `null` to be done, it would have been done before because
310
+ // the rulesof graphQL ensures that everything going into a given response name has same nullability
311
+ // constraints (https://spec.graphql.org/June2018/#SameResponseShape()).
312
+ return { updated: outputValue };
313
+ }
314
+
315
+ if (isNonNullType(type)) {
316
+ const { updated, hasErrors } = updateOutputValue({
317
+ outputValue,
318
+ type: type.ofType,
319
+ inputValue,
320
+ selectionSet,
321
+ path,
322
+ parameters,
323
+ parentType,
324
+ });
325
+ if (updated === null) {
326
+ if (!hasErrors) {
327
+ parameters.errors.push(ERRORS.INVALID_GRAPHQL.err(
328
+ `Cannot return null for non-nullable ${pathLastElementDescription(path, type.ofType, parentType)}.`,
329
+ { path: Array.from(path) }
330
+ ));
331
+ }
332
+ return { updated, isInvalid: true, hasErrors: true };
333
+ }
334
+ return { updated };
335
+ }
336
+
337
+ // Note that from that point, we never have to bubble up null since the type is nullable.
338
+
339
+ if (inputValue === null) {
340
+ // If the input is null, so is the output, no matter what. And since we already dealt with non-nullable, then it's
341
+ // an ok value at that point.
342
+ return { updated: null };
343
+ }
344
+
345
+ if (isListType(type)) {
346
+ // The current `outputValue` can't be `null` at that point, so it's either:
347
+ // 1. some array: we need to recurse into each value of that array, and deal with potential
348
+ // null bubbling up.
349
+ // 2. undefined: this is the first time we're computing anything for this value, so we
350
+ // want to recurse like in the previous case, but just with undefined as current value.
351
+ // Anything else means the subgraph sent us something fishy.
352
+ assert(Array.isArray(inputValue), () => `Invalid non-list value ${inputValue} returned by subgraph for list type ${type}`)
353
+ assert(outputValue === undefined || Array.isArray(outputValue), () => `Invalid non-list value ${outputValue} returned by subgraph for list type ${type}`)
354
+ const outputValueList: any[] = outputValue === undefined ? new Array(inputValue.length).fill(undefined) : outputValue;
355
+ // Note that if we already had an existing output value, then it was built from the same "input" list than we have now, so it should match length.
356
+ assert(inputValue.length === outputValueList.length, () => `[${inputValue}].length (${inputValue.length}) !== [${outputValueList}].length (${outputValueList.length})`)
357
+ let shouldNullify = false;
358
+ let hasErrors = false;
359
+ const updated = outputValueList.map((outputEltValue, idx) => {
360
+ path.push(idx);
361
+ const elt = updateOutputValue({
362
+ outputValue: outputEltValue,
363
+ type: type.ofType,
364
+ inputValue: inputValue[idx],
365
+ selectionSet,
366
+ path,
367
+ parameters,
368
+ parentType,
369
+ });
370
+ path.pop();
371
+ // If the element is invalid, it means it's a null but the list inner type is non-nullable, and we should nullify the whole list.
372
+ // We do continue iterating so we collect potential errors for other elements.
373
+ shouldNullify ||= !!elt.isInvalid;
374
+ hasErrors ||= !!elt.hasErrors;
375
+ return elt.updated;
376
+ });
377
+ // Note that we should pass up whether an error has already be logged for the inner elements or not, but the value is otherwise not
378
+ // invalid at this point, even if null.
379
+ return { updated: shouldNullify ? null : updated, hasErrors }
380
+ }
381
+
382
+ if (isCompositeType(type)) {
383
+ assert(selectionSet, () => `Invalid empty selection set for composite type ${type}`);
384
+ assert(typeof inputValue === 'object', () => `Invalid non-object value ${inputValue} returned by subgraph for composite type ${type}`)
385
+ assert(outputValue === undefined || typeof outputValue === 'object', () => `Invalid non-object value ${inputValue} returned by subgraph for composite type ${type}`)
386
+
387
+ const inputTypename = inputValue[typenameFieldName];
388
+ assert(inputTypename === undefined || typeof inputTypename === 'string', () => `Invalid non-string value ${inputTypename} for __typename at ${path}`)
389
+ let objType = type;
390
+ if (inputTypename) {
391
+ // If we do have a typename, but the type is not in our api schema (or is not a composite for some reason), we play it safe and
392
+ // return `null`.
393
+ const typenameType = parameters.schema.type(inputTypename);
394
+ if (!typenameType || !isCompositeType(typenameType)) {
395
+ // Please note that, as `parameters.schema` is the API schema, this is were we will get if a subgraph returns an object for type that is @inacessible.
396
+ // And as we don't want to leak inaccessible type names, we should _not_ include `inputTypename` in the error message (note that both `type` and
397
+ // `parentType` are fine to include because both come from the query and thus API schema; but typically, `type` might be an (accessible) interface
398
+ // while `inputTypename` is the name of an implementation that happens to not be accessible).
399
+ parameters.errors.push(ERRORS.INVALID_GRAPHQL.err(
400
+ `Invalid __typename found for object at ${pathLastElementDescription(path, type, parentType)}.`,
401
+ { path: Array.from(path) }
402
+ ));
403
+ return { updated: null, hasErrors: true };
404
+ }
405
+ objType = typenameType;
406
+ }
407
+
408
+ const outputValueObject: Record<string, any> = outputValue === undefined ? Object.create(null) : outputValue;
409
+
410
+ const res = applySelectionSet({
411
+ input: inputValue,
412
+ selectionSet,
413
+ output: outputValueObject,
414
+ parameters,
415
+ path,
416
+ parentType: objType,
417
+ });
418
+ // If we're bubbling up a null, then we return a null for the whole object; but we also know that a null error has
419
+ // already been logged and don't need to anymore.
420
+ const hasErrors = res === ApplyResult.NULL_BUBBLE_UP;
421
+ return { updated: hasErrors ? null : outputValueObject, hasErrors };
422
+ }
423
+
424
+ // Note that because of the initial condition of this function, and the fact we've essentially deal with the
425
+ // cases where `isCompositeType(baseType(type))`, we know that if we're still here, `outputValue` is undefined
426
+ // and was remains is just to validate that the input is a valid value for the type and return that.
427
+ assert(outputValue === undefined, () => `Excepted output to be undefined but got ${type} for type ${type}`)
428
+
429
+ const isValidValue = isValidLeafValue(parameters.schema, inputValue, type);
430
+ if (!isValidValue) {
431
+ parameters.errors.push(ERRORS.INVALID_GRAPHQL.err(
432
+ `Invalid value found for ${pathLastElementDescription(path, type, parentType)}.`,
433
+ { path: Array.from(path) }
434
+ ));
435
+ }
436
+ // Not that we're already logged an error that the value is invalid, so no point in throwing an addition null error if
437
+ // the type ends up being null on top of that.
438
+ return { updated: isValidValue ? inputValue : null, hasErrors: !isValidValue};
439
+ }
@@ -1,107 +0,0 @@
1
- import { execute, GraphQLError, parse } from 'graphql';
2
- import { cleanErrorOfInaccessibleNames } from '../cleanErrorOfInaccessibleNames';
3
- import { buildSchema } from '@apollo/federation-internals';
4
-
5
- describe('cleanErrorOfInaccessibleNames', () => {
6
- const coreSchema = buildSchema(`
7
- directive @core(
8
- feature: String!,
9
- as: String,
10
- for: core__Purpose
11
- ) repeatable on SCHEMA
12
-
13
- directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION
14
-
15
- schema
16
- @core(feature: "https://specs.apollo.dev/core/v0.2")
17
- @core(feature: "https://specs.apollo.dev/inaccessible/v0.1")
18
- {
19
- query: Query
20
- }
21
-
22
- enum core__Purpose {
23
- EXECUTION
24
- SECURITY
25
- }
26
-
27
- type Query {
28
- fooField: Foo
29
- bazField: Baz
30
- }
31
-
32
- interface Foo {
33
- someField: String
34
- }
35
-
36
- type Bar implements Foo @inaccessible {
37
- someField: String
38
- }
39
-
40
- type Bar2 @inaccessible {
41
- anotherField: String
42
- }
43
-
44
- type Baz {
45
- goodField: String
46
- inaccessibleField: String @inaccessible
47
- }
48
- `);
49
- const schema = coreSchema.toAPISchema().toGraphQLJSSchema();
50
-
51
- it('removes inaccessible type names from error messages', async () => {
52
- const result = await execute({
53
- schema,
54
- document: parse('{fooField{someField}}'),
55
- rootValue: {
56
- fooField: {
57
- __typename: 'Bar',
58
- someField: 'test',
59
- },
60
- },
61
- });
62
-
63
- const cleaned = cleanErrorOfInaccessibleNames(schema, result.errors![0]!);
64
- expect(cleaned.message).toMatchInlineSnapshot(
65
- `"Abstract type \\"Foo\\" was resolved to a type [inaccessible type] that does not exist inside the schema."`,
66
- );
67
- });
68
- it('removes multiple/repeated inaccessible type names from error messages', async () => {
69
- const contrivedError = new GraphQLError(
70
- `Something something "Bar" and "Bar" again, as well as "Bar2".`,
71
- );
72
- const cleaned = cleanErrorOfInaccessibleNames(schema, contrivedError);
73
- expect(cleaned.message).toMatchInlineSnapshot(
74
- `"Something something [inaccessible type] and [inaccessible type] again, as well as [inaccessible type]."`,
75
- );
76
- });
77
-
78
- it('removes inaccessible field names from error messages', async () => {
79
- const contrivedError = new GraphQLError(
80
- `Can't query inaccessible field "Baz.inaccessibleField".`,
81
- );
82
- const cleaned = cleanErrorOfInaccessibleNames(schema, contrivedError);
83
- expect(cleaned.message).toMatchInlineSnapshot(
84
- `"Can't query inaccessible field [inaccessible field]."`,
85
- );
86
- });
87
-
88
- it('removes multiple/repeated inaccessible field names from error messages', async () => {
89
- const contrivedError = new GraphQLError(
90
- `Can't query inaccessible field "Baz.inaccessibleField" and "Baz.inaccessibleField", as well as "Bar2.anotherField".`,
91
- );
92
- const cleaned = cleanErrorOfInaccessibleNames(schema, contrivedError);
93
- expect(cleaned.message).toMatchInlineSnapshot(
94
- `"Can't query inaccessible field [inaccessible field] and [inaccessible field], as well as [inaccessible field]."`,
95
- );
96
- });
97
-
98
- it("doesn't remove special-case double-quoted words from graphql error messages", () => {
99
- const graphqlError = new GraphQLError(
100
- `Something something "resolveType" something something "isTypeOf".`,
101
- );
102
- const cleaned = cleanErrorOfInaccessibleNames(schema, graphqlError);
103
- expect(cleaned.message).toMatchInlineSnapshot(
104
- `"Something something \\"resolveType\\" something something \\"isTypeOf\\"."`,
105
- );
106
- });
107
- });
@@ -1,29 +0,0 @@
1
- import { GraphQLError, GraphQLSchema } from 'graphql';
2
-
3
- export function cleanErrorOfInaccessibleNames(
4
- schema: GraphQLSchema,
5
- error: GraphQLError,
6
- ): GraphQLError {
7
-
8
- const typeDotFieldRegex = /"([_A-Za-z][_0-9A-Za-z]*)\.([_A-Za-z][_0-9A-Za-z]*)"/g;
9
- error.message = error.message.replace(typeDotFieldRegex, (match: string) => {
10
- const [typeName, fieldName] = match.replace(/"/g, '',).split('.');
11
- const type = schema.getType(typeName);
12
- if (!type) {
13
- return '[inaccessible field]';
14
- } else {
15
- const field = 'getFields' in type ? type.getFields()[fieldName] : null;
16
- return field ? match : '[inaccessible field]';
17
- }
18
- });
19
-
20
- const typeRegex = /"([_A-Za-z][_0-9A-Za-z]*)"/g;
21
- error.message = error.message.replace(typeRegex, (match: string) => {
22
- // Special cases in graphql-js that happen to match our regex.
23
- if (match === '"isTypeOf"' || match === '"resolveType"') return match;
24
- const typeName = match.replace(/"/g, '',);
25
- return schema.getType(typeName) ? match : '[inaccessible type]';
26
- })
27
-
28
- return error;
29
- }