@apollo/federation-internals 2.0.0-alpha.6 → 2.0.0-preview.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/buildSchema.d.ts +7 -3
  3. package/dist/buildSchema.d.ts.map +1 -1
  4. package/dist/buildSchema.js +41 -22
  5. package/dist/buildSchema.js.map +1 -1
  6. package/dist/coreSpec.d.ts +26 -4
  7. package/dist/coreSpec.d.ts.map +1 -1
  8. package/dist/coreSpec.js +86 -25
  9. package/dist/coreSpec.js.map +1 -1
  10. package/dist/definitions.d.ts +50 -43
  11. package/dist/definitions.d.ts.map +1 -1
  12. package/dist/definitions.js +201 -217
  13. package/dist/definitions.js.map +1 -1
  14. package/dist/directiveAndTypeSpecification.d.ts +38 -0
  15. package/dist/directiveAndTypeSpecification.d.ts.map +1 -0
  16. package/dist/directiveAndTypeSpecification.js +196 -0
  17. package/dist/directiveAndTypeSpecification.js.map +1 -0
  18. package/dist/error.d.ts +9 -1
  19. package/dist/error.d.ts.map +1 -1
  20. package/dist/error.js +20 -2
  21. package/dist/error.js.map +1 -1
  22. package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
  23. package/dist/extractSubgraphsFromSupergraph.js +28 -93
  24. package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
  25. package/dist/federation.d.ts +88 -46
  26. package/dist/federation.d.ts.map +1 -1
  27. package/dist/federation.js +706 -228
  28. package/dist/federation.js.map +1 -1
  29. package/dist/federationSpec.d.ts +19 -0
  30. package/dist/federationSpec.d.ts.map +1 -0
  31. package/dist/federationSpec.js +91 -0
  32. package/dist/federationSpec.js.map +1 -0
  33. package/dist/index.d.ts +2 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +7 -1
  36. package/dist/index.js.map +1 -1
  37. package/dist/joinSpec.d.ts +1 -0
  38. package/dist/joinSpec.d.ts.map +1 -1
  39. package/dist/joinSpec.js +1 -0
  40. package/dist/joinSpec.js.map +1 -1
  41. package/dist/operations.d.ts +8 -1
  42. package/dist/operations.d.ts.map +1 -1
  43. package/dist/operations.js +11 -4
  44. package/dist/operations.js.map +1 -1
  45. package/dist/print.d.ts +11 -9
  46. package/dist/print.d.ts.map +1 -1
  47. package/dist/print.js +21 -11
  48. package/dist/print.js.map +1 -1
  49. package/dist/schemaUpgrader.d.ts +108 -0
  50. package/dist/schemaUpgrader.d.ts.map +1 -0
  51. package/dist/schemaUpgrader.js +498 -0
  52. package/dist/schemaUpgrader.js.map +1 -0
  53. package/dist/sharing.d.ts +3 -0
  54. package/dist/sharing.d.ts.map +1 -0
  55. package/dist/sharing.js +51 -0
  56. package/dist/sharing.js.map +1 -0
  57. package/dist/supergraphs.d.ts.map +1 -1
  58. package/dist/supergraphs.js +2 -3
  59. package/dist/supergraphs.js.map +1 -1
  60. package/dist/tagSpec.d.ts.map +1 -1
  61. package/dist/tagSpec.js +1 -3
  62. package/dist/tagSpec.js.map +1 -1
  63. package/dist/utils.d.ts +7 -0
  64. package/dist/utils.d.ts.map +1 -1
  65. package/dist/utils.js +34 -1
  66. package/dist/utils.js.map +1 -1
  67. package/dist/validate.d.ts.map +1 -1
  68. package/dist/validate.js +9 -4
  69. package/dist/validate.js.map +1 -1
  70. package/dist/validation/KnownTypeNamesInFederationRule.d.ts.map +1 -1
  71. package/dist/validation/KnownTypeNamesInFederationRule.js +1 -2
  72. package/dist/validation/KnownTypeNamesInFederationRule.js.map +1 -1
  73. package/dist/values.d.ts +1 -0
  74. package/dist/values.d.ts.map +1 -1
  75. package/dist/values.js +3 -2
  76. package/dist/values.js.map +1 -1
  77. package/package.json +3 -3
  78. package/src/__tests__/definitions.test.ts +19 -17
  79. package/src/__tests__/federation.test.ts +31 -0
  80. package/src/__tests__/operations.test.ts +2 -3
  81. package/src/__tests__/schemaUpgrader.test.ts +168 -0
  82. package/src/__tests__/subgraphValidation.test.ts +5 -19
  83. package/src/__tests__/values.test.ts +2 -4
  84. package/src/buildSchema.ts +55 -36
  85. package/src/coreSpec.ts +112 -31
  86. package/src/definitions.ts +247 -260
  87. package/src/directiveAndTypeSpecification.ts +276 -0
  88. package/src/error.ts +55 -5
  89. package/src/extractSubgraphsFromSupergraph.ts +34 -118
  90. package/src/federation.ts +912 -295
  91. package/src/federationSpec.ts +113 -0
  92. package/src/index.ts +2 -0
  93. package/src/joinSpec.ts +2 -1
  94. package/src/operations.ts +22 -7
  95. package/src/print.ts +51 -38
  96. package/src/schemaUpgrader.ts +657 -0
  97. package/src/sharing.ts +68 -0
  98. package/src/supergraphs.ts +3 -3
  99. package/src/tagSpec.ts +1 -3
  100. package/src/utils.ts +63 -0
  101. package/src/validate.ts +13 -7
  102. package/src/validation/KnownTypeNamesInFederationRule.ts +1 -7
  103. package/src/values.ts +7 -3
  104. package/tsconfig.test.tsbuildinfo +1 -1
  105. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,276 @@
