@apollo/federation-internals 2.0.0-alpha.2 → 2.0.0-alpha.3

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 (42) hide show
  1. package/CHANGELOG.md +6 -1
  2. package/dist/debug.d.ts.map +1 -1
  3. package/dist/debug.js +2 -18
  4. package/dist/debug.js.map +1 -1
  5. package/dist/definitions.d.ts +11 -0
  6. package/dist/definitions.d.ts.map +1 -1
  7. package/dist/definitions.js +54 -0
  8. package/dist/definitions.js.map +1 -1
  9. package/dist/error.d.ts +87 -3
  10. package/dist/error.d.ts.map +1 -1
  11. package/dist/error.js +143 -5
  12. package/dist/error.js.map +1 -1
  13. package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
  14. package/dist/extractSubgraphsFromSupergraph.js +40 -3
  15. package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
  16. package/dist/federation.d.ts +4 -1
  17. package/dist/federation.d.ts.map +1 -1
  18. package/dist/federation.js +192 -53
  19. package/dist/federation.js.map +1 -1
  20. package/dist/genErrorCodeDoc.d.ts +2 -0
  21. package/dist/genErrorCodeDoc.d.ts.map +1 -0
  22. package/dist/genErrorCodeDoc.js +55 -0
  23. package/dist/genErrorCodeDoc.js.map +1 -0
  24. package/dist/tagSpec.js +3 -1
  25. package/dist/tagSpec.js.map +1 -1
  26. package/dist/utils.d.ts +1 -0
  27. package/dist/utils.d.ts.map +1 -1
  28. package/dist/utils.js +19 -1
  29. package/dist/utils.js.map +1 -1
  30. package/package.json +3 -3
  31. package/src/__tests__/extractSubgraphsFromSupergraph.test.ts +432 -0
  32. package/src/__tests__/subgraphValidation.test.ts +452 -0
  33. package/src/debug.ts +2 -19
  34. package/src/definitions.ts +98 -0
  35. package/src/error.ts +334 -7
  36. package/src/extractSubgraphsFromSupergraph.ts +49 -4
  37. package/src/federation.ts +229 -85
  38. package/src/genErrorCodeDoc.ts +69 -0
  39. package/src/tagSpec.ts +4 -4
  40. package/src/utils.ts +27 -0
  41. package/tsconfig.test.tsbuildinfo +1 -1
  42. package/tsconfig.tsbuildinfo +1 -1
package/src/error.ts CHANGED
@@ -1,16 +1,53 @@
1
1
  import { ASTNode, GraphQLError, Source } from "graphql";
2
+ import { SchemaRootKind } from "./definitions";
3
+ import { assert } from "./utils";
2
4
 
