@apollo/federation-internals 2.0.0-preview.7 → 2.0.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 (109) hide show
  1. package/CHANGELOG.md +32 -3
  2. package/dist/buildSchema.d.ts.map +1 -1
  3. package/dist/buildSchema.js +51 -41
  4. package/dist/buildSchema.js.map +1 -1
  5. package/dist/coreSpec.d.ts +16 -8
  6. package/dist/coreSpec.d.ts.map +1 -1
  7. package/dist/coreSpec.js +205 -53
  8. package/dist/coreSpec.js.map +1 -1
  9. package/dist/definitions.d.ts +28 -11
  10. package/dist/definitions.d.ts.map +1 -1
  11. package/dist/definitions.js +185 -67
  12. package/dist/definitions.js.map +1 -1
  13. package/dist/directiveAndTypeSpecification.d.ts +11 -1
  14. package/dist/directiveAndTypeSpecification.d.ts.map +1 -1
  15. package/dist/directiveAndTypeSpecification.js +77 -20
  16. package/dist/directiveAndTypeSpecification.js.map +1 -1
  17. package/dist/error.d.ts +17 -0
  18. package/dist/error.d.ts.map +1 -1
  19. package/dist/error.js +54 -2
  20. package/dist/error.js.map +1 -1
  21. package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
  22. package/dist/extractSubgraphsFromSupergraph.js +7 -1
  23. package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
  24. package/dist/federation.d.ts +22 -5
  25. package/dist/federation.d.ts.map +1 -1
  26. package/dist/federation.js +143 -86
  27. package/dist/federation.js.map +1 -1
  28. package/dist/federationSpec.d.ts +6 -2
  29. package/dist/federationSpec.d.ts.map +1 -1
  30. package/dist/federationSpec.js +47 -22
  31. package/dist/federationSpec.js.map +1 -1
  32. package/dist/inaccessibleSpec.d.ts +10 -2
  33. package/dist/inaccessibleSpec.d.ts.map +1 -1
  34. package/dist/inaccessibleSpec.js +634 -16
  35. package/dist/inaccessibleSpec.js.map +1 -1
  36. package/dist/index.d.ts +2 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +2 -0
  39. package/dist/index.js.map +1 -1
  40. package/dist/introspection.d.ts.map +1 -1
  41. package/dist/introspection.js +8 -3
  42. package/dist/introspection.js.map +1 -1
  43. package/dist/joinSpec.d.ts +5 -1
  44. package/dist/joinSpec.d.ts.map +1 -1
  45. package/dist/joinSpec.js +21 -0
  46. package/dist/joinSpec.js.map +1 -1
  47. package/dist/knownCoreFeatures.d.ts +4 -0
  48. package/dist/knownCoreFeatures.d.ts.map +1 -0
  49. package/dist/knownCoreFeatures.js +16 -0
  50. package/dist/knownCoreFeatures.js.map +1 -0
  51. package/dist/operations.d.ts +1 -0
  52. package/dist/operations.d.ts.map +1 -1
  53. package/dist/operations.js +16 -1
  54. package/dist/operations.js.map +1 -1
  55. package/dist/{sharing.d.ts → precompute.d.ts} +1 -1
  56. package/dist/precompute.d.ts.map +1 -0
  57. package/dist/{sharing.js → precompute.js} +3 -3
  58. package/dist/precompute.js.map +1 -0
  59. package/dist/schemaUpgrader.d.ts.map +1 -1
  60. package/dist/schemaUpgrader.js +17 -7
  61. package/dist/schemaUpgrader.js.map +1 -1
  62. package/dist/suggestions.d.ts +1 -1
  63. package/dist/suggestions.d.ts.map +1 -1
  64. package/dist/suggestions.js.map +1 -1
  65. package/dist/supergraphs.d.ts.map +1 -1
  66. package/dist/supergraphs.js +2 -0
  67. package/dist/supergraphs.js.map +1 -1
  68. package/dist/tagSpec.d.ts +7 -2
  69. package/dist/tagSpec.d.ts.map +1 -1
  70. package/dist/tagSpec.js +35 -14
  71. package/dist/tagSpec.js.map +1 -1
  72. package/dist/validate.js +13 -7
  73. package/dist/validate.js.map +1 -1
  74. package/dist/values.d.ts +2 -2
  75. package/dist/values.d.ts.map +1 -1
  76. package/dist/values.js +13 -11
  77. package/dist/values.js.map +1 -1
  78. package/package.json +4 -4
  79. package/src/__tests__/coreSpec.test.ts +212 -0
  80. package/src/__tests__/definitions.test.ts +75 -0
  81. package/src/__tests__/removeInaccessibleElements.test.ts +2229 -137
  82. package/src/__tests__/schemaUpgrader.test.ts +3 -2
  83. package/src/__tests__/subgraphValidation.test.ts +419 -4
  84. package/src/__tests__/values.test.ts +315 -3
  85. package/src/buildSchema.ts +98 -51
  86. package/src/coreSpec.ts +277 -65
  87. package/src/definitions.ts +317 -92
  88. package/src/directiveAndTypeSpecification.ts +98 -21
  89. package/src/error.ts +119 -1
  90. package/src/extractSubgraphsFromSupergraph.ts +7 -1
  91. package/src/federation.ts +184 -102
  92. package/src/federationSpec.ts +56 -24
  93. package/src/inaccessibleSpec.ts +985 -39
  94. package/src/index.ts +2 -0
  95. package/src/introspection.ts +8 -3
  96. package/src/joinSpec.ts +33 -3
  97. package/src/knownCoreFeatures.ts +13 -0
  98. package/src/operations.ts +15 -0
  99. package/src/{sharing.ts → precompute.ts} +3 -6
  100. package/src/schemaUpgrader.ts +29 -13
  101. package/src/suggestions.ts +1 -1
  102. package/src/supergraphs.ts +2 -0
  103. package/src/tagSpec.ts +49 -16
  104. package/src/validate.ts +20 -9
  105. package/src/values.ts +39 -12
  106. package/tsconfig.test.tsbuildinfo +1 -1
  107. package/tsconfig.tsbuildinfo +1 -1
  108. package/dist/sharing.d.ts.map +0 -1
  109. package/dist/sharing.js.map +0 -1
