@apollo/federation-internals 2.4.4 → 2.4.6
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/dist/Subgraph.d.ts +1 -0
- package/dist/Subgraph.d.ts.map +1 -0
- package/dist/Subgraph.js +2 -0
- package/dist/Subgraph.js.map +1 -0
- package/dist/argumentCompositionStrategies.d.ts +34 -0
- package/dist/argumentCompositionStrategies.d.ts.map +1 -0
- package/dist/argumentCompositionStrategies.js +35 -0
- package/dist/argumentCompositionStrategies.js.map +1 -0
- package/dist/buildSchema.d.ts +10 -0
- package/dist/buildSchema.d.ts.map +1 -0
- package/dist/buildSchema.js +362 -0
- package/dist/buildSchema.js.map +1 -0
- package/dist/coreSpec.d.ts +127 -0
- package/dist/coreSpec.d.ts.map +1 -0
- package/dist/coreSpec.js +590 -0
- package/dist/coreSpec.js.map +1 -0
- package/dist/debug.d.ts +15 -0
- package/dist/debug.d.ts.map +1 -0
- package/dist/debug.js +122 -0
- package/dist/debug.js.map +1 -0
- package/dist/definitions.d.ts +663 -0
- package/dist/definitions.d.ts.map +1 -0
- package/dist/definitions.js +2841 -0
- package/dist/definitions.js.map +1 -0
- package/dist/directiveAndTypeSpecification.d.ts +67 -0
- package/dist/directiveAndTypeSpecification.d.ts.map +1 -0
- package/dist/directiveAndTypeSpecification.js +271 -0
- package/dist/directiveAndTypeSpecification.js.map +1 -0
- package/dist/error.d.ts +128 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +315 -0
- package/dist/error.js.map +1 -0
- package/dist/extractSubgraphsFromSupergraph.d.ts +8 -0
- package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -0
- package/dist/extractSubgraphsFromSupergraph.js +576 -0
- package/dist/extractSubgraphsFromSupergraph.js.map +1 -0
- package/dist/federation.d.ts +175 -0
- package/dist/federation.d.ts.map +1 -0
- package/dist/federation.js +1414 -0
- package/dist/federation.js.map +1 -0
- package/dist/federationSpec.d.ts +25 -0
- package/dist/federationSpec.d.ts.map +1 -0
- package/dist/federationSpec.js +125 -0
- package/dist/federationSpec.js.map +1 -0
- package/dist/genErrorCodeDoc.d.ts +2 -0
- package/dist/genErrorCodeDoc.d.ts.map +1 -0
- package/dist/genErrorCodeDoc.js +61 -0
- package/dist/genErrorCodeDoc.js.map +1 -0
- package/dist/graphQLJSSchemaToAST.d.ts +8 -0
- package/dist/graphQLJSSchemaToAST.d.ts.map +1 -0
- package/dist/graphQLJSSchemaToAST.js +96 -0
- package/dist/graphQLJSSchemaToAST.js.map +1 -0
- package/dist/inaccessibleSpec.d.ts +18 -0
- package/dist/inaccessibleSpec.d.ts.map +1 -0
- package/dist/inaccessibleSpec.js +655 -0
- package/dist/inaccessibleSpec.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/introspection.d.ts +6 -0
- package/dist/introspection.d.ts.map +1 -0
- package/dist/introspection.js +96 -0
- package/dist/introspection.js.map +1 -0
- package/dist/joinSpec.d.ts +51 -0
- package/dist/joinSpec.d.ts.map +1 -0
- package/dist/joinSpec.js +160 -0
- package/dist/joinSpec.js.map +1 -0
- package/dist/knownCoreFeatures.d.ts +5 -0
- package/dist/knownCoreFeatures.d.ts.map +1 -0
- package/dist/knownCoreFeatures.js +20 -0
- package/dist/knownCoreFeatures.js.map +1 -0
- package/dist/operations.d.ts +418 -0
- package/dist/operations.d.ts.map +1 -0
- package/dist/operations.js +2068 -0
- package/dist/operations.js.map +1 -0
- package/dist/precompute.d.ts +3 -0
- package/dist/precompute.d.ts.map +1 -0
- package/dist/precompute.js +54 -0
- package/dist/precompute.js.map +1 -0
- package/dist/print.d.ts +28 -0
- package/dist/print.d.ts.map +1 -0
- package/dist/print.js +299 -0
- package/dist/print.js.map +1 -0
- package/dist/schemaUpgrader.d.ts +121 -0
- package/dist/schemaUpgrader.d.ts.map +1 -0
- package/dist/schemaUpgrader.js +570 -0
- package/dist/schemaUpgrader.js.map +1 -0
- package/dist/suggestions.d.ts +3 -0
- package/dist/suggestions.d.ts.map +1 -0
- package/dist/suggestions.js +44 -0
- package/dist/suggestions.js.map +1 -0
- package/dist/supergraphs.d.ts +10 -0
- package/dist/supergraphs.d.ts.map +1 -0
- package/dist/supergraphs.js +76 -0
- package/dist/supergraphs.js.map +1 -0
- package/dist/tagSpec.d.ts +19 -0
- package/dist/tagSpec.d.ts.map +1 -0
- package/dist/tagSpec.js +66 -0
- package/dist/tagSpec.js.map +1 -0
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +64 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +64 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +326 -0
- package/dist/utils.js.map +1 -0
- package/dist/validate.d.ts +4 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +239 -0
- package/dist/validate.js.map +1 -0
- package/dist/validation/KnownTypeNamesInFederationRule.d.ts +4 -0
- package/dist/validation/KnownTypeNamesInFederationRule.d.ts.map +1 -0
- package/dist/validation/KnownTypeNamesInFederationRule.js +41 -0
- package/dist/validation/KnownTypeNamesInFederationRule.js.map +1 -0
- package/dist/values.d.ts +23 -0
- package/dist/values.d.ts.map +1 -0
- package/dist/values.js +580 -0
- package/dist/values.js.map +1 -0
- package/package.json +1 -1
- package/src/operations.ts +145 -20
- package/src/utils.ts +1 -1
- package/CHANGELOG.md +0 -205
- package/jest.config.js +0 -11
- package/src/__tests__/coreSpec.test.ts +0 -212
- package/src/__tests__/definitions.test.ts +0 -982
- package/src/__tests__/directiveAndTypeSpecifications.test.ts +0 -41
- package/src/__tests__/extractSubgraphsFromSupergraph.test.ts +0 -748
- package/src/__tests__/federation.test.ts +0 -31
- package/src/__tests__/graphQLJSSchemaToAST.test.ts +0 -156
- package/src/__tests__/matchers/index.ts +0 -1
- package/src/__tests__/matchers/toMatchString.ts +0 -87
- package/src/__tests__/operations.test.ts +0 -1266
- package/src/__tests__/removeInaccessibleElements.test.ts +0 -2471
- package/src/__tests__/schemaUpgrader.test.ts +0 -287
- package/src/__tests__/subgraphValidation.test.ts +0 -1254
- package/src/__tests__/supergraphSdl.graphql +0 -281
- package/src/__tests__/testUtils.ts +0 -28
- package/src/__tests__/toAPISchema.test.ts +0 -53
- package/src/__tests__/tsconfig.json +0 -7
- package/src/__tests__/utils.test.ts +0 -92
- package/src/__tests__/values.test.ts +0 -390
- package/tsconfig.json +0 -10
- package/tsconfig.test.json +0 -8
|
@@ -1,1254 +0,0 @@
|
|
|
1
|
-
import { DocumentNode } from 'graphql';
|
|
2
|
-
import gql from 'graphql-tag';
|
|
3
|
-
import { Subgraph } from '..';
|
|
4
|
-
import { buildSubgraph } from "../federation"
|
|
5
|
-
import { defaultPrintOptions, printSchema } from '../print';
|
|
6
|
-
import { buildForErrors } from './testUtils';
|
|
7
|
-
|
|
8
|
-
describe('fieldset-based directives', () => {
|
|
9
|
-
it('rejects field defined with arguments in @key', () => {
|
|
10
|
-
const subgraph = gql`
|
|
11
|
-
type Query {
|
|
12
|
-
t: T
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
type T @key(fields: "f") {
|
|
16
|
-
f(x: Int): Int
|
|
17
|
-
}
|
|
18
|
-
`
|
|
19
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
20
|
-
['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)']
|
|
21
|
-
]);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('rejects field defined with arguments in @provides', () => {
|
|
25
|
-
const subgraph = gql`
|
|
26
|
-
type Query {
|
|
27
|
-
t: T @provides(fields: "f")
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
type T {
|
|
31
|
-
f(x: Int): Int @external
|
|
32
|
-
}
|
|
33
|
-
`
|
|
34
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
35
|
-
['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)']
|
|
36
|
-
]);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('rejects @provides on non-external fields', () => {
|
|
40
|
-
const subgraph = gql`
|
|
41
|
-
type Query {
|
|
42
|
-
t: T @provides(fields: "f")
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
type T {
|
|
46
|
-
f: Int
|
|
47
|
-
}
|
|
48
|
-
`
|
|
49
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
50
|
-
['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)']
|
|
51
|
-
]);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('rejects @requires on non-external fields', () => {
|
|
55
|
-
const subgraph = gql`
|
|
56
|
-
type Query {
|
|
57
|
-
t: T
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
type T {
|
|
61
|
-
f: Int
|
|
62
|
-
g: Int @requires(fields: "f")
|
|
63
|
-
}
|
|
64
|
-
`
|
|
65
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
66
|
-
['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)']
|
|
67
|
-
]);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it.each(['2.0', '2.1', '2.2'])('rejects @key on interfaces _in the %p spec_', (version) => {
|
|
71
|
-
const subgraph = gql`
|
|
72
|
-
extend schema
|
|
73
|
-
@link(url: "https://specs.apollo.dev/federation/v${version}", import: ["@key"])
|
|
74
|
-
|
|
75
|
-
type Query {
|
|
76
|
-
t: T
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
interface T @key(fields: "f") {
|
|
80
|
-
f: Int
|
|
81
|
-
}
|
|
82
|
-
`
|
|
83
|
-
expect(buildForErrors(subgraph, { asFed2: false })).toStrictEqual([
|
|
84
|
-
['KEY_UNSUPPORTED_ON_INTERFACE', '[S] Cannot use @key on interface "T": @key is not yet supported on interfaces'],
|
|
85
|
-
]);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('rejects @provides on interfaces', () => {
|
|
89
|
-
const subgraph = gql`
|
|
90
|
-
type Query {
|
|
91
|
-
t: T
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
interface T {
|
|
95
|
-
f: U @provides(fields: "g")
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
type U {
|
|
99
|
-
g: Int @external
|
|
100
|
-
}
|
|
101
|
-
`
|
|
102
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
103
|
-
['PROVIDES_UNSUPPORTED_ON_INTERFACE', '[S] Cannot use @provides on field "T.f" of parent type "T": @provides is not yet supported within interfaces'],
|
|
104
|
-
]);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('rejects @requires on interfaces', () => {
|
|
108
|
-
const subgraph = gql`
|
|
109
|
-
type Query {
|
|
110
|
-
t: T
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
interface T {
|
|
114
|
-
f: Int @external
|
|
115
|
-
g: Int @requires(fields: "f")
|
|
116
|
-
}
|
|
117
|
-
`
|
|
118
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
119
|
-
['REQUIRES_UNSUPPORTED_ON_INTERFACE', '[S] Cannot use @requires on field "T.g" of parent type "T": @requires is not yet supported within interfaces' ],
|
|
120
|
-
['EXTERNAL_ON_INTERFACE', '[S] Interface type field "T.f" is marked @external but @external is not allowed on interface fields (it is nonsensical).' ],
|
|
121
|
-
]);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('rejects unused @external', () => {
|
|
125
|
-
const subgraph = gql`
|
|
126
|
-
type Query {
|
|
127
|
-
t: T
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
type T {
|
|
131
|
-
f: Int @external
|
|
132
|
-
}
|
|
133
|
-
`
|
|
134
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
135
|
-
['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).'],
|
|
136
|
-
]);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it('rejects @provides on non-object fields', () => {
|
|
140
|
-
const subgraph = gql`
|
|
141
|
-
type Query {
|
|
142
|
-
t: Int @provides(fields: "f")
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
type T {
|
|
146
|
-
f: Int
|
|
147
|
-
}
|
|
148
|
-
`
|
|
149
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
150
|
-
['PROVIDES_ON_NON_OBJECT_FIELD', '[S] Invalid @provides directive on field "Query.t": field has type "Int" which is not a Composite Type'],
|
|
151
|
-
]);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
it('rejects a non-string argument to @key', () => {
|
|
155
|
-
const subgraph = gql`
|
|
156
|
-
type Query {
|
|
157
|
-
t: T
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
type T @key(fields: ["f"]) {
|
|
161
|
-
f: Int
|
|
162
|
-
}
|
|
163
|
-
`
|
|
164
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
165
|
-
['KEY_INVALID_FIELDS_TYPE', '[S] On type "T", for @key(fields: ["f"]): Invalid value for argument "fields": must be a string.'],
|
|
166
|
-
]);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it('rejects a non-string argument to @provides', () => {
|
|
170
|
-
const subgraph = gql`
|
|
171
|
-
type Query {
|
|
172
|
-
t: T @provides(fields: ["f"])
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
type T {
|
|
176
|
-
f: Int @external
|
|
177
|
-
}
|
|
178
|
-
`
|
|
179
|
-
// Note: since the error here is that we cannot parse the key `fields`, this also mean that @external on
|
|
180
|
-
// `f` will appear unused and we get an error for it. It's kind of hard to avoid cleanly and hopefully
|
|
181
|
-
// not a big deal (having errors dependencies is not exactly unheard of).
|
|
182
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
183
|
-
['PROVIDES_INVALID_FIELDS_TYPE', '[S] On field "Query.t", for @provides(fields: ["f"]): Invalid value for argument "fields": must be a string.'],
|
|
184
|
-
['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).' ],
|
|
185
|
-
]);
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it('rejects a non-string argument to @requires', () => {
|
|
189
|
-
const subgraph = gql`
|
|
190
|
-
type Query {
|
|
191
|
-
t: T
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
type T {
|
|
195
|
-
f: Int @external
|
|
196
|
-
g: Int @requires(fields: ["f"])
|
|
197
|
-
}
|
|
198
|
-
`
|
|
199
|
-
// Note: since the error here is that we cannot parse the key `fields`, this also mean that @external on
|
|
200
|
-
// `f` will appear unused and we get an error for it. It's kind of hard to avoid cleanly and hopefully
|
|
201
|
-
// not a big deal (having errors dependencies is not exactly unheard of).
|
|
202
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
203
|
-
['REQUIRES_INVALID_FIELDS_TYPE', '[S] On field "T.g", for @requires(fields: ["f"]): Invalid value for argument "fields": must be a string.'],
|
|
204
|
-
['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).' ],
|
|
205
|
-
]);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
// Special case of non-string argument, specialized because it hits a different
|
|
209
|
-
// code-path due to enum values being parsed as string and requiring special care.
|
|
210
|
-
it('rejects an enum-like argument to @key', () => {
|
|
211
|
-
const subgraph = gql`
|
|
212
|
-
type Query {
|
|
213
|
-
t: T
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
type T @key(fields: f) {
|
|
217
|
-
f: Int
|
|
218
|
-
}
|
|
219
|
-
`
|
|
220
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
221
|
-
['KEY_INVALID_FIELDS_TYPE', '[S] On type "T", for @key(fields: f): Invalid value for argument "fields": must be a string.'],
|
|
222
|
-
]);
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
// Special case of non-string argument, specialized because it hits a different
|
|
226
|
-
// code-path due to enum values being parsed as string and requiring special care.
|
|
227
|
-
it('rejects an enum-lik argument to @provides', () => {
|
|
228
|
-
const subgraph = gql`
|
|
229
|
-
type Query {
|
|
230
|
-
t: T @provides(fields: f)
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
type T {
|
|
234
|
-
f: Int @external
|
|
235
|
-
}
|
|
236
|
-
`
|
|
237
|
-
// Note: since the error here is that we cannot parse the key `fields`, this also mean that @external on
|
|
238
|
-
// `f` will appear unused and we get an error for it. It's kind of hard to avoid cleanly and hopefully
|
|
239
|
-
// not a big deal (having errors dependencies is not exactly unheard of).
|
|
240
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
241
|
-
['PROVIDES_INVALID_FIELDS_TYPE', '[S] On field "Query.t", for @provides(fields: f): Invalid value for argument "fields": must be a string.'],
|
|
242
|
-
['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).' ],
|
|
243
|
-
]);
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
// Special case of non-string argument, specialized because it hits a different
|
|
247
|
-
// code-path due to enum values being parsed as string and requiring special care.
|
|
248
|
-
it('rejects an enum-like argument to @requires', () => {
|
|
249
|
-
const subgraph = gql`
|
|
250
|
-
type Query {
|
|
251
|
-
t: T
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
type T {
|
|
255
|
-
f: Int @external
|
|
256
|
-
g: Int @requires(fields: f)
|
|
257
|
-
}
|
|
258
|
-
`
|
|
259
|
-
// Note: since the error here is that we cannot parse the key `fields`, this also mean that @external on
|
|
260
|
-
// `f` will appear unused and we get an error for it. It's kind of hard to avoid cleanly and hopefully
|
|
261
|
-
// not a big deal (having errors dependencies is not exactly unheard of).
|
|
262
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
263
|
-
['REQUIRES_INVALID_FIELDS_TYPE', '[S] On field "T.g", for @requires(fields: f): Invalid value for argument "fields": must be a string.'],
|
|
264
|
-
['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).' ],
|
|
265
|
-
]);
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
it('rejects an invalid `fields` argument to @key', () => {
|
|
269
|
-
const subgraph = gql`
|
|
270
|
-
type Query {
|
|
271
|
-
t: T
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
type T @key(fields: ":f") {
|
|
275
|
-
f: Int
|
|
276
|
-
}
|
|
277
|
-
`
|
|
278
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
279
|
-
['KEY_INVALID_FIELDS', '[S] On type "T", for @key(fields: ":f"): Syntax Error: Expected Name, found ":".'],
|
|
280
|
-
]);
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
it('rejects an invalid `fields` argument to @provides', () => {
|
|
284
|
-
const subgraph = gql`
|
|
285
|
-
type Query {
|
|
286
|
-
t: T @provides(fields: "{{f}}")
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
type T {
|
|
290
|
-
f: Int @external
|
|
291
|
-
}
|
|
292
|
-
`
|
|
293
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
294
|
-
['PROVIDES_INVALID_FIELDS', '[S] On field "Query.t", for @provides(fields: "{{f}}"): Syntax Error: Expected Name, found "{".'],
|
|
295
|
-
['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).' ],
|
|
296
|
-
]);
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
it('rejects an invalid `fields` argument to @requires', () => {
|
|
300
|
-
const subgraph = gql`
|
|
301
|
-
type Query {
|
|
302
|
-
t: T
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
type T {
|
|
306
|
-
f: Int @external
|
|
307
|
-
g: Int @requires(fields: "f b")
|
|
308
|
-
}
|
|
309
|
-
`
|
|
310
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
311
|
-
['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).'],
|
|
312
|
-
]);
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
it('rejects @key on an interface field', () => {
|
|
316
|
-
const subgraph = gql`
|
|
317
|
-
type Query {
|
|
318
|
-
t: T
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
type T @key(fields: "f") {
|
|
322
|
-
f: I
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
interface I {
|
|
326
|
-
i: Int
|
|
327
|
-
}
|
|
328
|
-
`
|
|
329
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
330
|
-
['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'],
|
|
331
|
-
]);
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
it('rejects @key on an union field', () => {
|
|
335
|
-
const subgraph = gql`
|
|
336
|
-
type Query {
|
|
337
|
-
t: T
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
type T @key(fields: "f") {
|
|
341
|
-
f: U
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
union U = Query | T
|
|
345
|
-
`
|
|
346
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
347
|
-
['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'],
|
|
348
|
-
]);
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
it('rejects directive applications in @key', () => {
|
|
352
|
-
const subgraph = gql`
|
|
353
|
-
type Query {
|
|
354
|
-
t: T
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
type T @key(fields: "v { x ... @include(if: false) { y }}") {
|
|
358
|
-
v: V
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
type V {
|
|
362
|
-
x: Int
|
|
363
|
-
y: Int
|
|
364
|
-
}
|
|
365
|
-
`
|
|
366
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
367
|
-
['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).'],
|
|
368
|
-
]);
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
it('rejects directive applications in @provides', () => {
|
|
372
|
-
const subgraph = gql`
|
|
373
|
-
type Query {
|
|
374
|
-
t: T @provides(fields: "v { ... on V @skip(if: true) { x y } }")
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
type T @key(fields: "id") {
|
|
378
|
-
id: ID
|
|
379
|
-
v: V @external
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
type V {
|
|
383
|
-
x: Int
|
|
384
|
-
y: Int
|
|
385
|
-
}
|
|
386
|
-
`
|
|
387
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
388
|
-
['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).'],
|
|
389
|
-
]);
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
it('rejects directive applications in @requires', () => {
|
|
393
|
-
const subgraph = gql`
|
|
394
|
-
type Query {
|
|
395
|
-
t: T
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
type T @key(fields: "id") {
|
|
399
|
-
id: ID
|
|
400
|
-
a: Int @requires(fields: "... @skip(if: false) { b }")
|
|
401
|
-
b: Int @external
|
|
402
|
-
}
|
|
403
|
-
`
|
|
404
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
405
|
-
['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).'],
|
|
406
|
-
]);
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
it('can collect multiple errors in a single `fields` argument', () => {
|
|
410
|
-
const subgraph = gql`
|
|
411
|
-
type Query {
|
|
412
|
-
t: T @provides(fields: "f(x: 3)")
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
type T @key(fields: "id") {
|
|
416
|
-
id: ID
|
|
417
|
-
f(x: Int): Int
|
|
418
|
-
}
|
|
419
|
-
`
|
|
420
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
421
|
-
['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)'],
|
|
422
|
-
['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)'],
|
|
423
|
-
]);
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
it('rejects aliases in @key', () => {
|
|
427
|
-
const subgraph = gql`
|
|
428
|
-
type Query {
|
|
429
|
-
t: T
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
type T @key(fields: "foo: id") {
|
|
433
|
-
id: ID!
|
|
434
|
-
}
|
|
435
|
-
`
|
|
436
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
437
|
-
[ 'KEY_INVALID_FIELDS', '[S] On type "T", for @key(fields: "foo: id"): Cannot use alias "foo" in "foo: id": aliases are not currently supported in @key' ],
|
|
438
|
-
]);
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
it('rejects aliases in @provides', () => {
|
|
442
|
-
const subgraph = gql`
|
|
443
|
-
type Query {
|
|
444
|
-
t: T @provides(fields: "bar: x")
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
type T @key(fields: "id") {
|
|
448
|
-
id: ID!
|
|
449
|
-
x: Int @external
|
|
450
|
-
}
|
|
451
|
-
`
|
|
452
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
453
|
-
[ 'PROVIDES_INVALID_FIELDS', '[S] On field "Query.t", for @provides(fields: "bar: x"): Cannot use alias "bar" in "bar: x": aliases are not currently supported in @provides' ],
|
|
454
|
-
]);
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
it('rejects aliases in @requires', () => {
|
|
458
|
-
const subgraph = gql`
|
|
459
|
-
type Query {
|
|
460
|
-
t: T
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
type T {
|
|
464
|
-
x: X @external
|
|
465
|
-
y: Int @external
|
|
466
|
-
g: Int @requires(fields: "foo: y")
|
|
467
|
-
h: Int @requires(fields: "x { m: a n: b }")
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
type X {
|
|
471
|
-
a: Int
|
|
472
|
-
b: Int
|
|
473
|
-
}
|
|
474
|
-
`
|
|
475
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
476
|
-
[ 'REQUIRES_INVALID_FIELDS', '[S] On field "T.g", for @requires(fields: "foo: y"): Cannot use alias "foo" in "foo: y": aliases are not currently supported in @requires' ],
|
|
477
|
-
[ 'REQUIRES_INVALID_FIELDS', '[S] On field "T.h", for @requires(fields: "x { m: a n: b }"): Cannot use alias "m" in "m: a": aliases are not currently supported in @requires' ],
|
|
478
|
-
]);
|
|
479
|
-
});
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
describe('root types', () => {
|
|
483
|
-
it('rejects using Query as type name if not the query root', () => {
|
|
484
|
-
const subgraph = gql`
|
|
485
|
-
schema {
|
|
486
|
-
query: MyQuery
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
type MyQuery {
|
|
490
|
-
f: Int
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
type Query {
|
|
494
|
-
g: Int
|
|
495
|
-
}
|
|
496
|
-
`
|
|
497
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
498
|
-
['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.'],
|
|
499
|
-
]);
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
it('rejects using Mutation as type name if not the mutation root', () => {
|
|
503
|
-
const subgraph = gql`
|
|
504
|
-
schema {
|
|
505
|
-
mutation: MyMutation
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
type MyMutation {
|
|
509
|
-
f: Int
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
type Mutation {
|
|
513
|
-
g: Int
|
|
514
|
-
}
|
|
515
|
-
`
|
|
516
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
517
|
-
['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.'],
|
|
518
|
-
]);
|
|
519
|
-
});
|
|
520
|
-
|
|
521
|
-
it('rejects using Subscription as type name if not the subscription root', () => {
|
|
522
|
-
const subgraph = gql`
|
|
523
|
-
schema {
|
|
524
|
-
subscription: MySubscription
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
type MySubscription {
|
|
528
|
-
f: Int
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
type Subscription {
|
|
532
|
-
g: Int
|
|
533
|
-
}
|
|
534
|
-
`
|
|
535
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
536
|
-
['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.'],
|
|
537
|
-
]);
|
|
538
|
-
});
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
describe('custom error message for misnamed directives', () => {
|
|
542
|
-
it.each([
|
|
543
|
-
{ name: 'fed1', extraMsg: ' If so, note that it is a federation 2 directive but this schema is a federation 1 one. To be a federation 2 schema, it needs to @link to the federation specifcation v2.' },
|
|
544
|
-
{ name: 'fed2', extraMsg: '' },
|
|
545
|
-
|
|
546
|
-
])('has suggestions if a federation directive name is mispelled in $name', ({name, extraMsg}) => {
|
|
547
|
-
const subgraph = gql`
|
|
548
|
-
type T @keys(fields: "id") {
|
|
549
|
-
id: Int @foo
|
|
550
|
-
foo: String @sharable
|
|
551
|
-
}
|
|
552
|
-
`;
|
|
553
|
-
|
|
554
|
-
expect(buildForErrors(subgraph, { asFed2 : name === 'fed2' })).toStrictEqual([
|
|
555
|
-
['INVALID_GRAPHQL', `[S] Unknown directive "@foo".`],
|
|
556
|
-
['INVALID_GRAPHQL', `[S] Unknown directive "@sharable". Did you mean "@shareable"?${extraMsg}`],
|
|
557
|
-
['INVALID_GRAPHQL', `[S] Unknown directive "@keys". Did you mean "@key"?`],
|
|
558
|
-
]);
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
it('has suggestions if a fed2 directive is used in fed1', () => {
|
|
562
|
-
const subgraph = gql`
|
|
563
|
-
type T @key(fields: "id") {
|
|
564
|
-
id: Int
|
|
565
|
-
foo: String @shareable
|
|
566
|
-
}
|
|
567
|
-
`;
|
|
568
|
-
|
|
569
|
-
expect(buildForErrors(subgraph, { asFed2 : false })).toStrictEqual([
|
|
570
|
-
['INVALID_GRAPHQL', `[S] Unknown directive "@shareable". If you meant the \"@shareable\" federation 2 directive, note that this schema is a federation 1 schema. To be a federation 2 schema, it needs to @link to the federation specifcation v2.`],
|
|
571
|
-
]);
|
|
572
|
-
});
|
|
573
|
-
|
|
574
|
-
it('has suggestions if a fed2 directive is used under the wrong name (for the schema)', () => {
|
|
575
|
-
const subgraph = gql`
|
|
576
|
-
extend schema
|
|
577
|
-
@link(url: "https://specs.apollo.dev/federation/v2.0",
|
|
578
|
-
import: [ { name: "@key", as: "@myKey"} ])
|
|
579
|
-
|
|
580
|
-
type T @key(fields: "id") {
|
|
581
|
-
id: Int
|
|
582
|
-
foo: String @shareable
|
|
583
|
-
}
|
|
584
|
-
`;
|
|
585
|
-
|
|
586
|
-
// Note: it's a fed2 schema, but we manually add the @link, so we pass `asFed2: false` to avoid having added twice.
|
|
587
|
-
expect(buildForErrors(subgraph, { asFed2 : false })).toStrictEqual([
|
|
588
|
-
['INVALID_GRAPHQL', `[S] Unknown directive "@shareable". If you meant the \"@shareable\" federation directive, you should use fully-qualified name "@federation__shareable" or add "@shareable" to the \`import\` argument of the @link to the federation specification.`],
|
|
589
|
-
['INVALID_GRAPHQL', `[S] Unknown directive "@key". If you meant the "@key" federation directive, you should use "@myKey" as it is imported under that name in the @link to the federation specification of this schema.`],
|
|
590
|
-
]);
|
|
591
|
-
});
|
|
592
|
-
});
|
|
593
|
-
|
|
594
|
-
function buildAndValidate(doc: DocumentNode): Subgraph {
|
|
595
|
-
const name = 'S';
|
|
596
|
-
return buildSubgraph(name, `http://${name}`, doc).validate();
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
describe('@core/@link handling', () => {
|
|
600
|
-
const expectedFullSchema = `
|
|
601
|
-
schema
|
|
602
|
-
@link(url: "https://specs.apollo.dev/link/v1.0")
|
|
603
|
-
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
604
|
-
{
|
|
605
|
-
query: Query
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA
|
|
609
|
-
|
|
610
|
-
directive @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
|
|
611
|
-
|
|
612
|
-
directive @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION
|
|
613
|
-
|
|
614
|
-
directive @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION
|
|
615
|
-
|
|
616
|
-
directive @federation__external(reason: String) on OBJECT | FIELD_DEFINITION
|
|
617
|
-
|
|
618
|
-
directive @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
|
|
619
|
-
|
|
620
|
-
directive @federation__extends on OBJECT | INTERFACE
|
|
621
|
-
|
|
622
|
-
directive @federation__shareable on OBJECT | FIELD_DEFINITION
|
|
623
|
-
|
|
624
|
-
directive @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
|
|
625
|
-
|
|
626
|
-
directive @federation__override(from: String!) on FIELD_DEFINITION
|
|
627
|
-
|
|
628
|
-
type T
|
|
629
|
-
@key(fields: "k")
|
|
630
|
-
{
|
|
631
|
-
k: ID!
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
enum link__Purpose {
|
|
635
|
-
"""
|
|
636
|
-
\`SECURITY\` features provide metadata necessary to securely resolve fields.
|
|
637
|
-
"""
|
|
638
|
-
SECURITY
|
|
639
|
-
|
|
640
|
-
"""
|
|
641
|
-
\`EXECUTION\` features provide metadata necessary for operation execution.
|
|
642
|
-
"""
|
|
643
|
-
EXECUTION
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
scalar link__Import
|
|
647
|
-
|
|
648
|
-
scalar federation__FieldSet
|
|
649
|
-
|
|
650
|
-
scalar _Any
|
|
651
|
-
|
|
652
|
-
type _Service {
|
|
653
|
-
sdl: String
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
union _Entity = T
|
|
657
|
-
|
|
658
|
-
type Query {
|
|
659
|
-
_entities(representations: [_Any!]!): [_Entity]!
|
|
660
|
-
_service: _Service!
|
|
661
|
-
}
|
|
662
|
-
`
|
|
663
|
-
const validateFullSchema = (subgraph: Subgraph) => {
|
|
664
|
-
// Note: we merge types and extensions to avoid having to care whether the @link are on a schema definition or schema extension
|
|
665
|
-
// as 1) this will vary (we add them to extensions in our test, but when auto-added, they are added to the schema definition)
|
|
666
|
-
// and 2) it doesn't matter in practice, it's valid in all cases.
|
|
667
|
-
expect(printSchema(subgraph.schema, { ...defaultPrintOptions, mergeTypesAndExtensions: true})).toMatchString(expectedFullSchema);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
it('expands everything if only the federation spec is linked', () => {
|
|
671
|
-
const doc = gql`
|
|
672
|
-
extend schema
|
|
673
|
-
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
674
|
-
|
|
675
|
-
type T @key(fields: "k") {
|
|
676
|
-
k: ID!
|
|
677
|
-
}
|
|
678
|
-
`;
|
|
679
|
-
|
|
680
|
-
validateFullSchema(buildAndValidate(doc));
|
|
681
|
-
});
|
|
682
|
-
|
|
683
|
-
it('expands definitions if both the federation spec and link spec are linked', () => {
|
|
684
|
-
const doc = gql`
|
|
685
|
-
extend schema
|
|
686
|
-
@link(url: "https://specs.apollo.dev/link/v1.0")
|
|
687
|
-
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
688
|
-
|
|
689
|
-
type T @key(fields: "k") {
|
|
690
|
-
k: ID!
|
|
691
|
-
}
|
|
692
|
-
`;
|
|
693
|
-
|
|
694
|
-
validateFullSchema(buildAndValidate(doc));
|
|
695
|
-
});
|
|
696
|
-
|
|
697
|
-
it('is valid if a schema is complete from the get-go', () => {
|
|
698
|
-
validateFullSchema(buildAndValidate(gql(expectedFullSchema)));
|
|
699
|
-
});
|
|
700
|
-
|
|
701
|
-
it('expands missing definitions when some are partially provided', () => {
|
|
702
|
-
const docs = [
|
|
703
|
-
gql`
|
|
704
|
-
extend schema
|
|
705
|
-
@link(url: "https://specs.apollo.dev/link/v1.0")
|
|
706
|
-
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
707
|
-
|
|
708
|
-
type T @key(fields: "k") {
|
|
709
|
-
k: ID!
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
directive @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
|
|
713
|
-
|
|
714
|
-
scalar federation__FieldSet
|
|
715
|
-
|
|
716
|
-
scalar link__Import
|
|
717
|
-
`,
|
|
718
|
-
gql`
|
|
719
|
-
extend schema
|
|
720
|
-
@link(url: "https://specs.apollo.dev/link/v1.0")
|
|
721
|
-
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
722
|
-
|
|
723
|
-
type T @key(fields: "k") {
|
|
724
|
-
k: ID!
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
scalar link__Import
|
|
728
|
-
`,
|
|
729
|
-
gql`
|
|
730
|
-
extend schema
|
|
731
|
-
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
732
|
-
|
|
733
|
-
type T @key(fields: "k") {
|
|
734
|
-
k: ID!
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
scalar link__Import
|
|
738
|
-
`,
|
|
739
|
-
gql`
|
|
740
|
-
extend schema
|
|
741
|
-
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
742
|
-
|
|
743
|
-
type T @key(fields: "k") {
|
|
744
|
-
k: ID!
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
directive @federation__external(reason: String) on OBJECT | FIELD_DEFINITION
|
|
748
|
-
`,
|
|
749
|
-
gql`
|
|
750
|
-
extend schema
|
|
751
|
-
@link(url: "https://specs.apollo.dev/federation/v2.0")
|
|
752
|
-
|
|
753
|
-
type T {
|
|
754
|
-
k: ID!
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
enum link__Purpose {
|
|
758
|
-
EXECUTION
|
|
759
|
-
SECURITY
|
|
760
|
-
}
|
|
761
|
-
`,
|
|
762
|
-
];
|
|
763
|
-
|
|
764
|
-
// Note that we cannot use `validateFullSchema` as-is for those examples because the order or directive is going
|
|
765
|
-
// to be different. But that's ok, we mostly care that the validation doesn't throw since validation ultimately
|
|
766
|
-
// calls the graphQL-js validation, so we can be somewhat sure that if something necessary wasn't expanded
|
|
767
|
-
// properly, we would have an issue. The main reason we did validate the full schema in prior tests is
|
|
768
|
-
// so we had at least one full example of a subgraph expansion in the tests.
|
|
769
|
-
docs.forEach((doc) => buildAndValidate(doc));
|
|
770
|
-
});
|
|
771
|
-
|
|
772
|
-
it('allows known directives with incomplete but compatible definitions', () => {
|
|
773
|
-
const docs = [
|
|
774
|
-
// @key has a `resolvable` argument in its full definition, but it is optional.
|
|
775
|
-
gql`
|
|
776
|
-
extend schema
|
|
777
|
-
@link(url: "https://specs.apollo.dev/link/v1.0")
|
|
778
|
-
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
779
|
-
|
|
780
|
-
type T @key(fields: "k") {
|
|
781
|
-
k: ID!
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
directive @key(fields: federation__FieldSet!) repeatable on OBJECT | INTERFACE
|
|
785
|
-
|
|
786
|
-
scalar federation__FieldSet
|
|
787
|
-
`,
|
|
788
|
-
// @inacessible can be put in a bunch of locations, but you're welcome to restrict yourself to just fields.
|
|
789
|
-
gql`
|
|
790
|
-
extend schema
|
|
791
|
-
@link(url: "https://specs.apollo.dev/link/v1.0")
|
|
792
|
-
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@inaccessible"])
|
|
793
|
-
|
|
794
|
-
type T {
|
|
795
|
-
k: ID! @inaccessible
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
directive @inaccessible on FIELD_DEFINITION
|
|
799
|
-
`,
|
|
800
|
-
// @key is repeatable, but you're welcome to restrict yourself to never repeating it.
|
|
801
|
-
gql`
|
|
802
|
-
extend schema
|
|
803
|
-
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
804
|
-
|
|
805
|
-
type T @key(fields: "k") {
|
|
806
|
-
k: ID!
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
directive @key(fields: federation__FieldSet!, resolvable: Boolean = true) on OBJECT | INTERFACE
|
|
810
|
-
|
|
811
|
-
scalar federation__FieldSet
|
|
812
|
-
`,
|
|
813
|
-
// @key `resolvable` argument is optional, but you're welcome to force users to always provide it.
|
|
814
|
-
gql`
|
|
815
|
-
extend schema
|
|
816
|
-
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
817
|
-
|
|
818
|
-
type T @key(fields: "k", resolvable: true) {
|
|
819
|
-
k: ID!
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
directive @key(fields: federation__FieldSet!, resolvable: Boolean!) repeatable on OBJECT | INTERFACE
|
|
823
|
-
|
|
824
|
-
scalar federation__FieldSet
|
|
825
|
-
`,
|
|
826
|
-
// @link `url` argument is allowed to be `null` now, but it used not too, so making sure we still
|
|
827
|
-
// accept definition where it's mandatory.
|
|
828
|
-
gql`
|
|
829
|
-
extend schema
|
|
830
|
-
@link(url: "https://specs.apollo.dev/link/v1.0")
|
|
831
|
-
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
832
|
-
|
|
833
|
-
type T @key(fields: "k") {
|
|
834
|
-
k: ID!
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
directive @link(url: String!, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA
|
|
838
|
-
|
|
839
|
-
scalar link__Import
|
|
840
|
-
scalar link__Purpose
|
|
841
|
-
`,
|
|
842
|
-
];
|
|
843
|
-
|
|
844
|
-
// Like above, we really only care that the examples validate.
|
|
845
|
-
docs.forEach((doc) => buildAndValidate(doc));
|
|
846
|
-
});
|
|
847
|
-
|
|
848
|
-
it('errors on invalid known directive location', () => {
|
|
849
|
-
const doc = gql`
|
|
850
|
-
extend schema
|
|
851
|
-
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
852
|
-
|
|
853
|
-
type T @key(fields: "k") {
|
|
854
|
-
k: ID!
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
directive @federation__external(reason: String) on OBJECT | FIELD_DEFINITION | SCHEMA
|
|
858
|
-
`;
|
|
859
|
-
|
|
860
|
-
// @external is not allowed on 'schema' and likely never will.
|
|
861
|
-
expect(buildForErrors(doc, { asFed2: false })).toStrictEqual([[
|
|
862
|
-
'DIRECTIVE_DEFINITION_INVALID',
|
|
863
|
-
'[S] Invalid definition for directive "@federation__external": "@federation__external" should have locations OBJECT, FIELD_DEFINITION, but found (non-subset) OBJECT, FIELD_DEFINITION, SCHEMA',
|
|
864
|
-
]]);
|
|
865
|
-
});
|
|
866
|
-
|
|
867
|
-
it('errors on invalid non-repeatable directive marked repeateable', () => {
|
|
868
|
-
const doc = gql`
|
|
869
|
-
extend schema
|
|
870
|
-
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
871
|
-
|
|
872
|
-
type T @key(fields: "k") {
|
|
873
|
-
k: ID!
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
directive @federation__external repeatable on OBJECT | FIELD_DEFINITION
|
|
877
|
-
`;
|
|
878
|
-
|
|
879
|
-
// @external is not repeatable (and has no reason to be since it has no arguments).
|
|
880
|
-
expect(buildForErrors(doc, { asFed2: false })).toStrictEqual([[
|
|
881
|
-
'DIRECTIVE_DEFINITION_INVALID',
|
|
882
|
-
'[S] Invalid definition for directive "@federation__external": "@federation__external" should not be repeatable',
|
|
883
|
-
]]);
|
|
884
|
-
});
|
|
885
|
-
|
|
886
|
-
it('errors on unknown argument of known directive', () => {
|
|
887
|
-
const doc = gql`
|
|
888
|
-
extend schema
|
|
889
|
-
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
890
|
-
|
|
891
|
-
type T @key(fields: "k") {
|
|
892
|
-
k: ID!
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
directive @federation__external(foo: Int) on OBJECT | FIELD_DEFINITION
|
|
896
|
-
`;
|
|
897
|
-
|
|
898
|
-
expect(buildForErrors(doc, { asFed2: false })).toStrictEqual([[
|
|
899
|
-
'DIRECTIVE_DEFINITION_INVALID',
|
|
900
|
-
'[S] Invalid definition for directive "@federation__external": unknown/unsupported argument "foo"',
|
|
901
|
-
]]);
|
|
902
|
-
});
|
|
903
|
-
|
|
904
|
-
it('errors on invalid type for a known argument', () => {
|
|
905
|
-
const doc = gql`
|
|
906
|
-
extend schema
|
|
907
|
-
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
908
|
-
|
|
909
|
-
type T @key(fields: "k") {
|
|
910
|
-
k: ID!
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
directive @key(fields: String!, resolvable: String) repeatable on OBJECT | INTERFACE
|
|
914
|
-
`;
|
|
915
|
-
|
|
916
|
-
expect(buildForErrors(doc, { asFed2: false })).toStrictEqual([[
|
|
917
|
-
'DIRECTIVE_DEFINITION_INVALID',
|
|
918
|
-
'[S] Invalid definition for directive "@key": argument "resolvable" should have type "Boolean" but found type "String"',
|
|
919
|
-
]]);
|
|
920
|
-
});
|
|
921
|
-
|
|
922
|
-
it('errors on a required argument defined as optional', () => {
|
|
923
|
-
const doc = gql`
|
|
924
|
-
extend schema
|
|
925
|
-
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
926
|
-
|
|
927
|
-
type T @key(fields: "k") {
|
|
928
|
-
k: ID!
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
directive @key(fields: federation__FieldSet, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
|
|
932
|
-
|
|
933
|
-
scalar federation__FieldSet
|
|
934
|
-
`;
|
|
935
|
-
|
|
936
|
-
expect(buildForErrors(doc, { asFed2: false })).toStrictEqual([[
|
|
937
|
-
'DIRECTIVE_DEFINITION_INVALID',
|
|
938
|
-
'[S] Invalid definition for directive "@key": argument "fields" should have type "federation__FieldSet!" but found type "federation__FieldSet"',
|
|
939
|
-
]]);
|
|
940
|
-
});
|
|
941
|
-
|
|
942
|
-
it('errors on invalid definition for @link Purpose', () => {
|
|
943
|
-
const doc = gql`
|
|
944
|
-
extend schema
|
|
945
|
-
@link(url: "https://specs.apollo.dev/federation/v2.0")
|
|
946
|
-
|
|
947
|
-
type T {
|
|
948
|
-
k: ID!
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
enum link__Purpose {
|
|
952
|
-
EXECUTION
|
|
953
|
-
RANDOM
|
|
954
|
-
}
|
|
955
|
-
`;
|
|
956
|
-
|
|
957
|
-
expect(buildForErrors(doc, { asFed2: false })).toStrictEqual([[
|
|
958
|
-
'TYPE_DEFINITION_INVALID',
|
|
959
|
-
'[S] Invalid definition for type "Purpose": expected values [EXECUTION, SECURITY] but found [EXECUTION, RANDOM].',
|
|
960
|
-
]]);
|
|
961
|
-
});
|
|
962
|
-
|
|
963
|
-
it('allows any (non-scalar) type in redefinition when expected type is a scalar', () => {
|
|
964
|
-
const doc = gql`
|
|
965
|
-
extend schema
|
|
966
|
-
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
967
|
-
|
|
968
|
-
type T @key(fields: "k") {
|
|
969
|
-
k: ID!
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
# 'fields' should be of type 'federation_FieldSet!', but ensure we allow 'String!' alternatively.
|
|
973
|
-
directive @key(fields: String!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
|
|
974
|
-
`;
|
|
975
|
-
|
|
976
|
-
// Just making sure this don't error out.
|
|
977
|
-
buildAndValidate(doc);
|
|
978
|
-
});
|
|
979
|
-
|
|
980
|
-
it('allows defining a repeatable directive as non-repeatable but validates usages', () => {
|
|
981
|
-
const doc = gql`
|
|
982
|
-
type T @key(fields: "k1") @key(fields: "k2") {
|
|
983
|
-
k1: ID!
|
|
984
|
-
k2: ID!
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
directive @key(fields: String!) on OBJECT
|
|
988
|
-
`;
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
// Test for fed2 (with @key being @link-ed)
|
|
992
|
-
expect(buildForErrors(doc)).toStrictEqual([[
|
|
993
|
-
'INVALID_GRAPHQL',
|
|
994
|
-
'[S] The directive "@key" can only be used once at this location.',
|
|
995
|
-
]]);
|
|
996
|
-
|
|
997
|
-
// Test for fed1
|
|
998
|
-
expect(buildForErrors(doc, { asFed2: false })).toStrictEqual([[
|
|
999
|
-
'INVALID_GRAPHQL',
|
|
1000
|
-
'[S] The directive "@key" can only be used once at this location.',
|
|
1001
|
-
]]);
|
|
1002
|
-
});
|
|
1003
|
-
});
|
|
1004
|
-
|
|
1005
|
-
describe('federation 1 schema', () => {
|
|
1006
|
-
it('accepts federation directive definitions without arguments', () => {
|
|
1007
|
-
const doc = gql`
|
|
1008
|
-
type Query {
|
|
1009
|
-
a: Int
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
directive @key on OBJECT | INTERFACE
|
|
1013
|
-
directive @requires on FIELD_DEFINITION
|
|
1014
|
-
`;
|
|
1015
|
-
|
|
1016
|
-
buildAndValidate(doc);
|
|
1017
|
-
});
|
|
1018
|
-
|
|
1019
|
-
it('accepts federation directive definitions with nullable arguments', () => {
|
|
1020
|
-
const doc = gql`
|
|
1021
|
-
type Query {
|
|
1022
|
-
a: Int
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
type T @key(fields: "id") {
|
|
1026
|
-
id: ID! @requires(fields: "x")
|
|
1027
|
-
x: Int @external
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
# Tests with the _FieldSet argument non-nullable
|
|
1031
|
-
scalar _FieldSet
|
|
1032
|
-
directive @key(fields: _FieldSet) on OBJECT | INTERFACE
|
|
1033
|
-
|
|
1034
|
-
# Tests with the argument as String and non-nullable
|
|
1035
|
-
directive @requires(fields: String) on FIELD_DEFINITION
|
|
1036
|
-
`;
|
|
1037
|
-
|
|
1038
|
-
buildAndValidate(doc);
|
|
1039
|
-
});
|
|
1040
|
-
|
|
1041
|
-
it('accepts federation directive definitions with "FieldSet" type instead of "_FieldSet"', () => {
|
|
1042
|
-
const doc = gql`
|
|
1043
|
-
type Query {
|
|
1044
|
-
a: Int
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
type T @key(fields: "id") {
|
|
1048
|
-
id: ID!
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
scalar FieldSet
|
|
1052
|
-
directive @key(fields: FieldSet) on OBJECT | INTERFACE
|
|
1053
|
-
`;
|
|
1054
|
-
|
|
1055
|
-
buildAndValidate(doc);
|
|
1056
|
-
});
|
|
1057
|
-
|
|
1058
|
-
it('rejects federation directive definition with unknown arguments', () => {
|
|
1059
|
-
const doc = gql`
|
|
1060
|
-
type Query {
|
|
1061
|
-
a: Int
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
type T @key(fields: "id", unknown: 42) {
|
|
1065
|
-
id: ID!
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
scalar _FieldSet
|
|
1069
|
-
directive @key(fields: _FieldSet!, unknown: Int) on OBJECT | INTERFACE
|
|
1070
|
-
`;
|
|
1071
|
-
|
|
1072
|
-
expect(buildForErrors(doc, { asFed2: false })).toStrictEqual([[
|
|
1073
|
-
'DIRECTIVE_DEFINITION_INVALID',
|
|
1074
|
-
'[S] Invalid definition for directive "@key": unknown/unsupported argument "unknown"'
|
|
1075
|
-
]]);
|
|
1076
|
-
});
|
|
1077
|
-
})
|
|
1078
|
-
|
|
1079
|
-
describe('@shareable', () => {
|
|
1080
|
-
it('can only be applied to fields of object types', () => {
|
|
1081
|
-
const doc = gql`
|
|
1082
|
-
interface I {
|
|
1083
|
-
a: Int @shareable
|
|
1084
|
-
}
|
|
1085
|
-
`;
|
|
1086
|
-
|
|
1087
|
-
expect(buildForErrors(doc)).toStrictEqual([[
|
|
1088
|
-
'INVALID_SHAREABLE_USAGE',
|
|
1089
|
-
'[S] Invalid use of @shareable on field "I.a": only object type fields can be marked with @shareable'
|
|
1090
|
-
]]);
|
|
1091
|
-
});
|
|
1092
|
-
|
|
1093
|
-
it('rejects duplicate @shareable on the same definition declaration', () => {
|
|
1094
|
-
const doc = gql`
|
|
1095
|
-
type E @shareable @key(fields: "id") @shareable {
|
|
1096
|
-
id: ID!
|
|
1097
|
-
a: Int
|
|
1098
|
-
}
|
|
1099
|
-
`;
|
|
1100
|
-
|
|
1101
|
-
expect(buildForErrors(doc)).toStrictEqual([[
|
|
1102
|
-
'INVALID_SHAREABLE_USAGE',
|
|
1103
|
-
'[S] Invalid duplicate application of @shareable on the same type declaration of "E": @shareable is only repeatable on types so it can be used simultaneously on a type definition and its extensions, but it should not be duplicated on the same definition/extension declaration'
|
|
1104
|
-
]]);
|
|
1105
|
-
});
|
|
1106
|
-
|
|
1107
|
-
it('rejects duplicate @shareable on the same extension declaration', () => {
|
|
1108
|
-
const doc = gql`
|
|
1109
|
-
type E @shareable {
|
|
1110
|
-
id: ID!
|
|
1111
|
-
a: Int
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
extend type E @shareable @shareable {
|
|
1115
|
-
b: Int
|
|
1116
|
-
}
|
|
1117
|
-
`;
|
|
1118
|
-
expect(buildForErrors(doc)).toStrictEqual([[
|
|
1119
|
-
'INVALID_SHAREABLE_USAGE',
|
|
1120
|
-
'[S] Invalid duplicate application of @shareable on the same type declaration of "E": @shareable is only repeatable on types so it can be used simultaneously on a type definition and its extensions, but it should not be duplicated on the same definition/extension declaration'
|
|
1121
|
-
]]);
|
|
1122
|
-
});
|
|
1123
|
-
|
|
1124
|
-
it('rejects duplicate @shareable on a field', () => {
|
|
1125
|
-
const doc = gql`
|
|
1126
|
-
type E {
|
|
1127
|
-
a: Int @shareable @shareable
|
|
1128
|
-
}
|
|
1129
|
-
`;
|
|
1130
|
-
|
|
1131
|
-
expect(buildForErrors(doc)).toStrictEqual([[
|
|
1132
|
-
'INVALID_SHAREABLE_USAGE',
|
|
1133
|
-
'[S] Invalid duplicate application of @shareable on field "E.a": @shareable is only repeatable on types so it can be used simultaneously on a type definition and its extensions, but it should not be duplicated on the same definition/extension declaration'
|
|
1134
|
-
]]);
|
|
1135
|
-
});
|
|
1136
|
-
});
|
|
1137
|
-
|
|
1138
|
-
describe('@interfaceObject/@key on interfaces validation', () => {
|
|
1139
|
-
it('@key on interfaces require @key on all implementations', () => {
|
|
1140
|
-
const doc = gql`
|
|
1141
|
-
interface I @key(fields: "id1") @key(fields: "id2") {
|
|
1142
|
-
id1: ID!
|
|
1143
|
-
id2: ID!
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
type A implements I @key(fields: "id2") {
|
|
1147
|
-
id1: ID!
|
|
1148
|
-
id2: ID!
|
|
1149
|
-
a: Int
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
type B implements I @key(fields: "id1") @key(fields: "id2") {
|
|
1153
|
-
id1: ID!
|
|
1154
|
-
id2: ID!
|
|
1155
|
-
b: Int
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
type C implements I @key(fields: "id2") {
|
|
1159
|
-
id1: ID!
|
|
1160
|
-
id2: ID!
|
|
1161
|
-
c: Int
|
|
1162
|
-
}
|
|
1163
|
-
`;
|
|
1164
|
-
|
|
1165
|
-
expect(buildForErrors(doc)).toStrictEqual([[
|
|
1166
|
-
'INTERFACE_KEY_NOT_ON_IMPLEMENTATION',
|
|
1167
|
-
'[S] Key @key(fields: "id1") on interface type "I" is missing on implementation types "A" and "C".',
|
|
1168
|
-
]]);
|
|
1169
|
-
});
|
|
1170
|
-
|
|
1171
|
-
it('@key on interfaces with @key on some implementation non resolvable', () => {
|
|
1172
|
-
const doc = gql`
|
|
1173
|
-
interface I @key(fields: "id1") {
|
|
1174
|
-
id1: ID!
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
type A implements I @key(fields: "id1") {
|
|
1178
|
-
id1: ID!
|
|
1179
|
-
a: Int
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
type B implements I @key(fields: "id1") {
|
|
1183
|
-
id1: ID!
|
|
1184
|
-
b: Int
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
type C implements I @key(fields: "id1", resolvable: false) {
|
|
1188
|
-
id1: ID!
|
|
1189
|
-
c: Int
|
|
1190
|
-
}
|
|
1191
|
-
`;
|
|
1192
|
-
|
|
1193
|
-
expect(buildForErrors(doc)).toStrictEqual([[
|
|
1194
|
-
'INTERFACE_KEY_NOT_ON_IMPLEMENTATION',
|
|
1195
|
-
'[S] Key @key(fields: "id1") on interface type "I" should be resolvable on all implementation types, but is declared with argument "@key(resolvable:)" set to false in type "C".',
|
|
1196
|
-
]]);
|
|
1197
|
-
});
|
|
1198
|
-
|
|
1199
|
-
it('ensures order of fields in key does not matter', () => {
|
|
1200
|
-
const doc = gql`
|
|
1201
|
-
interface I @key(fields: "a b c") {
|
|
1202
|
-
a: Int
|
|
1203
|
-
b: Int
|
|
1204
|
-
c: Int
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
type A implements I @key(fields: "c b a") {
|
|
1208
|
-
a: Int
|
|
1209
|
-
b: Int
|
|
1210
|
-
c: Int
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
type B implements I @key(fields: "a c b") {
|
|
1214
|
-
a: Int
|
|
1215
|
-
b: Int
|
|
1216
|
-
c: Int
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
type C implements I @key(fields: "a b c") {
|
|
1220
|
-
a: Int
|
|
1221
|
-
b: Int
|
|
1222
|
-
c: Int
|
|
1223
|
-
}
|
|
1224
|
-
`;
|
|
1225
|
-
|
|
1226
|
-
expect(buildForErrors(doc)).toBeUndefined();
|
|
1227
|
-
});
|
|
1228
|
-
|
|
1229
|
-
// There is no meaningful way to make @interfaceObject work on a value type at the moment, because
|
|
1230
|
-
// if you have an @interfaceObject, some other subgraph needs to be able to resolve the concrete
|
|
1231
|
-
// type, and that imply that you have key to go to that other subgraph.
|
|
1232
|
-
// To be clear, the @key on the @interfaceObject technically con't need to be "resolvable", and the
|
|
1233
|
-
// difference between no key and a non-resolvable key is arguably more convention than a genuine
|
|
1234
|
-
// mechanical difference at the moment, but still a good idea to rely on that convention to help
|
|
1235
|
-
// catching obvious mistakes early.
|
|
1236
|
-
it('only allow @interfaceObject on entity types', () => {
|
|
1237
|
-
const doc = gql`
|
|
1238
|
-
# This one shouldn't raise an error
|
|
1239
|
-
type A @key(fields: "id", resolvable: false) @interfaceObject {
|
|
1240
|
-
id: ID!
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
# This one should
|
|
1244
|
-
type B @interfaceObject {
|
|
1245
|
-
x: Int
|
|
1246
|
-
}
|
|
1247
|
-
`;
|
|
1248
|
-
|
|
1249
|
-
expect(buildForErrors(doc)).toStrictEqual([[
|
|
1250
|
-
'INTERFACE_OBJECT_USAGE_ERROR',
|
|
1251
|
-
'[S] The @interfaceObject directive can only be applied to entity types but type "B" has no @key in this subgraph.'
|
|
1252
|
-
]]);
|
|
1253
|
-
});
|
|
1254
|
-
});
|