3
- export function error(
4
- code: string,
5
+ /*
6
+ * We didn't track errors addition precisely pre-2.0 and tracking it now has an
7
+ * unclear ROI, so we just mark all the error code that predates 2.0 as 0.x.
8
+ */
9
+ const FED1_CODE = '0.x';
10
+
11
+ export type ErrorCodeMetadata = {
12
+ addedIn: string,
13
+ replaces?: string[],
14
+ }
15
+
16
+ export type GraphQLErrorArgs = {
5
17
  message: string,
6
18
  nodes?: readonly ASTNode[] | ASTNode,
7
19
  source?: Source,
8
20
  positions?: readonly number[],
9
21
  path?: readonly (string | number)[],
10
- originalError?: Error,
11
- extensions?: { [key: string]: any },
12
- ) {
13
- return new GraphQLError(
22
+ originalError?: Error | null,
23
+ extensions?: { [key: string]: unknown },
24
+ };
25
+
26
+
27
+ export type ErrorCodeDefinition = {
28
+ code: string,
29
+ description: string,
30
+ metadata: ErrorCodeMetadata,
31
+ err: (args: GraphQLErrorArgs) => GraphQLError,
32
+ }
33
+
34
+ const makeCodeDefinition = (
35
+ code: string,
36
+ description: string,
37
+ metadata: ErrorCodeMetadata = DEFAULT_METADATA
38
+ ): ErrorCodeDefinition => ({
39
+ code,
40
+ description,
41
+ metadata,
42
+ err: ({
43
+ message,
44
+ nodes,
45
+ source,
46
+ positions,
47
+ path,
48
+ originalError,
49
+ extensions,
50
+ }: GraphQLErrorArgs) => new GraphQLError(
14
51
  message,
15
52
  nodes,
16
53
  source,
@@ -21,5 +58,295 @@ export function error(
21
58
  ...extensions,
22
59
  code,
23
60
  },
24
- );
61
+ ),
62
+ });
63
+
64
+ /*
65
+ * Most codes currently originate from the initial fed 2 release so we use this for convenience.
66
+ * This can be changed later, inline versions everywhere, if that becomes irrelevant.
67
+ */
68
+ const DEFAULT_METADATA = { addedIn: '2.0.0' };
69
+
70
+ export type ErrorCodeCategory<TElement = string> = {
71
+ get(element: TElement): ErrorCodeDefinition;
72
+ }
73
+
74
+ const makeErrorCodeCategory = <TElement = string>(
75
+ extractCode: (element: TElement) => string,
76
+ makeDescription: (element: TElement) => string,
77
+ metadata: ErrorCodeMetadata = DEFAULT_METADATA,
78
+ ): ErrorCodeCategory<TElement> & { createCode(element: TElement): ErrorCodeDefinition } => ({
79
+ createCode: (element: TElement) => {
80
+ return makeCodeDefinition(extractCode(element), makeDescription(element), metadata);
81
+ },
82
+ get: (element: TElement) => {
83
+ const def = codeDefByCode[extractCode(element)];
84
+ assert(def, `Unexpected element: ${element}`);
85
+ return def;
86
+ }
87
+ });
88
+
89
+ const makeFederationDirectiveErrorCodeCategory = (
90
+ codeSuffix: string,
91
+ makeDescription: (directiveName: string) => string,
92
+ metadata: ErrorCodeMetadata = DEFAULT_METADATA,
93
+ ) => makeErrorCodeCategory((directive) => `${directive.toLocaleUpperCase()}_${codeSuffix}`, makeDescription, metadata);
94
+
95
+
96
+ export function errorCode(e: GraphQLError): string | undefined {
97
+ if (!('code' in e.extensions)) {
98
+ return undefined;
99
+ }
100
+ return e.extensions.code as string;
101
+ }
102
+
103
+ export function errorCodeDef(e: GraphQLError | string): ErrorCodeDefinition | undefined {
104
+ const code = typeof e === 'string' ? e : errorCode(e);
105
+ return code ? codeDefByCode[code] : undefined;
25
106
  }
107
+
108
+ const INVALID_GRAPHQL = makeCodeDefinition(
109
+ 'INVALID_GRAPHQL',
110
+ 'A schema is invalid GraphQL: it violates one of the rule of the specification.'
111
+ );
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 },
117
+ );
118
+
119
+ const FIELDS_HAS_ARGS = makeFederationDirectiveErrorCodeCategory(
120
+ 'FIELDS_HAS_ARGS',
121
+ (directive) => `The \`fields\` argument of a \`@${directive}\` directive includes a field defined with arguments (which is not currently supported).`
122
+ );
123
+
124
+ const KEY_FIELDS_HAS_ARGS = FIELDS_HAS_ARGS.createCode('key');
125
+ const PROVIDES_FIELDS_HAS_ARGS = FIELDS_HAS_ARGS.createCode('provides');
126
+ const REQUIRES_FIELDS_HAS_ARGS = FIELDS_HAS_ARGS.createCode('requires');
127
+
128
+ const DIRECTIVE_FIELDS_MISSING_EXTERNAL = makeFederationDirectiveErrorCodeCategory(
129
+ 'FIELDS_MISSING_EXTERNAL',
130
+ (directive) => `The \`fields\` argument of a \`@${directive}\` directive includes a field that is not marked as \`@external\`.`,
131
+ { addedIn: FED1_CODE },
132
+ );
133
+
134
+ const PROVIDES_MISSING_EXTERNAL = DIRECTIVE_FIELDS_MISSING_EXTERNAL.createCode('provides');
135
+ const REQUIRES_MISSING_EXTERNAL = DIRECTIVE_FIELDS_MISSING_EXTERNAL.createCode('requires');
136
+
137
+ const DIRECTIVE_UNSUPPORTED_ON_INTERFACE = makeFederationDirectiveErrorCodeCategory(
138
+ 'UNSUPPORTED_ON_INTERFACE',
139
+ (directive) => `A \`@${directive}\` directive is used on an interface, which is not (yet) supported.`,
140
+ );
141
+
142
+ const KEY_UNSUPPORTED_ON_INTERFACE = DIRECTIVE_UNSUPPORTED_ON_INTERFACE.createCode('key');
143
+ const PROVIDES_UNSUPPORTED_ON_INTERFACE = DIRECTIVE_UNSUPPORTED_ON_INTERFACE.createCode('provides');
144
+ const REQUIRES_UNSUPPORTED_ON_INTERFACE = DIRECTIVE_UNSUPPORTED_ON_INTERFACE.createCode('requires');
145
+
146
+ const EXTERNAL_UNUSED = makeCodeDefinition(
147
+ 'EXTERNAL_UNUSED',
148
+ 'An `@external` field is not being used by any instance of `@key`, `@requires`, `@provides` or to satisfy an interface implememtation.',
149
+ { addedIn: FED1_CODE },
150
+ );
151
+
152
+ const PROVIDES_ON_NON_OBJECT_FIELD = makeCodeDefinition(
153
+ 'PROVIDES_ON_NON_OBJECT_FIELD',
154
+ 'A `@provides` directive is used to mark a field whose base type is not an object type.'
155
+ );
156
+
157
+ const DIRECTIVE_INVALID_FIELDS_TYPE = makeFederationDirectiveErrorCodeCategory(
158
+ 'INVALID_FIELDS_TYPE',
159
+ (directive) => `The value passed to the \`fields\` argument of a \`@${directive}\` directive is not a string.`,
160
+ );
161
+
162
+ const KEY_INVALID_FIELDS_TYPE = DIRECTIVE_INVALID_FIELDS_TYPE.createCode('key');
163
+ const PROVIDES_INVALID_FIELDS_TYPE = DIRECTIVE_INVALID_FIELDS_TYPE.createCode('provides');
164
+ const REQUIRES_INVALID_FIELDS_TYPE = DIRECTIVE_INVALID_FIELDS_TYPE.createCode('requires');
165
+
166
+ const DIRECTIVE_INVALID_FIELDS = makeFederationDirectiveErrorCodeCategory(
167
+ 'INVALID_FIELDS',
168
+ (directive) => `The \`fields\` argument of a \`@${directive}\` directive is invalid (it has invalid syntax, includes unknown fields, ...).`,
169
+ );
170
+
171
+ const KEY_INVALID_FIELDS = DIRECTIVE_INVALID_FIELDS.createCode('key');
172
+ const PROVIDES_INVALID_FIELDS = DIRECTIVE_INVALID_FIELDS.createCode('provides');
173
+ const REQUIRES_INVALID_FIELDS = DIRECTIVE_INVALID_FIELDS.createCode('requires');
174
+
175
+ const KEY_FIELDS_SELECT_INVALID_TYPE = makeCodeDefinition(
176
+ 'KEY_FIELDS_SELECT_INVALID_TYPE',
177
+ 'The `fields` argument of `@key` directive includes a field whose type is a list, interface, or union type. Fields of these types cannot be part of a `@key`',
178
+ { addedIn: FED1_CODE },
179
+ )
180
+
181
+ const ROOT_TYPE_USED = makeErrorCodeCategory<SchemaRootKind>(
182
+ (kind) => `ROOT_${kind.toLocaleUpperCase()}_USED`,
183
+ (kind) => `A subgraph's schema defines a type with the name \`${kind}\`, while also specifying a _different_ type name as the root query object. This is not allowed.`,
184
+ { addedIn: FED1_CODE },
185
+ );
186
+
187
+ const ROOT_QUERY_USED = ROOT_TYPE_USED.createCode('query');
188
+ const ROOT_MUTATION_USED = ROOT_TYPE_USED.createCode('mutation');
189
+ const ROOT_SUBSCRIPTION_USED = ROOT_TYPE_USED.createCode('subscription');
190
+
191
+ const INVALID_SUBGRAPH_NAME = makeCodeDefinition(
192
+ 'INVALID_SUBGRAPH_NAME',
193
+ 'A subgraph name is invalid (subgraph names cannot be a single underscore ("_")).'
194
+ );
195
+
196
+ const NO_QUERIES = makeCodeDefinition(
197
+ 'NO_QUERIES',
198
+ 'None of the composed subgraphs expose any query.'
199
+ );
200
+
201
+ const INTERFACE_FIELD_NO_IMPLEM = makeCodeDefinition(
202
+ 'INTERFACE_FIELD_NO_IMPLEM',
203
+ 'After subgraph merging, an implemenation is missing a field of one of the interface it implements (which can happen for valid subgraphs).'
204
+ );
205
+
206
+ const TYPE_KIND_MISMATCH = makeCodeDefinition(
207
+ 'TYPE_KIND_MISMATCH',
208
+ 'A type has the same name in different subgraphs, but a different kind. For instance, one definition is an object type but another is an interface.',
209
+ { ...DEFAULT_METADATA, replaces: ['VALUE_TYPE_KIND_MISMATCH', 'EXTENSION_OF_WRONG_KIND', 'ENUM_MISMATCH_TYPE'] },
210
+ );
211
+
212
+ const EXTERNAL_TYPE_MISMATCH = makeCodeDefinition(
213
+ 'EXTERNAL_TYPE_MISMATCH',
214
+ 'An `@external` field has a type that is incompatible with the declaration(s) of that field in other subgraphs.',
215
+ { addedIn: FED1_CODE },
216
+ );
217
+
218
+ const EXTERNAL_ARGUMENT_MISSING = makeCodeDefinition(
219
+ 'EXTERNAL_ARGUMENT_MISSING',
220
+ 'An `@external` field is missing some arguments present in the declaration(s) of that field in other subgraphs.',
221
+ );
222
+
223
+ const EXTERNAL_ARGUMENT_TYPE_MISMATCH = makeCodeDefinition(
224
+ 'EXTERNAL_ARGUMENT_TYPE_MISMATCH',
225
+ 'An `@external` field declares an argument with a type that is incompatible with the corresponding argument in the declaration(s) of that field in other subgtaphs.',
226
+ );
227
+
228
+ const EXTERNAL_ARGUMENT_DEFAULT_MISMATCH = makeCodeDefinition(
229
+ 'EXTERNAL_ARGUMENT_DEFAULT_MISMATCH',
230
+ '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
+ );
232
+
233
+ const FIELD_TYPE_MISMATCH = makeCodeDefinition(
234
+ 'FIELD_TYPE_MISMATCH',
235
+ 'A field has a type that is incompatible with other declarations of that field in other subgraphs.',
236
+ { ...DEFAULT_METADATA, replaces: ['VALUE_TYPE_FIELD_TYPE_MISMATCH'] },
237
+ );
238
+
239
+ const ARGUMENT_TYPE_MISMATCH = makeCodeDefinition(
240
+ 'FIELD_ARGUMENT_TYPE_MISMATCH',
241
+ 'An argument (of a field/directive) has a type that is incompatible with that of other declarations of that same argument in other subgraphs.',
242
+ { ...DEFAULT_METADATA, replaces: ['VALUE_TYPE_INPUT_VALUE_MISMATCH'] },
243
+ );
244
+
245
+ const INPUT_FIELD_DEFAULT_MISMATCH = makeCodeDefinition(
246
+ 'INPUT_FIELD_DEFAULT_MISMATCH',
247
+ 'An input field has a default value that is incompatible with other declarations of that field in other subgraphs.',
248
+ );
249
+
250
+ const ARGUMENT_DEFAULT_MISMATCH = makeCodeDefinition(
251
+ 'FIELD_ARGUMENT_DEFAULT_MISMATCH',
252
+ '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
+ );
254
+
255
+ const EXTENSION_WITH_NO_BASE = makeCodeDefinition(
256
+ 'EXTENSION_WITH_NO_BASE',
257
+ 'A subgraph is attempting to `extend` a type that is not originally defined in any known subgraph.',
258
+ { addedIn: FED1_CODE },
259
+ );
260
+
261
+ const EXTERNAL_MISSING_ON_BASE = makeCodeDefinition(
262
+ 'EXTERNAL_MISSING_ON_BASE',
263
+ 'A field is marked as `@external` in a subgraph but with no non-external declaration in any other subgraph.',
264
+ { addedIn: FED1_CODE },
265
+ );
266
+
267
+ const SATISFIABILITY_ERROR = makeCodeDefinition(
268
+ 'SATISFIABILITY_ERROR',
269
+ 'Subgraphs can be merged, but the resulting supergraph API would have queries that cannot be satisfied by those subgraphs.',
270
+ );
271
+
272
+ export const ERROR_CATEGORIES = {
273
+ DIRECTIVE_FIELDS_MISSING_EXTERNAL,
274
+ DIRECTIVE_UNSUPPORTED_ON_INTERFACE,
275
+ DIRECTIVE_INVALID_FIELDS_TYPE,
276
+ DIRECTIVE_INVALID_FIELDS,
277
+ FIELDS_HAS_ARGS,
278
+ ROOT_TYPE_USED,
279
+ }
280
+
281
+ export const ERRORS = {
282
+ INVALID_GRAPHQL,
283
+ TAG_DEFINITION_INVALID,
284
+ KEY_FIELDS_HAS_ARGS,
285
+ PROVIDES_FIELDS_HAS_ARGS,
286
+ REQUIRES_FIELDS_HAS_ARGS,
287
+ PROVIDES_MISSING_EXTERNAL,
288
+ REQUIRES_MISSING_EXTERNAL,
289
+ KEY_UNSUPPORTED_ON_INTERFACE,
290
+ PROVIDES_UNSUPPORTED_ON_INTERFACE,
291
+ REQUIRES_UNSUPPORTED_ON_INTERFACE,
292
+ EXTERNAL_UNUSED,
293
+ PROVIDES_ON_NON_OBJECT_FIELD,
294
+ KEY_INVALID_FIELDS_TYPE,
295
+ PROVIDES_INVALID_FIELDS_TYPE,
296
+ REQUIRES_INVALID_FIELDS_TYPE,
297
+ KEY_INVALID_FIELDS,
298
+ PROVIDES_INVALID_FIELDS,
299
+ REQUIRES_INVALID_FIELDS,
300
+ KEY_FIELDS_SELECT_INVALID_TYPE,
301
+ ROOT_QUERY_USED,
302
+ ROOT_MUTATION_USED,
303
+ ROOT_SUBSCRIPTION_USED,
304
+ INVALID_SUBGRAPH_NAME,
305
+ NO_QUERIES,
306
+ INTERFACE_FIELD_NO_IMPLEM,
307
+ TYPE_KIND_MISMATCH,
308
+ EXTERNAL_TYPE_MISMATCH,
309
+ EXTERNAL_ARGUMENT_MISSING,
310
+ EXTERNAL_ARGUMENT_TYPE_MISMATCH,
311
+ EXTERNAL_ARGUMENT_DEFAULT_MISMATCH,
312
+ FIELD_TYPE_MISMATCH,
313
+ ARGUMENT_TYPE_MISMATCH,
314
+ INPUT_FIELD_DEFAULT_MISMATCH,
315
+ ARGUMENT_DEFAULT_MISMATCH,
316
+ EXTENSION_WITH_NO_BASE,
317
+ EXTERNAL_MISSING_ON_BASE,
318
+ SATISFIABILITY_ERROR,
319
+ };
320
+
321
+ const codeDefByCode = Object.values(ERRORS).reduce((obj: {[code: string]: ErrorCodeDefinition}, codeDef: ErrorCodeDefinition) => { obj[codeDef.code] = codeDef; return obj; }, {});
322
+
323
+ /*
324
+ * A list of now-removed errors, each as a pair of the old code and a comment for the removal.
325
+ * This exist mostly for the sake of being included in the auto-generated documentation. But
326
+ * having it here means that grepping any error code in this file should turn up something:
327
+ * - either a currently active code.
328
+ * - or one that has been replaced/generalized (in a `replaces:` of an active code).
329
+ * - or a now removed code below.
330
+ */
331
+ export const REMOVED_ERRORS = [
332
+ ['KEY_FIELDS_MISSING_ON_BASE', 'Keys can now use any field from any other subgraph.'],
333
+ ['KEY_FIELDS_MISSING_EXTERNAL', 'Using `@external` for key fields is now decouraged, unless the field is truly meant to be external.'],
334
+ ['KEY_MISSING_ON_BASE', 'Each subgraph is now free to declare a key only if it needs it.'],
335
+ ['MULTIPLE_KEYS_ON_EXTENSION', 'Every subgraph can have multiple keys, as necessary.'],
336
+ ['KEY_NOT_SPECIFIED', 'Each subgraph can declare key independently of any other subgraph.'],
337
+ ['EXTERNAL_USED_ON_BASE', 'As there is not type ownership anymore, there is also no particular limitation as to where a field can be external.'],
338
+
339
+ ['PROVIDES_NOT_ON_ENTITY', '@provides can now be used on any type.'],
340
+ ['REQUIRES_FIELDS_MISSING_ON_BASE', 'Fields in @requires can now be from any subgraph.'],
341
+ ['REQUIRES_USED_ON_BASE', 'As there is not type ownership anymore, there is also no particular limitation as to which subgraph can use a @requires.'],
342
+
343
+ ['DUPLICATE_SCALAR_DEFINITION', 'As duplicate scalar definitions is invalid GraphQL, this will now be an error with code `INVALID_GRAPHQL`'],
344
+ ['DUPLICATE_ENUM_DEFINITION', 'As duplicate enum definitions is invalid GraphQL, this will now be an error with code `INVALID_GRAPHQL`'],
345
+ ['DUPLICATE_ENUM_VALUE', 'As duplicate enum values is invalid GraphQL, this will now be an error with code `INVALID_GRAPHQL`'],
346
+
347
+ ['ENUM_MISMATCH', 'Subgraph definitions for an enum are now merged by composition'],
348
+ ['VALUE_TYPE_NO_ENTITY', 'There is no strong different between entity and value types in the model (they are just usage pattern) and a type can have keys in one subgraph but not another.'],
349
+ ['VALUE_TYPE_UNION_TYPES_MISMATCH', 'Subgraph definitions for an union are now merged by composition'],
350
+ ['PROVIDES_FIELDS_SELECT_INVALID_TYPE', '@provides can now be used on field of interface, union and list types'],
351
+ ['RESERVED_FIELD_USED', 'This error was previously not correctly enforced: the _service and _entities, if present, were overriden; this is still the case'],
352
+ ];
@@ -29,6 +29,10 @@ import { builtTypeReference } from "./buildSchema";
29
29
  import { GraphQLError } from "graphql";
