@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.
- package/CHANGELOG.md +7 -0
- package/dist/buildSchema.d.ts.map +1 -1
- package/dist/buildSchema.js +17 -6
- package/dist/buildSchema.js.map +1 -1
- package/dist/definitions.js +4 -1
- package/dist/definitions.js.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.js +170 -152
- package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
- package/dist/inaccessibleSpec.js +1 -1
- package/dist/inaccessibleSpec.js.map +1 -1
- package/dist/operations.d.ts +18 -3
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +55 -7
- package/dist/operations.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/definitions.test.ts +66 -0
- package/src/__tests__/extractSubgraphsFromSupergraph.test.ts +83 -0
- package/src/__tests__/operations.test.ts +119 -1
- package/src/__tests__/removeInaccessibleElements.test.ts +47 -0
- package/src/buildSchema.ts +32 -5
- package/src/definitions.ts +8 -1
- package/src/extractSubgraphsFromSupergraph.ts +229 -204
- package/src/inaccessibleSpec.ts +12 -2
- package/src/operations.ts +112 -7
- package/tsconfig.test.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
160
|
-
|
|
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
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
188
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
256
|
-
|
|
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
|
-
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
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
|
|
package/src/inaccessibleSpec.ts
CHANGED
|
@@ -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
|
-
|
|
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) ||
|