1
+ import { DirectiveLocation, GraphQLError } from "graphql";
2
+ import {
3
+ ArgumentDefinition,
4
+ DirectiveDefinition,
5
+ InputType,
6
+ isNonNullType,
7
+ isObjectType,
8
+ isUnionType,
9
+ NamedType,
10
+ ObjectType,
11
+ OutputType,
12
+ ScalarType,
13
+ Schema,
14
+ UnionType,
15
+ } from "./definitions";
16
+ import { ERRORS } from "./error";
17
+ import { valueEquals, valueToString } from "./values";
18
+ import { sameType } from "./types";
19
+ import { arrayEquals, assert } from "./utils";
20
+
21
+ export type DirectiveSpecification = {
22
+ name: string,
23
+ checkOrAdd: (schema: Schema, nameInSchema?: string, asBuiltIn?: boolean) => GraphQLError[],
24
+ }
25
+
26
+ export type TypeSpecification = {
27
+ name: string,
28
+ checkOrAdd: (schema: Schema, nameInSchema?: string, asBuiltIn?: boolean) => GraphQLError[],
29
+ }
30
+
31
+ export type ArgumentSpecification = {
32
+ name: string,
33
+ type: InputType,
34
+ defaultValue?: any,
35
+ }
36
+
37
+ export type FieldSpecification = {
38
+ name: string,
39
+ type: OutputType,
40
+ args?: ArgumentSpecification[],
41
+ };
42
+
43
+ export function createDirectiveSpecification({
44
+ name,
45
+ locations,
46
+ repeatable = false,
47
+ argumentFct = undefined,
48
+ }: {
49
+ name: string,
50
+ locations: DirectiveLocation[],
51
+ repeatable?: boolean,
52
+ argumentFct?: (schema: Schema) => ArgumentSpecification[],
53
+ }): DirectiveSpecification {
54
+ return {
55
+ name,
56
+ checkOrAdd: (schema: Schema, nameInSchema?: string, asBuiltIn?: boolean) => {
57
+ const args = argumentFct ? argumentFct(schema) : [];
58
+ const actualName = nameInSchema ?? name;
59
+ const existing = schema.directive(actualName);
60
+ if (existing) {
61
+ return ensureSameDirectiveStructure({name: actualName, locations, repeatable, args}, existing);
62
+ } else {
63
+ const directive = schema.addDirectiveDefinition(new DirectiveDefinition(actualName, asBuiltIn));
64
+ directive.repeatable = repeatable;
65
+ directive.addLocations(...locations);
66
+ for (const { name, type, defaultValue } of args) {
67
+ directive.addArgument(name, type, defaultValue);
68
+ }
69
+ return [];
70
+ }
71
+ },
72
+ }
73
+ }
74
+
75
+ export function createScalarTypeSpecification({ name }: { name: string }): TypeSpecification {
76
+ return {
77
+ name,
78
+ checkOrAdd: (schema: Schema, nameInSchema?: string, asBuiltIn?: boolean) => {
79
+ const actualName = nameInSchema ?? name;
80
+ const existing = schema.type(actualName);
81
+ if (existing) {
82
+ return ensureSameTypeKind('ScalarType', existing);
83
+ } else {
84
+ schema.addType(new ScalarType(actualName, asBuiltIn));
85
+ return [];
86
+ }
87
+ },
88
+ }
89
+ }
90
+
91
+ export function createObjectTypeSpecification({
92
+ name,
93
+ fieldsFct,
94
+ }: {
95
+ name: string,
96
+ fieldsFct: (schema: Schema) => FieldSpecification[],
97
+ }): TypeSpecification {
98
+ return {
99
+ name,
100
+ checkOrAdd: (schema: Schema, nameInSchema?: string, asBuiltIn?: boolean) => {
101
+ const actualName = nameInSchema ?? name;
102
+ const expectedFields = fieldsFct(schema);
103
+ const existing = schema.type(actualName);
104
+ if (existing) {
105
+ let errors = ensureSameTypeKind('ObjectType', existing);
106
+ if (errors.length > 0) {
107
+ return errors;
108
+ }
109
+ assert(isObjectType(existing), 'Should be an object type');
110
+ for (const { name, type, args } of expectedFields) {
111
+ const existingField = existing.field(name);
112
+ if (!existingField) {
113
+ errors = errors.concat(ERRORS.TYPE_DEFINITION_INVALID.err({
114
+ message: `Invalid definition of type ${name}: missing field ${name}`,
115
+ nodes: existing.sourceAST
116
+ }));
117
+ continue;
118
+ }
119
+ // We allow adding non-nullability because we've seen redefinition of the federation _Service type with type String! for the `sdl` field
120
+ // and we don't want to break backward compatibility as this doesn't feel too harmful.
121
+ let existingType = existingField.type!;
122
+ if (!isNonNullType(type) && isNonNullType(existingType)) {
123
+ existingType = existingType.ofType;
124
+ }
125
+ if (!sameType(type, existingType)) {
126
+ errors = errors.concat(ERRORS.TYPE_DEFINITION_INVALID.err({
127
+ message: `Invalid definition for field ${name} of type ${name}: should have type ${type} but found type ${existingField.type}`,
128
+ nodes: existingField.sourceAST
129
+ }));
130
+ }
131
+ errors = errors.concat(ensureSameArguments(
132
+ { name, args },
133
+ existingField,
134
+ `field ${existingField.coordinate}`,
135
+ ));
136
+ }
137
+ return errors;
138
+ } else {
139
+ const createdType = schema.addType(new ObjectType(actualName, asBuiltIn));
140
+ for (const { name, type, args } of expectedFields) {
141
+ const field = createdType.addField(name, type);
142
+ for (const { name: argName, type: argType, defaultValue } of args ?? []) {
143
+ field.addArgument(argName, argType, defaultValue);
144
+ }
145
+ }
146
+ return [];
147
+ }
148
+ },
149
+ }
150
+ }
151
+
152
+ export function createUnionTypeSpecification({
153
+ name,
154
+ membersFct,
155
+ }: {
156
+ name: string,
157
+ membersFct: (schema: Schema) => string[],
158
+ }): TypeSpecification {
159
+ return {
160
+ name,
161
+ checkOrAdd: (schema: Schema, nameInSchema?: string, asBuiltIn?: boolean) => {
162
+ const actualName = nameInSchema ?? name;
163
+ const existing = schema.type(actualName);
164
+ const expectedMembers = membersFct(schema).sort((n1, n2) => n1.localeCompare(n2));
165
+ if (expectedMembers.length === 0) {
166
+ if (existing) {
167
+ return [ERRORS.TYPE_DEFINITION_INVALID.err({
168
+ message: `Invalid definition of type ${name}: expected the union type to not exist/have no members but it is defined.`,
169
+ nodes: existing.sourceAST
170
+ })];
171
+ }
172
+ return [];
173
+ }
174
+ if (existing) {
175
+ let errors = ensureSameTypeKind('UnionType', existing);
176
+ if (errors.length > 0) {
177
+ return errors;
178
+ }
179
+ assert(isUnionType(existing), 'Should be an union type');
180
+ const actualMembers = existing.members().map(m => m.type.name).sort((n1, n2) => n1.localeCompare(n2));
181
+ // This is kind of fragile in a core schema world where members may have been renamed, but we currently
182
+ // only use this one for the _Entity type where that shouldn't be an issue.
183
+ if (!arrayEquals(expectedMembers, actualMembers)) {
184
+ errors = errors.concat(ERRORS.TYPE_DEFINITION_INVALID.err({
185
+ message: `Invalid definition of type ${name}: expected members [${expectedMembers}] but found [${actualMembers}].`,
186
+ nodes: existing.sourceAST
187
+ }));
188
+ }
189
+ return errors;
190
+ } else {
191
+ const type = schema.addType(new UnionType(actualName, asBuiltIn));
192
+ for (const member of expectedMembers) {
193
+ type.addType(member);
194
+ }
195
+ return [];
196
+ }
197
+ },
198
+ }
199
+ }
200
+
201
+ function ensureSameTypeKind(expected: NamedType['kind'], actual: NamedType): GraphQLError[] {
202
+ return expected === actual.kind
203
+ ? []
204
+ : [ERRORS.TYPE_DEFINITION_INVALID.err({
205
+ message: `Invalid definition for type ${actual.name}: ${actual.name} should be a ${expected} but is defined as a ${actual.kind}`,
206
+ nodes: actual.sourceAST
207
+ })];
208
+ }
209
+
210
+ function ensureSameDirectiveStructure(
211
+ expected: {
212
+ name: string,
213
+ locations: DirectiveLocation[],
214
+ repeatable: boolean,
215
+ args: ArgumentSpecification[]
216
+ },
217
+ actual: DirectiveDefinition<any>,
218
+ ): GraphQLError[] {
219
+ let errors = ensureSameArguments(expected, actual, `directive ${expected}`);
220
+ // It's ok to say you'll never repeat a repeatable directive. It's not ok to repeat one that isn't.
221
+ if (!expected.repeatable && actual.repeatable) {
222
+ errors = errors.concat(ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
223
+ message: `Invalid definition for directive ${expected}: ${expected} should${expected.repeatable ? "" : " not"} be repeatable`,
224
+ nodes: actual.sourceAST
225
+ }));
226
+ }
227
+ // 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.
228
+ if (!actual.locations.every(loc => expected.locations.includes(loc))) {
229
+ errors = errors.concat(ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
230
+ message: `Invalid efinition for directive ${expected}: ${expected} should have locations ${expected.locations.join(', ')}, but found (non-subset) ${actual.locations.join(', ')}`,
231
+ nodes: actual.sourceAST
232
+ }));
233
+ }
234
+ return errors;
235
+ }
236
+
237
+ function ensureSameArguments(
238
+ expected: {
239
+ name: string,
240
+ args?: ArgumentSpecification[]
241
+ },
242
+ actual: { argument(name: string): ArgumentDefinition<any> | undefined, arguments(): readonly ArgumentDefinition<any>[] },
243
+ what: string,
244
+ ): GraphQLError[] {
245
+ const expectedArguments = expected.args ?? [];
246
+ const foundArguments = actual.arguments();
247
+ if (expectedArguments.length !== foundArguments.length) {
248
+ return [ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
249
+ message: `Invalid definition for ${what}: should have ${expectedArguments.length} arguments but ${foundArguments.length} found`,
250
+ })];
251
+ }
252
+ let errors: GraphQLError[] = [];
253
+ for (const { name, type, defaultValue } of expectedArguments) {
254
+ const actualArgument = actual.argument(name)!;
255
+ let actualType = actualArgument.type!;
256
+ if (isNonNullType(actualType) && !isNonNullType(type)) {
257
+ // 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
258
+ // redefine @deprecated as `directive @deprecated(reason: String!)...` to get validation. In other words, you are allowed to always pass an argument that
259
+ // is optional if you so wish.
260
+ actualType = actualType.ofType;
261
+ }
262
+ if (!sameType(type, actualType)) {
263
+ errors = errors.concat(ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
264
+ message: `Invalid definition of ${what}: ${name} should have type ${type} but found type ${actualArgument.type!}`,
265
+ nodes: actualArgument.sourceAST
266
+ }));
267
+ } else if (!isNonNullType(actualType) && !valueEquals(defaultValue, actualArgument.defaultValue)) {
268
+ errors = errors.concat(ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
269
+ message: `Invalid definition of ${what}: ${name} should have default value ${valueToString(defaultValue)} but found default value ${valueToString(actualArgument.defaultValue)}`,
270
+ nodes: actualArgument.sourceAST
271
+ }));
272
+ }
273
+ }
274
+ return errors;
275
+ }
276
+
package/src/error.ts CHANGED
@@ -110,10 +110,20 @@ const INVALID_GRAPHQL = makeCodeDefinition(
110
110
  'A schema is invalid GraphQL: it violates one of the rule of the specification.'
111
111
  );