30
30
  import { selectionOfElement, SelectionSet } from "./operations";
31
31
  import { isSubtype } from "./types";
32
+ import { printSchema } from "./print";
33
+ import fs from 'fs';
34
+ import path from 'path';
35
+ import { validateStringContainsBoolean } from "./utils";
32
36
 
33
37
  function filteredTypes(
34
38
  supergraph: Schema,
@@ -232,7 +236,9 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
232
236
  case 'InterfaceType':
233
237
  case 'InputObjectType':
234
238
  if (!type.hasFields()) {
235
- type.remove();
239
+ // Note that we have to use removeRecursive or this could leave the subgraph invalid. But if the
240
+ // type was not in this subgraphs, nothing that depends on it should be either.
241
+ type.removeRecursive();
236
242
  }
237
243
  break;
238
244
  case 'UnionType':
@@ -274,15 +280,54 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
274
280
  try {
275
281
  subgraph.schema.validate();
276
282
  } catch (e) {
277
- // This is a "bug": we shouldn't be extracting invalid subgraphs if the supergraph is valid.
278
- const details = e instanceof GraphQLError ? addSubgraphToError(e, subgraph.name).toString() : String(e);
279
- throw new Error(`Unexpected error extracting subgraph information from the supergraph. This is either a bug, or the supergraph has been corrupted.\nCaused by: ${details}`);
283
+ // There is 2 reasons this could happen:
284
+ // 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
285
+ // containing something invalid that fed1 accepted and fed2 didn't (for instance, an invalid `@provides` selection).
286
+ // 2. otherwise, this would be a bug (because fed1 compatibility excluded, we shouldn't extract invalid subgraphs from valid supergraphs).
287
+ // We throw essentially the same thing in both cases, but adapt the message slightly.
288
+ if (isFed1) {
289
+ // Note that this could be a bug with the code handling fed1 as well, but it's more helpful to ask users to recompose their subgraphs with fed2 as either
290
+ // it'll solve the issue and that's good, or we'll hit the other message anyway.
291
+ 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
+ + '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)}`);
294
+ } else {
295
+ const msg = `Unexpected error extracting subgraph ${subgraph.name} from the supergraph: this is either a bug, or the supergraph has been corrupted.`;
296
+ const dumpMsg = maybeDumpSubgraphSchema(subgraph);
297
+ throw new Error(`${msg}.\n\nDetails:\n${errorToString(e, subgraph.name)}\n\n${dumpMsg}`);
298
+ }
280
299
  }
281
300
  }
