@apollo/federation-internals 2.0.0-alpha.0 → 2.0.0-alpha.4
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 +18 -0
- package/dist/buildSchema.d.ts.map +1 -1
- package/dist/buildSchema.js +3 -3
- package/dist/buildSchema.js.map +1 -1
- package/dist/coreSpec.d.ts +1 -1
- package/dist/coreSpec.d.ts.map +1 -1
- package/dist/coreSpec.js +2 -0
- package/dist/coreSpec.js.map +1 -1
- package/dist/debug.d.ts.map +1 -1
- package/dist/debug.js +7 -23
- package/dist/debug.js.map +1 -1
- package/dist/definitions.d.ts +28 -13
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +132 -71
- package/dist/definitions.js.map +1 -1
- package/dist/error.d.ts +87 -3
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +143 -5
- package/dist/error.js.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.js +60 -8
- package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
- package/dist/federation.d.ts +7 -6
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +227 -81
- package/dist/federation.js.map +1 -1
- package/dist/genErrorCodeDoc.d.ts +2 -0
- package/dist/genErrorCodeDoc.d.ts.map +1 -0
- package/dist/genErrorCodeDoc.js +55 -0
- package/dist/genErrorCodeDoc.js.map +1 -0
- package/dist/inaccessibleSpec.d.ts +1 -1
- package/dist/inaccessibleSpec.d.ts.map +1 -1
- package/dist/inaccessibleSpec.js +5 -5
- package/dist/inaccessibleSpec.js.map +1 -1
- package/dist/joinSpec.d.ts.map +1 -1
- package/dist/joinSpec.js +6 -5
- package/dist/joinSpec.js.map +1 -1
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +16 -16
- package/dist/operations.js.map +1 -1
- package/dist/print.d.ts +1 -1
- package/dist/print.d.ts.map +1 -1
- package/dist/print.js +4 -4
- package/dist/print.js.map +1 -1
- package/dist/suggestions.js +1 -1
- package/dist/suggestions.js.map +1 -1
- package/dist/tagSpec.d.ts +2 -2
- package/dist/tagSpec.d.ts.map +1 -1
- package/dist/tagSpec.js +10 -2
- package/dist/tagSpec.js.map +1 -1
- package/dist/utils.d.ts +16 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +82 -1
- package/dist/utils.js.map +1 -1
- package/dist/validate.js.map +1 -1
- package/dist/values.d.ts +2 -1
- package/dist/values.d.ts.map +1 -1
- package/dist/values.js +29 -4
- package/dist/values.js.map +1 -1
- package/package.json +5 -6
- package/src/__tests__/definitions.test.ts +3 -3
- package/src/__tests__/extractSubgraphsFromSupergraph.test.ts +432 -0
- package/src/__tests__/matchers/toMatchString.ts +2 -2
- package/src/__tests__/removeInaccessibleElements.test.ts +8 -8
- package/src/__tests__/subgraphValidation.test.ts +452 -0
- package/src/__tests__/utils.test.ts +92 -0
- package/src/buildSchema.ts +12 -11
- package/src/coreSpec.ts +12 -10
- package/src/debug.ts +8 -25
- package/src/definitions.ts +249 -115
- package/src/error.ts +334 -7
- package/src/extractSubgraphsFromSupergraph.ts +80 -19
- package/src/federation.ts +299 -138
- package/src/genErrorCodeDoc.ts +69 -0
- package/src/inaccessibleSpec.ts +13 -8
- package/src/joinSpec.ts +11 -8
- package/src/operations.ts +40 -38
- package/src/print.ts +8 -8
- package/src/suggestions.ts +1 -1
- package/src/tagSpec.ts +12 -7
- package/src/types.ts +1 -1
- package/src/utils.ts +109 -0
- package/src/validate.ts +4 -4
- package/src/values.ts +51 -9
- package/tsconfig.test.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import { DocumentNode } from 'graphql';
|
|
2
|
+
import gql from 'graphql-tag';
|
|
3
|
+
import { errorCauses } from '..';
|
|
4
|
+
import { buildSubgraph } from "../federation"
|
|
5
|
+
|
|
6
|
+
// Builds the provided subgraph (using name 'S' for the subgraph) and, if the
|
|
7
|
+
// subgraph is invalid/has errors, return those errors as a list of [code, message].
|
|
8
|
+
// If the subgraph is valid, return undefined.
|
|
9
|
+
function buildForErrors(subgraphDefs: DocumentNode, subgraphName: string = 'S'): [string, string][] | undefined {
|
|
10
|
+
try {
|
|
11
|
+
buildSubgraph(subgraphName, subgraphDefs);
|
|
12
|
+
return undefined;
|
|
13
|
+
} catch (e) {
|
|
14
|
+
const causes = errorCauses(e);
|
|
15
|
+
if (!causes) {
|
|
16
|
+
throw e;
|
|
17
|
+
}
|
|
18
|
+
return causes.map((err) => [err.extensions.code as string, err.message]);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('fieldset-based directives', () => {
|
|
23
|
+
it('rejects field defined with arguments in @key', () => {
|
|
24
|
+
const subgraph = gql`
|
|
25
|
+
type Query {
|
|
26
|
+
t: T
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type T @key(fields: "f") {
|
|
30
|
+
f(x: Int): Int
|
|
31
|
+
}
|
|
32
|
+
`
|
|
33
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
34
|
+
['KEY_FIELDS_HAS_ARGS', '[S] On type "T", for @key(fields: "f"): field T.f cannot be included because it has arguments (fields with argument are not allowed in @key)']
|
|
35
|
+
]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('rejects field defined with arguments in @provides', () => {
|
|
39
|
+
const subgraph = gql`
|
|
40
|
+
type Query {
|
|
41
|
+
t: T @provides(fields: "f")
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type T {
|
|
45
|
+
f(x: Int): Int @external
|
|
46
|
+
}
|
|
47
|
+
`
|
|
48
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
49
|
+
['PROVIDES_FIELDS_HAS_ARGS', '[S] On field "Query.t", for @provides(fields: "f"): field T.f cannot be included because it has arguments (fields with argument are not allowed in @provides)']
|
|
50
|
+
]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('rejects field defined with arguments in @requires', () => {
|
|
54
|
+
const subgraph = gql`
|
|
55
|
+
type Query {
|
|
56
|
+
t: T
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type T {
|
|
60
|
+
f(x: Int): Int @external
|
|
61
|
+
g: Int @requires(fields: "f")
|
|
62
|
+
}
|
|
63
|
+
`
|
|
64
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
65
|
+
['REQUIRES_FIELDS_HAS_ARGS', '[S] On field "T.g", for @requires(fields: "f"): field T.f cannot be included because it has arguments (fields with argument are not allowed in @requires)']
|
|
66
|
+
]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('rejects @provides on non-external fields', () => {
|
|
70
|
+
const subgraph = gql`
|
|
71
|
+
type Query {
|
|
72
|
+
t: T @provides(fields: "f")
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
type T {
|
|
76
|
+
f: Int
|
|
77
|
+
}
|
|
78
|
+
`
|
|
79
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
80
|
+
['PROVIDES_FIELDS_MISSING_EXTERNAL', '[S] On field "Query.t", for @provides(fields: "f"): field "T.f" should not be part of a @provides since it is already provided by this subgraph (it is not marked @external)']
|
|
81
|
+
]);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('rejects @requires on non-external fields', () => {
|
|
85
|
+
const subgraph = gql`
|
|
86
|
+
type Query {
|
|
87
|
+
t: T
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
type T {
|
|
91
|
+
f: Int
|
|
92
|
+
g: Int @requires(fields: "f")
|
|
93
|
+
}
|
|
94
|
+
`
|
|
95
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
96
|
+
['REQUIRES_FIELDS_MISSING_EXTERNAL', '[S] On field "T.g", for @requires(fields: "f"): field "T.f" should not be part of a @requires since it is already provided by this subgraph (it is not marked @external)']
|
|
97
|
+
]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('rejects @key on interfaces', () => {
|
|
101
|
+
const subgraph = gql`
|
|
102
|
+
type Query {
|
|
103
|
+
t: T
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
interface T @key(fields: "f") {
|
|
107
|
+
f: Int
|
|
108
|
+
}
|
|
109
|
+
`
|
|
110
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
111
|
+
['KEY_UNSUPPORTED_ON_INTERFACE', '[S] Cannot use @key on interface "T": @key is not yet supported on interfaces'],
|
|
112
|
+
]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('rejects @provides on interfaces', () => {
|
|
116
|
+
const subgraph = gql`
|
|
117
|
+
type Query {
|
|
118
|
+
t: T
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
interface T {
|
|
122
|
+
f: U @provides(fields: "g")
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
type U {
|
|
126
|
+
g: Int @external
|
|
127
|
+
}
|
|
128
|
+
`
|
|
129
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
130
|
+
['PROVIDES_UNSUPPORTED_ON_INTERFACE', '[S] Cannot use @provides on field "T.f" of parent type "T": @provides is not yet supported within interfaces'],
|
|
131
|
+
]);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('rejects @requires on interfaces', () => {
|
|
135
|
+
const subgraph = gql`
|
|
136
|
+
type Query {
|
|
137
|
+
t: T
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
interface T {
|
|
141
|
+
f: Int @external
|
|
142
|
+
g: Int @requires(fields: "f")
|
|
143
|
+
}
|
|
144
|
+
`
|
|
145
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
146
|
+
['REQUIRES_UNSUPPORTED_ON_INTERFACE', '[S] Cannot use @requires on field "T.g" of parent type "T": @requires is not yet supported within interfaces' ],
|
|
147
|
+
]);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('rejects unused @external', () => {
|
|
151
|
+
const subgraph = gql`
|
|
152
|
+
type Query {
|
|
153
|
+
t: T
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
type T {
|
|
157
|
+
f: Int @external
|
|
158
|
+
}
|
|
159
|
+
`
|
|
160
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
161
|
+
['EXTERNAL_UNUSED', '[S] Field "T.f" is marked @external but is not used in any federation directive (@key, @provides, @requires) or to satisfy an interface; the field declaration has no use and should be removed (or the field should not be @external).'],
|
|
162
|
+
]);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('rejects @provides on non-object fields', () => {
|
|
166
|
+
const subgraph = gql`
|
|
167
|
+
type Query {
|
|
168
|
+
t: Int @provides(fields: "f")
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
type T {
|
|
172
|
+
f: Int
|
|
173
|
+
}
|
|
174
|
+
`
|
|
175
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
176
|
+
['PROVIDES_ON_NON_OBJECT_FIELD', '[S] Invalid @provides directive on field "Query.t": field has type "Int" which is not a Composite Type'],
|
|
177
|
+
]);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('rejects a non-string argument to @key', () => {
|
|
181
|
+
const subgraph = gql`
|
|
182
|
+
type Query {
|
|
183
|
+
t: T
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
type T @key(fields: ["f"]) {
|
|
187
|
+
f: Int
|
|
188
|
+
}
|
|
189
|
+
`
|
|
190
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
191
|
+
['KEY_INVALID_FIELDS_TYPE', '[S] On type "T", for @key(fields: ["f"]): Invalid value for argument "fields": must be a string.'],
|
|
192
|
+
]);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('rejects a non-string argument to @provides', () => {
|
|
196
|
+
const subgraph = gql`
|
|
197
|
+
type Query {
|
|
198
|
+
t: T @provides(fields: ["f"])
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
type T {
|
|
202
|
+
f: Int @external
|
|
203
|
+
}
|
|
204
|
+
`
|
|
205
|
+
// Note: since the error here is that we cannot parse the key `fields`, this also mean that @external on
|
|
206
|
+
// `f` will appear unused and we get an error for it. It's kind of hard to avoid cleanly and hopefully
|
|
207
|
+
// not a big deal (having errors dependencies is not exactly unheard of).
|
|
208
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
209
|
+
['PROVIDES_INVALID_FIELDS_TYPE', '[S] On field "Query.t", for @provides(fields: ["f"]): Invalid value for argument "fields": must be a string.'],
|
|
210
|
+
['EXTERNAL_UNUSED', '[S] Field "T.f" is marked @external but is not used in any federation directive (@key, @provides, @requires) or to satisfy an interface; the field declaration has no use and should be removed (or the field should not be @external).' ],
|
|
211
|
+
]);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('rejects a non-string argument to @requires', () => {
|
|
215
|
+
const subgraph = gql`
|
|
216
|
+
type Query {
|
|
217
|
+
t: T
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
type T {
|
|
221
|
+
f: Int @external
|
|
222
|
+
g: Int @requires(fields: ["f"])
|
|
223
|
+
}
|
|
224
|
+
`
|
|
225
|
+
// Note: since the error here is that we cannot parse the key `fields`, this also mean that @external on
|
|
226
|
+
// `f` will appear unused and we get an error for it. It's kind of hard to avoid cleanly and hopefully
|
|
227
|
+
// not a big deal (having errors dependencies is not exactly unheard of).
|
|
228
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
229
|
+
['REQUIRES_INVALID_FIELDS_TYPE', '[S] On field "T.g", for @requires(fields: ["f"]): Invalid value for argument "fields": must be a string.'],
|
|
230
|
+
['EXTERNAL_UNUSED', '[S] Field "T.f" is marked @external but is not used in any federation directive (@key, @provides, @requires) or to satisfy an interface; the field declaration has no use and should be removed (or the field should not be @external).' ],
|
|
231
|
+
]);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Special case of non-string argument, specialized because it hits a different
|
|
235
|
+
// code-path due to enum values being parsed as string and requiring special care.
|
|
236
|
+
it('rejects an enum-like argument to @key', () => {
|
|
237
|
+
const subgraph = gql`
|
|
238
|
+
type Query {
|
|
239
|
+
t: T
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
type T @key(fields: f) {
|
|
243
|
+
f: Int
|
|
244
|
+
}
|
|
245
|
+
`
|
|
246
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
247
|
+
['KEY_INVALID_FIELDS_TYPE', '[S] On type "T", for @key(fields: f): Invalid value for argument "fields": must be a string.'],
|
|
248
|
+
]);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Special case of non-string argument, specialized because it hits a different
|
|
252
|
+
// code-path due to enum values being parsed as string and requiring special care.
|
|
253
|
+
it('rejects an enum-lik argument to @provides', () => {
|
|
254
|
+
const subgraph = gql`
|
|
255
|
+
type Query {
|
|
256
|
+
t: T @provides(fields: f)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
type T {
|
|
260
|
+
f: Int @external
|
|
261
|
+
}
|
|
262
|
+
`
|
|
263
|
+
// Note: since the error here is that we cannot parse the key `fields`, this also mean that @external on
|
|
264
|
+
// `f` will appear unused and we get an error for it. It's kind of hard to avoid cleanly and hopefully
|
|
265
|
+
// not a big deal (having errors dependencies is not exactly unheard of).
|
|
266
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
267
|
+
['PROVIDES_INVALID_FIELDS_TYPE', '[S] On field "Query.t", for @provides(fields: f): Invalid value for argument "fields": must be a string.'],
|
|
268
|
+
['EXTERNAL_UNUSED', '[S] Field "T.f" is marked @external but is not used in any federation directive (@key, @provides, @requires) or to satisfy an interface; the field declaration has no use and should be removed (or the field should not be @external).' ],
|
|
269
|
+
]);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Special case of non-string argument, specialized because it hits a different
|
|
273
|
+
// code-path due to enum values being parsed as string and requiring special care.
|
|
274
|
+
it('rejects an enum-like argument to @requires', () => {
|
|
275
|
+
const subgraph = gql`
|
|
276
|
+
type Query {
|
|
277
|
+
t: T
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
type T {
|
|
281
|
+
f: Int @external
|
|
282
|
+
g: Int @requires(fields: f)
|
|
283
|
+
}
|
|
284
|
+
`
|
|
285
|
+
// Note: since the error here is that we cannot parse the key `fields`, this also mean that @external on
|
|
286
|
+
// `f` will appear unused and we get an error for it. It's kind of hard to avoid cleanly and hopefully
|
|
287
|
+
// not a big deal (having errors dependencies is not exactly unheard of).
|
|
288
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
289
|
+
['REQUIRES_INVALID_FIELDS_TYPE', '[S] On field "T.g", for @requires(fields: f): Invalid value for argument "fields": must be a string.'],
|
|
290
|
+
['EXTERNAL_UNUSED', '[S] Field "T.f" is marked @external but is not used in any federation directive (@key, @provides, @requires) or to satisfy an interface; the field declaration has no use and should be removed (or the field should not be @external).' ],
|
|
291
|
+
]);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('rejects an invalid `fields` argument to @key', () => {
|
|
295
|
+
const subgraph = gql`
|
|
296
|
+
type Query {
|
|
297
|
+
t: T
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
type T @key(fields: ":f") {
|
|
301
|
+
f: Int
|
|
302
|
+
}
|
|
303
|
+
`
|
|
304
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
305
|
+
['KEY_INVALID_FIELDS', '[S] On type "T", for @key(fields: ":f"): Syntax Error: Expected Name, found ":".'],
|
|
306
|
+
]);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('rejects an invalid `fields` argument to @provides', () => {
|
|
310
|
+
const subgraph = gql`
|
|
311
|
+
type Query {
|
|
312
|
+
t: T @provides(fields: "{{f}}")
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
type T {
|
|
316
|
+
f: Int @external
|
|
317
|
+
}
|
|
318
|
+
`
|
|
319
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
320
|
+
['PROVIDES_INVALID_FIELDS', '[S] On field "Query.t", for @provides(fields: "{{f}}"): Syntax Error: Expected Name, found "{".'],
|
|
321
|
+
['EXTERNAL_UNUSED', '[S] Field "T.f" is marked @external but is not used in any federation directive (@key, @provides, @requires) or to satisfy an interface; the field declaration has no use and should be removed (or the field should not be @external).' ],
|
|
322
|
+
]);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('rejects an invalid `fields` argument to @requires', () => {
|
|
326
|
+
const subgraph = gql`
|
|
327
|
+
type Query {
|
|
328
|
+
t: T
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
type T {
|
|
332
|
+
f: Int @external
|
|
333
|
+
g: Int @requires(fields: "f b")
|
|
334
|
+
}
|
|
335
|
+
`
|
|
336
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
337
|
+
['REQUIRES_INVALID_FIELDS', '[S] On field "T.g", for @requires(fields: "f b"): Cannot query field "b" on type "T" (if the field is defined in another subgraph, you need to add it to this subgraph with @external).'],
|
|
338
|
+
['EXTERNAL_UNUSED', '[S] Field "T.f" is marked @external but is not used in any federation directive (@key, @provides, @requires) or to satisfy an interface; the field declaration has no use and should be removed (or the field should not be @external).' ],
|
|
339
|
+
]);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('rejects @key on a list field', () => {
|
|
343
|
+
const subgraph = gql`
|
|
344
|
+
type Query {
|
|
345
|
+
t: T
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
type T @key(fields: "f") {
|
|
349
|
+
f: [Int]
|
|
350
|
+
}
|
|
351
|
+
`
|
|
352
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
353
|
+
['KEY_FIELDS_SELECT_INVALID_TYPE', '[S] On type "T", for @key(fields: "f"): field "T.f" is a List type which is not allowed in @key'],
|
|
354
|
+
]);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('rejects @key on an interface field', () => {
|
|
358
|
+
const subgraph = gql`
|
|
359
|
+
type Query {
|
|
360
|
+
t: T
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
type T @key(fields: "f") {
|
|
364
|
+
f: I
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
interface I {
|
|
368
|
+
i: Int
|
|
369
|
+
}
|
|
370
|
+
`
|
|
371
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
372
|
+
['KEY_FIELDS_SELECT_INVALID_TYPE', '[S] On type "T", for @key(fields: "f"): field "T.f" is a Interface type which is not allowed in @key'],
|
|
373
|
+
]);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it('rejects @key on an union field', () => {
|
|
377
|
+
const subgraph = gql`
|
|
378
|
+
type Query {
|
|
379
|
+
t: T
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
type T @key(fields: "f") {
|
|
383
|
+
f: U
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
union U = Query | T
|
|
387
|
+
`
|
|
388
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
389
|
+
['KEY_FIELDS_SELECT_INVALID_TYPE', '[S] On type "T", for @key(fields: "f"): field "T.f" is a Union type which is not allowed in @key'],
|
|
390
|
+
]);
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
describe('root types', () => {
|
|
395
|
+
it('rejects using Query as type name if not the query root', () => {
|
|
396
|
+
const subgraph = gql`
|
|
397
|
+
schema {
|
|
398
|
+
query: MyQuery
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
type MyQuery {
|
|
402
|
+
f: Int
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
type Query {
|
|
406
|
+
g: Int
|
|
407
|
+
}
|
|
408
|
+
`
|
|
409
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
410
|
+
['ROOT_QUERY_USED', '[S] The schema has a type named "Query" but it is not set as the query root type ("MyQuery" is instead): this is not supported by federation. If a root type does not use its default name, there should be no other type with that default name.'],
|
|
411
|
+
]);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it('rejects using Mutation as type name if not the mutation root', () => {
|
|
415
|
+
const subgraph = gql`
|
|
416
|
+
schema {
|
|
417
|
+
mutation: MyMutation
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
type MyMutation {
|
|
421
|
+
f: Int
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
type Mutation {
|
|
425
|
+
g: Int
|
|
426
|
+
}
|
|
427
|
+
`
|
|
428
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
429
|
+
['ROOT_MUTATION_USED', '[S] The schema has a type named "Mutation" but it is not set as the mutation root type ("MyMutation" is instead): this is not supported by federation. If a root type does not use its default name, there should be no other type with that default name.'],
|
|
430
|
+
]);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it('rejects using Subscription as type name if not the subscription root', () => {
|
|
434
|
+
const subgraph = gql`
|
|
435
|
+
schema {
|
|
436
|
+
subscription: MySubscription
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
type MySubscription {
|
|
440
|
+
f: Int
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
type Subscription {
|
|
444
|
+
g: Int
|
|
445
|
+
}
|
|
446
|
+
`
|
|
447
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
448
|
+
['ROOT_SUBSCRIPTION_USED', '[S] The schema has a type named "Subscription" but it is not set as the subscription root type ("MySubscription" is instead): this is not supported by federation. If a root type does not use its default name, there should be no other type with that default name.'],
|
|
449
|
+
]);
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { OrderedMap } from '../utils';
|
|
2
|
+
|
|
3
|
+
describe('OrderedMap', () => {
|
|
4
|
+
it('updating value works', () => {
|
|
5
|
+
const orderedMap = new OrderedMap<string, number>();
|
|
6
|
+
orderedMap.add('one', 0);
|
|
7
|
+
expect(orderedMap.get('one')).toBe(0);
|
|
8
|
+
expect(orderedMap.size).toBe(1);
|
|
9
|
+
|
|
10
|
+
orderedMap.add('one', 1);
|
|
11
|
+
expect(orderedMap.get('one')).toBe(1);
|
|
12
|
+
expect(orderedMap.size).toBe(1);
|
|
13
|
+
expect(orderedMap.keys()).toEqual(['one']);
|
|
14
|
+
expect(orderedMap.values()).toEqual([1]);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('test lexicographical sorting of map (default sorting algorithm)', () => {
|
|
18
|
+
const orderedMap = new OrderedMap<string, number>();
|
|
19
|
+
orderedMap.add('one', 1);
|
|
20
|
+
orderedMap.add('two', 2);
|
|
21
|
+
orderedMap.add('three', 3);
|
|
22
|
+
orderedMap.add('four', 4);
|
|
23
|
+
orderedMap.add('five', 5);
|
|
24
|
+
orderedMap.add('six', 6);
|
|
25
|
+
orderedMap.add('seven', 7);
|
|
26
|
+
orderedMap.add('eight', 8);
|
|
27
|
+
orderedMap.add('nine', 9);
|
|
28
|
+
|
|
29
|
+
// keys are in alphabetical order
|
|
30
|
+
expect(orderedMap.keys()).toEqual(['eight', 'five', 'four', 'nine', 'one', 'seven', 'six', 'three', 'two']);
|
|
31
|
+
const sortedArr = [8,5,4,9,1,7,6,3,2];
|
|
32
|
+
expect(orderedMap.values()).toEqual(sortedArr);
|
|
33
|
+
|
|
34
|
+
// test using spread operator to make sure iterator is performing correctly
|
|
35
|
+
expect([...orderedMap]).toEqual(sortedArr);
|
|
36
|
+
|
|
37
|
+
// testing get function
|
|
38
|
+
expect(orderedMap.get('one')).toBe(1);
|
|
39
|
+
expect(orderedMap.get('ten')).toBeUndefined();
|
|
40
|
+
|
|
41
|
+
// testing size function
|
|
42
|
+
expect(orderedMap.size).toBe(9);
|
|
43
|
+
orderedMap.add('one', 1);
|
|
44
|
+
expect(orderedMap.size).toBe(9);
|
|
45
|
+
expect(orderedMap.values()).toEqual(sortedArr);
|
|
46
|
+
|
|
47
|
+
// has function
|
|
48
|
+
expect(orderedMap.has('one')).toBe(true);
|
|
49
|
+
expect(orderedMap.has('fifty')).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('sort by string length', () => {
|
|
53
|
+
const orderedMap = new OrderedMap<string, number>((a: string, b: string) => {
|
|
54
|
+
if (a.length < b.length) {
|
|
55
|
+
return -1;
|
|
56
|
+
} else if (b.length < a.length) {
|
|
57
|
+
return 1;
|
|
58
|
+
}
|
|
59
|
+
return 0;
|
|
60
|
+
});
|
|
61
|
+
orderedMap.add('eight', 8);
|
|
62
|
+
orderedMap.add('seventy', 70);
|
|
63
|
+
orderedMap.add('six', 6);
|
|
64
|
+
orderedMap.add('four', 4);
|
|
65
|
+
|
|
66
|
+
expect(orderedMap.keys()).toEqual(['six', 'four', 'eight', 'seventy']);
|
|
67
|
+
const sortedArr = [6,4,8,70];
|
|
68
|
+
expect(orderedMap.values()).toEqual(sortedArr);
|
|
69
|
+
|
|
70
|
+
// test using spread operator to make sure iterator is performing correctly
|
|
71
|
+
expect([...orderedMap]).toEqual(sortedArr);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('sort numerically', () => {
|
|
75
|
+
const orderedMap = new OrderedMap<number, number>();
|
|
76
|
+
orderedMap.add(4, 40);
|
|
77
|
+
orderedMap.add(1, 10);
|
|
78
|
+
orderedMap.add(7, 70);
|
|
79
|
+
orderedMap.add(2, 20);
|
|
80
|
+
orderedMap.add(6, 60);
|
|
81
|
+
orderedMap.add(3, 30);
|
|
82
|
+
orderedMap.add(9, 90);
|
|
83
|
+
orderedMap.add(5, 50);
|
|
84
|
+
orderedMap.add(8, 80);
|
|
85
|
+
|
|
86
|
+
const keys = [1,2,3,4,5,6,7,8,9];
|
|
87
|
+
const values = [10,20,30,40,50,60,70,80,90];
|
|
88
|
+
expect(orderedMap.keys()).toEqual(keys);
|
|
89
|
+
expect(orderedMap.values()).toEqual(values);
|
|
90
|
+
expect([...orderedMap]).toEqual(values);
|
|
91
|
+
});
|
|
92
|
+
});
|
package/src/buildSchema.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DefinitionNode,
|
|
3
3
|
DirectiveDefinitionNode,
|
|
4
|
-
|
|
4
|
+
DirectiveLocation,
|
|
5
5
|
DirectiveNode,
|
|
6
6
|
DocumentNode,
|
|
7
7
|
FieldDefinitionNode,
|
|
@@ -18,7 +18,8 @@ import {
|
|
|
18
18
|
StringValueNode,
|
|
19
19
|
ASTNode,
|
|
20
20
|
SchemaExtensionNode,
|
|
21
|
-
parseType
|
|
21
|
+
parseType,
|
|
22
|
+
Kind,
|
|
22
23
|
} from "graphql";
|
|
23
24
|
import { Maybe } from "graphql/jsutils/Maybe";
|
|
24
25
|
import {
|
|
@@ -57,7 +58,7 @@ function buildValue(value?: ValueNode): any {
|
|
|
57
58
|
// - for ID, which accepts strings and int, we don't get int converted to string.
|
|
58
59
|
// - for floats, we get either int or float, we don't get int converted to float.
|
|
59
60
|
// - we don't get any custom coercion (but neither is buildSchema in graphQL-js anyway).
|
|
60
|
-
// 2) type validation.
|
|
61
|
+
// 2) type validation.
|
|
61
62
|
return value ? valueFromASTUntyped(value) : undefined;
|
|
62
63
|
}
|
|
63
64
|
|
|
@@ -91,7 +92,7 @@ export function buildSchemaFromAST(documentNode: DocumentNode, builtIns: BuiltIn
|
|
|
91
92
|
case 'SchemaExtension':
|
|
92
93
|
buildSchemaDefinitionInner(
|
|
93
94
|
definitionNode,
|
|
94
|
-
schema.schemaDefinition,
|
|
95
|
+
schema.schemaDefinition,
|
|
95
96
|
schema.schemaDefinition.newExtension());
|
|
96
97
|
break;
|
|
97
98
|
case 'ScalarTypeDefinition':
|
|
@@ -300,7 +301,7 @@ function buildNamedTypeInner(
|
|
|
300
301
|
}
|
|
301
302
|
|
|
302
303
|
function buildFieldDefinitionInner(fieldNode: FieldDefinitionNode, field: FieldDefinition<any>) {
|
|
303
|
-
const type = buildTypeReferenceFromAST(fieldNode.type, field.schema()
|
|
304
|
+
const type = buildTypeReferenceFromAST(fieldNode.type, field.schema());
|
|
304
305
|
field.type = ensureOutputType(type, field.coordinate, fieldNode);
|
|
305
306
|
for (const inputValueDef of fieldNode.arguments ?? []) {
|
|
306
307
|
buildArgumentDefinitionInner(inputValueDef, field.addArgument(inputValueDef.name.value));
|
|
@@ -332,11 +333,11 @@ export function builtTypeReference(encodedType: string, schema: Schema): Type {
|
|
|
332
333
|
|
|
333
334
|
function buildTypeReferenceFromAST(typeNode: TypeNode, schema: Schema): Type {
|
|
334
335
|
switch (typeNode.kind) {
|
|
335
|
-
case
|
|
336
|
+
case Kind.LIST_TYPE:
|
|
336
337
|
return new ListType(buildTypeReferenceFromAST(typeNode.type, schema));
|
|
337
|
-
case
|
|
338
|
+
case Kind.NON_NULL_TYPE:
|
|
338
339
|
const wrapped = buildTypeReferenceFromAST(typeNode.type, schema);
|
|
339
|
-
if (wrapped.kind ==
|
|
340
|
+
if (wrapped.kind == Kind.NON_NULL_TYPE) {
|
|
340
341
|
throw new GraphQLError(`Cannot apply the non-null operator (!) twice to the same type`, typeNode);
|
|
341
342
|
}
|
|
342
343
|
return new NonNullType(wrapped);
|
|
@@ -346,7 +347,7 @@ function buildTypeReferenceFromAST(typeNode: TypeNode, schema: Schema): Type {
|
|
|
346
347
|
}
|
|
347
348
|
|
|
348
349
|
function buildArgumentDefinitionInner(inputNode: InputValueDefinitionNode, arg: ArgumentDefinition<any>) {
|
|
349
|
-
const type = buildTypeReferenceFromAST(inputNode.type, arg.schema()
|
|
350
|
+
const type = buildTypeReferenceFromAST(inputNode.type, arg.schema());
|
|
350
351
|
arg.type = ensureInputType(type, arg.coordinate, inputNode);
|
|
351
352
|
arg.defaultValue = buildValue(inputNode.defaultValue);
|
|
352
353
|
buildAppliedDirectives(inputNode, arg);
|
|
@@ -355,7 +356,7 @@ function buildArgumentDefinitionInner(inputNode: InputValueDefinitionNode, arg:
|
|
|
355
356
|
}
|
|
356
357
|
|
|
357
358
|
function buildInputFieldDefinitionInner(fieldNode: InputValueDefinitionNode, field: InputFieldDefinition) {
|
|
358
|
-
const type = buildTypeReferenceFromAST(fieldNode.type, field.schema()
|
|
359
|
+
const type = buildTypeReferenceFromAST(fieldNode.type, field.schema());
|
|
359
360
|
field.type = ensureInputType(type, field.coordinate, fieldNode);
|
|
360
361
|
field.defaultValue = buildValue(fieldNode.defaultValue);
|
|
361
362
|
buildAppliedDirectives(fieldNode, field);
|
|
@@ -368,7 +369,7 @@ function buildDirectiveDefinitionInner(directiveNode: DirectiveDefinitionNode, d
|
|
|
368
369
|
buildArgumentDefinitionInner(inputValueDef, directive.addArgument(inputValueDef.name.value));
|
|
369
370
|
}
|
|
370
371
|
directive.repeatable = directiveNode.repeatable;
|
|
371
|
-
const locations = directiveNode.locations.map(({ value }) => value as
|
|
372
|
+
const locations = directiveNode.locations.map(({ value }) => value as DirectiveLocation);
|
|
372
373
|
directive.addLocations(...locations);
|
|
373
374
|
directive.description = directiveNode.description?.value;
|
|
374
375
|
directive.sourceAST = directiveNode;
|