@apollo/federation-internals 2.0.0-alpha.6 → 2.0.0-preview.10
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 +43 -3
- package/dist/buildSchema.d.ts +7 -3
- package/dist/buildSchema.d.ts.map +1 -1
- package/dist/buildSchema.js +94 -61
- package/dist/buildSchema.js.map +1 -1
- package/dist/coreSpec.d.ts +39 -9
- package/dist/coreSpec.d.ts.map +1 -1
- package/dist/coreSpec.js +232 -42
- package/dist/coreSpec.js.map +1 -1
- package/dist/definitions.d.ts +71 -51
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +326 -231
- package/dist/definitions.js.map +1 -1
- package/dist/directiveAndTypeSpecification.d.ts +48 -0
- package/dist/directiveAndTypeSpecification.d.ts.map +1 -0
- package/dist/directiveAndTypeSpecification.js +253 -0
- package/dist/directiveAndTypeSpecification.js.map +1 -0
- package/dist/error.d.ts +21 -1
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +63 -3
- package/dist/error.js.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.js +42 -97
- package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
- package/dist/federation.d.ts +102 -46
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +762 -234
- package/dist/federation.js.map +1 -1
- package/dist/federationSpec.d.ts +23 -0
- package/dist/federationSpec.d.ts.map +1 -0
- package/dist/federationSpec.js +117 -0
- package/dist/federationSpec.js.map +1 -0
- package/dist/inaccessibleSpec.d.ts +5 -1
- package/dist/inaccessibleSpec.d.ts.map +1 -1
- package/dist/inaccessibleSpec.js +31 -3
- package/dist/inaccessibleSpec.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/introspection.d.ts.map +1 -1
- package/dist/introspection.js +8 -3
- package/dist/introspection.js.map +1 -1
- package/dist/joinSpec.d.ts +6 -1
- package/dist/joinSpec.d.ts.map +1 -1
- package/dist/joinSpec.js +22 -0
- package/dist/joinSpec.js.map +1 -1
- package/dist/knownCoreFeatures.d.ts +4 -0
- package/dist/knownCoreFeatures.d.ts.map +1 -0
- package/dist/knownCoreFeatures.js +16 -0
- package/dist/knownCoreFeatures.js.map +1 -0
- package/dist/operations.d.ts +9 -1
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +27 -5
- package/dist/operations.js.map +1 -1
- package/dist/precompute.d.ts +3 -0
- package/dist/precompute.d.ts.map +1 -0
- package/dist/precompute.js +51 -0
- package/dist/precompute.js.map +1 -0
- package/dist/print.d.ts +11 -9
- package/dist/print.d.ts.map +1 -1
- package/dist/print.js +32 -22
- package/dist/print.js.map +1 -1
- package/dist/schemaUpgrader.d.ts +108 -0
- package/dist/schemaUpgrader.d.ts.map +1 -0
- package/dist/schemaUpgrader.js +497 -0
- package/dist/schemaUpgrader.js.map +1 -0
- package/dist/suggestions.d.ts +1 -1
- package/dist/suggestions.d.ts.map +1 -1
- package/dist/suggestions.js.map +1 -1
- package/dist/supergraphs.d.ts.map +1 -1
- package/dist/supergraphs.js +3 -3
- package/dist/supergraphs.js.map +1 -1
- package/dist/tagSpec.d.ts +7 -2
- package/dist/tagSpec.d.ts.map +1 -1
- package/dist/tagSpec.js +36 -16
- package/dist/tagSpec.js.map +1 -1
- package/dist/utils.d.ts +7 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +34 -1
- package/dist/utils.js.map +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +19 -11
- package/dist/validate.js.map +1 -1
- package/dist/validation/KnownTypeNamesInFederationRule.d.ts.map +1 -1
- package/dist/validation/KnownTypeNamesInFederationRule.js +1 -2
- package/dist/validation/KnownTypeNamesInFederationRule.js.map +1 -1
- package/dist/values.d.ts +1 -0
- package/dist/values.d.ts.map +1 -1
- package/dist/values.js +3 -2
- package/dist/values.js.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/coreSpec.test.ts +100 -0
- package/src/__tests__/definitions.test.ts +98 -17
- package/src/__tests__/extractSubgraphsFromSupergraph.test.ts +64 -0
- package/src/__tests__/federation.test.ts +31 -0
- package/src/__tests__/operations.test.ts +2 -3
- package/src/__tests__/removeInaccessibleElements.test.ts +59 -6
- package/src/__tests__/schemaUpgrader.test.ts +169 -0
- package/src/__tests__/subgraphValidation.test.ts +422 -21
- package/src/__tests__/values.test.ts +2 -4
- package/src/buildSchema.ts +154 -84
- package/src/coreSpec.ts +294 -55
- package/src/definitions.ts +415 -275
- package/src/directiveAndTypeSpecification.ts +353 -0
- package/src/error.ts +143 -5
- package/src/extractSubgraphsFromSupergraph.ts +56 -122
- package/src/federation.ts +991 -302
- package/src/federationSpec.ts +146 -0
- package/src/inaccessibleSpec.ts +39 -11
- package/src/index.ts +4 -0
- package/src/introspection.ts +8 -3
- package/src/joinSpec.ts +35 -4
- package/src/knownCoreFeatures.ts +13 -0
- package/src/operations.ts +37 -7
- package/src/precompute.ts +65 -0
- package/src/print.ts +63 -48
- package/src/schemaUpgrader.ts +653 -0
- package/src/suggestions.ts +1 -1
- package/src/supergraphs.ts +4 -3
- package/src/tagSpec.ts +50 -18
- package/src/utils.ts +63 -0
- package/src/validate.ts +27 -16
- package/src/validation/KnownTypeNamesInFederationRule.ts +1 -7
- package/src/values.ts +7 -3
- package/tsconfig.test.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { ASTNode, DirectiveLocation, GraphQLError } from "graphql";
|
|
2
|
+
import {
|
|
3
|
+
ArgumentDefinition,
|
|
4
|
+
DirectiveDefinition,
|
|
5
|
+
EnumType,
|
|
6
|
+
InputType,
|
|
7
|
+
isCustomScalarType,
|
|
8
|
+
isEnumType,
|
|
9
|
+
isListType,
|
|
10
|
+
isNonNullType,
|
|
11
|
+
isObjectType,
|
|
12
|
+
isUnionType,
|
|
13
|
+
NamedType,
|
|
14
|
+
ObjectType,
|
|
15
|
+
OutputType,
|
|
16
|
+
ScalarType,
|
|
17
|
+
Schema,
|
|
18
|
+
UnionType,
|
|
19
|
+
} from "./definitions";
|
|
20
|
+
import { ERRORS } from "./error";
|
|
21
|
+
import { valueEquals, valueToString } from "./values";
|
|
22
|
+
import { sameType } from "./types";
|
|
23
|
+
import { arrayEquals, assert } from "./utils";
|
|
24
|
+
|
|
25
|
+
export type DirectiveSpecification = {
|
|
26
|
+
name: string,
|
|
27
|
+
checkOrAdd: (schema: Schema, nameInSchema?: string, asBuiltIn?: boolean) => GraphQLError[],
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type TypeSpecification = {
|
|
31
|
+
name: string,
|
|
32
|
+
checkOrAdd: (schema: Schema, nameInSchema?: string, asBuiltIn?: boolean) => GraphQLError[],
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type ArgumentSpecification = {
|
|
36
|
+
name: string,
|
|
37
|
+
type: InputType,
|
|
38
|
+
defaultValue?: any,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type FieldSpecification = {
|
|
42
|
+
name: string,
|
|
43
|
+
type: OutputType,
|
|
44
|
+
args?: ArgumentSpecification[],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export function createDirectiveSpecification({
|
|
48
|
+
name,
|
|
49
|
+
locations,
|
|
50
|
+
repeatable = false,
|
|
51
|
+
argumentFct = undefined,
|
|
52
|
+
}: {
|
|
53
|
+
name: string,
|
|
54
|
+
locations: DirectiveLocation[],
|
|
55
|
+
repeatable?: boolean,
|
|
56
|
+
argumentFct?: (schema: Schema, nameInSchema?: string) => { args: ArgumentSpecification[], errors: GraphQLError[] },
|
|
57
|
+
}): DirectiveSpecification {
|
|
58
|
+
return {
|
|
59
|
+
name,
|
|
60
|
+
checkOrAdd: (schema: Schema, nameInSchema?: string, asBuiltIn?: boolean) => {
|
|
61
|
+
const actualName = nameInSchema ?? name;
|
|
62
|
+
const {args, errors} = argumentFct ? argumentFct(schema, actualName) : { args: [], errors: []};
|
|
63
|
+
if (errors.length > 0) {
|
|
64
|
+
return errors;
|
|
65
|
+
}
|
|
66
|
+
const existing = schema.directive(actualName);
|
|
67
|
+
if (existing) {
|
|
68
|
+
return ensureSameDirectiveStructure({name: actualName, locations, repeatable, args}, existing);
|
|
69
|
+
} else {
|
|
70
|
+
const directive = schema.addDirectiveDefinition(new DirectiveDefinition(actualName, asBuiltIn));
|
|
71
|
+
directive.repeatable = repeatable;
|
|
72
|
+
directive.addLocations(...locations);
|
|
73
|
+
for (const { name, type, defaultValue } of args) {
|
|
74
|
+
directive.addArgument(name, type, defaultValue);
|
|
75
|
+
}
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function createScalarTypeSpecification({ name }: { name: string }): TypeSpecification {
|
|
83
|
+
return {
|
|
84
|
+
name,
|
|
85
|
+
checkOrAdd: (schema: Schema, nameInSchema?: string, asBuiltIn?: boolean) => {
|
|
86
|
+
const actualName = nameInSchema ?? name;
|
|
87
|
+
const existing = schema.type(actualName);
|
|
88
|
+
if (existing) {
|
|
89
|
+
return ensureSameTypeKind('ScalarType', existing);
|
|
90
|
+
} else {
|
|
91
|
+
schema.addType(new ScalarType(actualName, asBuiltIn));
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function createObjectTypeSpecification({
|
|
99
|
+
name,
|
|
100
|
+
fieldsFct,
|
|
101
|
+
}: {
|
|
102
|
+
name: string,
|
|
103
|
+
fieldsFct: (schema: Schema) => FieldSpecification[],
|
|
104
|
+
}): TypeSpecification {
|
|
105
|
+
return {
|
|
106
|
+
name,
|
|
107
|
+
checkOrAdd: (schema: Schema, nameInSchema?: string, asBuiltIn?: boolean) => {
|
|
108
|
+
const actualName = nameInSchema ?? name;
|
|
109
|
+
const expectedFields = fieldsFct(schema);
|
|
110
|
+
const existing = schema.type(actualName);
|
|
111
|
+
if (existing) {
|
|
112
|
+
let errors = ensureSameTypeKind('ObjectType', existing);
|
|
113
|
+
if (errors.length > 0) {
|
|
114
|
+
return errors;
|
|
115
|
+
}
|
|
116
|
+
assert(isObjectType(existing), 'Should be an object type');
|
|
117
|
+
for (const { name, type, args } of expectedFields) {
|
|
118
|
+
const existingField = existing.field(name);
|
|
119
|
+
if (!existingField) {
|
|
120
|
+
errors = errors.concat(ERRORS.TYPE_DEFINITION_INVALID.err({
|
|
121
|
+
message: `Invalid definition of type ${name}: missing field ${name}`,
|
|
122
|
+
nodes: existing.sourceAST
|
|
123
|
+
}));
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
// We allow adding non-nullability because we've seen redefinition of the federation _Service type with type String! for the `sdl` field
|
|
127
|
+
// and we don't want to break backward compatibility as this doesn't feel too harmful.
|
|
128
|
+
let existingType = existingField.type!;
|
|
129
|
+
if (!isNonNullType(type) && isNonNullType(existingType)) {
|
|
130
|
+
existingType = existingType.ofType;
|
|
131
|
+
}
|
|
132
|
+
if (!sameType(type, existingType)) {
|
|
133
|
+
errors = errors.concat(ERRORS.TYPE_DEFINITION_INVALID.err({
|
|
134
|
+
message: `Invalid definition for field ${name} of type ${name}: should have type ${type} but found type ${existingField.type}`,
|
|
135
|
+
nodes: existingField.sourceAST
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
errors = errors.concat(ensureSameArguments(
|
|
139
|
+
{ name, args },
|
|
140
|
+
existingField,
|
|
141
|
+
`field "${existingField.coordinate}"`,
|
|
142
|
+
));
|
|
143
|
+
}
|
|
144
|
+
return errors;
|
|
145
|
+
} else {
|
|
146
|
+
const createdType = schema.addType(new ObjectType(actualName, asBuiltIn));
|
|
147
|
+
for (const { name, type, args } of expectedFields) {
|
|
148
|
+
const field = createdType.addField(name, type);
|
|
149
|
+
for (const { name: argName, type: argType, defaultValue } of args ?? []) {
|
|
150
|
+
field.addArgument(argName, argType, defaultValue);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function createUnionTypeSpecification({
|
|
160
|
+
name,
|
|
161
|
+
membersFct,
|
|
162
|
+
}: {
|
|
163
|
+
name: string,
|
|
164
|
+
membersFct: (schema: Schema) => string[],
|
|
165
|
+
}): TypeSpecification {
|
|
166
|
+
return {
|
|
167
|
+
name,
|
|
168
|
+
checkOrAdd: (schema: Schema, nameInSchema?: string, asBuiltIn?: boolean) => {
|
|
169
|
+
const actualName = nameInSchema ?? name;
|
|
170
|
+
const existing = schema.type(actualName);
|
|
171
|
+
const expectedMembers = membersFct(schema).sort((n1, n2) => n1.localeCompare(n2));
|
|
172
|
+
if (expectedMembers.length === 0) {
|
|
173
|
+
if (existing) {
|
|
174
|
+
return [ERRORS.TYPE_DEFINITION_INVALID.err({
|
|
175
|
+
message: `Invalid definition of type ${name}: expected the union type to not exist/have no members but it is defined.`,
|
|
176
|
+
nodes: existing.sourceAST
|
|
177
|
+
})];
|
|
178
|
+
}
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
if (existing) {
|
|
182
|
+
let errors = ensureSameTypeKind('UnionType', existing);
|
|
183
|
+
if (errors.length > 0) {
|
|
184
|
+
return errors;
|
|
185
|
+
}
|
|
186
|
+
assert(isUnionType(existing), 'Should be an union type');
|
|
187
|
+
const actualMembers = existing.members().map(m => m.type.name).sort((n1, n2) => n1.localeCompare(n2));
|
|
188
|
+
// This is kind of fragile in a core schema world where members may have been renamed, but we currently
|
|
189
|
+
// only use this one for the _Entity type where that shouldn't be an issue.
|
|
190
|
+
if (!arrayEquals(expectedMembers, actualMembers)) {
|
|
191
|
+
errors = errors.concat(ERRORS.TYPE_DEFINITION_INVALID.err({
|
|
192
|
+
message: `Invalid definition of type ${name}: expected members [${expectedMembers}] but found [${actualMembers}].`,
|
|
193
|
+
nodes: existing.sourceAST
|
|
194
|
+
}));
|
|
195
|
+
}
|
|
196
|
+
return errors;
|
|
197
|
+
} else {
|
|
198
|
+
const type = schema.addType(new UnionType(actualName, asBuiltIn));
|
|
199
|
+
for (const member of expectedMembers) {
|
|
200
|
+
type.addType(member);
|
|
201
|
+
}
|
|
202
|
+
return [];
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function createEnumTypeSpecification({
|
|
209
|
+
name,
|
|
210
|
+
values,
|
|
211
|
+
}: {
|
|
212
|
+
name: string,
|
|
213
|
+
values: { name: string, description?: string}[],
|
|
214
|
+
}): TypeSpecification {
|
|
215
|
+
return {
|
|
216
|
+
name,
|
|
217
|
+
checkOrAdd: (schema: Schema, nameInSchema?: string, asBuiltIn?: boolean) => {
|
|
218
|
+
const actualName = nameInSchema ?? name;
|
|
219
|
+
const existing = schema.type(actualName);
|
|
220
|
+
const expectedValueNames = values.map((v) => v.name).sort((n1, n2) => n1.localeCompare(n2));
|
|
221
|
+
if (existing) {
|
|
222
|
+
let errors = ensureSameTypeKind('EnumType', existing);
|
|
223
|
+
if (errors.length > 0) {
|
|
224
|
+
return errors;
|
|
225
|
+
}
|
|
226
|
+
assert(isEnumType(existing), 'Should be an enum type');
|
|
227
|
+
const actualValueNames = existing.values.map(v => v.name).sort((n1, n2) => n1.localeCompare(n2));
|
|
228
|
+
if (!arrayEquals(expectedValueNames, actualValueNames)) {
|
|
229
|
+
errors = errors.concat(ERRORS.TYPE_DEFINITION_INVALID.err({
|
|
230
|
+
message: `Invalid definition of type ${name}: expected values [${expectedValueNames}] but found [${actualValueNames}].`,
|
|
231
|
+
nodes: existing.sourceAST
|
|
232
|
+
}));
|
|
233
|
+
}
|
|
234
|
+
return errors;
|
|
235
|
+
} else {
|
|
236
|
+
const type = schema.addType(new EnumType(actualName, asBuiltIn));
|
|
237
|
+
for (const {name, description} of values) {
|
|
238
|
+
type.addValue(name).description = description;
|
|
239
|
+
}
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function ensureSameTypeKind(expected: NamedType['kind'], actual: NamedType): GraphQLError[] {
|
|
247
|
+
return expected === actual.kind
|
|
248
|
+
? []
|
|
249
|
+
: [ERRORS.TYPE_DEFINITION_INVALID.err({
|
|
250
|
+
message: `Invalid definition for type ${actual.name}: ${actual.name} should be a ${expected} but is defined as a ${actual.kind}`,
|
|
251
|
+
nodes: actual.sourceAST
|
|
252
|
+
})];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function ensureSameDirectiveStructure(
|
|
256
|
+
expected: {
|
|
257
|
+
name: string,
|
|
258
|
+
locations: DirectiveLocation[],
|
|
259
|
+
repeatable: boolean,
|
|
260
|
+
args: ArgumentSpecification[]
|
|
261
|
+
},
|
|
262
|
+
actual: DirectiveDefinition<any>,
|
|
263
|
+
): GraphQLError[] {
|
|
264
|
+
const directiveName = `"@${expected.name}"`
|
|
265
|
+
let errors = ensureSameArguments(expected, actual, `directive ${directiveName}`);
|
|
266
|
+
// It's ok to say you'll never repeat a repeatable directive. It's not ok to repeat one that isn't.
|
|
267
|
+
if (!expected.repeatable && actual.repeatable) {
|
|
268
|
+
errors = errors.concat(ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
|
|
269
|
+
message: `Invalid definition for directive ${directiveName}: ${directiveName} should${expected.repeatable ? "" : " not"} be repeatable`,
|
|
270
|
+
nodes: actual.sourceAST
|
|
271
|
+
}));
|
|
272
|
+
}
|
|
273
|
+
// Similarly, it's ok to say that you will never use a directive in some locations, but not that you will use it in places not allowed by what is expected.
|
|
274
|
+
if (!actual.locations.every(loc => expected.locations.includes(loc))) {
|
|
275
|
+
errors = errors.concat(ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
|
|
276
|
+
message: `Invalid definition for directive ${directiveName}: ${directiveName} should have locations ${expected.locations.join(', ')}, but found (non-subset) ${actual.locations.join(', ')}`,
|
|
277
|
+
nodes: actual.sourceAST
|
|
278
|
+
}));
|
|
279
|
+
}
|
|
280
|
+
return errors;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function ensureSameArguments(
|
|
284
|
+
expected: {
|
|
285
|
+
name: string,
|
|
286
|
+
args?: ArgumentSpecification[]
|
|
287
|
+
},
|
|
288
|
+
actual: { argument(name: string): ArgumentDefinition<any> | undefined, arguments(): readonly ArgumentDefinition<any>[] },
|
|
289
|
+
what: string,
|
|
290
|
+
containerSourceAST?: ASTNode,
|
|
291
|
+
): GraphQLError[] {
|
|
292
|
+
const expectedArguments = expected.args ?? [];
|
|
293
|
+
const errors: GraphQLError[] = [];
|
|
294
|
+
for (const { name, type, defaultValue } of expectedArguments) {
|
|
295
|
+
const actualArgument = actual.argument(name);
|
|
296
|
+
if (!actualArgument) {
|
|
297
|
+
// Not declaring an optional argument is ok: that means you won't be able to pass a non-default value in your schema, but we allow you that.
|
|
298
|
+
// But missing a required argument it not ok.
|
|
299
|
+
if (isNonNullType(type) && defaultValue === undefined) {
|
|
300
|
+
errors.push(ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
|
|
301
|
+
message: `Invalid definition for ${what}: missing required argument "${name}"`,
|
|
302
|
+
nodes: containerSourceAST
|
|
303
|
+
}));
|
|
304
|
+
}
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
let actualType = actualArgument.type!;
|
|
309
|
+
if (isNonNullType(actualType) && !isNonNullType(type)) {
|
|
310
|
+
// It's ok to redefine an optional argument as mandatory. For instance, if you want to force people on your team to provide a "deprecation reason", you can
|
|
311
|
+
// redefine @deprecated as `directive @deprecated(reason: String!)...` to get validation. In other words, you are allowed to always pass an argument that
|
|
312
|
+
// is optional if you so wish.
|
|
313
|
+
actualType = actualType.ofType;
|
|
314
|
+
}
|
|
315
|
+
if (!sameType(type, actualType) && !isValidInputTypeRedefinition(type, actualType)) {
|
|
316
|
+
errors.push(ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
|
|
317
|
+
message: `Invalid definition for ${what}: argument "${name}" should have type "${type}" but found type "${actualArgument.type!}"`,
|
|
318
|
+
nodes: actualArgument.sourceAST
|
|
319
|
+
}));
|
|
320
|
+
} else if (!isNonNullType(actualArgument.type!) && !valueEquals(defaultValue, actualArgument.defaultValue)) {
|
|
321
|
+
errors.push(ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
|
|
322
|
+
message: `Invalid definition for ${what}: argument "${name}" should have default value ${valueToString(defaultValue)} but found default value ${valueToString(actualArgument.defaultValue)}`,
|
|
323
|
+
nodes: actualArgument.sourceAST
|
|
324
|
+
}));
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
for (const actualArgument of actual.arguments()) {
|
|
328
|
+
// If it's an expect argument, we already validated it. But we still need to reject unkown argument.
|
|
329
|
+
if (!expectedArguments.some((arg) => arg.name === actualArgument.name)) {
|
|
330
|
+
errors.push(ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
|
|
331
|
+
message: `Invalid definition for ${what}: unknown/unsupported argument "${actualArgument.name}"`,
|
|
332
|
+
nodes: actualArgument.sourceAST
|
|
333
|
+
}));
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return errors;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function isValidInputTypeRedefinition(expectedType: InputType, actualType: InputType): boolean {
|
|
340
|
+
// If the expected type is a custom scalar, then we allow the redefinition to be another type (unless it's a custom scalar, in which
|
|
341
|
+
// case it has to be the same scalar). The rational being that since graphQL does no validation of values passed to a custom scalar,
|
|
342
|
+
// any code that gets some value as input for a custom scalar has to do validation manually, and so there is little harm in allowing
|
|
343
|
+
// a redefinition with another type since any truly invalid value would failed that "manual validation". In practice, this leeway
|
|
344
|
+
// make sense because many scalar will tend to accept only one kind of values (say, strings) and exists only to inform that said string
|
|
345
|
+
// needs to follow a specific format, and in such case, letting user redefine the type as String adds flexibility while doing little harm.
|
|
346
|
+
if (isListType(expectedType)) {
|
|
347
|
+
return isListType(actualType) && isValidInputTypeRedefinition(expectedType.ofType, actualType.ofType);
|
|
348
|
+
}
|
|
349
|
+
if (isNonNullType(expectedType)) {
|
|
350
|
+
return isNonNullType(actualType) && isValidInputTypeRedefinition(expectedType.ofType, actualType.ofType);
|
|
351
|
+
}
|
|
352
|
+
return isCustomScalarType(expectedType) && !isCustomScalarType(actualType);
|
|
353
|
+
}
|
package/src/error.ts
CHANGED
|
@@ -105,15 +105,53 @@ export function errorCodeDef(e: GraphQLError | string): ErrorCodeDefinition | un
|
|
|
105
105
|
return code ? codeDefByCode[code] : undefined;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
export function withModifiedErrorMessage(e: GraphQLError, newMessage: string): GraphQLError {
|
|
109
|
+
return new GraphQLError(
|
|
110
|
+
newMessage,
|
|
111
|
+
{
|
|
112
|
+
nodes: e.nodes,
|
|
113
|
+
source: e.source,
|
|
114
|
+
positions: e.positions,
|
|
115
|
+
path: e.path,
|
|
116
|
+
originalError: e.originalError,
|
|
117
|
+
extensions: e.extensions
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function withModifiedErrorNodes(e: GraphQLError, newNodes: readonly ASTNode[] | ASTNode | undefined): GraphQLError {
|
|
123
|
+
return new GraphQLError(
|
|
124
|
+
e.message,
|
|
125
|
+
{
|
|
126
|
+
nodes: newNodes,
|
|
127
|
+
source: e.source,
|
|
128
|
+
positions: e.positions,
|
|
129
|
+
path: e.path,
|
|
130
|
+
originalError: e.originalError,
|
|
131
|
+
extensions: e.extensions
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
108
136
|
const INVALID_GRAPHQL = makeCodeDefinition(
|
|
109
137
|
'INVALID_GRAPHQL',
|
|
110
138
|
'A schema is invalid GraphQL: it violates one of the rule of the specification.'
|
|
111
139
|
);
|
|
112
140
|
|
|
113
|
-
const
|
|
114
|
-
'
|
|
115
|
-
'
|
|
116
|
-
{
|
|
141
|
+
const DIRECTIVE_DEFINITION_INVALID = makeCodeDefinition(
|
|
142
|
+
'DIRECTIVE_DEFINITION_INVALID',
|
|
143
|
+
'A built-in or federation directive has an invalid definition in the schema.',
|
|
144
|
+
{ ...DEFAULT_METADATA, replaces: ['TAG_DEFINITION_INVALID'] },
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const TYPE_DEFINITION_INVALID = makeCodeDefinition(
|
|
148
|
+
'TYPE_DEFINITION_INVALID',
|
|
149
|
+
'A built-in or federation type has an invalid definition in the schema.',
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const UNKNOWN_FEDERATION_LINK_VERSION = makeCodeDefinition(
|
|
153
|
+
'UNKNOWN_FEDERATION_LINK_VERSION',
|
|
154
|
+
'The version of federation in a @link directive on the schema is unknown.',
|
|
117
155
|
);
|
|
118
156
|
|
|
119
157
|
const FIELDS_HAS_ARGS = makeFederationDirectiveErrorCodeCategory(
|
|
@@ -149,6 +187,13 @@ const EXTERNAL_UNUSED = makeCodeDefinition(
|
|
|
149
187
|
{ addedIn: FED1_CODE },
|
|
150
188
|
);
|
|
151
189
|
|
|
190
|
+
const TYPE_WITH_ONLY_UNUSED_EXTERNAL = makeCodeDefinition(
|
|
191
|
+
'TYPE_WITH_ONLY_UNUSED_EXTERNAL',
|
|
192
|
+
'A federation 1 schema has a composite type comprised only of unused external fields.'
|
|
193
|
+
+ ` Note that this error can _only_ be raised for federation 1 schema as federation 2 schema do not allow unused external fields (and errors with code ${EXTERNAL_UNUSED.code} will be raised in that case).`
|
|
194
|
+
+ ' But when federation 1 schema are automatically migrated to federation 2 ones, unused external fields are automaticaly removed, and in rare case this can leave a type empty. If that happens, an error with this code will be raised',
|
|
195
|
+
);
|
|
196
|
+
|
|
152
197
|
const PROVIDES_ON_NON_OBJECT_FIELD = makeCodeDefinition(
|
|
153
198
|
'PROVIDES_ON_NON_OBJECT_FIELD',
|
|
154
199
|
'A `@provides` directive is used to mark a field whose base type is not an object type.'
|
|
@@ -230,6 +275,16 @@ const EXTERNAL_ARGUMENT_DEFAULT_MISMATCH = makeCodeDefinition(
|
|
|
230
275
|
'An `@external` field declares an argument with a default that is incompatible with the corresponding argument in the declaration(s) of that field in other subgtaphs.',
|
|
231
276
|
);
|
|
232
277
|
|
|
278
|
+
const EXTERNAL_ON_INTERFACE = makeCodeDefinition(
|
|
279
|
+
'EXTERNAL_ON_INTERFACE',
|
|
280
|
+
'The field of an interface type is marked with `@external`: as external is about marking field not resolved by the subgraph and as interface field are not resolved (only implementations of those fields are), an "external" interface field is nonsensical',
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
const MERGED_DIRECTIVE_APPLICATION_ON_EXTERNAL = makeCodeDefinition(
|
|
284
|
+
'MERGED_DIRECTIVE_APPLICATION_ON_EXTERNAL',
|
|
285
|
+
'In a subgraph, a field is both marked @external and has a merged directive applied to it',
|
|
286
|
+
);
|
|
287
|
+
|
|
233
288
|
const FIELD_TYPE_MISMATCH = makeCodeDefinition(
|
|
234
289
|
'FIELD_TYPE_MISMATCH',
|
|
235
290
|
'A field has a type that is incompatible with other declarations of that field in other subgraphs.',
|
|
@@ -252,6 +307,11 @@ const ARGUMENT_DEFAULT_MISMATCH = makeCodeDefinition(
|
|
|
252
307
|
'An argument (of a field/directive) has a default value that is incompatible with that of other declarations of that same argument in other subgraphs.',
|
|
253
308
|
);
|
|
254
309
|
|
|
310
|
+
const NON_REPEATABLE_DIRECTIVE_ARGUMENTS_MISMATCH = makeCodeDefinition(
|
|
311
|
+
'NON_REPEATABLE_DIRECTIVE_ARGUMENTS_MISMATCH',
|
|
312
|
+
'A non-repeatable directive is applied to a schema element in different subgraphs but with arguments that are different.',
|
|
313
|
+
);
|
|
314
|
+
|
|
255
315
|
const EXTENSION_WITH_NO_BASE = makeCodeDefinition(
|
|
256
316
|
'EXTENSION_WITH_NO_BASE',
|
|
257
317
|
'A subgraph is attempting to `extend` a type that is not originally defined in any known subgraph.',
|
|
@@ -269,11 +329,71 @@ const INTERFACE_FIELD_IMPLEM_TYPE_MISMATCH = makeCodeDefinition(
|
|
|
269
329
|
'For an interface field, some of its concrete implementations have @external or @requires and there is difference in those implementations return type (which is currently not supported; see https://github.com/apollographql/federation/issues/1257)'
|
|
270
330
|
);
|
|
271
331
|
|
|
332
|
+
const INVALID_FIELD_SHARING = makeCodeDefinition(
|
|
333
|
+
'INVALID_FIELD_SHARING',
|
|
334
|
+
'A field that is non-shareable in at least one subgraph is resolved by multiple subgraphs.'
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
const INVALID_LINK_DIRECTIVE_USAGE = makeCodeDefinition(
|
|
338
|
+
'INVALID_LINK_DIRECTIVE_USAGE',
|
|
339
|
+
'An application of the @link directive is invalid/does not respect the specification.'
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
const LINK_IMPORT_NAME_MISMATCH = makeCodeDefinition(
|
|
343
|
+
'LINK_IMPORT_NAME_MISMATCH',
|
|
344
|
+
'The import name for a merged directive (as declared by the relevant `@link(import:)` argument) is inconsistent between subgraphs.'
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
const REFERENCED_INACCESSIBLE = makeCodeDefinition(
|
|
348
|
+
'REFERENCED_INACCESSIBLE',
|
|
349
|
+
'An element is marked as @inaccessible but is referenced by a non-inaccessible element.'
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
const REQUIRED_INPUT_FIELD_MISSING_IN_SOME_SUBGRAPH = makeCodeDefinition(
|
|
353
|
+
'REQUIRED_INPUT_FIELD_MISSING_IN_SOME_SUBGRAPH',
|
|
354
|
+
'A field of an input object type is mandatory in some subgraphs, but the field is not defined in all the subgraphs that define the input object type.'
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
const REQUIRED_ARGUMENT_MISSING_IN_SOME_SUBGRAPH = makeCodeDefinition(
|
|
358
|
+
'REQUIRED_ARGUMENT_MISSING_IN_SOME_SUBGRAPH',
|
|
359
|
+
'An argument of a field or directive definition is mandatory in some subgraphs, but the argument is not defined in all the subgraphs that define the field or directive definition.'
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
const EMPTY_MERGED_INPUT_TYPE = makeCodeDefinition(
|
|
363
|
+
'EMPTY_MERGED_INPUT_TYPE',
|
|
364
|
+
'An input object type has no field common to all the subgraphs that define the type. Merging that type would result in an invalid empty input object type.'
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
const INCONSISTENT_ENUM_VALUE = makeCodeDefinition(
|
|
368
|
+
'INCONSISTENT_ENUM_VALUE',
|
|
369
|
+
'An enum type that is used as both an input and output type has a value that is not defined in all the subgraphs that define the enum type.'
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
const EMPTY_MERGED_ENUM_TYPE = makeCodeDefinition(
|
|
373
|
+
'EMPTY_MERGED_ENUM_TYPE',
|
|
374
|
+
'An enum type has no value common to all the subgraphs that define the type. Merging that type would result in an invalid empty enum type.'
|
|
375
|
+
);
|
|
376
|
+
|
|
272
377
|
const SATISFIABILITY_ERROR = makeCodeDefinition(
|
|
273
378
|
'SATISFIABILITY_ERROR',
|
|
274
379
|
'Subgraphs can be merged, but the resulting supergraph API would have queries that cannot be satisfied by those subgraphs.',
|
|
275
380
|
);
|
|
276
381
|
|
|
382
|
+
const OVERRIDE_FROM_SELF_ERROR = makeCodeDefinition(
|
|
383
|
+
'OVERRIDE_FROM_SELF_ERROR',
|
|
384
|
+
'Field with `@override` directive has "from" location that references its own subgraph.',
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
const OVERRIDE_SOURCE_HAS_OVERRIDE = makeCodeDefinition(
|
|
388
|
+
'OVERRIDE_SOURCE_HAS_OVERRIDE',
|
|
389
|
+
'Field which is overridden to another subgraph is also marked @override.',
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
const OVERRIDE_COLLISION_WITH_ANOTHER_DIRECTIVE = makeCodeDefinition(
|
|
393
|
+
'OVERRIDE_COLLISION_WITH_ANOTHER_DIRECTIVE',
|
|
394
|
+
'The @override directive cannot be used on external fields, nor to override fields with either @external, @provides, or @requires.',
|
|
395
|
+
);
|
|
396
|
+
|
|
277
397
|
export const ERROR_CATEGORIES = {
|
|
278
398
|
DIRECTIVE_FIELDS_MISSING_EXTERNAL,
|
|
279
399
|
DIRECTIVE_UNSUPPORTED_ON_INTERFACE,
|
|
@@ -285,7 +405,9 @@ export const ERROR_CATEGORIES = {
|
|
|
285
405
|
|
|
286
406
|
export const ERRORS = {
|
|
287
407
|
INVALID_GRAPHQL,
|
|
288
|
-
|
|
408
|
+
DIRECTIVE_DEFINITION_INVALID,
|
|
409
|
+
TYPE_DEFINITION_INVALID,
|
|
410
|
+
UNKNOWN_FEDERATION_LINK_VERSION,
|
|
289
411
|
KEY_FIELDS_HAS_ARGS,
|
|
290
412
|
PROVIDES_FIELDS_HAS_ARGS,
|
|
291
413
|
REQUIRES_FIELDS_HAS_ARGS,
|
|
@@ -295,6 +417,7 @@ export const ERRORS = {
|
|
|
295
417
|
PROVIDES_UNSUPPORTED_ON_INTERFACE,
|
|
296
418
|
REQUIRES_UNSUPPORTED_ON_INTERFACE,
|
|
297
419
|
EXTERNAL_UNUSED,
|
|
420
|
+
TYPE_WITH_ONLY_UNUSED_EXTERNAL,
|
|
298
421
|
PROVIDES_ON_NON_OBJECT_FIELD,
|
|
299
422
|
KEY_INVALID_FIELDS_TYPE,
|
|
300
423
|
PROVIDES_INVALID_FIELDS_TYPE,
|
|
@@ -314,14 +437,29 @@ export const ERRORS = {
|
|
|
314
437
|
EXTERNAL_ARGUMENT_MISSING,
|
|
315
438
|
EXTERNAL_ARGUMENT_TYPE_MISMATCH,
|
|
316
439
|
EXTERNAL_ARGUMENT_DEFAULT_MISMATCH,
|
|
440
|
+
EXTERNAL_ON_INTERFACE,
|
|
441
|
+
MERGED_DIRECTIVE_APPLICATION_ON_EXTERNAL,
|
|
317
442
|
FIELD_TYPE_MISMATCH,
|
|
318
443
|
ARGUMENT_TYPE_MISMATCH,
|
|
319
444
|
INPUT_FIELD_DEFAULT_MISMATCH,
|
|
320
445
|
ARGUMENT_DEFAULT_MISMATCH,
|
|
446
|
+
NON_REPEATABLE_DIRECTIVE_ARGUMENTS_MISMATCH,
|
|
321
447
|
EXTENSION_WITH_NO_BASE,
|
|
322
448
|
EXTERNAL_MISSING_ON_BASE,
|
|
323
449
|
INTERFACE_FIELD_IMPLEM_TYPE_MISMATCH,
|
|
450
|
+
INVALID_FIELD_SHARING,
|
|
451
|
+
INVALID_LINK_DIRECTIVE_USAGE,
|
|
452
|
+
LINK_IMPORT_NAME_MISMATCH,
|
|
453
|
+
REFERENCED_INACCESSIBLE,
|
|
454
|
+
REQUIRED_ARGUMENT_MISSING_IN_SOME_SUBGRAPH,
|
|
455
|
+
REQUIRED_INPUT_FIELD_MISSING_IN_SOME_SUBGRAPH,
|
|
456
|
+
EMPTY_MERGED_INPUT_TYPE,
|
|
457
|
+
INCONSISTENT_ENUM_VALUE,
|
|
458
|
+
EMPTY_MERGED_ENUM_TYPE,
|
|
324
459
|
SATISFIABILITY_ERROR,
|
|
460
|
+
OVERRIDE_COLLISION_WITH_ANOTHER_DIRECTIVE,
|
|
461
|
+
OVERRIDE_FROM_SELF_ERROR,
|
|
462
|
+
OVERRIDE_SOURCE_HAS_OVERRIDE,
|
|
325
463
|
};
|
|
326
464
|
|
|
327
465
|
const codeDefByCode = Object.values(ERRORS).reduce((obj: {[code: string]: ErrorCodeDefinition}, codeDef: ErrorCodeDefinition) => { obj[codeDef.code] = codeDef; return obj; }, {});
|