@apollo/federation-internals 2.0.0-preview.7 → 2.0.0
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.
- package/CHANGELOG.md +32 -3
- package/dist/buildSchema.d.ts.map +1 -1
- package/dist/buildSchema.js +51 -41
- package/dist/buildSchema.js.map +1 -1
- package/dist/coreSpec.d.ts +16 -8
- package/dist/coreSpec.d.ts.map +1 -1
- package/dist/coreSpec.js +205 -53
- package/dist/coreSpec.js.map +1 -1
- package/dist/definitions.d.ts +28 -11
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +185 -67
- package/dist/definitions.js.map +1 -1
- package/dist/directiveAndTypeSpecification.d.ts +11 -1
- package/dist/directiveAndTypeSpecification.d.ts.map +1 -1
- package/dist/directiveAndTypeSpecification.js +77 -20
- package/dist/directiveAndTypeSpecification.js.map +1 -1
- package/dist/error.d.ts +17 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +54 -2
- package/dist/error.js.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.js +7 -1
- package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
- package/dist/federation.d.ts +22 -5
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +143 -86
- package/dist/federation.js.map +1 -1
- package/dist/federationSpec.d.ts +6 -2
- package/dist/federationSpec.d.ts.map +1 -1
- package/dist/federationSpec.js +47 -22
- package/dist/federationSpec.js.map +1 -1
- package/dist/inaccessibleSpec.d.ts +10 -2
- package/dist/inaccessibleSpec.d.ts.map +1 -1
- package/dist/inaccessibleSpec.js +634 -16
- package/dist/inaccessibleSpec.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/introspection.d.ts.map +1 -1
- package/dist/introspection.js +8 -3
- package/dist/introspection.js.map +1 -1
- package/dist/joinSpec.d.ts +5 -1
- package/dist/joinSpec.d.ts.map +1 -1
- package/dist/joinSpec.js +21 -0
- package/dist/joinSpec.js.map +1 -1
- package/dist/knownCoreFeatures.d.ts +4 -0
- package/dist/knownCoreFeatures.d.ts.map +1 -0
- package/dist/knownCoreFeatures.js +16 -0
- package/dist/knownCoreFeatures.js.map +1 -0
- package/dist/operations.d.ts +1 -0
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +16 -1
- package/dist/operations.js.map +1 -1
- package/dist/{sharing.d.ts → precompute.d.ts} +1 -1
- package/dist/precompute.d.ts.map +1 -0
- package/dist/{sharing.js → precompute.js} +3 -3
- package/dist/precompute.js.map +1 -0
- package/dist/schemaUpgrader.d.ts.map +1 -1
- package/dist/schemaUpgrader.js +17 -7
- package/dist/schemaUpgrader.js.map +1 -1
- package/dist/suggestions.d.ts +1 -1
- package/dist/suggestions.d.ts.map +1 -1
- package/dist/suggestions.js.map +1 -1
- package/dist/supergraphs.d.ts.map +1 -1
- package/dist/supergraphs.js +2 -0
- package/dist/supergraphs.js.map +1 -1
- package/dist/tagSpec.d.ts +7 -2
- package/dist/tagSpec.d.ts.map +1 -1
- package/dist/tagSpec.js +35 -14
- package/dist/tagSpec.js.map +1 -1
- package/dist/validate.js +13 -7
- package/dist/validate.js.map +1 -1
- package/dist/values.d.ts +2 -2
- package/dist/values.d.ts.map +1 -1
- package/dist/values.js +13 -11
- package/dist/values.js.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/coreSpec.test.ts +212 -0
- package/src/__tests__/definitions.test.ts +75 -0
- package/src/__tests__/removeInaccessibleElements.test.ts +2229 -137
- package/src/__tests__/schemaUpgrader.test.ts +3 -2
- package/src/__tests__/subgraphValidation.test.ts +419 -4
- package/src/__tests__/values.test.ts +315 -3
- package/src/buildSchema.ts +98 -51
- package/src/coreSpec.ts +277 -65
- package/src/definitions.ts +317 -92
- package/src/directiveAndTypeSpecification.ts +98 -21
- package/src/error.ts +119 -1
- package/src/extractSubgraphsFromSupergraph.ts +7 -1
- package/src/federation.ts +184 -102
- package/src/federationSpec.ts +56 -24
- package/src/inaccessibleSpec.ts +985 -39
- package/src/index.ts +2 -0
- package/src/introspection.ts +8 -3
- package/src/joinSpec.ts +33 -3
- package/src/knownCoreFeatures.ts +13 -0
- package/src/operations.ts +15 -0
- package/src/{sharing.ts → precompute.ts} +3 -6
- package/src/schemaUpgrader.ts +29 -13
- package/src/suggestions.ts +1 -1
- package/src/supergraphs.ts +2 -0
- package/src/tagSpec.ts +49 -16
- package/src/validate.ts +20 -9
- package/src/values.ts +39 -12
- package/tsconfig.test.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/sharing.d.ts.map +0 -1
- package/dist/sharing.js.map +0 -1
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Schema,
|
|
3
|
-
} from '
|
|
4
|
-
import { buildSchema } from '
|
|
5
|
-
import { parseOperation } from '
|
|
3
|
+
} from '../definitions';
|
|
4
|
+
import { buildSchema } from '../buildSchema';
|
|
5
|
+
import { parseOperation } from '../operations';
|
|
6
|
+
import { buildForErrors } from './subgraphValidation.test';
|
|
7
|
+
import gql from 'graphql-tag';
|
|
8
|
+
import { printSchema } from '../print';
|
|
6
9
|
|
|
7
10
|
function parseSchema(schema: string): Schema {
|
|
8
11
|
try {
|
|
@@ -66,3 +69,312 @@ test('handles non-list value for list argument (as singleton)', () => {
|
|
|
66
69
|
}
|
|
67
70
|
`);
|
|
68
71
|
});
|
|
72
|
+
|
|
73
|
+
describe('default value validation', () => {
|
|
74
|
+
it('errors on invalid default value in field argument', () => {
|
|
75
|
+
const doc = gql`
|
|
76
|
+
type Query {
|
|
77
|
+
f(a: Int = "foo"): Int
|
|
78
|
+
}
|
|
79
|
+
`;
|
|
80
|
+
|
|
81
|
+
expect(buildForErrors(doc)).toStrictEqual([[
|
|
82
|
+
'INVALID_GRAPHQL',
|
|
83
|
+
'[S] Invalid default value (got: "foo") provided for argument Query.f(a:) of type Int.'
|
|
84
|
+
]]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('errors on invalid default value in directive argument', () => {
|
|
88
|
+
const doc = gql`
|
|
89
|
+
type Query {
|
|
90
|
+
f: Int
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
directive @myDirective(a: Int = "foo") on FIELD
|
|
94
|
+
`;
|
|
95
|
+
|
|
96
|
+
expect(buildForErrors(doc)).toStrictEqual([[
|
|
97
|
+
'INVALID_GRAPHQL',
|
|
98
|
+
'[S] Invalid default value (got: "foo") provided for argument @myDirective(a:) of type Int.'
|
|
99
|
+
]]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('errors on invalid default value in input field', () => {
|
|
103
|
+
const doc = gql`
|
|
104
|
+
input I {
|
|
105
|
+
x: Int = "foo"
|
|
106
|
+
}
|
|
107
|
+
`;
|
|
108
|
+
|
|
109
|
+
expect(buildForErrors(doc)).toStrictEqual([[
|
|
110
|
+
'INVALID_GRAPHQL',
|
|
111
|
+
'[S] Invalid default value (got: "foo") provided for input field I.x of type Int.'
|
|
112
|
+
]]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('errors on invalid default value for existing input field', () => {
|
|
116
|
+
const doc = gql`
|
|
117
|
+
type Query {
|
|
118
|
+
f(i: I = { x: 2, y: "3" }): Int
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
input I {
|
|
122
|
+
x: Int
|
|
123
|
+
y: Int
|
|
124
|
+
}
|
|
125
|
+
`;
|
|
126
|
+
|
|
127
|
+
expect(buildForErrors(doc)).toStrictEqual([[
|
|
128
|
+
'INVALID_GRAPHQL',
|
|
129
|
+
'[S] Invalid default value (got: {x: 2, y: "3"}) provided for argument Query.f(i:) of type I.'
|
|
130
|
+
]]);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('errors on default value containing unexpected input fields', () => {
|
|
134
|
+
const doc = gql`
|
|
135
|
+
type Query {
|
|
136
|
+
f(i: I = { x: 1, y: 2, z: 3 }): Int
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
input I {
|
|
140
|
+
x: Int
|
|
141
|
+
y: Int
|
|
142
|
+
}
|
|
143
|
+
`;
|
|
144
|
+
|
|
145
|
+
expect(buildForErrors(doc)).toStrictEqual([[
|
|
146
|
+
'INVALID_GRAPHQL',
|
|
147
|
+
'[S] Invalid default value (got: {x: 1, y: 2, z: 3}) provided for argument Query.f(i:) of type I.'
|
|
148
|
+
]]);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('errors on default value being unknown enum value', () => {
|
|
152
|
+
const doc = gql`
|
|
153
|
+
type Query {
|
|
154
|
+
f(e: E = THREE): Int
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
enum E {
|
|
158
|
+
ONE
|
|
159
|
+
TWO
|
|
160
|
+
}
|
|
161
|
+
`;
|
|
162
|
+
|
|
163
|
+
// Note that it is slightly imperfect that the error shows the value as a "string" but is the result
|
|
164
|
+
// of enum values being encoded by string value internally (and while, when a value type-check correctly,
|
|
165
|
+
// we can use the type to display enum values properly, this is exactly a case where the value is not
|
|
166
|
+
// correctly type-checked, so we currently don't have a good way to figure out it's an enum when we display
|
|
167
|
+
// it in the error message). We could fix this someday if we change to using a specific class/object for
|
|
168
|
+
// enum values internally (though this might have backward compatbility constraints), but in the meantime,
|
|
169
|
+
// it's unlikely to trip users too much.
|
|
170
|
+
expect(buildForErrors(doc)).toStrictEqual([[
|
|
171
|
+
'INVALID_GRAPHQL',
|
|
172
|
+
'[S] Invalid default value (got: "THREE") provided for argument Query.f(e:) of type E.'
|
|
173
|
+
]]);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('errors on default value being unknown enum value (as string)', () => {
|
|
177
|
+
const doc = gql`
|
|
178
|
+
type Query {
|
|
179
|
+
f(e: E = "TWOO"): Int
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
enum E {
|
|
183
|
+
ONE
|
|
184
|
+
TWO
|
|
185
|
+
}
|
|
186
|
+
`;
|
|
187
|
+
|
|
188
|
+
expect(buildForErrors(doc)).toStrictEqual([[
|
|
189
|
+
'INVALID_GRAPHQL',
|
|
190
|
+
'[S] Invalid default value (got: "TWOO") provided for argument Query.f(e:) of type E.'
|
|
191
|
+
]]);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('accepts default value enum value as string, if a valid enum value', () => {
|
|
195
|
+
// Please note that this test show we accept strings for enum even though the GraphQL spec kind
|
|
196
|
+
// of say we shouldn't. But the graphQL spec also doesn't really do default value validation,
|
|
197
|
+
// which be believe is just wrong, and as a consequence we've seen customer schema with string-for-enum
|
|
198
|
+
// in default values, and it doesn't sound very harmfull to allow it (the spec even admits that some
|
|
199
|
+
// transport may have to deal with enums as string anyway), so we prefer having that allowance in
|
|
200
|
+
// federation (if this ever become a huge issue for some users, we could imagine to add a "strict"
|
|
201
|
+
// more that start refusing this).
|
|
202
|
+
const doc = gql`
|
|
203
|
+
type Query {
|
|
204
|
+
f(e: E = "TWO"): Int
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
enum E {
|
|
208
|
+
ONE
|
|
209
|
+
TWO
|
|
210
|
+
}
|
|
211
|
+
`;
|
|
212
|
+
|
|
213
|
+
expect(buildForErrors(doc)).toBeUndefined();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('accepts any value for a custom scalar in field agument', () => {
|
|
217
|
+
const doc = gql`
|
|
218
|
+
type Query {
|
|
219
|
+
f(i: Scalar = { x: 2, y: "3" }): Int
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
scalar Scalar
|
|
223
|
+
`;
|
|
224
|
+
|
|
225
|
+
expect(buildForErrors(doc)).toBeUndefined();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('accepts any value for a custom scalar in directive agument', () => {
|
|
229
|
+
const doc = gql`
|
|
230
|
+
type Query {
|
|
231
|
+
f: Int
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
directive @myDirective(i: Scalar = { x: 2, y: "3" }) on FIELD
|
|
235
|
+
scalar Scalar
|
|
236
|
+
`;
|
|
237
|
+
|
|
238
|
+
expect(buildForErrors(doc)).toBeUndefined();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('accepts any value for a custom scalar in an input field', () => {
|
|
242
|
+
const doc = gql`
|
|
243
|
+
input I {
|
|
244
|
+
x: Scalar = { z: { a: 4} }
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
scalar Scalar
|
|
248
|
+
`;
|
|
249
|
+
|
|
250
|
+
expect(buildForErrors(doc)).toBeUndefined();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('accepts default value coercible to list for a list type', () => {
|
|
254
|
+
const doc = gql`
|
|
255
|
+
type Query {
|
|
256
|
+
f(x: [String] = "foo"): Int
|
|
257
|
+
}
|
|
258
|
+
`;
|
|
259
|
+
|
|
260
|
+
expect(buildForErrors(doc)).toBeUndefined();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('accepts default value coercible to list for a list type through multiple coercions', () => {
|
|
264
|
+
const doc = gql`
|
|
265
|
+
type Query {
|
|
266
|
+
f(x: [[[String]!]]! = "foo"): Int
|
|
267
|
+
}
|
|
268
|
+
`;
|
|
269
|
+
|
|
270
|
+
expect(buildForErrors(doc)).toBeUndefined();
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('errors on default value no coercible to list for a list type through multiple coercions', () => {
|
|
274
|
+
const doc = gql`
|
|
275
|
+
type Query {
|
|
276
|
+
f(x: [[[String]!]]! = 2): Int
|
|
277
|
+
}
|
|
278
|
+
`;
|
|
279
|
+
|
|
280
|
+
expect(buildForErrors(doc)).toStrictEqual([[
|
|
281
|
+
'INVALID_GRAPHQL',
|
|
282
|
+
'[S] Invalid default value (got: 2) provided for argument Query.f(x:) of type [[[String]!]]!.'
|
|
283
|
+
]]);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('accepts default value coercible to its type but needing multiple/nested coercions', () => {
|
|
287
|
+
const doc = gql`
|
|
288
|
+
type Query {
|
|
289
|
+
f(x: I = { j: {x: 1, z: "Foo"} }): Int
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
input I {
|
|
293
|
+
j: [J]
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
input J {
|
|
297
|
+
x: ID
|
|
298
|
+
y: ID
|
|
299
|
+
z: ID
|
|
300
|
+
}
|
|
301
|
+
`;
|
|
302
|
+
|
|
303
|
+
expect(buildForErrors(doc)).toBeUndefined();
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('accepts default values that, if actually coerced, woudl result in infinite loops', () => {
|
|
307
|
+
// This example is stolen from this comment: https://github.com/graphql/graphql-spec/pull/793#issuecomment-738736539
|
|
308
|
+
// It essentially show that while, as the other tests of this file show, we 1) validate default value against
|
|
309
|
+
// their type and 2) ensures default values coercible to said type don't fail such validation, we also do
|
|
310
|
+
// _not_ do the actual coercion of those values, which in this example would lead to an infinite loop.
|
|
311
|
+
const doc = gql`
|
|
312
|
+
input A {
|
|
313
|
+
b: B = {}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
input B {
|
|
317
|
+
a: A = {}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
type Query {
|
|
321
|
+
q(a: A = {}): Int
|
|
322
|
+
}
|
|
323
|
+
`;
|
|
324
|
+
|
|
325
|
+
expect(buildForErrors(doc)).toBeUndefined();
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('errors on null default value for non-nullable input', () => {
|
|
329
|
+
const doc = gql`
|
|
330
|
+
type Query {
|
|
331
|
+
f(i: Int! = null): Int
|
|
332
|
+
}
|
|
333
|
+
`;
|
|
334
|
+
|
|
335
|
+
expect(buildForErrors(doc)).toStrictEqual([[
|
|
336
|
+
'INVALID_GRAPHQL',
|
|
337
|
+
'[S] Invalid default value (got: null) provided for argument Query.f(i:) of type Int!.'
|
|
338
|
+
]]);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('Accepts null default value for nullable input', () => {
|
|
342
|
+
const doc = gql`
|
|
343
|
+
type Query {
|
|
344
|
+
f(i: Int = null): Int
|
|
345
|
+
}
|
|
346
|
+
`;
|
|
347
|
+
|
|
348
|
+
expect(buildForErrors(doc)).toBeUndefined();
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
describe('values printing', () => {
|
|
353
|
+
it('prints enums value correctly within multiple lists', () => {
|
|
354
|
+
const sdl = `
|
|
355
|
+
type Query {
|
|
356
|
+
f(a: [[[E]!]!] = [[[FOO], [BAR]]]): Int
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
enum E {
|
|
360
|
+
FOO
|
|
361
|
+
BAR
|
|
362
|
+
}
|
|
363
|
+
`
|
|
364
|
+
expect(printSchema(parseSchema(sdl))).toMatchString(sdl);
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
it('prints enums value when its coercible to list through multiple coercions', () => {
|
|
368
|
+
const sdl = `
|
|
369
|
+
type Query {
|
|
370
|
+
f(a: [[[E]!]!] = FOO): Int
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
enum E {
|
|
374
|
+
FOO
|
|
375
|
+
BAR
|
|
376
|
+
}
|
|
377
|
+
`
|
|
378
|
+
expect(printSchema(parseSchema(sdl))).toMatchString(sdl);
|
|
379
|
+
})
|
|
380
|
+
});
|
package/src/buildSchema.ts
CHANGED
|
@@ -46,7 +46,9 @@ import {
|
|
|
46
46
|
UnionType,
|
|
47
47
|
InputObjectType,
|
|
48
48
|
EnumType,
|
|
49
|
-
Extension
|
|
49
|
+
Extension,
|
|
50
|
+
ErrGraphQLValidationFailed,
|
|
51
|
+
errorCauses,
|
|
50
52
|
} from "./definitions";
|
|
51
53
|
|
|
52
54
|
function buildValue(value?: ValueNode): any {
|
|
@@ -66,6 +68,7 @@ export function buildSchemaFromAST(
|
|
|
66
68
|
documentNode: DocumentNode,
|
|
67
69
|
options?: BuildSchemaOptions,
|
|
68
70
|
): Schema {
|
|
71
|
+
const errors: GraphQLError[] = [];
|
|
69
72
|
const schema = new Schema(options?.blueprint);
|
|
70
73
|
// We do a first pass to add all empty types and directives definition. This ensure any reference on one of
|
|
71
74
|
// those can be resolved in the 2nd pass, regardless of the order of the definitions in the AST.
|
|
@@ -77,29 +80,30 @@ export function buildSchemaFromAST(
|
|
|
77
80
|
// populated (and at this point, we don't really know the name of the `@core` directive since it can be renamed, so
|
|
78
81
|
// we just handle all directives).
|
|
79
82
|
for (const directiveDefinitionNode of directiveDefinitions) {
|
|
80
|
-
buildDirectiveDefinitionInner(directiveDefinitionNode, schema.directive(directiveDefinitionNode.name.value)
|
|
83
|
+
buildDirectiveDefinitionInner(directiveDefinitionNode, schema.directive(directiveDefinitionNode.name.value)!, errors);
|
|
81
84
|
}
|
|
82
85
|
for (const schemaDefinition of schemaDefinitions) {
|
|
83
|
-
buildSchemaDefinitionInner(schemaDefinition, schema.schemaDefinition);
|
|
86
|
+
buildSchemaDefinitionInner(schemaDefinition, schema.schemaDefinition, errors);
|
|
84
87
|
}
|
|
85
88
|
for (const schemaExtension of schemaExtensions) {
|
|
86
|
-
buildSchemaDefinitionInner(schemaExtension, schema.schemaDefinition, schema.schemaDefinition.newExtension());
|
|
89
|
+
buildSchemaDefinitionInner(schemaExtension, schema.schemaDefinition, errors, schema.schemaDefinition.newExtension());
|
|
87
90
|
}
|
|
88
91
|
|
|
89
|
-
schema.blueprint.onDirectiveDefinitionAndSchemaParsed(schema);
|
|
92
|
+
errors.push(...schema.blueprint.onDirectiveDefinitionAndSchemaParsed(schema));
|
|
90
93
|
|
|
91
94
|
for (const definitionNode of documentNode.definitions) {
|
|
92
95
|
switch (definitionNode.kind) {
|
|
93
96
|
case 'OperationDefinition':
|
|
94
97
|
case 'FragmentDefinition':
|
|
95
|
-
|
|
98
|
+
errors.push(new GraphQLError("Invalid executable definition found while building schema", definitionNode));
|
|
99
|
+
continue;
|
|
96
100
|
case 'ScalarTypeDefinition':
|
|
97
101
|
case 'ObjectTypeDefinition':
|
|
98
102
|
case 'InterfaceTypeDefinition':
|
|
99
103
|
case 'UnionTypeDefinition':
|
|
100
104
|
case 'EnumTypeDefinition':
|
|
101
105
|
case 'InputObjectTypeDefinition':
|
|
102
|
-
buildNamedTypeInner(definitionNode, schema.type(definitionNode.name.value)!, schema.blueprint);
|
|
106
|
+
buildNamedTypeInner(definitionNode, schema.type(definitionNode.name.value)!, schema.blueprint, errors);
|
|
103
107
|
break;
|
|
104
108
|
case 'ScalarTypeExtension':
|
|
105
109
|
case 'ObjectTypeExtension':
|
|
@@ -110,11 +114,20 @@ export function buildSchemaFromAST(
|
|
|
110
114
|
const toExtend = schema.type(definitionNode.name.value)!;
|
|
111
115
|
const extension = toExtend.newExtension();
|
|
112
116
|
extension.sourceAST = definitionNode;
|
|
113
|
-
buildNamedTypeInner(definitionNode, toExtend, schema.blueprint, extension);
|
|
117
|
+
buildNamedTypeInner(definitionNode, toExtend, schema.blueprint, errors, extension);
|
|
114
118
|
break;
|
|
115
119
|
}
|
|
116
120
|
}
|
|
117
121
|
|
|
122
|
+
// Note: we could try calling `schema.validate()` regardless of errors building the schema and merge the resulting
|
|
123
|
+
// errors, and there is some subset of cases where this be a tad more convenient (as the user would get all the errors
|
|
124
|
+
// at once), but in most cases a bunch of the errors thrown by `schema.validate()` would actually be consequences of
|
|
125
|
+
// the schema not be properly built in the first place and those errors would be confusing to the user. And avoiding
|
|
126
|
+
// confusing users probably trumps a rare minor convenience.
|
|
127
|
+
if (errors.length > 0) {
|
|
128
|
+
throw ErrGraphQLValidationFailed(errors);
|
|
129
|
+
}
|
|
130
|
+
|
|
118
131
|
if (options?.validate ?? true) {
|
|
119
132
|
schema.validate();
|
|
120
133
|
}
|
|
@@ -187,21 +200,24 @@ function getReferencedType(node: NamedTypeNode, schema: Schema): NamedType {
|
|
|
187
200
|
return type;
|
|
188
201
|
}
|
|
189
202
|
|
|
190
|
-
function withNodeAttachedToError(operation: () => void, node: ASTNode) {
|
|
203
|
+
function withNodeAttachedToError(operation: () => void, node: ASTNode, errors: GraphQLError[]) {
|
|
191
204
|
try {
|
|
192
205
|
operation();
|
|
193
206
|
} catch (e) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
207
|
+
const causes = errorCauses(e);
|
|
208
|
+
if (causes) {
|
|
209
|
+
for (const cause of causes) {
|
|
210
|
+
const allNodes: ASTNode | ASTNode[] = cause.nodes ? [node, ...cause.nodes] : node;
|
|
211
|
+
errors.push(new GraphQLError(
|
|
212
|
+
cause.message,
|
|
213
|
+
allNodes,
|
|
214
|
+
cause.source,
|
|
215
|
+
cause.positions,
|
|
216
|
+
cause.path,
|
|
217
|
+
cause,
|
|
218
|
+
cause.extensions
|
|
219
|
+
));
|
|
220
|
+
}
|
|
205
221
|
} else {
|
|
206
222
|
throw e;
|
|
207
223
|
}
|
|
@@ -211,31 +227,39 @@ function withNodeAttachedToError(operation: () => void, node: ASTNode) {
|
|
|
211
227
|
function buildSchemaDefinitionInner(
|
|
212
228
|
schemaNode: SchemaDefinitionNode | SchemaExtensionNode,
|
|
213
229
|
schemaDefinition: SchemaDefinition,
|
|
230
|
+
errors: GraphQLError[],
|
|
214
231
|
extension?: Extension<SchemaDefinition>
|
|
215
232
|
) {
|
|
216
233
|
for (const opTypeNode of schemaNode.operationTypes ?? []) {
|
|
217
234
|
withNodeAttachedToError(
|
|
218
235
|
() => schemaDefinition.setRoot(opTypeNode.operation, opTypeNode.type.name.value).setOfExtension(extension),
|
|
219
|
-
opTypeNode
|
|
236
|
+
opTypeNode,
|
|
237
|
+
errors,
|
|
238
|
+
);
|
|
220
239
|
}
|
|
221
240
|
schemaDefinition.sourceAST = schemaNode;
|
|
222
241
|
if ('description' in schemaNode) {
|
|
223
242
|
schemaDefinition.description = schemaNode.description?.value;
|
|
224
243
|
}
|
|
225
|
-
buildAppliedDirectives(schemaNode, schemaDefinition, extension);
|
|
244
|
+
buildAppliedDirectives(schemaNode, schemaDefinition, errors, extension);
|
|
226
245
|
}
|
|
227
246
|
|
|
228
247
|
function buildAppliedDirectives(
|
|
229
248
|
elementNode: NodeWithDirectives,
|
|
230
249
|
element: SchemaElement<any, any>,
|
|
250
|
+
errors: GraphQLError[],
|
|
231
251
|
extension?: Extension<any>
|
|
232
252
|
) {
|
|
233
253
|
for (const directive of elementNode.directives ?? []) {
|
|
234
|
-
withNodeAttachedToError(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
254
|
+
withNodeAttachedToError(
|
|
255
|
+
() => {
|
|
256
|
+
const d = element.applyDirective(directive.name.value, buildArgs(directive));
|
|
257
|
+
d.setOfExtension(extension);
|
|
258
|
+
d.sourceAST = directive;
|
|
259
|
+
},
|
|
260
|
+
directive,
|
|
261
|
+
errors,
|
|
262
|
+
);
|
|
239
263
|
}
|
|
240
264
|
}
|
|
241
265
|
|
|
@@ -251,6 +275,7 @@ function buildNamedTypeInner(
|
|
|
251
275
|
definitionNode: DefinitionNode & NodeWithDirectives & NodeWithDescription,
|
|
252
276
|
type: NamedType,
|
|
253
277
|
blueprint: SchemaBlueprint,
|
|
278
|
+
errors: GraphQLError[],
|
|
254
279
|
extension?: Extension<any>,
|
|
255
280
|
) {
|
|
256
281
|
switch (definitionNode.kind) {
|
|
@@ -265,18 +290,20 @@ function buildNamedTypeInner(
|
|
|
265
290
|
}
|
|
266
291
|
const field = fieldBasedType.addField(fieldNode.name.value);
|
|
267
292
|
field.setOfExtension(extension);
|
|
268
|
-
buildFieldDefinitionInner(fieldNode, field);
|
|
293
|
+
buildFieldDefinitionInner(fieldNode, field, errors);
|
|
269
294
|
}
|
|
270
295
|
for (const itfNode of definitionNode.interfaces ?? []) {
|
|
271
296
|
withNodeAttachedToError(
|
|
272
297
|
() => {
|
|
273
298
|
const itfName = itfNode.name.value;
|
|
274
299
|
if (fieldBasedType.implementsInterface(itfName)) {
|
|
275
|
-
throw new GraphQLError(`Type ${type} can only implement ${itfName} once.`);
|
|
300
|
+
throw new GraphQLError(`Type "${type}" can only implement "${itfName}" once.`);
|
|
276
301
|
}
|
|
277
302
|
fieldBasedType.addImplementedInterface(itfName).setOfExtension(extension);
|
|
278
303
|
},
|
|
279
|
-
itfNode
|
|
304
|
+
itfNode,
|
|
305
|
+
errors,
|
|
306
|
+
);
|
|
280
307
|
}
|
|
281
308
|
break;
|
|
282
309
|
case 'UnionTypeDefinition':
|
|
@@ -287,11 +314,13 @@ function buildNamedTypeInner(
|
|
|
287
314
|
() => {
|
|
288
315
|
const name = namedType.name.value;
|
|
289
316
|
if (unionType.hasTypeMember(name)) {
|
|
290
|
-
throw new GraphQLError(`Union type ${unionType} can only include type ${name} once.`);
|
|
317
|
+
throw new GraphQLError(`Union type "${unionType}" can only include type "${name}" once.`);
|
|
291
318
|
}
|
|
292
319
|
unionType.addType(name).setOfExtension(extension);
|
|
293
320
|
},
|
|
294
|
-
namedType
|
|
321
|
+
namedType,
|
|
322
|
+
errors,
|
|
323
|
+
);
|
|
295
324
|
}
|
|
296
325
|
break;
|
|
297
326
|
case 'EnumTypeDefinition':
|
|
@@ -303,7 +332,7 @@ function buildNamedTypeInner(
|
|
|
303
332
|
v.description = enumVal.description.value;
|
|
304
333
|
}
|
|
305
334
|
v.setOfExtension(extension);
|
|
306
|
-
buildAppliedDirectives(enumVal, v);
|
|
335
|
+
buildAppliedDirectives(enumVal, v, errors);
|
|
307
336
|
}
|
|
308
337
|
break;
|
|
309
338
|
case 'InputObjectTypeDefinition':
|
|
@@ -312,41 +341,47 @@ function buildNamedTypeInner(
|
|
|
312
341
|
for (const fieldNode of definitionNode.fields ?? []) {
|
|
313
342
|
const field = inputObjectType.addField(fieldNode.name.value);
|
|
314
343
|
field.setOfExtension(extension);
|
|
315
|
-
buildInputFieldDefinitionInner(fieldNode, field);
|
|
344
|
+
buildInputFieldDefinitionInner(fieldNode, field, errors);
|
|
316
345
|
}
|
|
317
346
|
break;
|
|
318
347
|
}
|
|
319
|
-
buildAppliedDirectives(definitionNode, type, extension);
|
|
348
|
+
buildAppliedDirectives(definitionNode, type, errors, extension);
|
|
320
349
|
if (definitionNode.description) {
|
|
321
350
|
type.description = definitionNode.description.value;
|
|
322
351
|
}
|
|
323
352
|
type.sourceAST = definitionNode;
|
|
324
353
|
}
|
|
325
354
|
|
|
326
|
-
function buildFieldDefinitionInner(
|
|
355
|
+
function buildFieldDefinitionInner(
|
|
356
|
+
fieldNode: FieldDefinitionNode,
|
|
357
|
+
field: FieldDefinition<any>,
|
|
358
|
+
errors: GraphQLError[],
|
|
359
|
+
) {
|
|
327
360
|
const type = buildTypeReferenceFromAST(fieldNode.type, field.schema());
|
|
328
|
-
field.type =
|
|
361
|
+
field.type = validateOutputType(type, field.coordinate, fieldNode, errors);
|
|
329
362
|
for (const inputValueDef of fieldNode.arguments ?? []) {
|
|
330
|
-
buildArgumentDefinitionInner(inputValueDef, field.addArgument(inputValueDef.name.value));
|
|
363
|
+
buildArgumentDefinitionInner(inputValueDef, field.addArgument(inputValueDef.name.value), errors);
|
|
331
364
|
}
|
|
332
|
-
buildAppliedDirectives(fieldNode, field);
|
|
365
|
+
buildAppliedDirectives(fieldNode, field, errors);
|
|
333
366
|
field.description = fieldNode.description?.value;
|
|
334
367
|
field.sourceAST = fieldNode;
|
|
335
368
|
}
|
|
336
369
|
|
|
337
|
-
function
|
|
370
|
+
function validateOutputType(type: Type, what: string, node: ASTNode, errors: GraphQLError[]): OutputType | undefined {
|
|
338
371
|
if (isOutputType(type)) {
|
|
339
372
|
return type;
|
|
340
373
|
} else {
|
|
341
|
-
|
|
374
|
+
errors.push(new GraphQLError(`The type of "${what}" must be Output Type but got "${type}", a ${type.kind}.`, node));
|
|
375
|
+
return undefined;
|
|
342
376
|
}
|
|
343
377
|
}
|
|
344
378
|
|
|
345
|
-
function
|
|
379
|
+
function validateInputType(type: Type, what: string, node: ASTNode, errors: GraphQLError[]): InputType | undefined {
|
|
346
380
|
if (isInputType(type)) {
|
|
347
381
|
return type;
|
|
348
382
|
} else {
|
|
349
|
-
|
|
383
|
+
errors.push(new GraphQLError(`The type of "${what}" must be Input Type but got "${type}", a ${type.kind}.`, node));
|
|
384
|
+
return undefined;
|
|
350
385
|
}
|
|
351
386
|
}
|
|
352
387
|
|
|
@@ -369,27 +404,39 @@ function buildTypeReferenceFromAST(typeNode: TypeNode, schema: Schema): Type {
|
|
|
369
404
|
}
|
|
370
405
|
}
|
|
371
406
|
|
|
372
|
-
function buildArgumentDefinitionInner(
|
|
407
|
+
function buildArgumentDefinitionInner(
|
|
408
|
+
inputNode: InputValueDefinitionNode,
|
|
409
|
+
arg: ArgumentDefinition<any>,
|
|
410
|
+
errors: GraphQLError[],
|
|
411
|
+
) {
|
|
373
412
|
const type = buildTypeReferenceFromAST(inputNode.type, arg.schema());
|
|
374
|
-
arg.type =
|
|
413
|
+
arg.type = validateInputType(type, arg.coordinate, inputNode, errors);
|
|
375
414
|
arg.defaultValue = buildValue(inputNode.defaultValue);
|
|
376
|
-
buildAppliedDirectives(inputNode, arg);
|
|
415
|
+
buildAppliedDirectives(inputNode, arg, errors);
|
|
377
416
|
arg.description = inputNode.description?.value;
|
|
378
417
|
arg.sourceAST = inputNode;
|
|
379
418
|
}
|
|
380
419
|
|
|
381
|
-
function buildInputFieldDefinitionInner(
|
|
420
|
+
function buildInputFieldDefinitionInner(
|
|
421
|
+
fieldNode: InputValueDefinitionNode,
|
|
422
|
+
field: InputFieldDefinition,
|
|
423
|
+
errors: GraphQLError[],
|
|
424
|
+
) {
|
|
382
425
|
const type = buildTypeReferenceFromAST(fieldNode.type, field.schema());
|
|
383
|
-
field.type =
|
|
426
|
+
field.type = validateInputType(type, field.coordinate, fieldNode, errors);
|
|
384
427
|
field.defaultValue = buildValue(fieldNode.defaultValue);
|
|
385
|
-
buildAppliedDirectives(fieldNode, field);
|
|
428
|
+
buildAppliedDirectives(fieldNode, field, errors);
|
|
386
429
|
field.description = fieldNode.description?.value;
|
|
387
430
|
field.sourceAST = fieldNode;
|
|
388
431
|
}
|
|
389
432
|
|
|
390
|
-
function buildDirectiveDefinitionInner(
|
|
433
|
+
function buildDirectiveDefinitionInner(
|
|
434
|
+
directiveNode: DirectiveDefinitionNode,
|
|
435
|
+
directive: DirectiveDefinition,
|
|
436
|
+
errors: GraphQLError[],
|
|
437
|
+
) {
|
|
391
438
|
for (const inputValueDef of directiveNode.arguments ?? []) {
|
|
392
|
-
buildArgumentDefinitionInner(inputValueDef, directive.addArgument(inputValueDef.name.value));
|
|
439
|
+
buildArgumentDefinitionInner(inputValueDef, directive.addArgument(inputValueDef.name.value), errors);
|
|
393
440
|
}
|
|
394
441
|
directive.repeatable = directiveNode.repeatable;
|
|
395
442
|
const locations = directiveNode.locations.map(({ value }) => value as DirectiveLocation);
|