@apollo/federation-internals 2.2.2 → 2.3.0-alpha.0
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/dist/definitions.d.ts +2 -0
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +14 -2
- package/dist/definitions.js.map +1 -1
- package/dist/error.d.ts +3 -1
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +17 -12
- package/dist/error.js.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.js +31 -5
- package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
- package/dist/federation.d.ts +14 -6
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +141 -62
- package/dist/federation.js.map +1 -1
- package/dist/federationSpec.d.ts +2 -1
- package/dist/federationSpec.d.ts.map +1 -1
- package/dist/federationSpec.js +9 -1
- package/dist/federationSpec.js.map +1 -1
- package/dist/joinSpec.d.ts +9 -1
- package/dist/joinSpec.d.ts.map +1 -1
- package/dist/joinSpec.js +24 -2
- package/dist/joinSpec.js.map +1 -1
- package/dist/operations.d.ts +12 -0
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +59 -5
- package/dist/operations.js.map +1 -1
- package/dist/schemaUpgrader.d.ts.map +1 -1
- package/dist/schemaUpgrader.js +9 -0
- package/dist/schemaUpgrader.js.map +1 -1
- package/dist/supergraphs.d.ts.map +1 -1
- package/dist/supergraphs.js +1 -0
- package/dist/supergraphs.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/extractSubgraphsFromSupergraph.test.ts +0 -1
- package/src/__tests__/schemaUpgrader.test.ts +43 -0
- package/src/__tests__/subgraphValidation.test.ts +126 -57
- package/src/__tests__/testUtils.ts +28 -0
- package/src/__tests__/values.test.ts +1 -1
- package/src/definitions.ts +12 -0
- package/src/error.ts +34 -16
- package/src/extractSubgraphsFromSupergraph.ts +40 -9
- package/src/federation.ts +178 -73
- package/src/federationSpec.ts +10 -1
- package/src/joinSpec.ts +40 -11
- package/src/operations.ts +76 -8
- package/src/schemaUpgrader.ts +13 -0
- package/src/supergraphs.ts +1 -0
- package/tsconfig.test.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -202,3 +202,46 @@ test('remove tag on external field if found on definition', () => {
|
|
|
202
202
|
expect(typeAInS1.field("y")?.appliedDirectivesOf('tag').map((d) => d.toString())).toStrictEqual([]);
|
|
203
203
|
expect(typeAInS2.field("y")?.appliedDirectivesOf('tag').map((d) => d.toString())).toStrictEqual([ '@tag(name: "a tag")' ]);
|
|
204
204
|
})
|
|
205
|
+
|
|
206
|
+
test('reject @interfaceObject usage if not all subgraphs are fed2', () => {
|
|
207
|
+
// Note that this test both validates the rejection of fed1 subgraph when @interfaceObject is used somewhere, but also
|
|
208
|
+
// illustrate why we do so: fed1 schema can use @key on interface for backward compatibility, but it is ignored and
|
|
209
|
+
// the schema upgrader removes them. Given that actual support for @key on interfaces is necesarry to make @interfaceObject
|
|
210
|
+
// work, it would be really confusing to not reject the example below right away, since it "looks" like it the @key on
|
|
211
|
+
// the interface in the 2nd subgraph should work, but it actually won't.
|
|
212
|
+
|
|
213
|
+
const s1 = `
|
|
214
|
+
extend schema
|
|
215
|
+
@link(url: "https://specs.apollo.dev/federation/v2.3", import: [ "@key", "@interfaceObject"])
|
|
216
|
+
|
|
217
|
+
type Query {
|
|
218
|
+
a: A
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
type A @key(fields: "id") @interfaceObject {
|
|
222
|
+
id: String
|
|
223
|
+
x: Int
|
|
224
|
+
}
|
|
225
|
+
`;
|
|
226
|
+
|
|
227
|
+
const s2 = `
|
|
228
|
+
interface A @key(fields: "id") {
|
|
229
|
+
id: String
|
|
230
|
+
y: Int
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
type X implements A @key(fields: "id") {
|
|
234
|
+
id: String
|
|
235
|
+
y: Int
|
|
236
|
+
}
|
|
237
|
+
`;
|
|
238
|
+
|
|
239
|
+
const subgraphs = new Subgraphs();
|
|
240
|
+
subgraphs.add(buildSubgraph('s1', 'http://s1', s1));
|
|
241
|
+
subgraphs.add(buildSubgraph('s2', 'http://s2', s2));
|
|
242
|
+
const res = upgradeSubgraphsIfNecessary(subgraphs);
|
|
243
|
+
expect(res.errors?.map((e) => e.message)).toStrictEqual([
|
|
244
|
+
'The @interfaceObject directive can only be used if all subgraphs have federation 2 subgraph schema (schema with a `@link` to "https://specs.apollo.dev/federation" version 2.0 or newer): '
|
|
245
|
+
+ '@interfaceObject is used in subgraph "s1" but subgraph "s2" is not a federation 2 subgraph schema.'
|
|
246
|
+
]);
|
|
247
|
+
})
|
|
@@ -1,33 +1,9 @@
|
|
|
1
1
|
import { DocumentNode } from 'graphql';
|
|
2
2
|
import gql from 'graphql-tag';
|
|
3
|
-
import { Subgraph
|
|
4
|
-
import {
|
|
3
|
+
import { Subgraph } from '..';
|
|
4
|
+
import { buildSubgraph } from "../federation"
|
|
5
5
|
import { defaultPrintOptions, printSchema } from '../print';
|
|
6
|
-
import './
|
|
7
|
-
|
|
8
|
-
// Builds the provided subgraph (using name 'S' for the subgraph) and, if the
|
|
9
|
-
// subgraph is invalid/has errors, return those errors as a list of [code, message].
|
|
10
|
-
// If the subgraph is valid, return undefined.
|
|
11
|
-
export function buildForErrors(
|
|
12
|
-
subgraphDefs: DocumentNode,
|
|
13
|
-
options?: {
|
|
14
|
-
subgraphName?: string,
|
|
15
|
-
asFed2?: boolean,
|
|
16
|
-
}
|
|
17
|
-
): [string, string][] | undefined {
|
|
18
|
-
try {
|
|
19
|
-
const doc = (options?.asFed2 ?? true) ? asFed2SubgraphDocument(subgraphDefs) : subgraphDefs;
|
|
20
|
-
const name = options?.subgraphName ?? 'S';
|
|
21
|
-
buildSubgraph(name, `http://${name}`, doc).validate();
|
|
22
|
-
return undefined;
|
|
23
|
-
} catch (e) {
|
|
24
|
-
const causes = errorCauses(e);
|
|
25
|
-
if (!causes) {
|
|
26
|
-
throw e;
|
|
27
|
-
}
|
|
28
|
-
return causes.map((err) => [err.extensions.code as string, err.message]);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
6
|
+
import { buildForErrors } from './testUtils';
|
|
31
7
|
|
|
32
8
|
describe('fieldset-based directives', () => {
|
|
33
9
|
it('rejects field defined with arguments in @key', () => {
|
|
@@ -91,8 +67,11 @@ describe('fieldset-based directives', () => {
|
|
|
91
67
|
]);
|
|
92
68
|
});
|
|
93
69
|
|
|
94
|
-
it('rejects @key on interfaces', () => {
|
|
70
|
+
it.each(['2.0', '2.1', '2.2'])('rejects @key on interfaces _in the %p spec_', (version) => {
|
|
95
71
|
const subgraph = gql`
|
|
72
|
+
extend schema
|
|
73
|
+
@link(url: "https://specs.apollo.dev/federation/v${version}", import: ["@key"])
|
|
74
|
+
|
|
96
75
|
type Query {
|
|
97
76
|
t: T
|
|
98
77
|
}
|
|
@@ -101,7 +80,7 @@ describe('fieldset-based directives', () => {
|
|
|
101
80
|
f: Int
|
|
102
81
|
}
|
|
103
82
|
`
|
|
104
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
83
|
+
expect(buildForErrors(subgraph, { asFed2: false })).toStrictEqual([
|
|
105
84
|
['KEY_UNSUPPORTED_ON_INTERFACE', '[S] Cannot use @key on interface "T": @key is not yet supported on interfaces'],
|
|
106
85
|
]);
|
|
107
86
|
});
|
|
@@ -559,34 +538,6 @@ describe('root types', () => {
|
|
|
559
538
|
});
|
|
560
539
|
});
|
|
561
540
|
|
|
562
|
-
it('validates all implementations of interface field have same type if any has @external', () => {
|
|
563
|
-
const subgraph = gql`
|
|
564
|
-
type Query {
|
|
565
|
-
is: [I!]!
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
interface I {
|
|
569
|
-
f: Int
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
type T1 implements I {
|
|
573
|
-
f: Int
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
type T2 implements I {
|
|
577
|
-
f: Int!
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
type T3 implements I {
|
|
581
|
-
id: ID!
|
|
582
|
-
f: Int @external
|
|
583
|
-
}
|
|
584
|
-
`;
|
|
585
|
-
expect(buildForErrors(subgraph)).toStrictEqual([
|
|
586
|
-
['INTERFACE_FIELD_IMPLEM_TYPE_MISMATCH', '[S] Some of the runtime implementations of interface field "I.f" are marked @external or have a @require ("T3.f") so all the implementations should use the same type (a current limitation of federation; see https://github.com/apollographql/federation/issues/1257), but "T1.f" and "T3.f" have type "Int" while "T2.f" has type "Int!".'],
|
|
587
|
-
]);
|
|
588
|
-
})
|
|
589
|
-
|
|
590
541
|
describe('custom error message for misnamed directives', () => {
|
|
591
542
|
it.each([
|
|
592
543
|
{ name: 'fed1', extraMsg: ' If so, note that it is a federation 2 directive but this schema is a federation 1 one. To be a federation 2 schema, it needs to @link to the federation specifcation v2.' },
|
|
@@ -1183,3 +1134,121 @@ describe('@shareable', () => {
|
|
|
1183
1134
|
]]);
|
|
1184
1135
|
});
|
|
1185
1136
|
});
|
|
1137
|
+
|
|
1138
|
+
describe('@interfaceObject/@key on interfaces validation', () => {
|
|
1139
|
+
it('@key on interfaces require @key on all implementations', () => {
|
|
1140
|
+
const doc = gql`
|
|
1141
|
+
interface I @key(fields: "id1") @key(fields: "id2") {
|
|
1142
|
+
id1: ID!
|
|
1143
|
+
id2: ID!
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
type A implements I @key(fields: "id2") {
|
|
1147
|
+
id1: ID!
|
|
1148
|
+
id2: ID!
|
|
1149
|
+
a: Int
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
type B implements I @key(fields: "id1") @key(fields: "id2") {
|
|
1153
|
+
id1: ID!
|
|
1154
|
+
id2: ID!
|
|
1155
|
+
b: Int
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
type C implements I @key(fields: "id2") {
|
|
1159
|
+
id1: ID!
|
|
1160
|
+
id2: ID!
|
|
1161
|
+
c: Int
|
|
1162
|
+
}
|
|
1163
|
+
`;
|
|
1164
|
+
|
|
1165
|
+
expect(buildForErrors(doc)).toStrictEqual([[
|
|
1166
|
+
'INTERFACE_KEY_NOT_ON_IMPLEMENTATION',
|
|
1167
|
+
'[S] Key @key(fields: "id1") on interface type "I" is missing on implementation types "A" and "C".',
|
|
1168
|
+
]]);
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
it('@key on interfaces with @key on some implementation non resolvable', () => {
|
|
1172
|
+
const doc = gql`
|
|
1173
|
+
interface I @key(fields: "id1") {
|
|
1174
|
+
id1: ID!
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
type A implements I @key(fields: "id1") {
|
|
1178
|
+
id1: ID!
|
|
1179
|
+
a: Int
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
type B implements I @key(fields: "id1") {
|
|
1183
|
+
id1: ID!
|
|
1184
|
+
b: Int
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
type C implements I @key(fields: "id1", resolvable: false) {
|
|
1188
|
+
id1: ID!
|
|
1189
|
+
c: Int
|
|
1190
|
+
}
|
|
1191
|
+
`;
|
|
1192
|
+
|
|
1193
|
+
expect(buildForErrors(doc)).toStrictEqual([[
|
|
1194
|
+
'INTERFACE_KEY_NOT_ON_IMPLEMENTATION',
|
|
1195
|
+
'[S] Key @key(fields: "id1") on interface type "I" should be resolvable on all implementation types, but is declared with argument "@key(resolvable:)" set to false in type "C".',
|
|
1196
|
+
]]);
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
it('ensures order of fields in key does not matter', () => {
|
|
1200
|
+
const doc = gql`
|
|
1201
|
+
interface I @key(fields: "a b c") {
|
|
1202
|
+
a: Int
|
|
1203
|
+
b: Int
|
|
1204
|
+
c: Int
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
type A implements I @key(fields: "c b a") {
|
|
1208
|
+
a: Int
|
|
1209
|
+
b: Int
|
|
1210
|
+
c: Int
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
type B implements I @key(fields: "a c b") {
|
|
1214
|
+
a: Int
|
|
1215
|
+
b: Int
|
|
1216
|
+
c: Int
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
type C implements I @key(fields: "a b c") {
|
|
1220
|
+
a: Int
|
|
1221
|
+
b: Int
|
|
1222
|
+
c: Int
|
|
1223
|
+
}
|
|
1224
|
+
`;
|
|
1225
|
+
|
|
1226
|
+
expect(buildForErrors(doc)).toBeUndefined();
|
|
1227
|
+
});
|
|
1228
|
+
|
|
1229
|
+
// There is no meaningful way to make @interfaceObject work on a value type at the moment, because
|
|
1230
|
+
// if you have an @interfaceObject, some other subgraph needs to be able to resolve the concrete
|
|
1231
|
+
// type, and that imply that you have key to go to that other subgraph.
|
|
1232
|
+
// To be clear, the @key on the @interfaceObject technically con't need to be "resolvable", and the
|
|
1233
|
+
// difference between no key and a non-resolvable key is arguably more convention than a genuine
|
|
1234
|
+
// mechanical difference at the moment, but still a good idea to rely on that convention to help
|
|
1235
|
+
// catching obvious mistakes early.
|
|
1236
|
+
it('only allow @interfaceObject on entity types', () => {
|
|
1237
|
+
const doc = gql`
|
|
1238
|
+
# This one shouldn't raise an error
|
|
1239
|
+
type A @key(fields: "id", resolvable: false) @interfaceObject {
|
|
1240
|
+
id: ID!
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
# This one should
|
|
1244
|
+
type B @interfaceObject {
|
|
1245
|
+
x: Int
|
|
1246
|
+
}
|
|
1247
|
+
`;
|
|
1248
|
+
|
|
1249
|
+
expect(buildForErrors(doc)).toStrictEqual([[
|
|
1250
|
+
'INTERFACE_OBJECT_USAGE_ERROR',
|
|
1251
|
+
'[S] The @interfaceObject directive can only be applied to entity types but type "B" has no @key in this subgraph.'
|
|
1252
|
+
]]);
|
|
1253
|
+
});
|
|
1254
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { DocumentNode } from "graphql";
|
|
2
|
+
import { asFed2SubgraphDocument, buildSubgraph, errorCauses } from "..";
|
|
3
|
+
import './matchers';
|
|
4
|
+
|
|
5
|
+
// Builds the provided subgraph (using name 'S' for the subgraph) and, if the
|
|
6
|
+
// subgraph is invalid/has errors, return those errors as a list of [code, message].
|
|
7
|
+
// If the subgraph is valid, return undefined.
|
|
8
|
+
export function buildForErrors(
|
|
9
|
+
subgraphDefs: DocumentNode,
|
|
10
|
+
options?: {
|
|
11
|
+
subgraphName?: string,
|
|
12
|
+
asFed2?: boolean,
|
|
13
|
+
}
|
|
14
|
+
): [string, string][] | undefined {
|
|
15
|
+
try {
|
|
16
|
+
const doc = (options?.asFed2 ?? true) ? asFed2SubgraphDocument(subgraphDefs) : subgraphDefs;
|
|
17
|
+
const name = options?.subgraphName ?? 'S';
|
|
18
|
+
buildSubgraph(name, `http://${name}`, doc).validate();
|
|
19
|
+
return undefined;
|
|
20
|
+
} catch (e) {
|
|
21
|
+
const causes = errorCauses(e);
|
|
22
|
+
if (!causes) {
|
|
23
|
+
throw e;
|
|
24
|
+
}
|
|
25
|
+
return causes.map((err) => [err.extensions.code as string, err.message]);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
} from '../definitions';
|
|
4
4
|
import { buildSchema } from '../buildSchema';
|
|
5
5
|
import { parseOperation } from '../operations';
|
|
6
|
-
import { buildForErrors } from './subgraphValidation.test';
|
|
7
6
|
import gql from 'graphql-tag';
|
|
8
7
|
import { printSchema } from '../print';
|
|
9
8
|
import { valueEquals } from '../values';
|
|
9
|
+
import { buildForErrors } from './testUtils';
|
|
10
10
|
|
|
11
11
|
function parseSchema(schema: string): Schema {
|
|
12
12
|
try {
|
package/src/definitions.ts
CHANGED
|
@@ -241,6 +241,14 @@ export function runtimeTypesIntersects(t1: CompositeType, t2: CompositeType): bo
|
|
|
241
241
|
return false;
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
+
export function supertypes(type: CompositeType): readonly CompositeType[] {
|
|
245
|
+
switch (type.kind) {
|
|
246
|
+
case 'InterfaceType': return type.interfaces();
|
|
247
|
+
case 'UnionType': return [];
|
|
248
|
+
case 'ObjectType': return (type.interfaces() as CompositeType[]).concat(type.unionsWhereMember());
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
244
252
|
export function isConditionalDirective(directive: Directive<any, any> | DirectiveDefinition<any>): boolean {
|
|
245
253
|
return ['include', 'skip'].includes(directive.name);
|
|
246
254
|
}
|
|
@@ -2098,6 +2106,10 @@ export class ObjectType extends FieldBasedType<ObjectType, ObjectTypeReferencer>
|
|
|
2098
2106
|
break;
|
|
2099
2107
|
}
|
|
2100
2108
|
}
|
|
2109
|
+
|
|
2110
|
+
unionsWhereMember(): readonly UnionType[] {
|
|
2111
|
+
return this._referencers?.filter<UnionType>((r): r is UnionType => r instanceof BaseNamedType && isUnionType(r)) ?? [];
|
|
2112
|
+
}
|
|
2101
2113
|
}
|
|
2102
2114
|
|
|
2103
2115
|
export class InterfaceType extends FieldBasedType<InterfaceType, InterfaceTypeReferencer> {
|
package/src/error.ts
CHANGED
|
@@ -235,7 +235,7 @@ const REQUIRES_MISSING_EXTERNAL = DIRECTIVE_FIELDS_MISSING_EXTERNAL.createCode('
|
|
|
235
235
|
|
|
236
236
|
const DIRECTIVE_UNSUPPORTED_ON_INTERFACE = makeFederationDirectiveErrorCodeCategory(
|
|
237
237
|
'UNSUPPORTED_ON_INTERFACE',
|
|
238
|
-
(directive) => `A \`@${directive}\` directive is used on an interface, which is not (yet) supported.`,
|
|
238
|
+
(directive) => `A \`@${directive}\` directive is used on an interface, which is ${directive === 'key' ? 'only supported when @linking to federation 2.3+' : 'not (yet) supported'}.`,
|
|
239
239
|
);
|
|
240
240
|
|
|
241
241
|
const KEY_UNSUPPORTED_ON_INTERFACE = DIRECTIVE_UNSUPPORTED_ON_INTERFACE.createCode('key');
|
|
@@ -395,11 +395,6 @@ const EXTERNAL_MISSING_ON_BASE = makeCodeDefinition(
|
|
|
395
395
|
{ addedIn: FED1_CODE },
|
|
396
396
|
);
|
|
397
397
|
|
|
398
|
-
const INTERFACE_FIELD_IMPLEM_TYPE_MISMATCH = makeCodeDefinition(
|
|
399
|
-
'INTERFACE_FIELD_IMPLEM_TYPE_MISMATCH',
|
|
400
|
-
'For an interface field, some of its concrete implementations have @external or @requires and there is difference in those implementations return type (which is currently not supported; see https://github.com/apollographql/federation/issues/1257)'
|
|
401
|
-
);
|
|
402
|
-
|
|
403
398
|
const INVALID_FIELD_SHARING = makeCodeDefinition(
|
|
404
399
|
'INVALID_FIELD_SHARING',
|
|
405
400
|
'A field that is non-shareable in at least one subgraph is resolved by multiple subgraphs.'
|
|
@@ -531,6 +526,25 @@ const DIRECTIVE_COMPOSITION_ERROR = makeCodeDefinition(
|
|
|
531
526
|
{ addedIn: '2.1.0' },
|
|
532
527
|
);
|
|
533
528
|
|
|
529
|
+
const INTERFACE_OBJECT_USAGE_ERROR = makeCodeDefinition(
|
|
530
|
+
'INTERFACE_OBJECT_USAGE_ERROR',
|
|
531
|
+
'Error in the usage of the @interfaceObject directive.',
|
|
532
|
+
{ addedIn: '2.3.0' },
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
const INTERFACE_KEY_NOT_ON_IMPLEMENTATION = makeCodeDefinition(
|
|
536
|
+
'INTERFACE_KEY_NOT_ON_IMPLEMENTATION',
|
|
537
|
+
'A `@key` is defined on an interface type, but is not defined (or is not resolvable) on at least one of the interface implementations',
|
|
538
|
+
{ addedIn: '2.3.0' },
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
const INTERFACE_KEY_MISSING_IMPLEMENTATION_TYPE = makeCodeDefinition(
|
|
542
|
+
'INTERFACE_KEY_MISSING_IMPLEMENTATION_TYPE',
|
|
543
|
+
'A subgraph has a `@key` on an interface type, but that subgraph does not define an implementation (in the supergraph) of that interface',
|
|
544
|
+
{ addedIn: '2.3.0' },
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
|
|
534
548
|
export const ERROR_CATEGORIES = {
|
|
535
549
|
DIRECTIVE_FIELDS_MISSING_EXTERNAL,
|
|
536
550
|
DIRECTIVE_UNSUPPORTED_ON_INTERFACE,
|
|
@@ -585,7 +599,6 @@ export const ERRORS = {
|
|
|
585
599
|
ARGUMENT_DEFAULT_MISMATCH,
|
|
586
600
|
EXTENSION_WITH_NO_BASE,
|
|
587
601
|
EXTERNAL_MISSING_ON_BASE,
|
|
588
|
-
INTERFACE_FIELD_IMPLEM_TYPE_MISMATCH,
|
|
589
602
|
INVALID_FIELD_SHARING,
|
|
590
603
|
INVALID_SHAREABLE_USAGE,
|
|
591
604
|
INVALID_LINK_DIRECTIVE_USAGE,
|
|
@@ -614,6 +627,9 @@ export const ERRORS = {
|
|
|
614
627
|
PROVIDES_HAS_DIRECTIVE_IN_FIELDS_ARGS,
|
|
615
628
|
REQUIRES_HAS_DIRECTIVE_IN_FIELDS_ARGS,
|
|
616
629
|
DIRECTIVE_COMPOSITION_ERROR,
|
|
630
|
+
INTERFACE_OBJECT_USAGE_ERROR,
|
|
631
|
+
INTERFACE_KEY_NOT_ON_IMPLEMENTATION,
|
|
632
|
+
INTERFACE_KEY_MISSING_IMPLEMENTATION_TYPE,
|
|
617
633
|
};
|
|
618
634
|
|
|
619
635
|
const codeDefByCode = Object.values(ERRORS).reduce((obj: {[code: string]: ErrorCodeDefinition}, codeDef: ErrorCodeDefinition) => { obj[codeDef.code] = codeDef; return obj; }, {});
|
|
@@ -638,16 +654,18 @@ export const REMOVED_ERRORS = [
|
|
|
638
654
|
['REQUIRES_FIELDS_MISSING_ON_BASE', 'Fields in @requires can now be from any subgraph.'],
|
|
639
655
|
['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.'],
|
|
640
656
|
|
|
641
|
-
['DUPLICATE_SCALAR_DEFINITION', 'As duplicate scalar definitions is invalid GraphQL, this will now be an error with code `INVALID_GRAPHQL
|
|
642
|
-
['DUPLICATE_ENUM_DEFINITION', 'As duplicate enum definitions is invalid GraphQL, this will now be an error with code `INVALID_GRAPHQL
|
|
643
|
-
['DUPLICATE_ENUM_VALUE', 'As duplicate enum values is invalid GraphQL, this will now be an error with code `INVALID_GRAPHQL
|
|
657
|
+
['DUPLICATE_SCALAR_DEFINITION', 'As duplicate scalar definitions is invalid GraphQL, this will now be an error with code `INVALID_GRAPHQL`.'],
|
|
658
|
+
['DUPLICATE_ENUM_DEFINITION', 'As duplicate enum definitions is invalid GraphQL, this will now be an error with code `INVALID_GRAPHQL`.'],
|
|
659
|
+
['DUPLICATE_ENUM_VALUE', 'As duplicate enum values is invalid GraphQL, this will now be an error with code `INVALID_GRAPHQL`.'],
|
|
644
660
|
|
|
645
|
-
['ENUM_MISMATCH', 'Subgraph definitions for an enum are now merged by composition'],
|
|
661
|
+
['ENUM_MISMATCH', 'Subgraph definitions for an enum are now merged by composition.'],
|
|
646
662
|
['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.'],
|
|
647
|
-
['VALUE_TYPE_UNION_TYPES_MISMATCH', 'Subgraph definitions for an union are now merged by composition'],
|
|
648
|
-
['PROVIDES_FIELDS_SELECT_INVALID_TYPE', '@provides can now be used on field of interface, union and list types'],
|
|
649
|
-
['RESERVED_FIELD_USED', 'This error was previously not correctly enforced: the _service and _entities, if present, were overridden; this is still the case'],
|
|
663
|
+
['VALUE_TYPE_UNION_TYPES_MISMATCH', 'Subgraph definitions for an union are now merged by composition.'],
|
|
664
|
+
['PROVIDES_FIELDS_SELECT_INVALID_TYPE', '@provides can now be used on field of interface, union and list types.'],
|
|
665
|
+
['RESERVED_FIELD_USED', 'This error was previously not correctly enforced: the _service and _entities, if present, were overridden; this is still the case.'],
|
|
666
|
+
|
|
667
|
+
['NON_REPEATABLE_DIRECTIVE_ARGUMENTS_MISMATCH', 'Since federation 2.1.0, the case this error used to cover is now a warning (with code `INCONSISTENT_NON_REPEATABLE_DIRECTIVE_ARGUMENTS`) instead of an error.'],
|
|
668
|
+
['REQUIRES_FIELDS_HAS_ARGS', 'Since federation 2.1.1, using fields with arguments in a @requires is fully supported.'],
|
|
650
669
|
|
|
651
|
-
['
|
|
652
|
-
['REQUIRES_FIELDS_HAS_ARGS', 'Since federation 2.1.1, using fields with arguments in a @requires is fully supported'],
|
|
670
|
+
['INTERFACE_FIELD_IMPLEM_TYPE_MISMATCH', 'This error was thrown by a validation introduced to avoid running into a known runtime bug. Since federation 2.3, the underlying runtime bug has been addressed and the validation/limitation was no longer necessary and has been removed.'],
|
|
653
671
|
];
|
|
@@ -201,8 +201,13 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
|
|
|
201
201
|
const implementsDirective = joinSpec.implementsDirective(supergraph);
|
|
202
202
|
const ownerDirective = joinSpec.ownerDirective(supergraph);
|
|
203
203
|
const fieldDirective = joinSpec.fieldDirective(supergraph);
|
|
204
|
+
const unionMemberDirective = joinSpec.unionMemberDirective(supergraph);
|
|
205
|
+
const enumValueDirective = joinSpec.enumValueDirective(supergraph);
|
|
204
206
|
|
|
205
|
-
const getSubgraph = (application: Directive<any, { graph
|
|
207
|
+
const getSubgraph = (application: Directive<any, { graph?: string }>) => {
|
|
208
|
+
const graph = application.arguments().graph;
|
|
209
|
+
return graph ? graphEnumNameToSubgraphName.get(graph) : undefined;
|
|
210
|
+
};
|
|
206
211
|
|
|
207
212
|
/*
|
|
208
213
|
* Fed2 supergraph have "provenance" information for all types and fields, so we can faithfully extract subgraph relatively easily.
|
|
@@ -221,7 +226,7 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
|
|
|
221
226
|
supergraph,
|
|
222
227
|
subgraphs.names(),
|
|
223
228
|
(f, name) => {
|
|
224
|
-
const fieldApplications: Directive<any, { graph
|
|
229
|
+
const fieldApplications: Directive<any, { graph?: string, requires?: string, provides?: string }>[] = f.appliedDirectivesOf(fieldDirective);
|
|
225
230
|
if (fieldApplications.length) {
|
|
226
231
|
const application = fieldApplications.find((application) => getSubgraph(application) === name);
|
|
227
232
|
if (application) {
|
|
@@ -274,7 +279,11 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
|
|
|
274
279
|
// We can have more than one type directive for a given subgraph
|
|
275
280
|
let subgraphType = schema.type(type.name);
|
|
276
281
|
if (!subgraphType) {
|
|
277
|
-
|
|
282
|
+
const kind = args.isInterfaceObject ? 'ObjectType' : type.kind;
|
|
283
|
+
subgraphType = schema.addType(newNamedType(kind, type.name));
|
|
284
|
+
if (args.isInterfaceObject) {
|
|
285
|
+
subgraphType.applyDirective('interfaceObject');
|
|
286
|
+
}
|
|
278
287
|
}
|
|
279
288
|
if (args.key) {
|
|
280
289
|
const { resolvable } = args;
|
|
@@ -353,6 +362,11 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
|
|
|
353
362
|
|
|
354
363
|
for (const application of fieldApplications) {
|
|
355
364
|
const args = application.arguments();
|
|
365
|
+
// We use a @join__field with no graph to indicates when a field in the supergraph does not come
|
|
366
|
+
// directly from any subgraph and there is thus nothing to do to "extract" it.
|
|
367
|
+
if (!args.graph) {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
356
370
|
const subgraph = subgraphs.get(graphEnumNameToSubgraphName.get(args.graph)!)!;
|
|
357
371
|
const subgraphField = addSubgraphField(field, subgraph, args.type);
|
|
358
372
|
if (!subgraphField) {
|
|
@@ -394,23 +408,40 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
|
|
|
394
408
|
continue;
|
|
395
409
|
}
|
|
396
410
|
assert(isEnumType(subgraphEnum), () => `${subgraphEnum} should be an enum but found a ${subgraphEnum.kind}`);
|
|
411
|
+
|
|
397
412
|
for (const value of type.values) {
|
|
398
|
-
|
|
413
|
+
// Before version 0.3 of the join spec (before `enumValueDirective`), we were not recording which subgraph defined which values,
|
|
414
|
+
// and instead aded all values to all subgraphs (at least if the type existed there).
|
|
415
|
+
const addValue = !enumValueDirective
|
|
416
|
+
|| value.appliedDirectivesOf(enumValueDirective).some((d) =>
|
|
417
|
+
graphEnumNameToSubgraphName.get(d.arguments().graph) === subgraph.name
|
|
418
|
+
);
|
|
419
|
+
if (addValue) {
|
|
420
|
+
subgraphEnum.addValue(value.name);
|
|
421
|
+
}
|
|
399
422
|
}
|
|
400
423
|
}
|
|
401
424
|
break;
|
|
402
425
|
case 'UnionType':
|
|
403
|
-
// TODO: Same as for enums. We need to know in which subgraph each member is defined.
|
|
404
|
-
// But for now, we also add every members to all subgraphs (as long as the subgraph has both the union type
|
|
405
|
-
// and the member in question).
|
|
406
426
|
for (const subgraph of subgraphs) {
|
|
407
427
|
const subgraphUnion = subgraph.schema.type(type.name);
|
|
408
428
|
if (!subgraphUnion) {
|
|
409
429
|
continue;
|
|
410
430
|
}
|
|
411
431
|
assert(isUnionType(subgraphUnion), () => `${subgraphUnion} should be an enum but found a ${subgraphUnion.kind}`);
|
|
412
|
-
|
|
413
|
-
|
|
432
|
+
let membersInSubgraph: string[];
|
|
433
|
+
if (unionMemberDirective) {
|
|
434
|
+
membersInSubgraph = type
|
|
435
|
+
.appliedDirectivesOf(unionMemberDirective)
|
|
436
|
+
.filter((d) => graphEnumNameToSubgraphName.get(d.arguments().graph) === subgraph.name)
|
|
437
|
+
.map((d) => d.arguments().member);
|
|
438
|
+
} else {
|
|
439
|
+
// Before version 0.3 of the join spec, we were not recording which subgraph defined which members,
|
|
440
|
+
// and instead aded all members to all subgraphs (at least if the type existed there).
|
|
441
|
+
membersInSubgraph = type.types().map((t) => t.name);
|
|
442
|
+
}
|
|
443
|
+
for (const memberTypeName of membersInSubgraph) {
|
|
444
|
+
const subgraphType = subgraph.schema.type(memberTypeName);
|
|
414
445
|
if (subgraphType) {
|
|
415
446
|
subgraphUnion.addType(subgraphType as ObjectType);
|
|
416
447
|
}
|