@apollo/federation-internals 2.0.6-rc.1 → 2.1.0-alpha.2
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 +7 -0
- package/dist/buildSchema.d.ts.map +1 -1
- package/dist/buildSchema.js +11 -10
- package/dist/buildSchema.js.map +1 -1
- package/dist/coreSpec.d.ts.map +1 -1
- package/dist/coreSpec.js +15 -42
- package/dist/coreSpec.js.map +1 -1
- package/dist/definitions.d.ts +5 -1
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +68 -103
- package/dist/definitions.js.map +1 -1
- package/dist/directiveAndTypeSpecification.js +14 -48
- package/dist/directiveAndTypeSpecification.js.map +1 -1
- package/dist/error.d.ts +20 -17
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +42 -7
- package/dist/error.js.map +1 -1
- package/dist/federation.d.ts +4 -1
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +77 -127
- package/dist/federation.js.map +1 -1
- package/dist/federationSpec.d.ts +13 -9
- package/dist/federationSpec.d.ts.map +1 -1
- package/dist/federationSpec.js +27 -5
- package/dist/federationSpec.js.map +1 -1
- package/dist/inaccessibleSpec.js +43 -65
- package/dist/inaccessibleSpec.js.map +1 -1
- package/dist/operations.d.ts +7 -4
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +54 -29
- package/dist/operations.js.map +1 -1
- package/dist/schemaUpgrader.js +2 -8
- package/dist/schemaUpgrader.js.map +1 -1
- package/dist/supergraphs.d.ts.map +1 -1
- package/dist/supergraphs.js +4 -4
- package/dist/supergraphs.js.map +1 -1
- package/dist/tagSpec.d.ts.map +1 -1
- package/dist/tagSpec.js +1 -3
- package/dist/tagSpec.js.map +1 -1
- package/dist/utils.d.ts +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +3 -1
- package/dist/utils.js.map +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +26 -22
- package/dist/validate.js.map +1 -1
- package/dist/validation/KnownTypeNamesInFederationRule.js +1 -1
- package/dist/validation/KnownTypeNamesInFederationRule.js.map +1 -1
- package/dist/values.d.ts.map +1 -1
- package/dist/values.js +19 -18
- package/dist/values.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/operations.test.ts +181 -99
- package/src/__tests__/subgraphValidation.test.ts +77 -0
- package/src/buildSchema.ts +11 -18
- package/src/coreSpec.ts +47 -43
- package/src/definitions.ts +86 -105
- package/src/directiveAndTypeSpecification.ts +50 -48
- package/src/error.ts +94 -41
- package/src/federation.ts +151 -135
- package/src/federationSpec.ts +32 -5
- package/src/inaccessibleSpec.ts +207 -191
- package/src/operations.ts +115 -43
- package/src/schemaUpgrader.ts +8 -8
- package/src/supergraphs.ts +5 -4
- package/src/tagSpec.ts +2 -3
- package/src/utils.ts +6 -0
- package/src/validate.ts +60 -52
- package/src/validation/KnownTypeNamesInFederationRule.ts +1 -1
- package/src/values.ts +19 -18
- package/tsconfig.test.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -13,133 +13,215 @@ function parseSchema(schema: string): Schema {
|
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
interface I {
|
|
23
|
-
b: Int
|
|
24
|
-
}
|
|
16
|
+
describe('fragments optimization', () => {
|
|
17
|
+
test('handles fragments using other fragments', () => {
|
|
18
|
+
const schema = parseSchema(`
|
|
19
|
+
type Query {
|
|
20
|
+
t: T1
|
|
21
|
+
}
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
u: U
|
|
30
|
-
}
|
|
23
|
+
interface I {
|
|
24
|
+
b: Int
|
|
25
|
+
}
|
|
31
26
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
27
|
+
type T1 {
|
|
28
|
+
a: Int
|
|
29
|
+
b: Int
|
|
30
|
+
u: U
|
|
31
|
+
}
|
|
38
32
|
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
type T2 {
|
|
34
|
+
x: String
|
|
35
|
+
y: String
|
|
36
|
+
b: Int
|
|
37
|
+
u: U
|
|
38
|
+
}
|
|
41
39
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
a
|
|
45
|
-
b
|
|
46
|
-
}
|
|
40
|
+
union U = T1 | T2
|
|
41
|
+
`);
|
|
47
42
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
43
|
+
const operation = parseOperation(schema, `
|
|
44
|
+
fragment OnT1 on T1 {
|
|
45
|
+
a
|
|
46
|
+
b
|
|
47
|
+
}
|
|
52
48
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
fragment OnT2 on T2 {
|
|
50
|
+
x
|
|
51
|
+
y
|
|
52
|
+
}
|
|
56
53
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
...OnT2
|
|
61
|
-
}
|
|
54
|
+
fragment OnI on I {
|
|
55
|
+
b
|
|
56
|
+
}
|
|
62
57
|
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
fragment OnU on U {
|
|
59
|
+
...OnI
|
|
65
60
|
...OnT1
|
|
66
61
|
...OnT2
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
query {
|
|
65
|
+
t {
|
|
66
|
+
...OnT1
|
|
67
|
+
...OnT2
|
|
68
|
+
...OnI
|
|
69
|
+
u {
|
|
70
|
+
...OnU
|
|
71
|
+
}
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
`);
|
|
75
|
+
|
|
76
|
+
const withoutFragments = parseOperation(schema, operation.toString(true, true));
|
|
77
|
+
expect(withoutFragments.toString()).toMatchString(`
|
|
78
|
+
{
|
|
79
|
+
t {
|
|
80
|
+
... on T1 {
|
|
81
|
+
a
|
|
82
|
+
b
|
|
83
|
+
}
|
|
84
|
+
... on T2 {
|
|
85
|
+
x
|
|
86
|
+
y
|
|
87
|
+
}
|
|
88
|
+
... on I {
|
|
89
|
+
b
|
|
90
|
+
}
|
|
91
|
+
u {
|
|
92
|
+
... on U {
|
|
93
|
+
... on I {
|
|
94
|
+
b
|
|
95
|
+
}
|
|
96
|
+
... on T1 {
|
|
97
|
+
a
|
|
98
|
+
b
|
|
99
|
+
}
|
|
100
|
+
... on T2 {
|
|
101
|
+
x
|
|
102
|
+
y
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
`);
|
|
109
|
+
|
|
110
|
+
const optimized = withoutFragments.optimize(operation.selectionSet.fragments!);
|
|
111
|
+
// Note that we expect onU to *not* be recreated because, by default, optimize only
|
|
112
|
+
// add add back a fragment if it is used at least twice (otherwise, the fragment just
|
|
113
|
+
// make the query bigger).
|
|
114
|
+
expect(optimized.toString()).toMatchString(`
|
|
115
|
+
fragment OnT1 on T1 {
|
|
116
|
+
a
|
|
117
|
+
b
|
|
118
|
+
}
|
|
74
119
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
120
|
+
fragment OnT2 on T2 {
|
|
121
|
+
x
|
|
122
|
+
y
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
fragment OnI on I {
|
|
126
|
+
b
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
{
|
|
130
|
+
t {
|
|
131
|
+
...OnT1
|
|
132
|
+
...OnT2
|
|
133
|
+
...OnI
|
|
134
|
+
u {
|
|
135
|
+
...OnI
|
|
136
|
+
...OnT1
|
|
137
|
+
...OnT2
|
|
138
|
+
}
|
|
82
139
|
}
|
|
83
|
-
|
|
140
|
+
}
|
|
141
|
+
`);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('handles fragments with nested selections', () => {
|
|
145
|
+
const schema = parseSchema(`
|
|
146
|
+
type Query {
|
|
147
|
+
t1a: T1
|
|
148
|
+
t2a: T1
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
type T1 {
|
|
152
|
+
t2: T2
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
type T2 {
|
|
156
|
+
x: String
|
|
157
|
+
y: String
|
|
158
|
+
}
|
|
159
|
+
`);
|
|
160
|
+
|
|
161
|
+
const operation = parseOperation(schema, `
|
|
162
|
+
fragment OnT1 on T1 {
|
|
163
|
+
t2 {
|
|
84
164
|
x
|
|
85
|
-
y
|
|
86
165
|
}
|
|
87
|
-
|
|
88
|
-
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
query {
|
|
169
|
+
t1a {
|
|
170
|
+
...OnT1
|
|
171
|
+
t2 {
|
|
172
|
+
y
|
|
173
|
+
}
|
|
89
174
|
}
|
|
90
|
-
|
|
91
|
-
...
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
175
|
+
t2a {
|
|
176
|
+
...OnT1
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
`);
|
|
180
|
+
|
|
181
|
+
const withoutFragments = parseOperation(schema, operation.toString(true, true));
|
|
182
|
+
expect(withoutFragments.toString()).toMatchString(`
|
|
183
|
+
{
|
|
184
|
+
t1a {
|
|
185
|
+
... on T1 {
|
|
186
|
+
t2 {
|
|
187
|
+
x
|
|
98
188
|
}
|
|
99
|
-
|
|
189
|
+
}
|
|
190
|
+
t2 {
|
|
191
|
+
y
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
t2a {
|
|
195
|
+
... on T1 {
|
|
196
|
+
t2 {
|
|
100
197
|
x
|
|
101
|
-
y
|
|
102
198
|
}
|
|
103
199
|
}
|
|
104
200
|
}
|
|
105
201
|
}
|
|
106
|
-
|
|
107
|
-
`);
|
|
202
|
+
`);
|
|
108
203
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
b
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
fragment OnT2 on T2 {
|
|
120
|
-
x
|
|
121
|
-
y
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
fragment OnI on I {
|
|
125
|
-
b
|
|
126
|
-
}
|
|
204
|
+
const optimized = withoutFragments.optimize(operation.selectionSet.fragments!);
|
|
205
|
+
expect(optimized.toString()).toMatchString(`
|
|
206
|
+
fragment OnT1 on T1 {
|
|
207
|
+
t2 {
|
|
208
|
+
x
|
|
209
|
+
}
|
|
210
|
+
}
|
|
127
211
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
u {
|
|
134
|
-
... on U {
|
|
135
|
-
...OnI
|
|
136
|
-
...OnT1
|
|
137
|
-
...OnT2
|
|
212
|
+
{
|
|
213
|
+
t1a {
|
|
214
|
+
...OnT1
|
|
215
|
+
t2 {
|
|
216
|
+
y
|
|
138
217
|
}
|
|
139
218
|
}
|
|
219
|
+
t2a {
|
|
220
|
+
...OnT1
|
|
221
|
+
}
|
|
140
222
|
}
|
|
141
|
-
|
|
142
|
-
|
|
223
|
+
`);
|
|
224
|
+
});
|
|
143
225
|
});
|
|
144
226
|
|
|
145
227
|
describe('selection set freezing', () => {
|
|
@@ -385,6 +385,81 @@ describe('fieldset-based directives', () => {
|
|
|
385
385
|
['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'],
|
|
386
386
|
]);
|
|
387
387
|
});
|
|
388
|
+
|
|
389
|
+
it('rejects directive applications in @key', () => {
|
|
390
|
+
const subgraph = gql`
|
|
391
|
+
type Query {
|
|
392
|
+
t: T
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
type T @key(fields: "v { x ... @include(if: false) { y }}") {
|
|
396
|
+
v: V
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
type V {
|
|
400
|
+
x: Int
|
|
401
|
+
y: Int
|
|
402
|
+
}
|
|
403
|
+
`
|
|
404
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
405
|
+
['KEY_DIRECTIVE_IN_FIELDS_ARG', '[S] On type "T", for @key(fields: "v { x ... @include(if: false) { y }}"): cannot have directive applications in the @key(fields:) argument but found @include(if: false).'],
|
|
406
|
+
]);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it('rejects directive applications in @provides', () => {
|
|
410
|
+
const subgraph = gql`
|
|
411
|
+
type Query {
|
|
412
|
+
t: T @provides(fields: "v { ... on V @skip(if: true) { x y } }")
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
type T @key(fields: "id") {
|
|
416
|
+
id: ID
|
|
417
|
+
v: V @external
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
type V {
|
|
421
|
+
x: Int
|
|
422
|
+
y: Int
|
|
423
|
+
}
|
|
424
|
+
`
|
|
425
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
426
|
+
['PROVIDES_DIRECTIVE_IN_FIELDS_ARG', '[S] On field "Query.t", for @provides(fields: "v { ... on V @skip(if: true) { x y } }"): cannot have directive applications in the @provides(fields:) argument but found @skip(if: true).'],
|
|
427
|
+
]);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it('rejects directive applications in @requires', () => {
|
|
431
|
+
const subgraph = gql`
|
|
432
|
+
type Query {
|
|
433
|
+
t: T
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
type T @key(fields: "id") {
|
|
437
|
+
id: ID
|
|
438
|
+
a: Int @requires(fields: "... @skip(if: false) { b }")
|
|
439
|
+
b: Int @external
|
|
440
|
+
}
|
|
441
|
+
`
|
|
442
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
443
|
+
['REQUIRES_DIRECTIVE_IN_FIELDS_ARG', '[S] On field "T.a", for @requires(fields: "... @skip(if: false) { b }"): cannot have directive applications in the @requires(fields:) argument but found @skip(if: false).'],
|
|
444
|
+
]);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it('can collect multiple errors in a single `fields` argument', () => {
|
|
448
|
+
const subgraph = gql`
|
|
449
|
+
type Query {
|
|
450
|
+
t: T @provides(fields: "f(x: 3)")
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
type T @key(fields: "id") {
|
|
454
|
+
id: ID
|
|
455
|
+
f(x: Int): Int
|
|
456
|
+
}
|
|
457
|
+
`
|
|
458
|
+
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
459
|
+
['PROVIDES_FIELDS_HAS_ARGS', '[S] On field "Query.t", for @provides(fields: "f(x: 3)"): field T.f cannot be included because it has arguments (fields with argument are not allowed in @provides)'],
|
|
460
|
+
['PROVIDES_FIELDS_MISSING_EXTERNAL', '[S] On field "Query.t", for @provides(fields: "f(x: 3)"): field "T.f" should not be part of a @provides since it is already provided by this subgraph (it is not marked @external)'],
|
|
461
|
+
]);
|
|
462
|
+
});
|
|
388
463
|
});
|
|
389
464
|
|
|
390
465
|
describe('root types', () => {
|
|
@@ -561,6 +636,8 @@ describe('@core/@link handling', () => {
|
|
|
561
636
|
|
|
562
637
|
directive @federation__override(from: String!) on FIELD_DEFINITION
|
|
563
638
|
|
|
639
|
+
directive @federation__composeDirective(name: String) repeatable on SCHEMA
|
|
640
|
+
|
|
564
641
|
type T
|
|
565
642
|
@key(fields: "k")
|
|
566
643
|
{
|
package/src/buildSchema.ts
CHANGED
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
errorCauses,
|
|
56
56
|
NamedSchemaElement,
|
|
57
57
|
} from "./definitions";
|
|
58
|
+
import { ERRORS, withModifiedErrorNodes } from "./error";
|
|
58
59
|
|
|
59
60
|
function buildValue(value?: ValueNode): any {
|
|
60
61
|
return value ? valueFromASTUntyped(value) : undefined;
|
|
@@ -195,7 +196,7 @@ function buildNamedTypeAndDirectivesShallow(documentNode: DocumentNode, schema:
|
|
|
195
196
|
switch (definitionNode.kind) {
|
|
196
197
|
case 'OperationDefinition':
|
|
197
198
|
case 'FragmentDefinition':
|
|
198
|
-
errors.push(
|
|
199
|
+
errors.push(ERRORS.INVALID_GRAPHQL.err("Invalid executable definition found while building schema", { nodes: definitionNode }));
|
|
199
200
|
continue;
|
|
200
201
|
case 'SchemaDefinition':
|
|
201
202
|
schemaDefinitions.push(definitionNode);
|
|
@@ -220,7 +221,7 @@ function buildNamedTypeAndDirectivesShallow(documentNode: DocumentNode, schema:
|
|
|
220
221
|
type = schema.addType(newNamedType(withoutTrailingDefinition(definitionNode.kind), definitionNode.name.value));
|
|
221
222
|
} else if (type.preserveEmptyDefinition) {
|
|
222
223
|
// Note: we reuse the same error message than graphQL-js would output
|
|
223
|
-
throw
|
|
224
|
+
throw ERRORS.INVALID_GRAPHQL.err(`There can be only one type named "${definitionNode.name.value}"`);
|
|
224
225
|
}
|
|
225
226
|
// It's possible for the type definition to be empty, because it is valid graphQL to have:
|
|
226
227
|
// type Foo
|
|
@@ -251,7 +252,7 @@ function buildNamedTypeAndDirectivesShallow(documentNode: DocumentNode, schema:
|
|
|
251
252
|
if (!existing) {
|
|
252
253
|
schema.addType(newNamedType(withoutTrailingDefinition(definitionNode.kind), definitionNode.name.value));
|
|
253
254
|
} else if (existing.isBuiltIn) {
|
|
254
|
-
throw
|
|
255
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Cannot extend built-in type "${definitionNode.name.value}"`);
|
|
255
256
|
}
|
|
256
257
|
break;
|
|
257
258
|
case 'DirectiveDefinition':
|
|
@@ -281,7 +282,7 @@ function withoutTrailingDefinition(str: string): NamedTypeKind {
|
|
|
281
282
|
function getReferencedType(node: NamedTypeNode, schema: Schema): NamedType {
|
|
282
283
|
const type = schema.type(node.name.value);
|
|
283
284
|
if (!type) {
|
|
284
|
-
throw
|
|
285
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Unknown type ${node.name.value}`, { nodes: node });
|
|
285
286
|
}
|
|
286
287
|
return type;
|
|
287
288
|
}
|
|
@@ -294,15 +295,7 @@ function withNodeAttachedToError(operation: () => void, node: ASTNode, errors: G
|
|
|
294
295
|
if (causes) {
|
|
295
296
|
for (const cause of causes) {
|
|
296
297
|
const allNodes: ASTNode | ASTNode[] = cause.nodes ? [node, ...cause.nodes] : node;
|
|
297
|
-
errors.push(
|
|
298
|
-
cause.message,
|
|
299
|
-
allNodes,
|
|
300
|
-
cause.source,
|
|
301
|
-
cause.positions,
|
|
302
|
-
cause.path,
|
|
303
|
-
cause,
|
|
304
|
-
cause.extensions
|
|
305
|
-
));
|
|
298
|
+
errors.push(withModifiedErrorNodes(cause, allNodes));
|
|
306
299
|
}
|
|
307
300
|
} else {
|
|
308
301
|
throw e;
|
|
@@ -392,7 +385,7 @@ function buildNamedTypeInner(
|
|
|
392
385
|
() => {
|
|
393
386
|
const itfName = itfNode.name.value;
|
|
394
387
|
if (fieldBasedType.implementsInterface(itfName)) {
|
|
395
|
-
throw
|
|
388
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Type "${type}" can only implement "${itfName}" once.`);
|
|
396
389
|
}
|
|
397
390
|
fieldBasedType.addImplementedInterface(itfName).setOfExtension(extension);
|
|
398
391
|
},
|
|
@@ -409,7 +402,7 @@ function buildNamedTypeInner(
|
|
|
409
402
|
() => {
|
|
410
403
|
const name = namedType.name.value;
|
|
411
404
|
if (unionType.hasTypeMember(name)) {
|
|
412
|
-
throw
|
|
405
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Union type "${unionType}" can only include type "${name}" once.`);
|
|
413
406
|
}
|
|
414
407
|
unionType.addType(name).setOfExtension(extension);
|
|
415
408
|
},
|
|
@@ -477,7 +470,7 @@ function validateOutputType(type: Type, what: string, node: ASTNode, errors: Gra
|
|
|
477
470
|
if (isOutputType(type)) {
|
|
478
471
|
return type;
|
|
479
472
|
} else {
|
|
480
|
-
errors.push(
|
|
473
|
+
errors.push(ERRORS.INVALID_GRAPHQL.err(`The type of "${what}" must be Output Type but got "${type}", a ${type.kind}.`, { nodes: node }));
|
|
481
474
|
return undefined;
|
|
482
475
|
}
|
|
483
476
|
}
|
|
@@ -486,7 +479,7 @@ function validateInputType(type: Type, what: string, node: ASTNode, errors: Grap
|
|
|
486
479
|
if (isInputType(type)) {
|
|
487
480
|
return type;
|
|
488
481
|
} else {
|
|
489
|
-
errors.push(
|
|
482
|
+
errors.push(ERRORS.INVALID_GRAPHQL.err(`The type of "${what}" must be Input Type but got "${type}", a ${type.kind}.`, { nodes: node }));
|
|
490
483
|
return undefined;
|
|
491
484
|
}
|
|
492
485
|
}
|
|
@@ -502,7 +495,7 @@ function buildTypeReferenceFromAST(typeNode: TypeNode, schema: Schema): Type {
|
|
|
502
495
|
case Kind.NON_NULL_TYPE:
|
|
503
496
|
const wrapped = buildTypeReferenceFromAST(typeNode.type, schema);
|
|
504
497
|
if (wrapped.kind == Kind.NON_NULL_TYPE) {
|
|
505
|
-
throw
|
|
498
|
+
throw ERRORS.INVALID_GRAPHQL.err(`Cannot apply the non-null operator (!) twice to the same type`, { nodes: typeNode });
|
|
506
499
|
}
|
|
507
500
|
return new NonNullType(wrapped);
|
|
508
501
|
default:
|
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, ErrGraphQLAPISchemaValidationFailed, ErrGraphQLValidationFailed, InputType, ListType, NamedType, NonNullType, ScalarType, Schema, SchemaDefinition, SchemaElement } from "./definitions";
|
|
3
|
+
import { CoreFeature, Directive, DirectiveDefinition, EnumType, ErrGraphQLAPISchemaValidationFailed, ErrGraphQLValidationFailed, InputType, ListType, NamedType, NonNullType, ScalarType, Schema, SchemaDefinition, SchemaElement, sourceASTs } from "./definitions";
|
|
4
4
|
import { sameType } from "./types";
|
|
5
5
|
import { err } from '@apollo/core-schema';
|
|
6
6
|
import { assert, firstOf } from './utils';
|
|
@@ -185,10 +185,10 @@ export function extractCoreFeatureImports(url: FeatureUrl, directive: Directive<
|
|
|
185
185
|
continue;
|
|
186
186
|
}
|
|
187
187
|
if (typeof elt !== 'object') {
|
|
188
|
-
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
189
|
-
|
|
190
|
-
nodes: directive.sourceAST
|
|
191
|
-
|
|
188
|
+
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
189
|
+
`Invalid sub-value ${valueToString(elt)} for @link(import:) argument: values should be either strings or input object values of the form { name: "<importedElement>", as: "<alias>" }.`,
|
|
190
|
+
{ nodes: directive.sourceAST },
|
|
191
|
+
));
|
|
192
192
|
continue;
|
|
193
193
|
}
|
|
194
194
|
let name: string | undefined;
|
|
@@ -196,28 +196,28 @@ export function extractCoreFeatureImports(url: FeatureUrl, directive: Directive<
|
|
|
196
196
|
switch (key) {
|
|
197
197
|
case 'name':
|
|
198
198
|
if (typeof value !== 'string') {
|
|
199
|
-
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
200
|
-
|
|
201
|
-
nodes: directive.sourceAST
|
|
202
|
-
|
|
199
|
+
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
200
|
+
`Invalid value for the "name" field for sub-value ${valueToString(elt)} of @link(import:) argument: must be a string.`,
|
|
201
|
+
{ nodes: directive.sourceAST },
|
|
202
|
+
));
|
|
203
203
|
continue importArgLoop;
|
|
204
204
|
}
|
|
205
205
|
name = value;
|
|
206
206
|
break;
|
|
207
207
|
case 'as':
|
|
208
208
|
if (typeof value !== 'string') {
|
|
209
|
-
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
210
|
-
|
|
211
|
-
nodes: directive.sourceAST
|
|
212
|
-
|
|
209
|
+
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
210
|
+
`Invalid value for the "as" field for sub-value ${valueToString(elt)} of @link(import:) argument: must be a string.`,
|
|
211
|
+
{ nodes: directive.sourceAST },
|
|
212
|
+
));
|
|
213
213
|
continue importArgLoop;
|
|
214
214
|
}
|
|
215
215
|
break;
|
|
216
216
|
default:
|
|
217
|
-
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
218
|
-
|
|
219
|
-
nodes: directive.sourceAST
|
|
220
|
-
|
|
217
|
+
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
218
|
+
`Unknown field "${key}" for sub-value ${valueToString(elt)} of @link(import:) argument.`,
|
|
219
|
+
{ nodes: directive.sourceAST },
|
|
220
|
+
));
|
|
221
221
|
continue importArgLoop;
|
|
222
222
|
}
|
|
223
223
|
}
|
|
@@ -226,24 +226,24 @@ export function extractCoreFeatureImports(url: FeatureUrl, directive: Directive<
|
|
|
226
226
|
imports.push(i);
|
|
227
227
|
if (i.as) {
|
|
228
228
|
if (i.name.charAt(0) === '@' && i.as.charAt(0) !== '@') {
|
|
229
|
-
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
230
|
-
|
|
231
|
-
nodes: directive.sourceAST
|
|
232
|
-
|
|
229
|
+
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
230
|
+
`Invalid @link import renaming: directive "${i.name}" imported name should start with a '@' character, but got "${i.as}".`,
|
|
231
|
+
{ nodes: directive.sourceAST },
|
|
232
|
+
));
|
|
233
233
|
}
|
|
234
234
|
else if (i.name.charAt(0) !== '@' && i.as.charAt(0) === '@') {
|
|
235
|
-
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
236
|
-
|
|
237
|
-
nodes: directive.sourceAST
|
|
238
|
-
|
|
235
|
+
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
236
|
+
`Invalid @link import renaming: type "${i.name}" imported name should not start with a '@' character, but got "${i.as}" (or, if @${i.name} is a directive, then it should be referred to with a '@').`,
|
|
237
|
+
{ nodes: directive.sourceAST },
|
|
238
|
+
));
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
241
|
validateImportedName(name, knownElements, errors, directive);
|
|
242
242
|
} else {
|
|
243
|
-
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
244
|
-
|
|
245
|
-
nodes: directive.sourceAST
|
|
246
|
-
|
|
243
|
+
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
244
|
+
`Invalid sub-value ${valueToString(elt)} for @link(import:) argument: missing mandatory "name" field.`,
|
|
245
|
+
{ nodes: directive.sourceAST },
|
|
246
|
+
));
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
249
|
|
|
@@ -264,10 +264,10 @@ function validateImportedName(name: string, knownElements: string[] | undefined,
|
|
|
264
264
|
details = didYouMean(suggestions);
|
|
265
265
|
}
|
|
266
266
|
}
|
|
267
|
-
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
268
|
-
|
|
269
|
-
nodes: directive.sourceAST
|
|
270
|
-
|
|
267
|
+
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
268
|
+
`Cannot import unknown element "${name}".${details}`,
|
|
269
|
+
{ nodes: directive.sourceAST },
|
|
270
|
+
));
|
|
271
271
|
}
|
|
272
272
|
}
|
|
273
273
|
|
|
@@ -419,9 +419,9 @@ export class CoreSpecDefinition extends FeatureDefinition {
|
|
|
419
419
|
// Already exists with the same version, let it be.
|
|
420
420
|
return [];
|
|
421
421
|
} else {
|
|
422
|
-
return [ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
423
|
-
|
|
424
|
-
|
|
422
|
+
return [ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
|
|
423
|
+
`Cannot add feature ${this} to the schema, it already uses ${existingCore.coreItself.url}`
|
|
424
|
+
)];
|
|
425
425
|
}
|
|
426
426
|
}
|
|
427
427
|
|
|
@@ -555,7 +555,7 @@ export class FeatureVersion {
|
|
|
555
555
|
public static parse(input: string): FeatureVersion {
|
|
556
556
|
const match = input.match(this.VERSION_RE)
|
|
557
557
|
if (!match) {
|
|
558
|
-
throw
|
|
558
|
+
throw ERRORS.INVALID_LINK_IDENTIFIER.err(`Expected a version string (of the form v1.2), got ${input}`);
|
|
559
559
|
}
|
|
560
560
|
return new this(+match[1], +match[2])
|
|
561
561
|
}
|
|
@@ -663,17 +663,17 @@ export class FeatureUrl {
|
|
|
663
663
|
public static parse(input: string, node?: ASTNode): FeatureUrl {
|
|
664
664
|
const url = new URL(input)
|
|
665
665
|
if (!url.pathname || url.pathname === '/') {
|
|
666
|
-
throw
|
|
666
|
+
throw ERRORS.INVALID_LINK_IDENTIFIER.err(`Missing path in feature url '${url}'`, { nodes: node })
|
|
667
667
|
}
|
|
668
668
|
const path = url.pathname.split('/')
|
|
669
669
|
const verStr = path.pop()
|
|
670
670
|
if (!verStr) {
|
|
671
|
-
throw
|
|
671
|
+
throw ERRORS.INVALID_LINK_IDENTIFIER.err(`Missing version component in feature url '${url}'`, { nodes: node })
|
|
672
672
|
}
|
|
673
673
|
const version = FeatureVersion.parse(verStr)
|
|
674
674
|
const name = path[path.length - 1]
|
|
675
675
|
if (!name) {
|
|
676
|
-
throw
|
|
676
|
+
throw ERRORS.INVALID_LINK_IDENTIFIER.err(`Missing feature name component in feature url '${url}'`, { nodes: node })
|
|
677
677
|
}
|
|
678
678
|
const element = url.hash ? url.hash.slice(1): undefined
|
|
679
679
|
url.hash = ''
|
|
@@ -804,11 +804,15 @@ export function removeAllCoreFeatures(schema: Schema) {
|
|
|
804
804
|
for (const { feature, type, references } of typeReferences) {
|
|
805
805
|
const referencesInSchema = references.filter(r => r.isAttached());
|
|
806
806
|
if (referencesInSchema.length > 0) {
|
|
807
|
-
|
|
807
|
+
// Note: using REFERENCED_INACCESSIBLE is slightly abusive because the reference element is not marked
|
|
808
|
+
// @inacessible exactly. Instead, it is inacessible due to core elements being removed, but that's very
|
|
809
|
+
// very close semantically. Overall, adding a publicly documented error code just to minor difference
|
|
810
|
+
// doesn't feel worth it, especially since that case is super unlikely in the first place (and, as
|
|
811
|
+
// the prior comment says, may one day be removed too).
|
|
812
|
+
errors.push(ERRORS.REFERENCED_INACCESSIBLE.err(
|
|
808
813
|
`Cannot remove elements of feature ${feature} as feature type ${type}` +
|
|
809
814
|
` is referenced by elements: ${referencesInSchema.join(', ')}`,
|
|
810
|
-
|
|
811
|
-
.filter(n => n !== undefined) as ASTNode[]
|
|
815
|
+
{ nodes: sourceASTs(...references) },
|
|
812
816
|
));
|
|
813
817
|
}
|
|
814
818
|
}
|