282
301
 
283
302
  return subgraphs;
284
303
  }
285
304
 
305
+ const DEBUG_SUBGRAPHS_ENV_VARIABLE_NAME = 'APOLLO_FEDERATION_DEBUG_SUBGRAPHS';
306
+
307
+ function maybeDumpSubgraphSchema(subgraph: Subgraph): string {
308
+ const shouldDump = !!validateStringContainsBoolean(process.env[DEBUG_SUBGRAPHS_ENV_VARIABLE_NAME]);
309
+ if (!shouldDump) {
310
+ return `Re-run with environment variable '${DEBUG_SUBGRAPHS_ENV_VARIABLE_NAME}' set to 'true' to extract the invalid subgraph`;
311
+ }
312
+ try {
313
+ const filename = `extracted-subgraph-${subgraph.name}-${Date.now()}.graphql`;
314
+ const file = path.resolve(filename);
315
+ if (fs.existsSync(file)) {
316
+ // Note that this is caught directly by the surrounded catch.
317
+ throw new Error(`candidate file ${filename} already existed`);
318
+ }
319
+ fs.writeFileSync(file, printSchema(subgraph.schema));
320
+ return `The (invalid) extracted subgraph has been written in: ${file}.`;
321
+ }
322
+ catch (e2) {
323
+ return `Was not able to print generated subgraph because: ${errorToString(e2, subgraph.name)}`;
324
+ }
325
+ }
326
+
327
+ function errorToString(e: any, subgraphName: string): string {
328
+ return e instanceof GraphQLError ? addSubgraphToError(e, subgraphName).toString() : String(e);
329
+ }
330
+
286
331
  type AnyField = FieldDefinition<ObjectType | InterfaceType> | InputFieldDefinition;
287
332
 
288
333
  function addSubgraphField(supergraphField: AnyField, subgraph: Subgraph, encodedType?: string): AnyField | undefined {