@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.
Files changed (50) hide show
  1. package/dist/definitions.d.ts +2 -0
  2. package/dist/definitions.d.ts.map +1 -1
  3. package/dist/definitions.js +14 -2
  4. package/dist/definitions.js.map +1 -1
  5. package/dist/error.d.ts +3 -1
  6. package/dist/error.d.ts.map +1 -1
  7. package/dist/error.js +17 -12
  8. package/dist/error.js.map +1 -1
  9. package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
  10. package/dist/extractSubgraphsFromSupergraph.js +31 -5
  11. package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
  12. package/dist/federation.d.ts +14 -6
  13. package/dist/federation.d.ts.map +1 -1
  14. package/dist/federation.js +141 -62
  15. package/dist/federation.js.map +1 -1
  16. package/dist/federationSpec.d.ts +2 -1
  17. package/dist/federationSpec.d.ts.map +1 -1
  18. package/dist/federationSpec.js +9 -1
  19. package/dist/federationSpec.js.map +1 -1
  20. package/dist/joinSpec.d.ts +9 -1
  21. package/dist/joinSpec.d.ts.map +1 -1
  22. package/dist/joinSpec.js +24 -2
  23. package/dist/joinSpec.js.map +1 -1
  24. package/dist/operations.d.ts +12 -0
  25. package/dist/operations.d.ts.map +1 -1
  26. package/dist/operations.js +59 -5
  27. package/dist/operations.js.map +1 -1
  28. package/dist/schemaUpgrader.d.ts.map +1 -1
  29. package/dist/schemaUpgrader.js +9 -0
  30. package/dist/schemaUpgrader.js.map +1 -1
  31. package/dist/supergraphs.d.ts.map +1 -1
  32. package/dist/supergraphs.js +1 -0
  33. package/dist/supergraphs.js.map +1 -1
  34. package/package.json +2 -2
  35. package/src/__tests__/extractSubgraphsFromSupergraph.test.ts +0 -1
  36. package/src/__tests__/schemaUpgrader.test.ts +43 -0
  37. package/src/__tests__/subgraphValidation.test.ts +126 -57
  38. package/src/__tests__/testUtils.ts +28 -0
  39. package/src/__tests__/values.test.ts +1 -1
  40. package/src/definitions.ts +12 -0
  41. package/src/error.ts +34 -16
  42. package/src/extractSubgraphsFromSupergraph.ts +40 -9
  43. package/src/federation.ts +178 -73
  44. package/src/federationSpec.ts +10 -1
  45. package/src/joinSpec.ts +40 -11
  46. package/src/operations.ts +76 -8
  47. package/src/schemaUpgrader.ts +13 -0
  48. package/src/supergraphs.ts +1 -0
  49. package/tsconfig.test.tsbuildinfo +1 -1
  50. 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, errorCauses } from '..';
4
- import { asFed2SubgraphDocument, buildSubgraph } from "../federation"
3
+ import { Subgraph } from '..';
4
+ import { buildSubgraph } from "../federation"
5
5
  import { defaultPrintOptions, printSchema } from '../print';
6
- import './matchers';
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 {
@@ -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
- ['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'],
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: string }>) => graphEnumNameToSubgraphName.get(application.arguments().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: string, requires?: string, provides?: string }>[] = f.appliedDirectivesOf(fieldDirective);
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
- subgraphType = schema.addType(newNamedType(type.kind, type.name));
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
- subgraphEnum.addValue(value.name);
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
- for (const memberType of type.types()) {
413
- const subgraphType = subgraph.schema.type(memberType.name);
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
  }