package/src/federation.ts CHANGED
@@ -45,13 +45,15 @@ import { KnownTypeNamesInFederationRule } from "./validation/KnownTypeNamesInFed
45
45
  import { buildSchema, buildSchemaFromAST } from "./buildSchema";
46
46
  import { parseSelectionSet, selectionOfElement, SelectionSet } from './operations';
47
47
  import { TAG_VERSIONS } from "./tagSpec";
48
+ import { INACCESSIBLE_VERSIONS } from "./inaccessibleSpec";
48
49
  import {
49
50
  errorCodeDef,
50
51
  ErrorCodeDefinition,
51
52
  ERROR_CATEGORIES,
52
53
  ERRORS,
54
+ withModifiedErrorMessage,
53
55
  } from "./error";
54
- import { computeShareables } from "./sharing";
56
+ import { computeShareables } from "./precompute";
55
57
  import {
56
58
  CoreSpecDefinition,
57
59
  FeatureVersion,
@@ -70,14 +72,18 @@ import {
70
72
  externalDirectiveSpec,
71
73
  extendsDirectiveSpec,
72
74
  shareableDirectiveSpec,
73
- tagDirectiveSpec,
75
+ overrideDirectiveSpec,
74
76
  FEDERATION2_SPEC_DIRECTIVES,
77
+ ALL_FEDERATION_DIRECTIVES_DEFAULT_NAMES,
78
+ FEDERATION2_ONLY_SPEC_DIRECTIVES,
75
79
  } from "./federationSpec";
76
80
  import { defaultPrintOptions, PrintOptions as PrintOptions, printSchema } from "./print";
77
81
  import { createObjectTypeSpecification, createScalarTypeSpecification, createUnionTypeSpecification } from "./directiveAndTypeSpecification";
82
+ import { didYouMean, suggestionList } from "./suggestions";
78
83
 
79
84
  const linkSpec = LINK_VERSIONS.latest();
80
85
  const tagSpec = TAG_VERSIONS.latest();
86
+ const inaccessibleSpec = INACCESSIBLE_VERSIONS.latest();
81
87
  const federationSpec = FEDERATION_VERSIONS.latest();
82
88
 
83
89
  // We don't let user use this as a subgraph name. That allows us to use it in `query graphs` to name the source of roots
@@ -86,6 +92,8 @@ const federationSpec = FEDERATION_VERSIONS.latest();
86
92
  // disallowing it feels like more a good thing than a real restriction).
87
93
  export const FEDERATION_RESERVED_SUBGRAPH_NAME = '_';
88
94
 
95
+ export const FEDERATION_UNNAMED_SUBGRAPH_NAME = '<unnamed>';
96
+
89
97
  const FEDERATION_OMITTED_VALIDATION_RULES = [
90
98
  // We allow subgraphs to declare an extension even if the subgraph itself doesn't have a corresponding definition.
91
99
  // The implication being that the definition is in another subgraph.
@@ -102,6 +110,7 @@ const FEDERATION_SPECIFIC_VALIDATION_RULES = [
102
110
 
103
111
  const FEDERATION_VALIDATION_RULES = specifiedSDLRules.filter(rule => !FEDERATION_OMITTED_VALIDATION_RULES.includes(rule)).concat(FEDERATION_SPECIFIC_VALIDATION_RULES);
104
112
 
113
+
105
114
  // Returns a list of the coordinate of all the fields in the selection that are marked external.
106
115
  function validateFieldSetSelections(
107
116
  directiveName: string,
@@ -257,53 +266,49 @@ function validateAllFieldSet<TParent extends SchemaElement<any, any>>(
257
266
  }
258
267
  }
259
268
 
260
- export function collectUsedExternalFieldsCoordinates(metadata: FederationMetadata): Set<string> {
261
- const usedExternalCoordinates = new Set<string>();
269
+ export function collectUsedFields(metadata: FederationMetadata): Set<FieldDefinition<CompositeType>> {
270
+ const usedFields = new Set<FieldDefinition<CompositeType>>();
262
271
 
263
272
  // Collects all external fields used by a key, requires or provides
264
- collectUsedExternaFieldsForDirective<CompositeType>(
265
- metadata,
273
+ collectUsedFieldsForDirective<CompositeType>(
266
274
  metadata.keyDirective(),
267
275
  type => type,
268
- usedExternalCoordinates,
276
+ usedFields,
269
277
  );
270
- collectUsedExternaFieldsForDirective<FieldDefinition<CompositeType>>(
271
- metadata,
278
+ collectUsedFieldsForDirective<FieldDefinition<CompositeType>>(
272
279
  metadata.requiresDirective(),
273
280
  field => field.parent!,
274
- usedExternalCoordinates,
281
+ usedFields,
275
282
  );
276
- collectUsedExternaFieldsForDirective<FieldDefinition<CompositeType>>(
277
- metadata,
283
+ collectUsedFieldsForDirective<FieldDefinition<CompositeType>>(
278
284
  metadata.providesDirective(),
279
285
  field => {
280
286
  const type = baseType(field.type!);
281
287
  return isCompositeType(type) ? type : undefined;
282
288
  },
283
- usedExternalCoordinates,
289
+ usedFields,
284
290
  );
285
291
 
286
- // Collects all external fields used to satisfy an interface constraint
287
- for (const itfType of metadata.schema.types<InterfaceType>('InterfaceType')) {
292
+ // Collects all fields used to satisfy an interface constraint
293
+ for (const itfType of metadata.schema.interfaceTypes()) {
288
294
  const runtimeTypes = itfType.possibleRuntimeTypes();
289
295
  for (const field of itfType.fields()) {
290
296
  for (const runtimeType of runtimeTypes) {
291
297
  const implemField = runtimeType.field(field.name);
292
- if (implemField && metadata.isFieldExternal(implemField)) {
293
- usedExternalCoordinates.add(implemField.coordinate);
298
+ if (implemField) {
299
+ usedFields.add(implemField);
294
300
  }
295
301
  }
296
302
  }
297
303
  }
298
304
 
299
- return usedExternalCoordinates;
305
+ return usedFields;
300
306
  }
301
307
 
302
- function collectUsedExternaFieldsForDirective<TParent extends SchemaElement<any, any>>(
303
- metadata: FederationMetadata,
308
+ function collectUsedFieldsForDirective<TParent extends SchemaElement<any, any>>(
304
309
  definition: DirectiveDefinition<{fields: any}>,
305
310
  targetTypeExtractor: (element: TParent) => CompositeType | undefined,
306
- usedExternalCoordinates: Set<string>
311
+ usedFieldDefs: Set<FieldDefinition<CompositeType>>
307
312
  ) {
308
313
  for (const application of definition.applications()) {
309
314
  const type = targetTypeExtractor(application.parent! as TParent);
@@ -321,8 +326,7 @@ function collectUsedExternaFieldsForDirective<TParent extends SchemaElement<any,
321
326
  directive: application as Directive<any, {fields: any}>,
322
327
  includeInterfaceFieldsImplementations: true,
323
328
  validate: false,
324
- }).filter((field) => metadata.isFieldExternal(field))
325
- .forEach((field) => usedExternalCoordinates.add(field.coordinate));
329
+ }).forEach((field) => usedFieldDefs.add(field));
326
330
  }
327
331
  }
328
332
 
@@ -331,29 +335,26 @@ function collectUsedExternaFieldsForDirective<TParent extends SchemaElement<any,
331
335
  * interface implementation. Otherwise, the field declaration is somewhat useless.
332
336
  */
333
337
  function validateAllExternalFieldsUsed(metadata: FederationMetadata, errorCollector: GraphQLError[]): void {
334
- const allUsedExternals = collectUsedExternalFieldsCoordinates(metadata);
335
338
  for (const type of metadata.schema.types()) {
336
339
  if (!isObjectType(type) && !isInterfaceType(type)) {
337
340
  continue;
338
341
  }
339
342
  for (const field of type.fields()) {
340
- if (!metadata.isFieldExternal(field) || allUsedExternals.has(field.coordinate)) {
343
+ if (!metadata.isFieldExternal(field) || metadata.isFieldUsed(field)) {
341
344
  continue;
342
345
  }
343
346
 
344
- if (!isFieldSatisfyingInterface(field)) {
345
- errorCollector.push(ERRORS.EXTERNAL_UNUSED.err({
346
- message: `Field "${field.coordinate}" is marked @external but is not used in any federation directive (@key, @provides, @requires) or to satisfy an interface;`
347
- + ' the field declaration has no use and should be removed (or the field should not be @external).',
348
- nodes: field.sourceAST,
349
- }));
350
- }
347
+ errorCollector.push(ERRORS.EXTERNAL_UNUSED.err({
348
+ message: `Field "${field.coordinate}" is marked @external but is not used in any federation directive (@key, @provides, @requires) or to satisfy an interface;`
349
+ + ' the field declaration has no use and should be removed (or the field should not be @external).',
350
+ nodes: field.sourceAST,
351
+ }));
351
352
  }
352
353
  }
353
354
  }
354
355
 
355
356
  function validateNoExternalOnInterfaceFields(metadata: FederationMetadata, errorCollector: GraphQLError[]) {
356
- for (const itf of metadata.schema.types<InterfaceType>('InterfaceType')) {
357
+ for (const itf of metadata.schema.interfaceTypes()) {
357
358
  for (const field of itf.fields()) {
358
359
  if (metadata.isFieldExternal(field)) {
359
360
  errorCollector.push(ERRORS.EXTERNAL_ON_INTERFACE.err({
@@ -365,10 +366,6 @@ function validateNoExternalOnInterfaceFields(metadata: FederationMetadata, error
365
366
  }
366
367
  }
367
368
 
368
- function isFieldSatisfyingInterface(field: FieldDefinition<ObjectType | InterfaceType>): boolean {
369
- return field.parent.interfaces().some(itf => itf.field(field.name));
370
- }
371
-
372
369
  /**
373
370
  * Register errors when, for an interface field, some of the implementations of that field are @external
374
371
  * _and_ not all of those field implementation have the same type (which otherwise allowed because field
@@ -379,7 +376,7 @@ function isFieldSatisfyingInterface(field: FieldDefinition<ObjectType | Interfac
379
376
  */
380
377
  function validateInterfaceRuntimeImplementationFieldsTypes(
381
378
  itf: InterfaceType,
382
- metadata: FederationMetadata,
379
+ metadata: FederationMetadata,
383
380
  errorCollector: GraphQLError[],
384
381
  ): void {
385
382
  const requiresDirective = federationMetadata(itf.schema())?.requiresDirective();
@@ -417,19 +414,10 @@ function formatFieldsToReturnType([type, implems]: [string, FieldDefinition<Obje
417
414
  return `${joinStrings(implems.map(printFieldCoordinate))} ${implems.length == 1 ? 'has' : 'have'} type "${type}"`;
418
415
  }
419
416
 
420
- function checkIfFed2Schema(schema: Schema): boolean {
421
- const core = schema.coreFeatures;
422
- if (!core) {
423
- return false
424
- }
425
-
426
- const federationFeature = core.getByIdentity(federationSpec.identity);
427
- return !!federationFeature && federationFeature.url.version.satisfies(new FeatureVersion(2, 0));
428
- }
429
-
430
417
  export class FederationMetadata {
431
418
  private _externalTester?: ExternalTester;
432
419
  private _sharingPredicate?: (field: FieldDefinition<CompositeType>) => boolean;
420
+ private _fieldUsedPredicate?: (field: FieldDefinition<CompositeType>) => boolean;
433
421
  private _isFed2Schema?: boolean;
434
422
 
435
423
  constructor(readonly schema: Schema) {
@@ -439,15 +427,21 @@ export class FederationMetadata {
439
427
  this._externalTester = undefined;
440
428
  this._sharingPredicate = undefined;
441
429
  this._isFed2Schema = undefined;
430
+ this._fieldUsedPredicate = undefined;
442
431
  }
443
432
 
444
433
  isFed2Schema(): boolean {
445
434
  if (!this._isFed2Schema) {
446
- this._isFed2Schema = checkIfFed2Schema(this.schema);
435
+ const feature = this.federationFeature();
436
+ this._isFed2Schema = !!feature && feature.url.version.satisfies(new FeatureVersion(2, 0))
447
437
  }
448
438
  return this._isFed2Schema;
449
439
  }
450
440
 
441
+ federationFeature(): CoreFeature | undefined {
442
+ return this.schema.coreFeatures?.getByIdentity(federationSpec.identity);
443
+ }
444
+
451
445
  private externalTester(): ExternalTester {
452
446
  if (!this._externalTester) {
453
447
  this._externalTester = new ExternalTester(this.schema);
@@ -462,6 +456,18 @@ export class FederationMetadata {
462
456
  return this._sharingPredicate;
463
457
  }
464
458
 
459
+ private fieldUsedPredicate(): (field: FieldDefinition<CompositeType>) => boolean {
460
+ if (!this._fieldUsedPredicate) {
461
+ const usedFields = collectUsedFields(this);
462
+ this._fieldUsedPredicate = (field: FieldDefinition<CompositeType>) => !!usedFields.has(field);
463
+ }
464
+ return this._fieldUsedPredicate;
465
+ }
466
+
467
+ isFieldUsed(field: FieldDefinition<CompositeType>): boolean {
468
+ return this.fieldUsedPredicate()(field);
469
+ }
470
+
465
471
  isFieldExternal(field: FieldDefinition<any> | InputFieldDefinition) {
466
472
  return this.externalTester().isExternal(field);
467
473
  }
@@ -533,11 +539,15 @@ export class FederationMetadata {
533
539
  return this.getFederationDirective(keyDirectiveSpec.name);
534
540
  }
535
541
 
542
+ overrideDirective(): DirectiveDefinition<{from: string}> {
543
+ return this.getFederationDirective(overrideDirectiveSpec.name);
544
+ }
545
+
536
546
  extendsDirective(): DirectiveDefinition<Record<string, never>> {
537
547
  return this.getFederationDirective(extendsDirectiveSpec.name);
538
548
  }
539
549
 
540
- externalDirective(): DirectiveDefinition<Record<string, never>> {
550
+ externalDirective(): DirectiveDefinition<{reason: string}> {
541
551
  return this.getFederationDirective(externalDirectiveSpec.name);
542
552
  }
543
553
 
@@ -554,11 +564,17 @@ export class FederationMetadata {
554
564
  }
555
565
 
556
566
  tagDirective(): DirectiveDefinition<{name: string}> {
557
- return this.getFederationDirective(tagDirectiveSpec.name);
567
+ return this.getFederationDirective(tagSpec.tagDirectiveSpec.name);
568
+ }
569
+
570
+ inaccessibleDirective(): DirectiveDefinition<{}> {
571
+ return this.getFederationDirective(
572
+ inaccessibleSpec.inaccessibleDirectiveSpec.name
573
+ );
558
574
  }
559
575
 
560
576
  allFederationDirectives(): DirectiveDefinition[] {
561
- const baseDirectives = [
577
+ const baseDirectives: DirectiveDefinition[] = [
562
578
  this.keyDirective(),
563
579
  this.externalDirective(),
564
580
  this.requiresDirective(),
@@ -567,7 +583,7 @@ export class FederationMetadata {
567
583
  this.extendsDirective(),
568
584
  ];
569
585
  return this.isFed2Schema()
570
- ? baseDirectives.concat(this.shareableDirective())
586
+ ? baseDirectives.concat(this.shareableDirective(), this.inaccessibleDirective(), this.overrideDirective())
571
587
  : baseDirectives;
572
588
  }
573
589
 
@@ -603,6 +619,10 @@ export class FederationMetadata {
603
619
  }
604
620
 
605
621
  export class FederationBlueprint extends SchemaBlueprint {
622
+ constructor(private readonly withRootTypeRenaming: boolean) {
623
+ super();
624
+ }
625
+
606
626
  onAddedCoreFeature(schema: Schema, feature: CoreFeature) {
607
627
  super.onAddedCoreFeature(schema, feature);
608
628
  if (feature.url.identity === federationIdentity) {
@@ -613,19 +633,21 @@ export class FederationBlueprint extends SchemaBlueprint {
613
633
  }
614
634
  }
615
635
 
616
- onMissingDirectiveDefinition(schema: Schema, name: string): DirectiveDefinition | undefined {
636
+ onMissingDirectiveDefinition(schema: Schema, name: string, args?: {[key: string]: any}): DirectiveDefinition | GraphQLError[] | undefined {
617
637
  if (name === linkDirectiveDefaultName) {
618
- linkSpec.addToSchema(schema);
619
- return schema.directive(name);
638
+ const url = args && (args['url'] as string | undefined);
639
+ const as = url && url.startsWith(linkSpec.identity) ? (args['as'] as string | undefined) : undefined;
640
+ const errors = linkSpec.addDefinitionsToSchema(schema, as);
641
+ return errors.length > 0 ? errors : schema.directive(name);
620
642
  }
621
- return super.onMissingDirectiveDefinition(schema, name);
643
+ return super.onMissingDirectiveDefinition(schema, name, args);
622
644
  }
623
645
 
624
646
  ignoreParsedField(type: NamedType, fieldName: string): boolean {
625
647
  // Historically, federation 1 has accepted invalid schema, including some where the Query type included
626
648
  // the definition of `_entities` (so `_entities(representations: [_Any!]!): [_Entity]!`) but _without_
627
649
  // defining the `_Any` or `_Entity` type. So while we want to be stricter for fed2 (so this kind of
628
- // really weird case can be fixed), we want fed2 to accept as much fed1 schema as possible.
650
+ // really weird case can be fixed), we want fed2 to accept as much fed1 schema as possible.
629
651
  //
630
652
  // So, to avoid this problem, we ignore the _entities and _service fields if we parse them from
631
653
  // a fed1 input schema. Those will be added back anyway (along with the proper types) post-parsing.
@@ -643,8 +665,8 @@ export class FederationBlueprint extends SchemaBlueprint {
643
665
  }
644
666
  }
645
667
 
646
- onDirectiveDefinitionAndSchemaParsed(schema: Schema) {
647
- completeSubgraphSchema(schema);
668
+ onDirectiveDefinitionAndSchemaParsed(schema: Schema): GraphQLError[] {
669
+ return completeSubgraphSchema(schema);
648
670
  }
649
671
 
650
672
  onInvalidation(schema: Schema) {
@@ -659,22 +681,24 @@ export class FederationBlueprint extends SchemaBlueprint {
659
681
 
660
682
  // We rename all root type to their default names (we do here rather than in `prepareValidation` because
661
683
  // that can actually fail).
662
- for (const k of allSchemaRootKinds) {
663
- const type = schema.schemaDefinition.root(k)?.type;
664
- const defaultName = defaultRootName(k);
665
- if (type && type.name !== defaultName) {
666
- // We first ensure there is no other type using the default root name. If there is, this is a
667
- // composition error.
668
- const existing = schema.type(defaultName);
669
- if (existing) {
670
- errors.push(ERROR_CATEGORIES.ROOT_TYPE_USED.get(k).err({
671
- message: `The schema has a type named "${defaultName}" but it is not set as the ${k} root type ("${type.name}" is instead): `
684
+ if (this.withRootTypeRenaming) {
685
+ for (const k of allSchemaRootKinds) {
686
+ const type = schema.schemaDefinition.root(k)?.type;
687
+ const defaultName = defaultRootName(k);
688
+ if (type && type.name !== defaultName) {
689
+ // We first ensure there is no other type using the default root name. If there is, this is a
690
+ // composition error.
691
+ const existing = schema.type(defaultName);
692
+ if (existing) {
693
+ errors.push(ERROR_CATEGORIES.ROOT_TYPE_USED.get(k).err({
694
+ message: `The schema has a type named "${defaultName}" but it is not set as the ${k} root type ("${type.name}" is instead): `
672
695
  + 'this is not supported by federation. '
673
696
  + 'If a root type does not use its default name, there should be no other type with that default name.',
674
- nodes: sourceASTs(type, existing),
675
- }));
697
+ nodes: sourceASTs(type, existing),
698
+ }));
699
+ }
700
+ type.rename(defaultName);
676
701
  }
677
- type.rename(defaultName);
678
702
  }
679
703
  }
680
704
 
@@ -759,7 +783,7 @@ export class FederationBlueprint extends SchemaBlueprint {
759
783
  }
760
784
  }
761
785
 
762
- for (const itf of schema.types<InterfaceType>('InterfaceType')) {
786
+ for (const itf of schema.interfaceTypes()) {
763
787
  validateInterfaceRuntimeImplementationFieldsTypes(itf, metadata, errors);
764
788
  }
765
789
 
@@ -769,9 +793,52 @@ export class FederationBlueprint extends SchemaBlueprint {
769
793
  validationRules(): readonly SDLValidationRule[] {
770
794
  return FEDERATION_VALIDATION_RULES;
771
795
  }
772
- }
773
796
 
774
- const federationBlueprint = new FederationBlueprint();
797
+ onUnknownDirectiveValidationError(schema: Schema, unknownDirectiveName: string, error: GraphQLError): GraphQLError {
798
+ const metadata = federationMetadata(schema);
799
+ assert(metadata, `This method should only have been called on a subgraph schema`)
800
+ if (ALL_FEDERATION_DIRECTIVES_DEFAULT_NAMES.includes(unknownDirectiveName)) {
801
+ // The directive name is "unknown" but it is a default federation directive name. So it means one of a few things
802
+ // happened:
803
+ // 1. it's a fed1 schema but the directive is a fed2 only one (only possible case for fed1 schema).
804
+ // 2. the directive has not been imported at all (so needs to be prefixed for it to work).
805
+ // 3. the directive has an `import`, but it's been aliased to another name.
806
+ if (metadata.isFed2Schema()) {
807
+ const federationFeature = metadata.federationFeature();
808
+ assert(federationFeature, 'Fed2 subgraph _must_ link to the federation feature')
809
+ const directiveNameInSchema = federationFeature.directiveNameInSchema(unknownDirectiveName);
810
+ if (directiveNameInSchema.startsWith(federationFeature.nameInSchema + '__')) {
811
+ // There is no import for that directive
812
+ return withModifiedErrorMessage(
813
+ error,
814
+ `${error.message} If you meant the "@${unknownDirectiveName}" federation directive, you should use fully-qualified name "@${directiveNameInSchema}" or add "@${unknownDirectiveName}" to the \`import\` argument of the @link to the federation specification.`
815
+ );
816
+ } else {
817
+ // There's an import, but it's renamed
818
+ return withModifiedErrorMessage(
819
+ error,
820
+ `${error.message} If you meant the "@${unknownDirectiveName}" federation directive, you should use "@${directiveNameInSchema}" as it is imported under that name in the @link to the federation specification of this schema.`
821
+ );
822
+ }
823
+ } else {
824
+ return withModifiedErrorMessage(
825
+ error,
826
+ `${error.message} If you meant the "@${unknownDirectiveName}" federation 2 directive, note that this schema is a federation 1 schema. To be a federation 2 schema, it needs to @link to the federation specifcation v2.`
827
+ );
828
+ }
829
+ } else if (!metadata.isFed2Schema()) {
830
+ // We could get here in the case where a fed1 schema has tried to use a fed2 directive but mispelled it.
831
+ const suggestions = suggestionList(unknownDirectiveName, FEDERATION2_ONLY_SPEC_DIRECTIVES.map((spec) => spec.name));
832
+ if (suggestions.length > 0) {
833
+ return withModifiedErrorMessage(
834
+ error,
835
+ `${error.message}${didYouMean(suggestions.map((s) => '@' + s))} If so, note that ${suggestions.length === 1 ? 'it is a federation 2 directive' : 'they are federation 2 directives'} but this schema is a federation 1 one. To be a federation 2 schema, it needs to @link to the federation specifcation v2.`
836
+ );
837
+ }
838
+ }
839
+ return error;
840
+ }
841
+ }
775
842
 
776
843
  function findUnusedNamedForLinkDirective(schema: Schema): string | undefined {
777
844
  if (!schema.directive(linkSpec.url.name)) {
@@ -800,7 +867,10 @@ export function setSchemaAsFed2Subgraph(schema: Schema) {
800
867
  assert(spec.url.version.satisfies(linkSpec.version), `Fed2 schema must use @link with version >= 1.0, but schema uses ${spec.url}`);
801
868
  } else {
802
869
  const alias = findUnusedNamedForLinkDirective(schema);
803
- linkSpec.addToSchema(schema, alias);
870
+ const errors = linkSpec.addToSchema(schema, alias);
871
+ if (errors.length > 0) {
872
+ throw ErrGraphQLValidationFailed(errors);
873
+ }
804
874
  spec = linkSpec;
805
875
  core = schema.coreFeatures;
806
876
  assert(core, 'Schema should now be a core schema');
@@ -814,9 +884,16 @@ export function setSchemaAsFed2Subgraph(schema: Schema) {
814
884
  import: FEDERATION2_SPEC_DIRECTIVES.map((spec) => `@${spec.name}`),
815
885
  }
816
886
  );
817
- completeSubgraphSchema(schema);
887
+ const errors = completeSubgraphSchema(schema);
888
+ if (errors.length > 0) {
889
+ throw ErrGraphQLValidationFailed(errors);
890
+ }
818
891
  }
819
892
 
893
+ // This is the full @link declaration as added by `asFed2SubgraphDocument`. It's here primarily for uses by tests that print and match
894
+ // subgraph schema to avoid having to update 20+ tests every time we use a new directive or the order of import changes ...
895
+ export const FEDERATION2_LINK_WTH_FULL_IMPORTS = '@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override"])';
896
+
820
897
  export function asFed2SubgraphDocument(document: DocumentNode): DocumentNode {
821
898
  const fed2LinkExtension: SchemaExtensionNode = {
822
899
  kind: Kind.SCHEMA_EXTENSION,
@@ -878,10 +955,11 @@ export function isEntityType(type: NamedType): boolean {
878
955
  export function buildSubgraph(
879
956
  name: string,
880
957
  url: string,
881
- source: DocumentNode | string
958
+ source: DocumentNode | string,
959
+ withRootTypeRenaming: boolean = true,
882
960
  ): Subgraph {
883
961
  const buildOptions = {
884
- blueprint: federationBlueprint,
962
+ blueprint: new FederationBlueprint(withRootTypeRenaming),
885
963
  validate: false,
886
964
  };
887
965
  let subgraph: Subgraph;
@@ -891,7 +969,7 @@ export function buildSubgraph(
891
969
  : buildSchemaFromAST(source, buildOptions)
892
970
  subgraph = new Subgraph(name, url, schema);
893
971
  } catch (e) {
894
- if (e instanceof GraphQLError) {
972
+ if (e instanceof GraphQLError && name !== FEDERATION_UNNAMED_SUBGRAPH_NAME) {
895
973
  throw addSubgraphToError(e, name, ERRORS.INVALID_GRAPHQL);
896
974
  } else {
897
975
  throw e;
@@ -901,27 +979,30 @@ export function buildSubgraph(
901
979
  }
902
980
 
903
981
  export function newEmptyFederation2Schema(): Schema {
904
- const schema = new Schema(federationBlueprint);
982
+ const schema = new Schema(new FederationBlueprint(true));
905
983
  setSchemaAsFed2Subgraph(schema);
906
984
  return schema;
907
985
  }
908
986
 
909
- function completeSubgraphSchema(schema: Schema) {
987
+ function completeSubgraphSchema(schema: Schema): GraphQLError[] {
910
988
  const coreFeatures = schema.coreFeatures;
911
989
  if (coreFeatures) {
912
990
  const fedFeature = coreFeatures.getByIdentity(federationIdentity);
913
991
  if (fedFeature) {
914
- completeFed2SubgraphSchema(schema);
992
+ return completeFed2SubgraphSchema(schema);
915
993
  } else {
916
- completeFed1SubgraphSchema(schema);
994
+ return completeFed1SubgraphSchema(schema);
917
995
  }
918
996
  } else {
919
997
  const fedLink = schema.schemaDefinition.appliedDirectivesOf(linkDirectiveDefaultName).find(isFedSpecLinkDirective);
920
998
  if (fedLink) {
921
- linkSpec.addToSchema(schema);
922
- completeFed2SubgraphSchema(schema);
999
+ const errors = linkSpec.addToSchema(schema);
1000
+ if (errors.length > 0) {
1001
+ return errors;
1002
+ }
1003
+ return completeFed2SubgraphSchema(schema);
923
1004
  } else {
924
- completeFed1SubgraphSchema(schema);
1005
+ return completeFed1SubgraphSchema(schema);
925
1006
  }
926
1007
  }
927
1008
  }
@@ -931,15 +1012,16 @@ function isFedSpecLinkDirective(directive: Directive<SchemaDefinition>): directi
931
1012
  return directive.name === linkDirectiveDefaultName && args['url'] && (args['url'] as string).startsWith(federationIdentity);
932
1013
  }
933
1014
 
934
- function completeFed1SubgraphSchema(schema: Schema) {
935
- fieldSetTypeSpec.checkOrAdd(schema, '_' + fieldSetTypeSpec.name);
936
-
937
- keyDirectiveSpec.checkOrAdd(schema);
938
- requiresDirectiveSpec.checkOrAdd(schema);
939
- providesDirectiveSpec.checkOrAdd(schema);
940
- extendsDirectiveSpec.checkOrAdd(schema);
941
- externalDirectiveSpec.checkOrAdd(schema);
942
- tagDirectiveSpec.checkOrAdd(schema);
1015
+ function completeFed1SubgraphSchema(schema: Schema): GraphQLError[] {
1016
+ return [
1017
+ fieldSetTypeSpec.checkOrAdd(schema, '_' + fieldSetTypeSpec.name),
1018
+ keyDirectiveSpec.checkOrAdd(schema),
1019
+ requiresDirectiveSpec.checkOrAdd(schema),
1020
+ providesDirectiveSpec.checkOrAdd(schema),
1021
+ extendsDirectiveSpec.checkOrAdd(schema),
1022
+ externalDirectiveSpec.checkOrAdd(schema),
1023
+ tagSpec.tagDirectiveSpec.checkOrAdd(schema),
1024
+ ].flat();
943
1025
  }
944
1026
 
945
1027
  function completeFed2SubgraphSchema(schema: Schema) {
@@ -951,13 +1033,13 @@ function completeFed2SubgraphSchema(schema: Schema) {
951
1033
 
952
1034
  const spec = FEDERATION_VERSIONS.find(fedFeature.url.version);
953
1035
  if (!spec) {
954
- throw ERRORS.UNKNOWN_FEDERATION_LINK_VERSION.err({
1036
+ return [ERRORS.UNKNOWN_FEDERATION_LINK_VERSION.err({
955
1037
  message: `Invalid version ${fedFeature.url.version} for the federation feature in @link direction on schema`,
956
1038
  nodes: fedFeature.directive.sourceAST
957
- });
1039
+ })];
958
1040
  }
959
1041
 
960
- spec.addElementsToSchema(schema);
1042
+ return spec.addElementsToSchema(schema);
961
1043
  }
962
1044
 
963
1045
  export function parseFieldSetArgument({
@@ -1178,7 +1260,7 @@ export const serviceTypeSpec = createObjectTypeSpecification({
1178
1260
  export const entityTypeSpec = createUnionTypeSpecification({
1179
1261
  name: '_Entity',
1180
1262
  membersFct: (schema) => {
1181
- return schema.types<ObjectType>("ObjectType").filter(isEntityType).map((t) => t.name);
1263
+ return schema.objectTypes().filter(isEntityType).map((t) => t.name);
1182
1264
  },
1183
1265
  });
1184
1266
 
@@ -1239,7 +1321,7 @@ export class Subgraph {
1239
1321
  }
1240
1322
 
1241
1323
  if (!queryType.field(serviceFieldName)) {
1242
- queryType.addField(serviceFieldName, metadata.serviceType());
1324
+ queryType.addField(serviceFieldName, new NonNullType(metadata.serviceType()));
1243
1325
  }
1244
1326
  }
1245
1327