@apollo/federation-internals 2.0.1 → 2.0.2-alpha.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.
- package/CHANGELOG.md +16 -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.d.ts.map +1 -1
- package/dist/definitions.js +13 -6
- package/dist/definitions.js.map +1 -1
- package/dist/error.js +7 -7
- package/dist/error.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/federation.d.ts.map +1 -1
- package/dist/federation.js +15 -0
- package/dist/federation.js.map +1 -1
- package/dist/genErrorCodeDoc.js +12 -6
- package/dist/genErrorCodeDoc.js.map +1 -1
- package/dist/inaccessibleSpec.js +1 -1
- package/dist/inaccessibleSpec.js.map +1 -1
- package/dist/operations.d.ts +38 -4
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +107 -8
- package/dist/operations.js.map +1 -1
- package/dist/schemaUpgrader.d.ts +8 -1
- package/dist/schemaUpgrader.d.ts.map +1 -1
- package/dist/schemaUpgrader.js +40 -1
- package/dist/schemaUpgrader.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/definitions.test.ts +87 -0
- package/src/__tests__/extractSubgraphsFromSupergraph.test.ts +83 -0
- package/src/__tests__/operations.test.ts +119 -1
- package/src/__tests__/removeInaccessibleElements.test.ts +84 -1
- package/src/__tests__/schemaUpgrader.test.ts +38 -0
- package/src/__tests__/subgraphValidation.test.ts +74 -0
- package/src/buildSchema.ts +32 -5
- package/src/definitions.ts +20 -7
- package/src/error.ts +7 -7
- package/src/extractSubgraphsFromSupergraph.ts +229 -204
- package/src/federation.ts +50 -0
- package/src/genErrorCodeDoc.ts +13 -7
- package/src/inaccessibleSpec.ts +12 -2
- package/src/operations.ts +179 -8
- package/src/schemaUpgrader.ts +45 -0
- package/tsconfig.test.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
package/src/definitions.ts
CHANGED
|
@@ -946,11 +946,13 @@ export class CoreFeature {
|
|
|
946
946
|
}
|
|
947
947
|
|
|
948
948
|
directiveNameInSchema(name: string): string {
|
|
949
|
-
if (name === this.url.name) {
|
|
950
|
-
return this.nameInSchema;
|
|
951
|
-
}
|
|
952
949
|
const elementImport = this.imports.find((i) => i.name.charAt(0) === '@' && i.name.slice(1) === name);
|
|
953
|
-
return elementImport
|
|
950
|
+
return elementImport
|
|
951
|
+
? (elementImport.as?.slice(1) ?? name)
|
|
952
|
+
: (name === this.url.name
|
|
953
|
+
? this.nameInSchema
|
|
954
|
+
: this.nameInSchema + '__' + name
|
|
955
|
+
);
|
|
954
956
|
}
|
|
955
957
|
|
|
956
958
|
typeNameInSchema(name: string): string {
|
|
@@ -2104,7 +2106,11 @@ export class EnumType extends BaseNamedType<OutputTypeReferencer, EnumType> {
|
|
|
2104
2106
|
}
|
|
2105
2107
|
|
|
2106
2108
|
protected removeInnerElements(): void {
|
|
2107
|
-
|
|
2109
|
+
// Make a copy, since EnumValue.remove() will modify this._values.
|
|
2110
|
+
const values = Array.from(this._values);
|
|
2111
|
+
for (const value of values) {
|
|
2112
|
+
value.remove();
|
|
2113
|
+
}
|
|
2108
2114
|
}
|
|
2109
2115
|
|
|
2110
2116
|
protected hasNonExtensionInnerElements(): boolean {
|
|
@@ -2762,7 +2768,7 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
|
|
|
2762
2768
|
assert(false, `Directive definition ${this} can't reference other types (it's arguments can); shouldn't be asked to remove reference to ${type}`);
|
|
2763
2769
|
}
|
|
2764
2770
|
|
|
2765
|
-
|
|
2771
|
+
/**
|
|
2766
2772
|
* Removes this directive definition from its parent schema.
|
|
2767
2773
|
*
|
|
2768
2774
|
* After calling this method, this directive definition will be "detached": it will have no parent, schema, or
|
|
@@ -3213,9 +3219,16 @@ function copy(source: Schema, dest: Schema) {
|
|
|
3213
3219
|
for (const type of typesToCopy(source, dest)) {
|
|
3214
3220
|
dest.addType(newNamedType(type.kind, type.name));
|
|
3215
3221
|
}
|
|
3222
|
+
// Directives can use other directives in their arguments. So, like types, we first shallow copy
|
|
3223
|
+
// directives so future references to any of them can be dereferenced. We'll copy the actual
|
|
3224
|
+
// definition later after all directives are defined.
|
|
3216
3225
|
for (const directive of directivesToCopy(source, dest)) {
|
|
3217
|
-
|
|
3226
|
+
dest.addDirectiveDefinition(directive.name);
|
|
3218
3227
|
}
|
|
3228
|
+
for (const directive of directivesToCopy(source, dest)) {
|
|
3229
|
+
copyDirectiveDefinitionInner(directive, dest.directive(directive.name)!);
|
|
3230
|
+
}
|
|
3231
|
+
|
|
3219
3232
|
copySchemaDefinitionInner(source.schemaDefinition, dest.schemaDefinition);
|
|
3220
3233
|
for (const type of typesToCopy(source, dest)) {
|
|
3221
3234
|
copyNamedTypeInner(type, dest.type(type.name)!);
|
package/src/error.ts
CHANGED
|
@@ -183,7 +183,7 @@ const REQUIRES_UNSUPPORTED_ON_INTERFACE = DIRECTIVE_UNSUPPORTED_ON_INTERFACE.cre
|
|
|
183
183
|
|
|
184
184
|
const EXTERNAL_UNUSED = makeCodeDefinition(
|
|
185
185
|
'EXTERNAL_UNUSED',
|
|
186
|
-
'An `@external` field is not being used by any instance of `@key`, `@requires`, `@provides` or to satisfy an interface
|
|
186
|
+
'An `@external` field is not being used by any instance of `@key`, `@requires`, `@provides` or to satisfy an interface implementation.',
|
|
187
187
|
{ addedIn: FED1_CODE },
|
|
188
188
|
);
|
|
189
189
|
|
|
@@ -191,7 +191,7 @@ const TYPE_WITH_ONLY_UNUSED_EXTERNAL = makeCodeDefinition(
|
|
|
191
191
|
'TYPE_WITH_ONLY_UNUSED_EXTERNAL',
|
|
192
192
|
'A federation 1 schema has a composite type comprised only of unused external fields.'
|
|
193
193
|
+ ` Note that this error can _only_ be raised for federation 1 schema as federation 2 schema do not allow unused external fields (and errors with code ${EXTERNAL_UNUSED.code} will be raised in that case).`
|
|
194
|
-
+ ' But when federation 1 schema are automatically migrated to federation 2 ones, unused external fields are
|
|
194
|
+
+ ' But when federation 1 schema are automatically migrated to federation 2 ones, unused external fields are automatically removed, and in rare case this can leave a type empty. If that happens, an error with this code will be raised',
|
|
195
195
|
);
|
|
196
196
|
|
|
197
197
|
const PROVIDES_ON_NON_OBJECT_FIELD = makeCodeDefinition(
|
|
@@ -245,7 +245,7 @@ const NO_QUERIES = makeCodeDefinition(
|
|
|
245
245
|
|
|
246
246
|
const INTERFACE_FIELD_NO_IMPLEM = makeCodeDefinition(
|
|
247
247
|
'INTERFACE_FIELD_NO_IMPLEM',
|
|
248
|
-
'After subgraph merging, an
|
|
248
|
+
'After subgraph merging, an implementation is missing a field of one of the interface it implements (which can happen for valid subgraphs).'
|
|
249
249
|
);
|
|
250
250
|
|
|
251
251
|
const TYPE_KIND_MISMATCH = makeCodeDefinition(
|
|
@@ -267,12 +267,12 @@ const EXTERNAL_ARGUMENT_MISSING = makeCodeDefinition(
|
|
|
267
267
|
|
|
268
268
|
const EXTERNAL_ARGUMENT_TYPE_MISMATCH = makeCodeDefinition(
|
|
269
269
|
'EXTERNAL_ARGUMENT_TYPE_MISMATCH',
|
|
270
|
-
'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
|
|
270
|
+
'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 subgraphs.',
|
|
271
271
|
);
|
|
272
272
|
|
|
273
273
|
const EXTERNAL_ARGUMENT_DEFAULT_MISMATCH = makeCodeDefinition(
|
|
274
274
|
'EXTERNAL_ARGUMENT_DEFAULT_MISMATCH',
|
|
275
|
-
'An `@external` field declares an argument with a default that is incompatible with the corresponding argument in the declaration(s) of that field in other
|
|
275
|
+
'An `@external` field declares an argument with a default that is incompatible with the corresponding argument in the declaration(s) of that field in other subgraphs.',
|
|
276
276
|
);
|
|
277
277
|
|
|
278
278
|
const EXTERNAL_ON_INTERFACE = makeCodeDefinition(
|
|
@@ -510,7 +510,7 @@ const codeDefByCode = Object.values(ERRORS).reduce((obj: {[code: string]: ErrorC
|
|
|
510
510
|
*/
|
|
511
511
|
export const REMOVED_ERRORS = [
|
|
512
512
|
['KEY_FIELDS_MISSING_ON_BASE', 'Keys can now use any field from any other subgraph.'],
|
|
513
|
-
['KEY_FIELDS_MISSING_EXTERNAL', 'Using `@external` for key fields is now
|
|
513
|
+
['KEY_FIELDS_MISSING_EXTERNAL', 'Using `@external` for key fields is now discouraged, unless the field is truly meant to be external.'],
|
|
514
514
|
['KEY_MISSING_ON_BASE', 'Each subgraph is now free to declare a key only if it needs it.'],
|
|
515
515
|
['MULTIPLE_KEYS_ON_EXTENSION', 'Every subgraph can have multiple keys, as necessary.'],
|
|
516
516
|
['KEY_NOT_SPECIFIED', 'Each subgraph can declare key independently of any other subgraph.'],
|
|
@@ -528,5 +528,5 @@ export const REMOVED_ERRORS = [
|
|
|
528
528
|
['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.'],
|
|
529
529
|
['VALUE_TYPE_UNION_TYPES_MISMATCH', 'Subgraph definitions for an union are now merged by composition'],
|
|
530
530
|
['PROVIDES_FIELDS_SELECT_INVALID_TYPE', '@provides can now be used on field of interface, union and list types'],
|
|
531
|
-
['RESERVED_FIELD_USED', 'This error was previously not correctly enforced: the _service and _entities, if present, were
|
|
531
|
+
['RESERVED_FIELD_USED', 'This error was previously not correctly enforced: the _service and _entities, if present, were overridden; this is still the case'],
|
|
532
532
|
];
|
|
@@ -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
|
|