@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.
Files changed (63) 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 +5 -1
  6. package/dist/error.d.ts.map +1 -1
  7. package/dist/error.js +21 -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 +11 -2
  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 +11 -0
  25. package/dist/operations.d.ts.map +1 -1
  26. package/dist/operations.js +46 -1
  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 +2 -0
  33. package/dist/supergraphs.js.map +1 -1
  34. package/dist/tagSpec.d.ts +1 -0
  35. package/dist/tagSpec.d.ts.map +1 -1
  36. package/dist/tagSpec.js +9 -1
  37. package/dist/tagSpec.js.map +1 -1
  38. package/dist/types.d.ts.map +1 -1
  39. package/dist/types.js +3 -6
  40. package/dist/types.js.map +1 -1
  41. package/dist/utils.js +1 -1
  42. package/dist/utils.js.map +1 -1
  43. package/package.json +2 -3
  44. package/src/__tests__/extractSubgraphsFromSupergraph.test.ts +0 -1
  45. package/src/__tests__/matchers/toMatchString.ts +5 -1
  46. package/src/__tests__/schemaUpgrader.test.ts +43 -0
  47. package/src/__tests__/subgraphValidation.test.ts +126 -57
  48. package/src/__tests__/testUtils.ts +28 -0
  49. package/src/__tests__/values.test.ts +1 -1
  50. package/src/definitions.ts +12 -0
  51. package/src/error.ts +47 -16
  52. package/src/extractSubgraphsFromSupergraph.ts +40 -9
  53. package/src/federation.ts +178 -73
  54. package/src/federationSpec.ts +16 -2
  55. package/src/joinSpec.ts +40 -11
  56. package/src/operations.ts +59 -1
  57. package/src/schemaUpgrader.ts +13 -0
  58. package/src/supergraphs.ts +2 -0
  59. package/src/tagSpec.ts +10 -1
  60. package/src/types.ts +12 -15
  61. package/src/utils.ts +1 -1
  62. package/tsconfig.test.tsbuildinfo +1 -1
  63. 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, 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.'
@@ -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
- ['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'],
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: 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
  }