@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.
Files changed (127) hide show
  1. package/CHANGELOG.md +43 -3
  2. package/dist/buildSchema.d.ts +7 -3
  3. package/dist/buildSchema.d.ts.map +1 -1
  4. package/dist/buildSchema.js +94 -61
  5. package/dist/buildSchema.js.map +1 -1
  6. package/dist/coreSpec.d.ts +39 -9
  7. package/dist/coreSpec.d.ts.map +1 -1
  8. package/dist/coreSpec.js +232 -42
  9. package/dist/coreSpec.js.map +1 -1
  10. package/dist/definitions.d.ts +71 -51
  11. package/dist/definitions.d.ts.map +1 -1
  12. package/dist/definitions.js +326 -231
  13. package/dist/definitions.js.map +1 -1
  14. package/dist/directiveAndTypeSpecification.d.ts +48 -0
  15. package/dist/directiveAndTypeSpecification.d.ts.map +1 -0
  16. package/dist/directiveAndTypeSpecification.js +253 -0
  17. package/dist/directiveAndTypeSpecification.js.map +1 -0
  18. package/dist/error.d.ts +21 -1
  19. package/dist/error.d.ts.map +1 -1
  20. package/dist/error.js +63 -3
  21. package/dist/error.js.map +1 -1
  22. package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
  23. package/dist/extractSubgraphsFromSupergraph.js +42 -97
  24. package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
  25. package/dist/federation.d.ts +102 -46
  26. package/dist/federation.d.ts.map +1 -1
  27. package/dist/federation.js +762 -234
  28. package/dist/federation.js.map +1 -1
  29. package/dist/federationSpec.d.ts +23 -0
  30. package/dist/federationSpec.d.ts.map +1 -0
  31. package/dist/federationSpec.js +117 -0
  32. package/dist/federationSpec.js.map +1 -0
  33. package/dist/inaccessibleSpec.d.ts +5 -1
  34. package/dist/inaccessibleSpec.d.ts.map +1 -1
  35. package/dist/inaccessibleSpec.js +31 -3
  36. package/dist/inaccessibleSpec.js.map +1 -1
  37. package/dist/index.d.ts +4 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +9 -1
  40. package/dist/index.js.map +1 -1
  41. package/dist/introspection.d.ts.map +1 -1
  42. package/dist/introspection.js +8 -3
  43. package/dist/introspection.js.map +1 -1
  44. package/dist/joinSpec.d.ts +6 -1
  45. package/dist/joinSpec.d.ts.map +1 -1
  46. package/dist/joinSpec.js +22 -0
  47. package/dist/joinSpec.js.map +1 -1
  48. package/dist/knownCoreFeatures.d.ts +4 -0
  49. package/dist/knownCoreFeatures.d.ts.map +1 -0
  50. package/dist/knownCoreFeatures.js +16 -0
  51. package/dist/knownCoreFeatures.js.map +1 -0
  52. package/dist/operations.d.ts +9 -1
  53. package/dist/operations.d.ts.map +1 -1
  54. package/dist/operations.js +27 -5
  55. package/dist/operations.js.map +1 -1
  56. package/dist/precompute.d.ts +3 -0
  57. package/dist/precompute.d.ts.map +1 -0
  58. package/dist/precompute.js +51 -0
  59. package/dist/precompute.js.map +1 -0
  60. package/dist/print.d.ts +11 -9
  61. package/dist/print.d.ts.map +1 -1
  62. package/dist/print.js +32 -22
  63. package/dist/print.js.map +1 -1
  64. package/dist/schemaUpgrader.d.ts +108 -0
  65. package/dist/schemaUpgrader.d.ts.map +1 -0
  66. package/dist/schemaUpgrader.js +497 -0
  67. package/dist/schemaUpgrader.js.map +1 -0
  68. package/dist/suggestions.d.ts +1 -1
  69. package/dist/suggestions.d.ts.map +1 -1
  70. package/dist/suggestions.js.map +1 -1
  71. package/dist/supergraphs.d.ts.map +1 -1
  72. package/dist/supergraphs.js +3 -3
  73. package/dist/supergraphs.js.map +1 -1
  74. package/dist/tagSpec.d.ts +7 -2
  75. package/dist/tagSpec.d.ts.map +1 -1
  76. package/dist/tagSpec.js +36 -16
  77. package/dist/tagSpec.js.map +1 -1
  78. package/dist/utils.d.ts +7 -0
  79. package/dist/utils.d.ts.map +1 -1
  80. package/dist/utils.js +34 -1
  81. package/dist/utils.js.map +1 -1
  82. package/dist/validate.d.ts.map +1 -1
  83. package/dist/validate.js +19 -11
  84. package/dist/validate.js.map +1 -1
  85. package/dist/validation/KnownTypeNamesInFederationRule.d.ts.map +1 -1
  86. package/dist/validation/KnownTypeNamesInFederationRule.js +1 -2
  87. package/dist/validation/KnownTypeNamesInFederationRule.js.map +1 -1
  88. package/dist/values.d.ts +1 -0
  89. package/dist/values.d.ts.map +1 -1
  90. package/dist/values.js +3 -2
  91. package/dist/values.js.map +1 -1
  92. package/package.json +4 -4
  93. package/src/__tests__/coreSpec.test.ts +100 -0
  94. package/src/__tests__/definitions.test.ts +98 -17
  95. package/src/__tests__/extractSubgraphsFromSupergraph.test.ts +64 -0
  96. package/src/__tests__/federation.test.ts +31 -0
  97. package/src/__tests__/operations.test.ts +2 -3
  98. package/src/__tests__/removeInaccessibleElements.test.ts +59 -6
  99. package/src/__tests__/schemaUpgrader.test.ts +169 -0
  100. package/src/__tests__/subgraphValidation.test.ts +422 -21
  101. package/src/__tests__/values.test.ts +2 -4
  102. package/src/buildSchema.ts +154 -84
  103. package/src/coreSpec.ts +294 -55
  104. package/src/definitions.ts +415 -275
  105. package/src/directiveAndTypeSpecification.ts +353 -0
  106. package/src/error.ts +143 -5
  107. package/src/extractSubgraphsFromSupergraph.ts +56 -122
  108. package/src/federation.ts +991 -302
  109. package/src/federationSpec.ts +146 -0
  110. package/src/inaccessibleSpec.ts +39 -11
  111. package/src/index.ts +4 -0
  112. package/src/introspection.ts +8 -3
  113. package/src/joinSpec.ts +35 -4
  114. package/src/knownCoreFeatures.ts +13 -0
  115. package/src/operations.ts +37 -7
  116. package/src/precompute.ts +65 -0
  117. package/src/print.ts +63 -48
  118. package/src/schemaUpgrader.ts +653 -0
  119. package/src/suggestions.ts +1 -1
  120. package/src/supergraphs.ts +4 -3
  121. package/src/tagSpec.ts +50 -18
  122. package/src/utils.ts +63 -0
  123. package/src/validate.ts +27 -16
  124. package/src/validation/KnownTypeNamesInFederationRule.ts +1 -7
  125. package/src/values.ts +7 -3
  126. package/tsconfig.test.tsbuildinfo +1 -1
  127. 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 TAG_DEFINITION_INVALID = makeCodeDefinition(
114
- 'TAG_DIRECTIVE_DEFINITION_INVALID',
115
- 'The @tag directive has an invalid defintion in the schema.',
116
- { addedIn: FED1_CODE },
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
- TAG_DEFINITION_INVALID,
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; }, {});