@apollo/federation-internals 2.2.3 → 2.3.0-beta.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/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 +5 -1
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +21 -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 +11 -2
- 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 +11 -0
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +46 -1
- 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 +2 -0
- package/dist/supergraphs.js.map +1 -1
- package/dist/tagSpec.d.ts +1 -0
- package/dist/tagSpec.d.ts.map +1 -1
- package/dist/tagSpec.js +9 -1
- package/dist/tagSpec.js.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -6
- package/dist/types.js.map +1 -1
- package/dist/utils.js +1 -1
- package/dist/utils.js.map +1 -1
- package/package.json +2 -3
- package/src/__tests__/extractSubgraphsFromSupergraph.test.ts +0 -1
- package/src/__tests__/matchers/toMatchString.ts +5 -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 +47 -16
- package/src/extractSubgraphsFromSupergraph.ts +40 -9
- package/src/federation.ts +178 -73
- package/src/federationSpec.ts +16 -2
- package/src/joinSpec.ts +40 -11
- package/src/operations.ts +59 -1
- package/src/schemaUpgrader.ts +13 -0
- package/src/supergraphs.ts +2 -0
- package/src/tagSpec.ts +10 -1
- package/src/types.ts +12 -15
- package/src/utils.ts +1 -1
- package/tsconfig.test.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -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.'
|
|
@@ -487,6 +482,11 @@ const EMPTY_MERGED_ENUM_TYPE = makeCodeDefinition(
|
|
|
487
482
|
'An enum type has no value common to all the subgraphs that define the type. Merging that type would result in an invalid empty enum type.'
|
|
488
483
|
);
|
|
489
484
|
|
|
485
|
+
const SHAREABLE_HAS_MISMATCHED_RUNTIME_TYPES = makeCodeDefinition(
|
|
486
|
+
'SHAREABLE_HAS_MISMATCHED_RUNTIME_TYPES',
|
|
487
|
+
'A shareable field return type has mismatched possible runtime types in the subgraphs in which the field is declared. As shared fields must resolve the same way in all subgraphs, this is almost surely a mistake.'
|
|
488
|
+
);
|
|
489
|
+
|
|
490
490
|
const SATISFIABILITY_ERROR = makeCodeDefinition(
|
|
491
491
|
'SATISFIABILITY_ERROR',
|
|
492
492
|
'Subgraphs can be merged, but the resulting supergraph API would have queries that cannot be satisfied by those subgraphs.',
|
|
@@ -507,6 +507,12 @@ const OVERRIDE_COLLISION_WITH_ANOTHER_DIRECTIVE = makeCodeDefinition(
|
|
|
507
507
|
'The @override directive cannot be used on external fields, nor to override fields with either @external, @provides, or @requires.',
|
|
508
508
|
);
|
|
509
509
|
|
|
510
|
+
const OVERRIDE_ON_INTERFACE = makeCodeDefinition(
|
|
511
|
+
'OVERRIDE_ON_INTERFACE',
|
|
512
|
+
'The @override directive cannot be used on the fields of an interface type.',
|
|
513
|
+
{ addedIn: '2.3.0' },
|
|
514
|
+
);
|
|
515
|
+
|
|
510
516
|
const UNSUPPORTED_FEATURE = makeCodeDefinition(
|
|
511
517
|
'UNSUPPORTED_FEATURE',
|
|
512
518
|
'Indicates an error due to feature currently unsupported by federation.',
|
|
@@ -531,6 +537,25 @@ const DIRECTIVE_COMPOSITION_ERROR = makeCodeDefinition(
|
|
|
531
537
|
{ addedIn: '2.1.0' },
|
|
532
538
|
);
|
|
533
539
|
|
|
540
|
+
const INTERFACE_OBJECT_USAGE_ERROR = makeCodeDefinition(
|
|
541
|
+
'INTERFACE_OBJECT_USAGE_ERROR',
|
|
542
|
+
'Error in the usage of the @interfaceObject directive.',
|
|
543
|
+
{ addedIn: '2.3.0' },
|
|
544
|
+
);
|
|
545
|
+
|
|
546
|
+
const INTERFACE_KEY_NOT_ON_IMPLEMENTATION = makeCodeDefinition(
|
|
547
|
+
'INTERFACE_KEY_NOT_ON_IMPLEMENTATION',
|
|
548
|
+
'A `@key` is defined on an interface type, but is not defined (or is not resolvable) on at least one of the interface implementations',
|
|
549
|
+
{ addedIn: '2.3.0' },
|
|
550
|
+
);
|
|
551
|
+
|
|
552
|
+
const INTERFACE_KEY_MISSING_IMPLEMENTATION_TYPE = makeCodeDefinition(
|
|
553
|
+
'INTERFACE_KEY_MISSING_IMPLEMENTATION_TYPE',
|
|
554
|
+
'A subgraph has a `@key` on an interface type, but that subgraph does not define an implementation (in the supergraph) of that interface',
|
|
555
|
+
{ addedIn: '2.3.0' },
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
|
|
534
559
|
export const ERROR_CATEGORIES = {
|
|
535
560
|
DIRECTIVE_FIELDS_MISSING_EXTERNAL,
|
|
536
561
|
DIRECTIVE_UNSUPPORTED_ON_INTERFACE,
|
|
@@ -585,7 +610,6 @@ export const ERRORS = {
|
|
|
585
610
|
ARGUMENT_DEFAULT_MISMATCH,
|
|
586
611
|
EXTENSION_WITH_NO_BASE,
|
|
587
612
|
EXTERNAL_MISSING_ON_BASE,
|
|
588
|
-
INTERFACE_FIELD_IMPLEM_TYPE_MISMATCH,
|
|
589
613
|
INVALID_FIELD_SHARING,
|
|
590
614
|
INVALID_SHAREABLE_USAGE,
|
|
591
615
|
INVALID_LINK_DIRECTIVE_USAGE,
|
|
@@ -603,10 +627,12 @@ export const ERRORS = {
|
|
|
603
627
|
EMPTY_MERGED_INPUT_TYPE,
|
|
604
628
|
ENUM_VALUE_MISMATCH,
|
|
605
629
|
EMPTY_MERGED_ENUM_TYPE,
|
|
630
|
+
SHAREABLE_HAS_MISMATCHED_RUNTIME_TYPES,
|
|
606
631
|
SATISFIABILITY_ERROR,
|
|
607
632
|
OVERRIDE_COLLISION_WITH_ANOTHER_DIRECTIVE,
|
|
608
633
|
OVERRIDE_FROM_SELF_ERROR,
|
|
609
634
|
OVERRIDE_SOURCE_HAS_OVERRIDE,
|
|
635
|
+
OVERRIDE_ON_INTERFACE,
|
|
610
636
|
UNSUPPORTED_FEATURE,
|
|
611
637
|
INVALID_FEDERATION_SUPERGRAPH,
|
|
612
638
|
DOWNSTREAM_SERVICE_ERROR,
|
|
@@ -614,6 +640,9 @@ export const ERRORS = {
|
|
|
614
640
|
PROVIDES_HAS_DIRECTIVE_IN_FIELDS_ARGS,
|
|
615
641
|
REQUIRES_HAS_DIRECTIVE_IN_FIELDS_ARGS,
|
|
616
642
|
DIRECTIVE_COMPOSITION_ERROR,
|
|
643
|
+
INTERFACE_OBJECT_USAGE_ERROR,
|
|
644
|
+
INTERFACE_KEY_NOT_ON_IMPLEMENTATION,
|
|
645
|
+
INTERFACE_KEY_MISSING_IMPLEMENTATION_TYPE,
|
|
617
646
|
};
|
|
618
647
|
|
|
619
648
|
const codeDefByCode = Object.values(ERRORS).reduce((obj: {[code: string]: ErrorCodeDefinition}, codeDef: ErrorCodeDefinition) => { obj[codeDef.code] = codeDef; return obj; }, {});
|
|
@@ -638,16 +667,18 @@ export const REMOVED_ERRORS = [
|
|
|
638
667
|
['REQUIRES_FIELDS_MISSING_ON_BASE', 'Fields in @requires can now be from any subgraph.'],
|
|
639
668
|
['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
669
|
|
|
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
|
|
670
|
+
['DUPLICATE_SCALAR_DEFINITION', 'As duplicate scalar definitions is invalid GraphQL, this will now be an error with code `INVALID_GRAPHQL`.'],
|
|
671
|
+
['DUPLICATE_ENUM_DEFINITION', 'As duplicate enum definitions is invalid GraphQL, this will now be an error with code `INVALID_GRAPHQL`.'],
|
|
672
|
+
['DUPLICATE_ENUM_VALUE', 'As duplicate enum values is invalid GraphQL, this will now be an error with code `INVALID_GRAPHQL`.'],
|
|
644
673
|
|
|
645
|
-
['ENUM_MISMATCH', 'Subgraph definitions for an enum are now merged by composition'],
|
|
674
|
+
['ENUM_MISMATCH', 'Subgraph definitions for an enum are now merged by composition.'],
|
|
646
675
|
['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'],
|
|
676
|
+
['VALUE_TYPE_UNION_TYPES_MISMATCH', 'Subgraph definitions for an union are now merged by composition.'],
|
|
677
|
+
['PROVIDES_FIELDS_SELECT_INVALID_TYPE', '@provides can now be used on field of interface, union and list types.'],
|
|
678
|
+
['RESERVED_FIELD_USED', 'This error was previously not correctly enforced: the _service and _entities, if present, were overridden; this is still the case.'],
|
|
679
|
+
|
|
680
|
+
['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.'],
|
|
681
|
+
['REQUIRES_FIELDS_HAS_ARGS', 'Since federation 2.1.1, using fields with arguments in a @requires is fully supported.'],
|
|
650
682
|
|
|
651
|
-
['
|
|
652
|
-
['REQUIRES_FIELDS_HAS_ARGS', 'Since federation 2.1.1, using fields with arguments in a @requires is fully supported'],
|
|
683
|
+
['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
684
|
];
|
|
@@ -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
|
}
|