@apollo/federation-internals 2.0.0-preview.9 → 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 +25 -0
- package/dist/coreSpec.d.ts +1 -1
- package/dist/coreSpec.d.ts.map +1 -1
- package/dist/coreSpec.js +34 -11
- package/dist/coreSpec.js.map +1 -1
- package/dist/definitions.d.ts +20 -8
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +97 -58
- package/dist/definitions.js.map +1 -1
- package/dist/directiveAndTypeSpecification.d.ts.map +1 -1
- package/dist/directiveAndTypeSpecification.js +10 -1
- package/dist/directiveAndTypeSpecification.js.map +1 -1
- package/dist/error.d.ts +10 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +22 -2
- package/dist/error.js.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.js +1 -1
- package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
- package/dist/federation.d.ts +4 -1
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +33 -26
- package/dist/federation.js.map +1 -1
- package/dist/federationSpec.js +1 -1
- package/dist/federationSpec.js.map +1 -1
- package/dist/inaccessibleSpec.d.ts +7 -3
- package/dist/inaccessibleSpec.d.ts.map +1 -1
- package/dist/inaccessibleSpec.js +622 -32
- package/dist/inaccessibleSpec.js.map +1 -1
- package/dist/precompute.d.ts.map +1 -1
- package/dist/precompute.js +2 -2
- package/dist/precompute.js.map +1 -1
- package/dist/schemaUpgrader.d.ts.map +1 -1
- package/dist/schemaUpgrader.js +16 -5
- package/dist/schemaUpgrader.js.map +1 -1
- package/dist/supergraphs.d.ts.map +1 -1
- package/dist/supergraphs.js +1 -0
- package/dist/supergraphs.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 +3 -3
- package/src/__tests__/coreSpec.test.ts +112 -0
- package/src/__tests__/removeInaccessibleElements.test.ts +2216 -177
- package/src/__tests__/subgraphValidation.test.ts +22 -5
- package/src/__tests__/values.test.ts +315 -3
- package/src/coreSpec.ts +70 -16
- package/src/definitions.ts +201 -83
- package/src/directiveAndTypeSpecification.ts +18 -1
- package/src/error.ts +62 -2
- package/src/extractSubgraphsFromSupergraph.ts +1 -1
- package/src/federation.ts +36 -26
- package/src/federationSpec.ts +2 -2
- package/src/inaccessibleSpec.ts +973 -55
- package/src/precompute.ts +2 -4
- package/src/schemaUpgrader.ts +25 -6
- package/src/supergraphs.ts +1 -0
- package/src/validate.ts +20 -9
- package/src/values.ts +39 -12
- package/tsconfig.test.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -9,7 +9,7 @@ import './matchers';
|
|
|
9
9
|
// Builds the provided subgraph (using name 'S' for the subgraph) and, if the
|
|
10
10
|
// subgraph is invalid/has errors, return those errors as a list of [code, message].
|
|
11
11
|
// If the subgraph is valid, return undefined.
|
|
12
|
-
function buildForErrors(
|
|
12
|
+
export function buildForErrors(
|
|
13
13
|
subgraphDefs: DocumentNode,
|
|
14
14
|
options?: {
|
|
15
15
|
subgraphName?: string,
|
|
@@ -557,7 +557,7 @@ describe('@core/@link handling', () => {
|
|
|
557
557
|
|
|
558
558
|
directive @federation__shareable on OBJECT | FIELD_DEFINITION
|
|
559
559
|
|
|
560
|
-
directive @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION
|
|
560
|
+
directive @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
|
|
561
561
|
|
|
562
562
|
directive @federation__override(from: String!) on FIELD_DEFINITION
|
|
563
563
|
|
|
@@ -593,7 +593,7 @@ describe('@core/@link handling', () => {
|
|
|
593
593
|
|
|
594
594
|
type Query {
|
|
595
595
|
_entities(representations: [_Any!]!): [_Entity]!
|
|
596
|
-
_service: _Service
|
|
596
|
+
_service: _Service!
|
|
597
597
|
}
|
|
598
598
|
`
|
|
599
599
|
const validateFullSchema = (subgraph: Subgraph) => {
|
|
@@ -833,12 +833,12 @@ describe('@core/@link handling', () => {
|
|
|
833
833
|
k: ID!
|
|
834
834
|
}
|
|
835
835
|
|
|
836
|
-
directive @key(fields:
|
|
836
|
+
directive @key(fields: String!, resolvable: String) repeatable on OBJECT | INTERFACE
|
|
837
837
|
`;
|
|
838
838
|
|
|
839
839
|
expect(buildForErrors(doc, { asFed2: false })).toStrictEqual([[
|
|
840
840
|
'DIRECTIVE_DEFINITION_INVALID',
|
|
841
|
-
'[S] Invalid definition for directive "@key": argument "
|
|
841
|
+
'[S] Invalid definition for directive "@key": argument "resolvable" should have type "Boolean" but found type "String"',
|
|
842
842
|
]]);
|
|
843
843
|
});
|
|
844
844
|
|
|
@@ -861,4 +861,21 @@ describe('@core/@link handling', () => {
|
|
|
861
861
|
'[S] Invalid definition for directive "@key": argument "fields" should have type "federation__FieldSet!" but found type "federation__FieldSet"',
|
|
862
862
|
]]);
|
|
863
863
|
});
|
|
864
|
+
|
|
865
|
+
it('allows any (non-scalar) type in redefinition when expected type is a scalar', () => {
|
|
866
|
+
const doc = gql`
|
|
867
|
+
extend schema
|
|
868
|
+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
869
|
+
|
|
870
|
+
type T @key(fields: "k") {
|
|
871
|
+
k: ID!
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
# 'fields' should be of type 'federation_FieldSet!', but ensure we allow 'String!' alternatively.
|
|
875
|
+
directive @key(fields: String!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
|
|
876
|
+
`;
|
|
877
|
+
|
|
878
|
+
// Just making sure this don't error out.
|
|
879
|
+
buildAndValidate(doc);
|
|
880
|
+
});
|
|
864
881
|
});
|
|
@@ -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/coreSpec.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ASTNode, DirectiveLocation, GraphQLError, StringValueNode } from "graphql";
|
|
2
2
|
import { URL } from "url";
|
|
3
|
-
import { CoreFeature, Directive, DirectiveDefinition, EnumType, ErrGraphQLValidationFailed, InputType, ListType, NamedType, NonNullType, ScalarType, Schema, SchemaDefinition } from "./definitions";
|
|
3
|
+
import { CoreFeature, Directive, DirectiveDefinition, EnumType, ErrGraphQLAPISchemaValidationFailed, ErrGraphQLValidationFailed, InputType, ListType, NamedType, NonNullType, ScalarType, Schema, SchemaDefinition, SchemaElement } from "./definitions";
|
|
4
4
|
import { sameType } from "./types";
|
|
5
5
|
import { err } from '@apollo/core-schema';
|
|
6
6
|
import { assert } from './utils';
|
|
@@ -706,20 +706,74 @@ export const LINK_VERSIONS = new FeatureDefinitions<CoreSpecDefinition>(linkIden
|
|
|
706
706
|
registerKnownFeature(CORE_VERSIONS);
|
|
707
707
|
registerKnownFeature(LINK_VERSIONS);
|
|
708
708
|
|
|
709
|
-
export function
|
|
710
|
-
//
|
|
711
|
-
//
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
709
|
+
export function removeAllCoreFeatures(schema: Schema) {
|
|
710
|
+
// Gather a list of core features up front, since we can't fetch them during
|
|
711
|
+
// removal. (Also note that core being a feature itself, this will remove core
|
|
712
|
+
// itself and mark the schema as 'not core').
|
|
713
|
+
const coreFeatures = [...(schema.coreFeatures?.allFeatures() ?? [])];
|
|
714
|
+
|
|
715
|
+
// Remove all feature elements, keeping track of any type references found
|
|
716
|
+
// along the way.
|
|
717
|
+
const typeReferences: {
|
|
718
|
+
feature: CoreFeature;
|
|
719
|
+
type: NamedType;
|
|
720
|
+
references: SchemaElement<any, any>[];
|
|
721
|
+
}[] = [];
|
|
722
|
+
for (const feature of coreFeatures) {
|
|
723
|
+
// Remove feature directive definitions and their applications.
|
|
724
|
+
const featureDirectiveDefs = schema.directives()
|
|
725
|
+
.filter(d => feature.isFeatureDefinition(d));
|
|
726
|
+
featureDirectiveDefs.forEach(def =>
|
|
727
|
+
def.remove().forEach(application => application.remove())
|
|
728
|
+
);
|
|
729
|
+
|
|
730
|
+
// Remove feature types.
|
|
731
|
+
const featureTypes = schema.types()
|
|
732
|
+
.filter(t => feature.isFeatureDefinition(t));
|
|
733
|
+
featureTypes.forEach(type => {
|
|
734
|
+
const references = type.remove();
|
|
735
|
+
if (references.length > 0) {
|
|
736
|
+
typeReferences.push({
|
|
737
|
+
feature,
|
|
738
|
+
type,
|
|
739
|
+
references,
|
|
740
|
+
});
|
|
723
741
|
}
|
|
724
|
-
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// Now that we're finished with removals, for any referencers encountered,
|
|
746
|
+
// check whether they're still attached to the schema (and fail if they are).
|
|
747
|
+
//
|
|
748
|
+
// We wait for after all removals are done, since it means we don't have to
|
|
749
|
+
// worry about the ordering of removals (e.g. if one feature element refers
|
|
750
|
+
// to a different feature's element) or any circular references.
|
|
751
|
+
//
|
|
752
|
+
// Note that we fail for ALL type referencers, regardless of whether removing
|
|
753
|
+
// the type necessitates removal of the type referencer. E.g. even if some
|
|
754
|
+
// non-core object type were to implement some core feature interface type, we
|
|
755
|
+
// would still require removal of the non-core object type. Users don't have
|
|
756
|
+
// to enact this removal by removing the object type from their supergraph
|
|
757
|
+
// schema though; they could also just mark it @inaccessible (since this
|
|
758
|
+
// function is called after removeInaccessibleElements()).
|
|
759
|
+
//
|
|
760
|
+
// In the future, we could potentially relax this validation once we determine
|
|
761
|
+
// the appropriate semantics. (This validation has already been relaxed for
|
|
762
|
+
// directive applications, since feature directive definition removal does not
|
|
763
|
+
// necessitate removal of elements with directive applications.)
|
|
764
|
+
const errors: GraphQLError[] = [];
|
|
765
|
+
for (const { feature, type, references } of typeReferences) {
|
|
766
|
+
const referencesInSchema = references.filter(r => r.isAttached());
|
|
767
|
+
if (referencesInSchema.length > 0) {
|
|
768
|
+
errors.push(new GraphQLError(
|
|
769
|
+
`Cannot remove elements of feature ${feature} as feature type ${type}` +
|
|
770
|
+
` is referenced by elements: ${referencesInSchema.join(', ')}`,
|
|
771
|
+
references.map(r => r.sourceAST)
|
|
772
|
+
.filter(n => n !== undefined) as ASTNode[]
|
|
773
|
+
));
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
if (errors.length > 0) {
|
|
777
|
+
throw ErrGraphQLAPISchemaValidationFailed(errors);
|
|
778
|
+
}
|
|
725
779
|
}
|