112
112
 
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 },
113
+ const DIRECTIVE_DEFINITION_INVALID = makeCodeDefinition(
114
+ 'DIRECTIVE_DEFINITION_INVALID',
115
+ 'A built-in or federation directive has an invalid definition in the schema.',
116
+ { ...DEFAULT_METADATA, replaces: ['TAG_DEFINITION_INVALID'] },
117
+ );
118
+
119
+ const TYPE_DEFINITION_INVALID = makeCodeDefinition(
120
+ 'TYPE_DEFINITION_INVALID',
121
+ 'A built-in or federation type has an invalid definition in the schema.',
122
+ );
123
+
124
+ const UNKNOWN_FEDERATION_LINK_VERSION = makeCodeDefinition(
125
+ 'UNKNOWN_FEDERATION_LINK_VERSION',
126
+ 'The version of federation in a @link directive on the schema is unknown.',
117
127
  );
118
128
 
119
129
  const FIELDS_HAS_ARGS = makeFederationDirectiveErrorCodeCategory(
@@ -149,6 +159,13 @@ const EXTERNAL_UNUSED = makeCodeDefinition(
149
159
  { addedIn: FED1_CODE },
150
160
  );
151
161
 
162
+ const TYPE_WITH_ONLY_UNUSED_EXTERNAL = makeCodeDefinition(
163
+ 'TYPE_WITH_ONLY_UNUSED_EXTERNAL',
164
+ 'A federation 1 schema has a composite type comprised only of unused external fields.'
165
+ + ` 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).`
166
+ + ' 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',
167
+ );
168
+
152
169
  const PROVIDES_ON_NON_OBJECT_FIELD = makeCodeDefinition(
153
170
  'PROVIDES_ON_NON_OBJECT_FIELD',
154
171
  'A `@provides` directive is used to mark a field whose base type is not an object type.'
@@ -230,6 +247,11 @@ const EXTERNAL_ARGUMENT_DEFAULT_MISMATCH = makeCodeDefinition(
230
247
  '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
248
  );
232
249
 
250
+ const EXTERNAL_ON_INTERFACE = makeCodeDefinition(
251
+ 'EXTERNAL_ON_INTERFACE',
252
+ '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',
253
+ );
254
+
233
255
  const FIELD_TYPE_MISMATCH = makeCodeDefinition(
234
256
  'FIELD_TYPE_MISMATCH',
235
257
  'A field has a type that is incompatible with other declarations of that field in other subgraphs.',
@@ -252,6 +274,11 @@ const ARGUMENT_DEFAULT_MISMATCH = makeCodeDefinition(
252
274
  '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
275
  );
254
276
 
277
+ const NON_REPEATABLE_DIRECTIVE_ARGUMENTS_MISMATCH = makeCodeDefinition(
278
+ 'NON_REPEATABLE_DIRECTIVE_ARGUMENTS_MISMATCH',
279
+ 'A non-repeatable directive is applied to a schema element in different subgraphs but with arguments that are different.',
280
+ );
281
+
255
282
  const EXTENSION_WITH_NO_BASE = makeCodeDefinition(
256
283
  'EXTENSION_WITH_NO_BASE',
257
284
  'A subgraph is attempting to `extend` a type that is not originally defined in any known subgraph.',
@@ -269,6 +296,21 @@ const INTERFACE_FIELD_IMPLEM_TYPE_MISMATCH = makeCodeDefinition(
269
296
  '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
297
  );
271
298
 
299
+ const INVALID_FIELD_SHARING = makeCodeDefinition(
300
+ 'INVALID_FIELD_SHARING',
301
+ 'A field that is non-shareable in at least one subgraph is resolved by multiple subgraphs.'
302
+ );
303
+
304
+ const INVALID_LINK_DIRECTIVE_USAGE = makeCodeDefinition(
305
+ 'INVALID_LINK_DIRECTIVE_USAGE',
306
+ 'An application of the @link directive is invalid/does not respect the specification.'
307
+ );
308
+
309
+ const REQUIRED_ARGUMENT_MISSING_IN_SOME_SUBGRAPH = makeCodeDefinition(
310
+ 'REQUIRED_ARGUMENT_MISSING_IN_SOME_SUBGRAPH',
311
+ 'An argument of a field or directive definition is mandatory in some subgraphs, but the argument is not defined in all subgraphs that define the field or directive definition.'
312
+ );
313
+
272
314
  const SATISFIABILITY_ERROR = makeCodeDefinition(
273
315
  'SATISFIABILITY_ERROR',
274
316
  'Subgraphs can be merged, but the resulting supergraph API would have queries that cannot be satisfied by those subgraphs.',
@@ -285,7 +327,9 @@ export const ERROR_CATEGORIES = {
285
327
 
286
328
  export const ERRORS = {
287
329
  INVALID_GRAPHQL,
288
- TAG_DEFINITION_INVALID,
330
+ DIRECTIVE_DEFINITION_INVALID,
331
+ TYPE_DEFINITION_INVALID,
332
+ UNKNOWN_FEDERATION_LINK_VERSION,
289
333
  KEY_FIELDS_HAS_ARGS,
290
334
  PROVIDES_FIELDS_HAS_ARGS,
291
335
  REQUIRES_FIELDS_HAS_ARGS,
@@ -295,6 +339,7 @@ export const ERRORS = {
295
339
  PROVIDES_UNSUPPORTED_ON_INTERFACE,
296
340
  REQUIRES_UNSUPPORTED_ON_INTERFACE,
297
341
  EXTERNAL_UNUSED,
342
+ TYPE_WITH_ONLY_UNUSED_EXTERNAL,
298
343
  PROVIDES_ON_NON_OBJECT_FIELD,
299
344
  KEY_INVALID_FIELDS_TYPE,
300
345
  PROVIDES_INVALID_FIELDS_TYPE,
@@ -314,13 +359,18 @@ export const ERRORS = {
314
359
  EXTERNAL_ARGUMENT_MISSING,
315
360
  EXTERNAL_ARGUMENT_TYPE_MISMATCH,
316
361
  EXTERNAL_ARGUMENT_DEFAULT_MISMATCH,
362
+ EXTERNAL_ON_INTERFACE,
317
363
  FIELD_TYPE_MISMATCH,
318
364
  ARGUMENT_TYPE_MISMATCH,
319
365
  INPUT_FIELD_DEFAULT_MISMATCH,
320
366
  ARGUMENT_DEFAULT_MISMATCH,
367
+ NON_REPEATABLE_DIRECTIVE_ARGUMENTS_MISMATCH,
321
368
  EXTENSION_WITH_NO_BASE,
322
369
  EXTERNAL_MISSING_ON_BASE,
323
370
  INTERFACE_FIELD_IMPLEM_TYPE_MISMATCH,
371
+ INVALID_FIELD_SHARING,
372
+ INVALID_LINK_DIRECTIVE_USAGE,
373
+ REQUIRED_ARGUMENT_MISSING_IN_SOME_SUBGRAPH,
324
374
  SATISFIABILITY_ERROR,
325
375
  };
326
376
 
@@ -19,20 +19,23 @@ import {
19
19
  Schema,
20
20
  Type,
21
21
  } from "./definitions";
22
- import { addSubgraphToError, externalDirectiveName, federationBuiltIns, parseFieldSetArgument } from "./federation";
22
+ import {
23
+ newEmptyFederation2Schema,
24
+ parseFieldSetArgument,
25
+ removeInactiveProvidesAndRequires,
26
+ } from "./federation";
23
27
  import { CoreSpecDefinition, FeatureVersion } from "./coreSpec";
24
28
  import { JoinSpecDefinition } from "./joinSpec";
25
- import { Subgraph, Subgraphs } from "./federation";
29
+ import { FederationMetadata, Subgraph, Subgraphs } from "./federation";
26
30
  import { assert } from "./utils";
27
31
  import { validateSupergraph } from "./supergraphs";
28
32
  import { builtTypeReference } from "./buildSchema";
29
- import { GraphQLError } from "graphql";
30
- import { selectionOfElement, SelectionSet } from "./operations";
31
33
  import { isSubtype } from "./types";
32
34
  import { printSchema } from "./print";
33
35
  import fs from 'fs';
34
36
  import path from 'path';
35
37
  import { validateStringContainsBoolean } from "./utils";
38
+ import { errorCauses, printErrors } from ".";
36
39
 
37
40
  function filteredTypes(
38
41
  supergraph: Schema,
@@ -62,7 +65,7 @@ function collectEmptySubgraphs(supergraph: Schema, joinSpec: JoinSpecDefinition)
62
65
  throw new Error(`Value ${value} of join__Graph enum has no @join__graph directive`);
63
66
  }
64
67
  const info = graphApplications[0].arguments();
65
- const subgraph = new Subgraph(info.name, info.url, new Schema(federationBuiltIns), false);
68
+ const subgraph = new Subgraph(info.name, info.url, newEmptyFederation2Schema());
66
69
  subgraphs.add(subgraph);
67
70
  graphEnumNameToSubgraphName.set(value.name, info.name);
68
71
  }
@@ -98,7 +101,8 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
98
101
  subgraphType = schema.addType(newNamedType(type.kind, type.name));
99
102
  }
100
103
  if (args.key) {
101
- const directive = subgraphType.applyDirective('key', {'fields': args.key});
104
+ const { resolvable } = args;
105
+ const directive = subgraphType.applyDirective('key', {'fields': args.key, resolvable});
102
106
  if (args.extension) {
103
107
  directive.setOfExtension(subgraphType.newExtension());
104
108
  }
@@ -169,13 +173,13 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
169
173
  const subgraphField = addSubgraphField(field, subgraph, args.type);
170
174
  assert(subgraphField, () => `Found join__field directive for graph ${subgraph.name} on field ${field.coordinate} but no corresponding join__type on ${type}`);
171
175
  if (args.requires) {
172
- subgraphField.applyDirective('requires', {'fields': args.requires});
176
+ subgraphField.applyDirective(subgraph.metadata().requiresDirective(), {'fields': args.requires});
173
177
  }
174
178
  if (args.provides) {
175
- subgraphField.applyDirective('provides', {'fields': args.provides});
179
+ subgraphField.applyDirective(subgraph.metadata().providesDirective(), {'fields': args.provides});
176
180
  }
177
181
  if (args.external) {
178
- subgraphField.applyDirective('external');
182
+ subgraphField.applyDirective(subgraph.metadata().externalDirective());
179
183
  }
180
184
  }
181
185
  }
@@ -222,7 +226,7 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
222
226
  // errors later.
223
227
  addExternalFields(subgraph, supergraph, isFed1);
224
228
  }
225
- removeNeedlessProvides(subgraph);
229
+ removeInactiveProvidesAndRequires(subgraph.schema);
226
230
 
227
231
  // We now do an additional path on all types because we sometimes added types to subgraphs without
228
232
  // being sure that the subgraph had the type in the first place (especially with the 0.1 join spec), and because
@@ -278,7 +282,7 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
278
282
  // all entities in all subgraphs are reachable from a query and so are properly included in the "query graph" later).
279
283
  for (const subgraph of subgraphs) {
280
284
  try {
281
- subgraph.schema.validate();
285
+ subgraph.validate();
282
286
  } catch (e) {
283
287
  // There is 2 reasons this could happen:
284
288
  // 1. if the subgraph is a Fed1 one, because fed2 has stricter validation than fed1, this could be due to the supergraph having been generated by fed1 and
@@ -290,11 +294,11 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
290
294
  // it'll solve the issue and that's good, or we'll hit the other message anyway.
291
295
  const msg = `Error extracting subgraph ${subgraph.name} from the supergraph: this might due to errors in subgraphs that were mistakenly ignored by federation 0.x versions but are rejected by federation 2.\n`
292
296
  + 'Please try composing your subgraphs with federation 2: this should help precisely pinpoint the errors and generate a correct federation 2 supergraph.';
293
- throw new Error(`${msg}.\n\nDetails:\n${errorToString(e, subgraph.name)}`);
297
+ throw new Error(`${msg}.\n\nDetails:\n${errorToString(e)}`);
294
298
  } else {
295
299
  const msg = `Unexpected error extracting subgraph ${subgraph.name} from the supergraph: this is either a bug, or the supergraph has been corrupted.`;
296
300
  const dumpMsg = maybeDumpSubgraphSchema(subgraph);
297
- throw new Error(`${msg}.\n\nDetails:\n${errorToString(e, subgraph.name)}\n\n${dumpMsg}`);
301
+ throw new Error(`${msg}.\n\nDetails:\n${errorToString(e)}\n\n${dumpMsg}`);
298
302
  }
299
303
  }
300
304
  }
@@ -320,12 +324,13 @@ function maybeDumpSubgraphSchema(subgraph: Subgraph): string {
320
324
  return `The (invalid) extracted subgraph has been written in: ${file}.`;
321
325
  }
322
326
  catch (e2) {
323
- return `Was not able to print generated subgraph because: ${errorToString(e2, subgraph.name)}`;
327
+ return `Was not able to print generated subgraph for "${subgraph.name}" because: ${errorToString(e2)}`;
324
328
  }
325
329
  }
326
330
 
327
- function errorToString(e: any, subgraphName: string): string {
328
- return e instanceof GraphQLError ? addSubgraphToError(e, subgraphName).toString() : String(e);
331
+ function errorToString(e: any,): string {
332
+ const causes = errorCauses(e);
333
+ return causes ? printErrors(causes) : String(e);
329
334
  }
330
335
 
331
336
  type AnyField = FieldDefinition<ObjectType | InterfaceType> | InputFieldDefinition;
@@ -396,13 +401,14 @@ function copyType(type: Type, subgraph: Schema, subgraphName: string): Type {
396
401
  }
397
402
 
398
403
  function addExternalFields(subgraph: Subgraph, supergraph: Schema, isFed1: boolean) {
404
+ const metadata = subgraph.metadata();
399
405
  for (const type of subgraph.schema.types()) {
400
406
  if (!isObjectType(type) && !isInterfaceType(type)) {
401
407
  continue;
402
408
  }
403
409
 
404
410
  // First, handle @key
405
- for (const keyApplication of type.appliedDirectivesOf(federationBuiltIns.keyDirective(subgraph.schema))) {
411
+ for (const keyApplication of type.appliedDirectivesOf(metadata.keyDirective())) {
406
412
  // Historically, the federation code for keys, when applied _to a type extension_:
407
413
  // 1) required @external on any field of the key
408
414
  // 2) but required the subgraph to resolve any field of that key
@@ -428,18 +434,18 @@ function addExternalFields(subgraph: Subgraph, supergraph: Schema, isFed1: boole
428
434
  }
429
435
  // Then any @requires or @provides on fields
430
436
  for (const field of type.fields()) {
431
- for (const requiresApplication of field.appliedDirectivesOf(federationBuiltIns.requiresDirective(subgraph.schema))) {
437
+ for (const requiresApplication of field.appliedDirectivesOf(metadata.requiresDirective())) {
432
438
  addExternalFieldsFromDirectiveFieldSet(subgraph, type, requiresApplication, supergraph);
433
439
  }
434
440
  const fieldBaseType = baseType(field.type!);
435
- for (const providesApplication of field.appliedDirectivesOf(federationBuiltIns.providesDirective(subgraph.schema))) {
441
+ for (const providesApplication of field.appliedDirectivesOf(metadata.providesDirective())) {
436
442
  assert(isObjectType(fieldBaseType) || isInterfaceType(fieldBaseType), () => `Found @provides on field ${field.coordinate} whose type ${field.type!} (${fieldBaseType.kind}) is not an object or interface `);
437
443
  addExternalFieldsFromDirectiveFieldSet(subgraph, fieldBaseType, providesApplication, supergraph);
438
444
  }
439
445
  }
440
446
 
441
447
  // And then any constraint due to implemented interfaces.
442
- addExternalFieldsFromInterface(type);
448
+ addExternalFieldsFromInterface(metadata, type);
443
449
  }
444
450
  }
445
451
 
@@ -450,9 +456,9 @@ function addExternalFieldsFromDirectiveFieldSet(
450
456
  supergraph: Schema,
451
457
  forceNonExternal: boolean = false,
452
458
  ) {
453
- const external = federationBuiltIns.externalDirective(subgraph.schema);
459
+ const external = subgraph.metadata().externalDirective();
454
460
 
455
- const accessor = function (type: CompositeType, fieldName: string): FieldDefinition<any> {
461
+ const fieldAccessor = function (type: CompositeType, fieldName: string): FieldDefinition<any> {
456
462
  const field = type.field(fieldName);
457
463
  if (field) {
458
464
  if (forceNonExternal && field.hasAppliedDirective(external)) {
@@ -473,16 +479,16 @@ function addExternalFieldsFromDirectiveFieldSet(
473
479
  }
474
480
  return created;
475
481
  };
476
- parseFieldSetArgument(parentType, directive, accessor);
482
+ parseFieldSetArgument({parentType, directive, fieldAccessor});
477
483
  }
478
484
 
479
- function addExternalFieldsFromInterface(type: ObjectType | InterfaceType) {
485
+ function addExternalFieldsFromInterface(metadata: FederationMetadata, type: ObjectType | InterfaceType) {
480
486
  for (const itf of type.interfaces()) {
481
487
  for (const field of itf.fields()) {
482
488
  const typeField = type.field(field.name);
483
489
  if (!typeField) {
484
- copyFieldAsExternal(field, type);
485
- } else if (typeField.hasAppliedDirective(externalDirectiveName)) {
490
+ copyFieldAsExternal(metadata, field, type);
491
+ } else if (typeField.hasAppliedDirective(metadata.externalDirective())) {
486
492
  // A subtlety here is that a type may implements multiple interfaces providing a given field, and the field may
487
493
  // not have the exact same definition in all interface. So if we may have added the field in a previous loop
488
494
  // iteration, we need to check if we shouldn't update the field type.
@@ -492,12 +498,12 @@ function addExternalFieldsFromInterface(type: ObjectType | InterfaceType) {
492
498
  }
493
499
  }
494
500
 
495
- function copyFieldAsExternal(field: FieldDefinition<InterfaceType>, type: ObjectType | InterfaceType) {
501
+ function copyFieldAsExternal(metadata: FederationMetadata, field: FieldDefinition<InterfaceType>, type: ObjectType | InterfaceType) {
496
502
  const newField = type.addField(field.name, field.type);
497
503
  for (const arg of field.arguments()) {
498
504
  newField.addArgument(arg.name, arg.type, arg.defaultValue);
499
505
  }
500
- newField.applyDirective(externalDirectiveName);
506
+ newField.applyDirective(metadata.externalDirective());
501
507
  }
502
508
 
503
509
  function maybeUpdateFieldForInterface(toModify: FieldDefinition<ObjectType | InterfaceType>, itfField: FieldDefinition<InterfaceType>) {
@@ -508,93 +514,3 @@ function maybeUpdateFieldForInterface(toModify: FieldDefinition<ObjectType | Int
508
514
  toModify.type = itfField.type!;
509
515
  }
510
516
  }
511
-
512
- /*
513
- *
514
- * It makes no sense to have a @provides on a non-external leaf field, and we usually reject it during schema
515
- * validation but we may still have some when:
516
- * 1. we get a fed 1 supergraph, where such validation hadn't been run.
517
- * 2. in the special case of key fields of type extensions that are marked @external without being so (see details in
518
- * `addExternalFields`). In that case, the validation will not have rejected it.
519
- *
520
- * This method checks for those cases and removes such fields (and often the whole @provides). The reason we do
521
- * it is that such provides have a negative impact on later query planning, because it sometimes make us to
522
- * try type-exploding some interfaces unnecessarily.
523
- */
524
- function removeNeedlessProvides(subgraph: Subgraph) {
525
- for (const type of subgraph.schema.types()) {
526
- if (!isObjectType(type) && !isInterfaceType(type)) {
527
- continue;
528
- }
529
-
530
- const providesDirective = federationBuiltIns.providesDirective(subgraph.schema);
531
- for (const field of type.fields()) {
532
- const fieldBaseType = baseType(field.type!);
533
- for (const providesApplication of field.appliedDirectivesOf(providesDirective)) {
534
- const selection = parseFieldSetArgument(fieldBaseType as CompositeType, providesApplication);
535
- if (selectsNonExternalLeafField(selection)) {
536
- providesApplication.remove();
537
- const updated = withoutNonExternalLeafFields(selection);
538
- if (!updated.isEmpty()) {
539
- field.applyDirective(providesDirective, { fields: updated.toString(true, false) });
540
- }
541
- }
542
- }
543
- }
544
- }
545
- }
546
-
547
- function isExternalOrHasExternalImplementations(field: FieldDefinition<CompositeType>): boolean {
548
- if (field.hasAppliedDirective(externalDirectiveName)) {
549
- return true;
550
- }
551
- const parentType = field.parent;
552
- if (isInterfaceType(parentType)) {
553
- for (const implem of parentType.possibleRuntimeTypes()) {
554
- const fieldInImplem = implem.field(field.name);
555
- if (fieldInImplem && fieldInImplem.hasAppliedDirective(externalDirectiveName)) {
556
- return true;
557
- }
558
- }
559
- }
560
- return false;
561
- }
562
-
563
- function selectsNonExternalLeafField(selection: SelectionSet): boolean {
564
- return selection.selections().some(s => {
565
- if (s.kind === 'FieldSelection') {
566
- // If it's external, we're good and don't need to recurse.
567
- if (isExternalOrHasExternalImplementations(s.field.definition)) {
568
- return false;
569
- }
570
- // Otherwise, we select a non-external if it's a leaf, or the sub-selection does.
571
- return !s.selectionSet || selectsNonExternalLeafField(s.selectionSet);
572
- } else {
573
- return selectsNonExternalLeafField(s.selectionSet);
574
- }
575
- });
576
- }
577
-
578
- function withoutNonExternalLeafFields(selectionSet: SelectionSet): SelectionSet {
579
- const newSelectionSet = new SelectionSet(selectionSet.parentType);
580
- for (const selection of selectionSet.selections()) {
581
- if (selection.kind === 'FieldSelection') {
582
- if (isExternalOrHasExternalImplementations(selection.field.definition)) {
583
- // That field is external, so we can add the selection back entirely.
584
- newSelectionSet.add(selection);
585
- continue;
586
- }
587
- }
588
- // Note that for fragments will always be true (and we just recurse), while
589
- // for fields, we'll only get here if the field is not external, and so
590
- // we want to add the selection only if it's not a leaf and even then, only
591
- // the part where we've recursed.
592
- if (selection.selectionSet) {
593
- const updated = withoutNonExternalLeafFields(selection.selectionSet);
594
- if (!updated.isEmpty()) {
595
- newSelectionSet.add(selectionOfElement(selection.element(), updated));
596
- }
597
- }
598
- }
599
- return newSelectionSet;
600
- }