@apollo/federation-internals 2.0.2-alpha.0 → 2.0.2-alpha.1

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.
@@ -72,244 +72,269 @@ function collectEmptySubgraphs(supergraph: Schema, joinSpec: JoinSpecDefinition)
72
72
  return [subgraphs, graphEnumNameToSubgraphName];
73
73
  }
74
74
 
75
+ class SubgraphExtractionError {
76
+ constructor(
77
+ readonly originalError: any,
78
+ readonly subgraph: Subgraph,
79
+ ) {
80
+ }
81
+ }
82
+
75
83
  export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
76
84
  const [coreFeatures, joinSpec] = validateSupergraph(supergraph);
77
85
  const isFed1 = joinSpec.version.equals(new FeatureVersion(0, 1));
86
+ try {
87
+ // We first collect the subgraphs (creating an empty schema that we'll populate next for each).
88
+ const [subgraphs, graphEnumNameToSubgraphName] = collectEmptySubgraphs(supergraph, joinSpec);
89
+ const typeDirective = joinSpec.typeDirective(supergraph);
90
+ const implementsDirective = joinSpec.implementsDirective(supergraph);
78
91
 
79
- // We first collect the subgraphs (creating an empty schema that we'll populate next for each).
80
- const [subgraphs, graphEnumNameToSubgraphName] = collectEmptySubgraphs(supergraph, joinSpec);
81
- const typeDirective = joinSpec.typeDirective(supergraph);
82
- const implementsDirective = joinSpec.implementsDirective(supergraph);
83
-
84
- // Next, we iterate on all types and add it to the proper subgraphs (along with any @key).
85
- // Note that we first add all types empty and populate the types next. This avoids having to care about the iteration
86
- // order if we have fields than depends on other types.
87
- for (const type of filteredTypes(supergraph, joinSpec, coreFeatures.coreDefinition)) {
88
- const typeApplications = type.appliedDirectivesOf(typeDirective);
89
- if (!typeApplications.length) {
90
- // Imply the type is in all subgraphs (technically, some subgraphs may not have had this type, but adding it
91
- // in that case is harmless because it will be unreachable anyway).
92
- subgraphs.values().map(sg => sg.schema).forEach(schema => schema.addType(newNamedType(type.kind, type.name)));
93
- } else {
94
- for (const application of typeApplications) {
95
- const args = application.arguments();
96
- const subgraphName = graphEnumNameToSubgraphName.get(args.graph)!;
97
- const schema = subgraphs.get(subgraphName)!.schema;
98
- // We can have more than one type directive for a given subgraph
99
- let subgraphType = schema.type(type.name);
100
- if (!subgraphType) {
101
- subgraphType = schema.addType(newNamedType(type.kind, type.name));
102
- }
103
- if (args.key) {
104
- const { resolvable } = args;
105
- const directive = subgraphType.applyDirective('key', {'fields': args.key, resolvable});
106
- if (args.extension) {
107
- directive.setOfExtension(subgraphType.newExtension());
92
+ // Next, we iterate on all types and add it to the proper subgraphs (along with any @key).
93
+ // Note that we first add all types empty and populate the types next. This avoids having to care about the iteration
94
+ // order if we have fields than depends on other types.
95
+ for (const type of filteredTypes(supergraph, joinSpec, coreFeatures.coreDefinition)) {
96
+ const typeApplications = type.appliedDirectivesOf(typeDirective);
97
+ if (!typeApplications.length) {
98
+ // Imply the type is in all subgraphs (technically, some subgraphs may not have had this type, but adding it
99
+ // in that case is harmless because it will be unreachable anyway).
100
+ subgraphs.values().map(sg => sg.schema).forEach(schema => schema.addType(newNamedType(type.kind, type.name)));
101
+ } else {
102
+ for (const application of typeApplications) {
103
+ const args = application.arguments();
104
+ const subgraphName = graphEnumNameToSubgraphName.get(args.graph)!;
105
+ const schema = subgraphs.get(subgraphName)!.schema;
106
+ // We can have more than one type directive for a given subgraph
107
+ let subgraphType = schema.type(type.name);
108
+ if (!subgraphType) {
109
+ subgraphType = schema.addType(newNamedType(type.kind, type.name));
110
+ }
111
+ if (args.key) {
112
+ const { resolvable } = args;
113
+ const directive = subgraphType.applyDirective('key', {'fields': args.key, resolvable});
114
+ if (args.extension) {
115
+ directive.setOfExtension(subgraphType.newExtension());
116
+ }
108
117
  }
109
118
  }
110
119
  }
111
120
  }
112
- }
113
121
 
114
- const ownerDirective = joinSpec.ownerDirective(supergraph);
115
- const fieldDirective = joinSpec.fieldDirective(supergraph);
116
- // We can now populate all those types (with relevant @provides and @requires on fields).
117
- for (const type of filteredTypes(supergraph, joinSpec, coreFeatures.coreDefinition)) {
118
- switch (type.kind) {
119
- case 'ObjectType':
120
- // @ts-expect-error: we fall-through the inputObjectType for fields.
121
- case 'InterfaceType':
122
- const addedInterfaces = [];
123
- const implementsApplications = implementsDirective ? type.appliedDirectivesOf(implementsDirective) : [];
124
- for (const application of implementsApplications) {
125
- const args = application.arguments();
126
- const subgraph = subgraphs.get(graphEnumNameToSubgraphName.get(args.graph)!)!;
127
- const schema = subgraph.schema;
128
- (schema.type(type.name)! as (ObjectType | InterfaceType)).addImplementedInterface(args.interface);
129
- addedInterfaces.push(args.interface);
130
- }
131
- for (const implementations of type.interfaceImplementations()) {
132
- // If the object/interface implements an interface but we had no @join__implements for it (which will
133
- // always be the case for join v0.1 in particular), then that means the object/interface should implement
134
- // the interface in all subgraphs (which contains both types).
135
- const name = implementations.interface.name;
136
- if (!addedInterfaces.includes(name)) {
137
- for (const subgraph of subgraphs) {
138
- const subgraphType = subgraph.schema.type(type.name);
139
- const subgraphItf = subgraph.schema.type(name);
140
- if (subgraphType && subgraphItf) {
141
- (subgraphType as (ObjectType | InterfaceType)).addImplementedInterface(name);
142
- }
143
- }
122
+ const ownerDirective = joinSpec.ownerDirective(supergraph);
123
+ const fieldDirective = joinSpec.fieldDirective(supergraph);
124
+ // We can now populate all those types (with relevant @provides and @requires on fields).
125
+ for (const type of filteredTypes(supergraph, joinSpec, coreFeatures.coreDefinition)) {
126
+ switch (type.kind) {
127
+ case 'ObjectType':
128
+ // @ts-expect-error: we fall-through the inputObjectType for fields.
129
+ case 'InterfaceType':
130
+ const addedInterfaces = [];
131
+ const implementsApplications = implementsDirective ? type.appliedDirectivesOf(implementsDirective) : [];
132
+ for (const application of implementsApplications) {
133
+ const args = application.arguments();
134
+ const subgraph = subgraphs.get(graphEnumNameToSubgraphName.get(args.graph)!)!;
135
+ const schema = subgraph.schema;
136
+ (schema.type(type.name)! as (ObjectType | InterfaceType)).addImplementedInterface(args.interface);
137
+ addedInterfaces.push(args.interface);
144
138
  }
145
- }
146
- // Fall-through on purpose.
147
- case 'InputObjectType':
148
- for (const field of type.fields()) {
149
- const fieldApplications = field.appliedDirectivesOf(fieldDirective);
150
- if (!fieldApplications.length) {
151
- // The meaning of having no join__field depends on whether the parent type has a join__owner.
152
- // If it does, it means the field is only on that owner subgraph. Otherwise, we kind of don't
153
- // know, so we add it to all subgraphs that have the parent type and, if the field base type
154
- // is a named type, know that field type.
155
- const ownerApplications = ownerDirective ? type.appliedDirectivesOf(ownerDirective) : [];
156
- if (!ownerApplications.length) {
157
- const fieldBaseType = baseType(field.type!);
139
+ for (const implementations of type.interfaceImplementations()) {
140
+ // If the object/interface implements an interface but we had no @join__implements for it (which will
141
+ // always be the case for join v0.1 in particular), then that means the object/interface should implement
142
+ // the interface in all subgraphs (which contains both types).
143
+ const name = implementations.interface.name;
144
+ if (!addedInterfaces.includes(name)) {
158
145
  for (const subgraph of subgraphs) {
159
- if (subgraph.schema.type(fieldBaseType.name)) {
160
- addSubgraphField(field, subgraph);
146
+ const subgraphType = subgraph.schema.type(type.name);
147
+ const subgraphItf = subgraph.schema.type(name);
148
+ if (subgraphType && subgraphItf) {
149
+ (subgraphType as (ObjectType | InterfaceType)).addImplementedInterface(name);
161
150
  }
162
151
  }
163
- } else {
164
- assert(ownerApplications.length == 1, () => `Found multiple join__owner directives on type ${type}`)
165
- const subgraph = subgraphs.get(graphEnumNameToSubgraphName.get(ownerApplications[0].arguments().graph)!)!;
166
- const subgraphField = addSubgraphField(field, subgraph);
167
- assert(subgraphField, () => `Found join__owner directive on ${type} but no corresponding join__type`);
168
152
  }
169
- } else {
170
- for (const application of fieldApplications) {
171
- const args = application.arguments();
172
- const subgraph = subgraphs.get(graphEnumNameToSubgraphName.get(args.graph)!)!;
173
- const subgraphField = addSubgraphField(field, subgraph, args.type);
174
- assert(subgraphField, () => `Found join__field directive for graph ${subgraph.name} on field ${field.coordinate} but no corresponding join__type on ${type}`);
175
- if (args.requires) {
176
- subgraphField.applyDirective(subgraph.metadata().requiresDirective(), {'fields': args.requires});
177
- }
178
- if (args.provides) {
179
- subgraphField.applyDirective(subgraph.metadata().providesDirective(), {'fields': args.provides});
180
- }
181
- if (args.external) {
182
- subgraphField.applyDirective(subgraph.metadata().externalDirective());
183
- }
184
- if (args.usedOverridden) {
185
- subgraphField.applyDirective(subgraph.metadata().externalDirective(), {'reason': '[overridden]'});
153
+ }
154
+ // Fall-through on purpose.
155
+ case 'InputObjectType':
156
+ for (const field of type.fields()) {
157
+ const fieldApplications = field.appliedDirectivesOf(fieldDirective);
158
+ if (!fieldApplications.length) {
159
+ // The meaning of having no join__field depends on whether the parent type has a join__owner.
160
+ // If it does, it means the field is only on that owner subgraph. Otherwise, we kind of don't
161
+ // know, so we add it to all subgraphs that have the parent type and, if the field base type
162
+ // is a named type, know that field type.
163
+ const ownerApplications = ownerDirective ? type.appliedDirectivesOf(ownerDirective) : [];
164
+ if (!ownerApplications.length) {
165
+ const fieldBaseType = baseType(field.type!);
166
+ for (const subgraph of subgraphs) {
167
+ if (subgraph.schema.type(fieldBaseType.name)) {
168
+ addSubgraphField(field, subgraph);
169
+ }
170
+ }
171
+ } else {
172
+ assert(ownerApplications.length == 1, () => `Found multiple join__owner directives on type ${type}`)
173
+ const subgraph = subgraphs.get(graphEnumNameToSubgraphName.get(ownerApplications[0].arguments().graph)!)!;
174
+ const subgraphField = addSubgraphField(field, subgraph);
175
+ assert(subgraphField, () => `Found join__owner directive on ${type} but no corresponding join__type`);
186
176
  }
187
- if (args.override) {
188
- subgraphField.applyDirective(subgraph.metadata().overrideDirective(), {'from': args.override});
177
+ } else {
178
+ for (const application of fieldApplications) {
179
+ const args = application.arguments();
180
+ const subgraph = subgraphs.get(graphEnumNameToSubgraphName.get(args.graph)!)!;
181
+ const subgraphField = addSubgraphField(field, subgraph, args.type);
182
+ assert(subgraphField, () => `Found join__field directive for graph ${subgraph.name} on field ${field.coordinate} but no corresponding join__type on ${type}`);
183
+ if (args.requires) {
184
+ subgraphField.applyDirective(subgraph.metadata().requiresDirective(), {'fields': args.requires});
185
+ }
186
+ if (args.provides) {
187
+ subgraphField.applyDirective(subgraph.metadata().providesDirective(), {'fields': args.provides});
188
+ }
189
+ if (args.external) {
190
+ subgraphField.applyDirective(subgraph.metadata().externalDirective());
191
+ }
192
+ if (args.usedOverridden) {
193
+ subgraphField.applyDirective(subgraph.metadata().externalDirective(), {'reason': '[overridden]'});
194
+ }
195
+ if (args.override) {
196
+ subgraphField.applyDirective(subgraph.metadata().overrideDirective(), {'from': args.override});
197
+ }
189
198
  }
190
199
  }
191
200
  }
192
- }
193
- break;
194
- case 'EnumType':
195
- // TODO: it's not guaranteed that every enum value was in every subgraph declaring the enum and we should preserve
196
- // that info with the join spec. But for now, we add every values to all subgraphs (having the enum)
197
- for (const subgraph of subgraphs) {
198
- const subgraphEnum = subgraph.schema.type(type.name);
199
- if (!subgraphEnum) {
200
- continue;
201
- }
202
- assert(isEnumType(subgraphEnum), () => `${subgraphEnum} should be an enum but found a ${subgraphEnum.kind}`);
203
- for (const value of type.values) {
204
- subgraphEnum.addValue(value.name);
205
- }
206
- }
207
- break;
208
- case 'UnionType':
209
- // TODO: Same as for enums. We need to know in which subgraph each member is defined.
210
- // But for now, we also add every members to all subgraphs (as long as the subgraph has both the union type
211
- // and the member in question).
212
- for (const subgraph of subgraphs) {
213
- const subgraphUnion = subgraph.schema.type(type.name);
214
- if (!subgraphUnion) {
215
- continue;
216
- }
217
- assert(isUnionType(subgraphUnion), () => `${subgraphUnion} should be an enum but found a ${subgraphUnion.kind}`);
218
- for (const memberType of type.types()) {
219
- const subgraphType = subgraph.schema.type(memberType.name);
220
- if (subgraphType) {
221
- subgraphUnion.addType(subgraphType as ObjectType);
201
+ break;
202
+ case 'EnumType':
203
+ // TODO: it's not guaranteed that every enum value was in every subgraph declaring the enum and we should preserve
204
+ // that info with the join spec. But for now, we add every values to all subgraphs (having the enum)
205
+ for (const subgraph of subgraphs) {
206
+ const subgraphEnum = subgraph.schema.type(type.name);
207
+ if (!subgraphEnum) {
208
+ continue;
209
+ }
210
+ assert(isEnumType(subgraphEnum), () => `${subgraphEnum} should be an enum but found a ${subgraphEnum.kind}`);
211
+ for (const value of type.values) {
212
+ subgraphEnum.addValue(value.name);
222
213
  }
223
- }
224
- }
225
- break;
226
- }
227
- }
228
-
229
- for (const subgraph of subgraphs) {
230
- if (isFed1) {
231
- // The join spec in fed1 was not including external fields. Let's make sure we had them or we'll get validation
232
- // errors later.
233
- addExternalFields(subgraph, supergraph, isFed1);
234
- }
235
- removeInactiveProvidesAndRequires(subgraph.schema);
236
-
237
- // We now do an additional path on all types because we sometimes added types to subgraphs without
238
- // being sure that the subgraph had the type in the first place (especially with the 0.1 join spec), and because
239
- // we later might not have added any fields/members to said type, they may be empty (indicating they clearly
240
- // didn't belong to the subgraph in the first) and we need to remove them.
241
- // Note that need to do this _after_ the `addExternalFields` call above since it may have added (external) fields
242
- // to some of the types.
243
- for (const type of subgraph.schema.types()) {
244
- switch (type.kind) {
245
- case 'ObjectType':
246
- case 'InterfaceType':
247
- case 'InputObjectType':
248
- if (!type.hasFields()) {
249
- // Note that we have to use removeRecursive or this could leave the subgraph invalid. But if the
250
- // type was not in this subgraphs, nothing that depends on it should be either.
251
- type.removeRecursive();
252
214
  }
253
215
  break;
254
216
  case 'UnionType':
255
- if (type.membersCount() === 0) {
256
- type.removeRecursive();
217
+ // TODO: Same as for enums. We need to know in which subgraph each member is defined.
218
+ // But for now, we also add every members to all subgraphs (as long as the subgraph has both the union type
219
+ // and the member in question).
220
+ for (const subgraph of subgraphs) {
221
+ const subgraphUnion = subgraph.schema.type(type.name);
222
+ if (!subgraphUnion) {
223
+ continue;
224
+ }
225
+ assert(isUnionType(subgraphUnion), () => `${subgraphUnion} should be an enum but found a ${subgraphUnion.kind}`);
226
+ for (const memberType of type.types()) {
227
+ const subgraphType = subgraph.schema.type(memberType.name);
228
+ if (subgraphType) {
229
+ subgraphUnion.addType(subgraphType as ObjectType);
230
+ }
231
+ }
257
232
  }
258
233
  break;
259
234
  }
260
235
  }
261
- }
262
236
 
263
- // TODO: Not sure that code is needed anymore (any field necessary to validate an interface will have been marked
264
- // external)?
265
- if (isFed1) {
266
- // We now make a pass on every field of every interface and check that all implementers do have that field (even if
267
- // external). If not (which can happen because, again, the v0.1 spec had no information on where an interface was
268
- // truly defined, so we've so far added them everywhere with all their fields, but some fields may have been part
269
- // of an extension and be only in a few subgraphs), we remove the field or the subgraph would be invalid.
270
237
  for (const subgraph of subgraphs) {
271
- for (const itf of subgraph.schema.interfaceTypes()) {
272
- // We only look at objects because interfaces are handled by this own loop in practice.
273
- const implementations = itf.possibleRuntimeTypes();
274
- for (const field of itf.fields()) {
275
- if (!implementations.every(implem => implem.field(field.name))) {
276
- field.remove();
277
- }
238
+ if (isFed1) {
239
+ // The join spec in fed1 was not including external fields. Let's make sure we had them or we'll get validation
240
+ // errors later.
241
+ addExternalFields(subgraph, supergraph, isFed1);
242
+ }
243
+ removeInactiveProvidesAndRequires(subgraph.schema);
244
+
245
+ // We now do an additional path on all types because we sometimes added types to subgraphs without
246
+ // being sure that the subgraph had the type in the first place (especially with the 0.1 join spec), and because
247
+ // we later might not have added any fields/members to said type, they may be empty (indicating they clearly
248
+ // didn't belong to the subgraph in the first) and we need to remove them.
249
+ // Note that need to do this _after_ the `addExternalFields` call above since it may have added (external) fields
250
+ // to some of the types.
251
+ for (const type of subgraph.schema.types()) {
252
+ switch (type.kind) {
253
+ case 'ObjectType':
254
+ case 'InterfaceType':
255
+ case 'InputObjectType':
256
+ if (!type.hasFields()) {
257
+ // Note that we have to use removeRecursive or this could leave the subgraph invalid. But if the
258
+ // type was not in this subgraphs, nothing that depends on it should be either.
259
+ type.removeRecursive();
260
+ }
261
+ break;
262
+ case 'UnionType':
263
+ if (type.membersCount() === 0) {
264
+ type.removeRecursive();
265
+ }
266
+ break;
278
267
  }
279
- // And it may be that the interface wasn't part of the subgraph at all!
280
- if (!itf.hasFields()) {
281
- itf.remove();
268
+ }
269
+ }
270
+
271
+ // TODO: Not sure that code is needed anymore (any field necessary to validate an interface will have been marked
272
+ // external)?
273
+ if (isFed1) {
274
+ // We now make a pass on every field of every interface and check that all implementers do have that field (even if
275
+ // external). If not (which can happen because, again, the v0.1 spec had no information on where an interface was
276
+ // truly defined, so we've so far added them everywhere with all their fields, but some fields may have been part
277
+ // of an extension and be only in a few subgraphs), we remove the field or the subgraph would be invalid.
278
+ for (const subgraph of subgraphs) {
279
+ for (const itf of subgraph.schema.interfaceTypes()) {
280
+ // We only look at objects because interfaces are handled by this own loop in practice.
281
+ const implementations = itf.possibleRuntimeTypes();
282
+ for (const field of itf.fields()) {
283
+ if (!implementations.every(implem => implem.field(field.name))) {
284
+ field.remove();
285
+ }
286
+ }
287
+ // And it may be that the interface wasn't part of the subgraph at all!
288
+ if (!itf.hasFields()) {
289
+ itf.remove();
290
+ }
282
291
  }
283
292
  }
284
293
  }
285
- }
286
294
 
287
- // We're done with the subgraphs, so call validate (which, amongst other things, sets up the _entities query field, which ensures
288
- // all entities in all subgraphs are reachable from a query and so are properly included in the "query graph" later).
289
- for (const subgraph of subgraphs) {
290
- try {
291
- subgraph.validate();
292
- } catch (e) {
293
- // There is 2 reasons this could happen:
294
- // 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
295
- // containing something invalid that fed1 accepted and fed2 didn't (for instance, an invalid `@provides` selection).
296
- // 2. otherwise, this would be a bug (because fed1 compatibility excluded, we shouldn't extract invalid subgraphs from valid supergraphs).
297
- // We throw essentially the same thing in both cases, but adapt the message slightly.
298
- if (isFed1) {
299
- // 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
300
- // it'll solve the issue and that's good, or we'll hit the other message anyway.
301
- const msg = `Error extracting subgraph "${subgraph.name}" from the supergraph: this might be due to errors in subgraphs that were mistakenly ignored by federation 0.x versions but are rejected by federation 2.\n`
302
- + 'Please try composing your subgraphs with federation 2: this should help precisely pinpoint the problems and, once fixed, generate a correct federation 2 supergraph';
303
- throw new Error(`${msg}.\n\nDetails:\n${errorToString(e)}`);
304
- } else {
305
- const msg = `Unexpected error extracting subgraph ${subgraph.name} from the supergraph: this is either a bug, or the supergraph has been corrupted`;
306
- const dumpMsg = maybeDumpSubgraphSchema(subgraph);
307
- throw new Error(`${msg}.\n\nDetails:\n${errorToString(e)}\n\n${dumpMsg}`);
295
+ // We're done with the subgraphs, so call validate (which, amongst other things, sets up the _entities query field, which ensures
296
+ // all entities in all subgraphs are reachable from a query and so are properly included in the "query graph" later).
297
+ for (const subgraph of subgraphs) {
298
+ try {
299
+ subgraph.validate();
300
+ } catch (e) {
301
+ // This is going to be caught directly by the enclosing try-catch, but this is so we indicate the subgraph having the issue.
302
+ throw new SubgraphExtractionError(e, subgraph);
308
303
  }
309
304
  }
310
- }
311
305
 
312
- return subgraphs;
306
+ return subgraphs;
307
+ } catch (e) {
308
+ let error = e;
309
+ let subgraph: Subgraph | undefined = undefined;
310
+ // We want this catch to capture all errors happening during extraction, but the most common
311
+ // case is likely going to be fed2 validation that fed1 didn't enforced, and those will be
312
+ // throw when validating the extracted subgraphs, and n that case we use
313
+ // `SubgraphExtractionError` to pass the subgraph that errored out, which allows us
314
+ // to provide a bit more context in those cases.
315
+ if (e instanceof SubgraphExtractionError) {
316
+ error = e.originalError;
317
+ subgraph = e.subgraph;
318
+ }
319
+
320
+ // There is 2 reasons this could happen:
321
+ // 1. if the supergraph is a Fed1 one, because fed2 has stricter validations than fed1, this could be due to the supergraph
322
+ // containing something invalid that fed1 accepted and fed2 didn't (for instance, an invalid `@provides` selection).
323
+ // 2. otherwise, this would be a bug (because fed1 compatibility excluded, we shouldn't extract invalid subgraphs from valid supergraphs).
324
+ // We throw essentially the same thing in both cases, but adapt the message slightly.
325
+ const impacted = subgraph ? `subgraph "${subgraph.name}"` : 'subgraphs';
326
+ if (isFed1) {
327
+ // 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
328
+ // it'll solve the issue and that's good, or we'll hit the other message anyway.
329
+ const msg = `Error extracting ${impacted} from the supergraph: this might be due to errors in subgraphs that were mistakenly ignored by federation 0.x versions but are rejected by federation 2.\n`
330
+ + 'Please try composing your subgraphs with federation 2: this should help precisely pinpoint the problems and, once fixed, generate a correct federation 2 supergraph';
331
+ throw new Error(`${msg}.\n\nDetails:\n${errorToString(error)}`);
332
+ } else {
333
+ const msg = `Unexpected error extracting ${impacted} from the supergraph: this is either a bug, or the supergraph has been corrupted`;
334
+ const dumpMsg = subgraph ? '\n\n' + maybeDumpSubgraphSchema(subgraph) : '';
335
+ throw new Error(`${msg}.\n\nDetails:\n${errorToString(error)}${dumpMsg}`);
336
+ }
337
+ }
313
338
  }
314
339
 
315
340
  const DEBUG_SUBGRAPHS_ENV_VARIABLE_NAME = 'APOLLO_FEDERATION_DEBUG_SUBGRAPHS';
@@ -401,8 +426,8 @@ function copyType(type: Type, subgraph: Schema, subgraphName: string): Type {
401
426
  return new NonNullType(copyType(type.ofType, subgraph, subgraphName) as NullableType);
402
427
  default:
403
428
  const subgraphType = subgraph.type(type.name);
404
- assert(subgraphType, () => `Cannot find type ${type.name} in subgraph ${subgraphName}`);
405
- return subgraphType!;
429
+ assert(subgraphType, () => `Cannot find type "${type.name}" in subgraph "${subgraphName}"`);
430
+ return subgraphType;
406
431
  }
407
432
  }
408
433
 
@@ -211,8 +211,18 @@ function validateInaccessibleElements(
211
211
  ) {
212
212
  // These are top-level elements. If they're not @inaccessible, the only
213
213
  // way they won't be in the API schema is if they're definitions of some
214
- // core feature.
215
- return !isFeatureDefinition(element);
214
+ // core feature. However, we do intend on introducing mechanisms for
215
+ // exposing core feature elements in the API schema in the near feature.
216
+ // Because such mechanisms aren't completely nailed down yet, we opt to
217
+ // pretend here that all core feature elements are in the API schema for
218
+ // simplicity sake.
219
+ //
220
+ // This has the effect that if a non-core schema element is referenced by
221
+ // a core schema element, that non-core schema element can't be marked
222
+ // @inaccessible, despite that the core schema element may likely not be
223
+ // in the API schema. This may be relaxed in a later version of the
224
+ // inaccessible spec.
225
+ return true;
216
226
  } else if (
217
227
  (element instanceof FieldDefinition) ||
218
228
  (element instanceof ArgumentDefinition) ||