@apollo/federation-internals 2.9.0 → 2.9.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.
@@ -984,12 +984,28 @@ export class CoreFeature {
984
984
  }
985
985
 
986
986
  directiveNameInSchema(name: string): string {
987
- const elementImport = this.imports.find((i) => i.name.charAt(0) === '@' && i.name.slice(1) === name);
987
+ return CoreFeature.directiveNameInSchemaForCoreArguments(
988
+ this.url,
989
+ this.nameInSchema,
990
+ this.imports,
991
+ name,
992
+ );
993
+ }
994
+
995
+ static directiveNameInSchemaForCoreArguments(
996
+ specUrl: FeatureUrl,
997
+ specNameInSchema: string,
998
+ imports: CoreImport[],
999
+ directiveNameInSpec: string,
1000
+ ): string {
1001
+ const elementImport = imports.find((i) =>
1002
+ i.name.charAt(0) === '@' && i.name.slice(1) === directiveNameInSpec
1003
+ );
988
1004
  return elementImport
989
- ? (elementImport.as?.slice(1) ?? name)
990
- : (name === this.url.name
991
- ? this.nameInSchema
992
- : this.nameInSchema + '__' + name
1005
+ ? (elementImport.as?.slice(1) ?? directiveNameInSpec)
1006
+ : (directiveNameInSpec === specUrl.name
1007
+ ? specNameInSchema
1008
+ : specNameInSchema + '__' + directiveNameInSpec
993
1009
  );
994
1010
  }
995
1011
 
@@ -1064,7 +1080,7 @@ export class CoreFeatures {
1064
1080
  const feature = this.byAlias.get(splitted[0]);
1065
1081
  return feature ? {
1066
1082
  feature,
1067
- nameInFeature: splitted[1],
1083
+ nameInFeature: splitted.slice(1).join('__'),
1068
1084
  isImported: false,
1069
1085
  } : undefined;
1070
1086
  } else {
@@ -1076,7 +1092,7 @@ export class CoreFeatures {
1076
1092
  if ((as ?? name) === importName) {
1077
1093
  return {
1078
1094
  feature,
1079
- nameInFeature: name.slice(1),
1095
+ nameInFeature: isDirective ? name.slice(1) : name,
1080
1096
  isImported: true,
1081
1097
  };
1082
1098
  }
@@ -1088,8 +1104,8 @@ export class CoreFeatures {
1088
1104
  if (directFeature && isDirective) {
1089
1105
  return {
1090
1106
  feature: directFeature,
1091
- nameInFeature: directFeature.imports.find(imp => imp.as === `@${element.name}`)?.name.slice(1) ?? element.name,
1092
- isImported: true,
1107
+ nameInFeature: element.name,
1108
+ isImported: false,
1093
1109
  };
1094
1110
  }
1095
1111
 
@@ -40,7 +40,7 @@ import { parseSelectionSet } from "./operations";
40
40
  import fs from 'fs';
41
41
  import path from 'path';
42
42
  import { validateStringContainsBoolean } from "./utils";
43
- import { CONTEXT_VERSIONS, ContextSpecDefinition, DirectiveDefinition, FeatureUrl, FederationDirectiveName, SchemaElement, errorCauses, isFederationDirectiveDefinedInSchema, printErrors } from ".";
43
+ import { ContextSpecDefinition, CostSpecDefinition, SchemaElement, errorCauses, isFederationDirectiveDefinedInSchema, printErrors } from ".";
44
44
 
45
45
  function filteredTypes(
46
46
  supergraph: Schema,
@@ -194,7 +194,7 @@ function typesUsedInFederationDirective(fieldSet: string | undefined, parentType
194
194
  }
195
195
 
196
196
  export function extractSubgraphsFromSupergraph(supergraph: Schema, validateExtractedSubgraphs: boolean = true): [Subgraphs, Map<string, string>] {
197
- const [coreFeatures, joinSpec] = validateSupergraph(supergraph);
197
+ const [coreFeatures, joinSpec, contextSpec, costSpec] = validateSupergraph(supergraph);
198
198
  const isFed1 = joinSpec.version.equals(new FeatureVersion(0, 1));
199
199
  try {
200
200
  // We first collect the subgraphs (creating an empty schema that we'll populate next for each).
@@ -224,13 +224,13 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema, validateExtra
224
224
  }
225
225
 
226
226
  const types = filteredTypes(supergraph, joinSpec, coreFeatures.coreDefinition);
227
- const originalDirectiveNames = getApolloDirectiveNames(supergraph);
228
227
  const args: ExtractArguments = {
229
228
  supergraph,
230
229
  subgraphs,
231
230
  joinSpec,
231
+ contextSpec,
232
+ costSpec,
232
233
  filteredTypes: types,
233
- originalDirectiveNames,
234
234
  getSubgraph,
235
235
  getSubgraphEnumValue,
236
236
  };
@@ -293,8 +293,9 @@ type ExtractArguments = {
293
293
  supergraph: Schema,
294
294
  subgraphs: Subgraphs,
295
295
  joinSpec: JoinSpecDefinition,
296
+ contextSpec: ContextSpecDefinition | undefined,
297
+ costSpec: CostSpecDefinition | undefined,
296
298
  filteredTypes: NamedType[],
297
- originalDirectiveNames: Record<string, string>,
298
299
  getSubgraph: (application: Directive<any, { graph?: string }>) => Subgraph | undefined,
299
300
  getSubgraphEnumValue: (subgraphName: string) => string
300
301
  }
@@ -352,7 +353,9 @@ function addAllEmptySubgraphTypes(args: ExtractArguments): TypesInfo {
352
353
  const subgraph = getSubgraph(application);
353
354
  assert(subgraph, () => `Should have found the subgraph for ${application}`);
354
355
  const subgraphType = subgraph.schema.addType(newNamedType(type.kind, type.name));
355
- propagateDemandControlDirectives(type, subgraphType, subgraph, args.originalDirectiveNames);
356
+ if (args.costSpec) {
357
+ propagateDemandControlDirectives(type, subgraphType, subgraph, args.costSpec);
358
+ }
356
359
  }
357
360
  break;
358
361
  }
@@ -401,17 +404,8 @@ function addEmptyType<T extends NamedType>(
401
404
  }
402
405
  }
403
406
  }
404
-
405
- const coreFeatures = supergraph.coreFeatures;
406
- assert(coreFeatures, 'Should have core features');
407
- const contextFeature = coreFeatures.getByIdentity(ContextSpecDefinition.identity);
408
- let supergraphContextDirective: DirectiveDefinition<{ name: string}> | undefined;
409
- if (contextFeature) {
410
- const contextSpec = CONTEXT_VERSIONS.find(contextFeature.url.version);
411
- assert(contextSpec, 'Should have context spec');
412
- supergraphContextDirective = contextSpec.contextDirective(supergraph);
413
- }
414
-
407
+
408
+ const supergraphContextDirective = args.contextSpec?.contextDirective(supergraph);
415
409
  if (supergraphContextDirective) {
416
410
  const contextApplications = type.appliedDirectivesOf(supergraphContextDirective);
417
411
  // for every application, apply the context directive to the correct subgraph
@@ -438,8 +432,6 @@ function extractObjOrItfContent(args: ExtractArguments, info: TypeInfo<ObjectTyp
438
432
  const implementsDirective = args.joinSpec.implementsDirective(args.supergraph);
439
433
  assert(implementsDirective, '@join__implements should existing for a fed2 supergraph');
440
434
 
441
- const originalDirectiveNames = args.originalDirectiveNames;
442
-
443
435
  for (const { type, subgraphsInfo } of info) {
444
436
  const implementsApplications = type.appliedDirectivesOf(implementsDirective);
445
437
  for (const application of implementsApplications) {
@@ -450,8 +442,10 @@ function extractObjOrItfContent(args: ExtractArguments, info: TypeInfo<ObjectTyp
450
442
  subgraphInfo.type.addImplementedInterface(args.interface);
451
443
  }
452
444
 
453
- for (const { type: subgraphType, subgraph } of subgraphsInfo.values()) {
454
- propagateDemandControlDirectives(type, subgraphType, subgraph, args.originalDirectiveNames);
445
+ if (args.costSpec) {
446
+ for (const { type: subgraphType, subgraph } of subgraphsInfo.values()) {
447
+ propagateDemandControlDirectives(type, subgraphType, subgraph, args.costSpec);
448
+ }
455
449
  }
456
450
 
457
451
  for (const field of type.fields()) {
@@ -460,7 +454,13 @@ function extractObjOrItfContent(args: ExtractArguments, info: TypeInfo<ObjectTyp
460
454
  // In fed2 subgraph, no @join__field means that the field is in all the subgraphs in which the type is.
461
455
  const isShareable = isObjectType(type) && subgraphsInfo.size > 1;
462
456
  for (const { type: subgraphType, subgraph } of subgraphsInfo.values()) {
463
- addSubgraphField({ field, type: subgraphType, subgraph, isShareable, originalDirectiveNames });
457
+ addSubgraphField({
458
+ field,
459
+ type: subgraphType,
460
+ subgraph,
461
+ isShareable,
462
+ costSpec: args.costSpec
463
+ });
464
464
  }
465
465
  } else {
466
466
  const isShareable = isObjectType(type)
@@ -478,58 +478,21 @@ function extractObjOrItfContent(args: ExtractArguments, info: TypeInfo<ObjectTyp
478
478
  }
479
479
 
480
480
  const { type: subgraphType, subgraph } = subgraphsInfo.get(joinFieldArgs.graph)!;
481
- addSubgraphField({ field, type: subgraphType, subgraph, isShareable, joinFieldArgs, originalDirectiveNames });
482
- }
483
- }
484
- }
485
- }
486
- }
487
-
488
- /**
489
- * Builds a map of original name to new name for Apollo feature directives. This is
490
- * used to handle cases where a directive is renamed via an import statement. For
491
- * example, importing a directive with a custom name like
492
- * ```graphql
493
- * @link(url: "https://specs.apollo.dev/cost/v0.1", import: [{ name: "@cost", as: "@renamedCost" }])
494
- * ```
495
- * results in a map entry of `cost -> renamedCost` with the `@` prefix removed.
496
- *
497
- * If the directive is imported under its default name, that also results in an entry. So,
498
- * ```graphql
499
- * @link(url: "https://specs.apollo.dev/cost/v0.1", import: ["@cost"])
500
- * ```
501
- * results in a map entry of `cost -> cost`. This duals as a way to check if a directive
502
- * is included in the supergraph schema.
503
- *
504
- * **Important:** This map does _not_ include directives imported from identities other
505
- * than `specs.apollo.dev`. This helps us avoid extracting directives to subgraphs
506
- * when a custom directive's name conflicts with that of a default one.
507
- */
508
- function getApolloDirectiveNames(supergraph: Schema): Record<string, string> {
509
- const originalDirectiveNames: Record<string, string> = {};
510
- for (const linkDirective of supergraph.schemaDefinition.appliedDirectivesOf("link")) {
511
- if (linkDirective.arguments().url && linkDirective.arguments().import) {
512
- const url = FeatureUrl.maybeParse(linkDirective.arguments().url);
513
- if (!url?.identity.includes("specs.apollo.dev")) {
514
- continue;
515
- }
516
-
517
- for (const importedDirective of linkDirective.arguments().import) {
518
- if (importedDirective.name && importedDirective.as) {
519
- originalDirectiveNames[importedDirective.name.replace('@', '')] = importedDirective.as.replace('@', '');
520
- } else if (typeof importedDirective === 'string') {
521
- originalDirectiveNames[importedDirective.replace('@', '')] = importedDirective.replace('@', '');
481
+ addSubgraphField({
482
+ field,
483
+ type: subgraphType,
484
+ subgraph, isShareable,
485
+ joinFieldArgs,
486
+ costSpec: args.costSpec
487
+ });
522
488
  }
523
489
  }
524
490
  }
525
491
  }
526
-
527
- return originalDirectiveNames;
528
492
  }
529
493
 
530
494
  function extractInputObjContent(args: ExtractArguments, info: TypeInfo<InputObjectType>[]) {
531
495
  const fieldDirective = args.joinSpec.fieldDirective(args.supergraph);
532
- const originalDirectiveNames = args.originalDirectiveNames;
533
496
 
534
497
  for (const { type, subgraphsInfo } of info) {
535
498
  for (const field of type.fields()) {
@@ -537,19 +500,30 @@ function extractInputObjContent(args: ExtractArguments, info: TypeInfo<InputObje
537
500
  if (fieldApplications.length === 0) {
538
501
  // In fed2 subgraph, no @join__field means that the field is in all the subgraphs in which the type is.
539
502
  for (const { type: subgraphType, subgraph } of subgraphsInfo.values()) {
540
- addSubgraphInputField({ field, type: subgraphType, subgraph, originalDirectiveNames });
503
+ addSubgraphInputField({
504
+ field,
505
+ type: subgraphType,
506
+ subgraph,
507
+ costSpec: args.costSpec
508
+ });
541
509
  }
542
510
  } else {
543
511
  for (const application of fieldApplications) {
544
- const args = application.arguments();
512
+ const joinFieldArgs = application.arguments();
545
513
  // We use a @join__field with no graph to indicates when a field in the supergraph does not come
546
514
  // directly from any subgraph and there is thus nothing to do to "extract" it.
547
- if (!args.graph) {
515
+ if (!joinFieldArgs.graph) {
548
516
  continue;
549
517
  }
550
518
 
551
- const { type: subgraphType, subgraph } = subgraphsInfo.get(args.graph)!;
552
- addSubgraphInputField({ field, type: subgraphType, subgraph, joinFieldArgs: args, originalDirectiveNames });
519
+ const { type: subgraphType, subgraph } = subgraphsInfo.get(joinFieldArgs.graph)!;
520
+ addSubgraphInputField({
521
+ field,
522
+ type: subgraphType,
523
+ subgraph,
524
+ joinFieldArgs,
525
+ costSpec: args.costSpec
526
+ });
553
527
  }
554
528
  }
555
529
  }
@@ -559,11 +533,12 @@ function extractInputObjContent(args: ExtractArguments, info: TypeInfo<InputObje
559
533
  function extractEnumTypeContent(args: ExtractArguments, info: TypeInfo<EnumType>[]) {
560
534
  // This was added in join 0.3, so it can genuinely be undefined.
561
535
  const enumValueDirective = args.joinSpec.enumValueDirective(args.supergraph);
562
- const originalDirectiveNames = args.originalDirectiveNames;
563
536
 
564
537
  for (const { type, subgraphsInfo } of info) {
565
- for (const { type: subgraphType, subgraph } of subgraphsInfo.values()) {
566
- propagateDemandControlDirectives(type, subgraphType, subgraph, originalDirectiveNames);
538
+ if (args.costSpec) {
539
+ for (const { type: subgraphType, subgraph } of subgraphsInfo.values()) {
540
+ propagateDemandControlDirectives(type, subgraphType, subgraph, args.costSpec);
541
+ }
567
542
  }
568
543
 
569
544
  for (const value of type.values) {
@@ -678,20 +653,20 @@ function maybeDumpSubgraphSchema(subgraph: Subgraph): string {
678
653
  }
679
654
  }
680
655
 
681
- function propagateDemandControlDirectives(source: SchemaElement<any, any>, dest: SchemaElement<any, any>, subgraph: Subgraph, originalDirectiveNames?: Record<string, string>) {
682
- const costDirectiveName = originalDirectiveNames?.[FederationDirectiveName.COST];
683
- if (costDirectiveName) {
684
- const costDirective = source.appliedDirectivesOf(costDirectiveName).pop();
685
- if (costDirective) {
686
- dest.applyDirective(subgraph.metadata().costDirective().name, costDirective.arguments());
656
+ function propagateDemandControlDirectives(source: SchemaElement<any, any>, dest: SchemaElement<any, any>, subgraph: Subgraph, costSpec: CostSpecDefinition) {
657
+ const costDirective = costSpec.costDirective(source.schema());
658
+ if (costDirective) {
659
+ const application = source.appliedDirectivesOf(costDirective)[0];
660
+ if (application) {
661
+ dest.applyDirective(subgraph.metadata().costDirective().name, application.arguments());
687
662
  }
688
663
  }
689
664
 
690
- const listSizeDirectiveName = originalDirectiveNames?.[FederationDirectiveName.LIST_SIZE];
691
- if (listSizeDirectiveName) {
692
- const listSizeDirective = source.appliedDirectivesOf(listSizeDirectiveName).pop();
693
- if (listSizeDirective) {
694
- dest.applyDirective(subgraph.metadata().listSizeDirective().name, listSizeDirective.arguments());
665
+ const listSizeDirective = costSpec.listSizeDirective(source.schema());
666
+ if (listSizeDirective) {
667
+ const application = source.appliedDirectivesOf(listSizeDirective)[0];
668
+ if (application) {
669
+ dest.applyDirective(subgraph.metadata().listSizeDirective().name, application.arguments());
695
670
  }
696
671
  }
697
672
  }
@@ -707,14 +682,14 @@ function addSubgraphField({
707
682
  subgraph,
708
683
  isShareable,
709
684
  joinFieldArgs,
710
- originalDirectiveNames,
685
+ costSpec,
711
686
  }: {
712
687
  field: FieldDefinition<ObjectType | InterfaceType>,
713
688
  type: ObjectType | InterfaceType,
714
689
  subgraph: Subgraph,
715
690
  isShareable: boolean,
716
691
  joinFieldArgs?: JoinFieldDirectiveArguments,
717
- originalDirectiveNames?: Record<string, string>,
692
+ costSpec?: CostSpecDefinition,
718
693
  }): FieldDefinition<ObjectType | InterfaceType> {
719
694
  const copiedFieldType = joinFieldArgs?.type
720
695
  ? decodeType(joinFieldArgs.type, subgraph.schema, subgraph.name)
@@ -723,7 +698,9 @@ function addSubgraphField({
723
698
  const subgraphField = type.addField(field.name, copiedFieldType);
724
699
  for (const arg of field.arguments()) {
725
700
  const argDef = subgraphField.addArgument(arg.name, copyType(arg.type!, subgraph.schema, subgraph.name), arg.defaultValue);
726
- propagateDemandControlDirectives(arg, argDef, subgraph, originalDirectiveNames)
701
+ if (costSpec) {
702
+ propagateDemandControlDirectives(arg, argDef, subgraph, costSpec);
703
+ }
727
704
  }
728
705
  if (joinFieldArgs?.requires) {
729
706
  subgraphField.applyDirective(subgraph.metadata().requiresDirective(), {'fields': joinFieldArgs.requires});
@@ -769,7 +746,9 @@ function addSubgraphField({
769
746
  subgraphField.applyDirective(subgraph.metadata().shareableDirective());
770
747
  }
771
748
 
772
- propagateDemandControlDirectives(field, subgraphField, subgraph, originalDirectiveNames);
749
+ if (costSpec) {
750
+ propagateDemandControlDirectives(field, subgraphField, subgraph, costSpec);
751
+ }
773
752
 
774
753
  return subgraphField;
775
754
  }
@@ -779,13 +758,13 @@ function addSubgraphInputField({
779
758
  type,
780
759
  subgraph,
781
760
  joinFieldArgs,
782
- originalDirectiveNames,
761
+ costSpec,
783
762
  }: {
784
763
  field: InputFieldDefinition,
785
764
  type: InputObjectType,
786
765
  subgraph: Subgraph,
787
766
  joinFieldArgs?: JoinFieldDirectiveArguments,
788
- originalDirectiveNames?: Record<string, string>
767
+ costSpec?: CostSpecDefinition,
789
768
  }): InputFieldDefinition {
790
769
  const copiedType = joinFieldArgs?.type
791
770
  ? decodeType(joinFieldArgs?.type, subgraph.schema, subgraph.name)
@@ -794,7 +773,9 @@ function addSubgraphInputField({
794
773
  const inputField = type.addField(field.name, copiedType);
795
774
  inputField.defaultValue = field.defaultValue
796
775
 
797
- propagateDemandControlDirectives(field, inputField, subgraph, originalDirectiveNames);
776
+ if (costSpec) {
777
+ propagateDemandControlDirectives(field, inputField, subgraph, costSpec);
778
+ }
798
779
 
799
780
  return inputField;
800
781
  }
@@ -195,7 +195,8 @@ export type CoreDirectiveArgs = {
195
195
  url: undefined,
196
196
  feature: string,
197
197
  as?: string,
198
- for?: string
198
+ for?: string,
199
+ import: undefined,
199
200
  }
200
201
 
201
202
  export type LinkDirectiveArgs = {
@@ -203,7 +204,7 @@ export type LinkDirectiveArgs = {
203
204
  feature: undefined,
204
205
  as?: string,
205
206
  for?: string,
206
- import?: (string | CoreImport)[]
207
+ import?: (string | CoreImport)[],
207
208
  }
208
209
 
209
210
  export type CoreOrLinkDirectiveArgs = CoreDirectiveArgs | LinkDirectiveArgs;
@@ -539,36 +540,36 @@ export class CoreSpecDefinition extends FeatureDefinition {
539
540
  return feature.url.version;
540
541
  }
541
542
 
542
- applyFeatureToSchema(schema: Schema, feature: FeatureDefinition, as?: string, purpose?: CorePurpose): GraphQLError[] {
543
+ applyFeatureToSchema(
544
+ schema: Schema,
545
+ feature: FeatureDefinition,
546
+ as?: string,
547
+ purpose?: CorePurpose,
548
+ imports?: CoreImport[],
549
+ ): GraphQLError[] {
543
550
  const coreDirective = this.coreDirective(schema);
544
551
  const args = {
545
552
  [this.urlArgName()]: feature.toString(),
546
553
  as,
547
- } as CoreDirectiveArgs;
548
- if (this.supportPurposes() && purpose) {
549
- args.for = purpose;
550
- }
551
- schema.schemaDefinition.applyDirective(coreDirective, args);
552
- return feature.addElementsToSchema(schema);
553
- }
554
-
555
- applyFeatureAsLink(schema: Schema, feature: FeatureDefinition, purpose?: CorePurpose, imports?: CoreImport[]): GraphQLError[] {
556
- const existing = schema.schemaDefinition.appliedDirectivesOf(linkDirectiveDefaultName).find((link) => link.arguments().url === feature.toString());
557
- if (existing) {
558
- existing.remove();
554
+ } as CoreOrLinkDirectiveArgs;
555
+ if (purpose) {
556
+ if (this.supportPurposes()) {
557
+ args.for = purpose;
558
+ } else {
559
+ return [new GraphQLError(
560
+ `Cannot apply feature ${feature} with purpose since the schema's @core/@link version does not support it.`
561
+ )];
562
+ }
559
563
  }
560
-
561
- const coreDirective = this.coreDirective(schema);
562
- const args: LinkDirectiveArgs = {
563
- url: feature.toString(),
564
- import: (existing?.arguments().import ?? []).concat(imports?.map((i) => i.as ? { name: `@${i.name}`, as: `@${i.as}` } : `@${i.name}`)),
565
- feature: undefined,
566
- };
567
-
568
- if (this.supportPurposes() && purpose) {
569
- args.for = purpose;
564
+ if (imports && imports.length > 0) {
565
+ if (this.supportImport()) {
566
+ args.import = imports.map(i => i.as ? i : i.name);
567
+ } else {
568
+ return [new GraphQLError(
569
+ `Cannot apply feature ${feature} with imports since the schema's @core/@link version does not support it.`
570
+ )];
571
+ }
570
572
  }
571
-
572
573
  schema.schemaDefinition.applyDirective(coreDirective, args);
573
574
  return feature.addElementsToSchema(schema);
574
575
  }
@@ -1,7 +1,7 @@
1
1
  import { DirectiveLocation } from 'graphql';
2
2
  import { createDirectiveSpecification } from '../directiveAndTypeSpecification';
3
3
  import { FeatureDefinition, FeatureDefinitions, FeatureUrl, FeatureVersion } from './coreSpec';
4
- import { ListType, NonNullType } from '../definitions';
4
+ import { DirectiveDefinition, ListType, NonNullType, Schema } from '../definitions';
5
5
  import { registerKnownFeature } from '../knownCoreFeatures';
6
6
  import { ARGUMENT_COMPOSITION_STRATEGIES } from '../argumentCompositionStrategies';
7
7
 
@@ -41,6 +41,14 @@ export class CostSpecDefinition extends FeatureDefinition {
41
41
  supergraphSpecification: (fedVersion) => COST_VERSIONS.getMinimumRequiredVersion(fedVersion)
42
42
  }));
43
43
  }
44
+
45
+ costDirective(schema: Schema): DirectiveDefinition<CostDirectiveArguments> | undefined {
46
+ return this.directive(schema, 'cost');
47
+ }
48
+
49
+ listSizeDirective(schema: Schema): DirectiveDefinition<ListSizeDirectiveArguments> | undefined {
50
+ return this.directive(schema, 'listSize');
51
+ }
44
52
  }
45
53
 
46
54
  export const COST_VERSIONS = new FeatureDefinitions<CostSpecDefinition>(costIdentity)
@@ -1,7 +1,9 @@
1
1
  import { DocumentNode, GraphQLError } from "graphql";
2
- import { ErrCoreCheckFailed, FeatureUrl, FeatureVersion } from "./specs/coreSpec";
3
2
  import { CoreFeatures, Schema, sourceASTs } from "./definitions";
3
+ import { ErrCoreCheckFailed, FeatureUrl, FeatureVersion } from "./specs/coreSpec";
4
4
  import { joinIdentity, JoinSpecDefinition, JOIN_VERSIONS } from "./specs/joinSpec";
5
+ import { CONTEXT_VERSIONS, ContextSpecDefinition } from "./specs/contextSpec";
6
+ import { COST_VERSIONS, costIdentity, CostSpecDefinition } from "./specs/costSpec";
5
7
  import { buildSchema, buildSchemaFromAST } from "./buildSchema";
6
8
  import { extractSubgraphsNamesAndUrlsFromSupergraph, extractSubgraphsFromSupergraph } from "./extractSubgraphsFromSupergraph";
7
9
  import { ERRORS } from "./error";
@@ -81,11 +83,17 @@ function checkFeatureSupport(coreFeatures: CoreFeatures, supportedFeatures: Set<
81
83
  }
82
84
  }
83
85
 
84
- export function validateSupergraph(supergraph: Schema): [CoreFeatures, JoinSpecDefinition] {
86
+ export function validateSupergraph(supergraph: Schema): [
87
+ CoreFeatures,
88
+ JoinSpecDefinition,
89
+ ContextSpecDefinition | undefined,
90
+ CostSpecDefinition | undefined,
91
+ ] {
85
92
  const coreFeatures = supergraph.coreFeatures;
86
93
  if (!coreFeatures) {
87
94
  throw ERRORS.INVALID_FEDERATION_SUPERGRAPH.err("Invalid supergraph: must be a core schema");
88
95
  }
96
+
89
97
  const joinFeature = coreFeatures.getByIdentity(joinIdentity);
90
98
  if (!joinFeature) {
91
99
  throw ERRORS.INVALID_FEDERATION_SUPERGRAPH.err("Invalid supergraph: must use the join spec");
@@ -95,7 +103,27 @@ export function validateSupergraph(supergraph: Schema): [CoreFeatures, JoinSpecD
95
103
  throw ERRORS.INVALID_FEDERATION_SUPERGRAPH.err(
96
104
  `Invalid supergraph: uses unsupported join spec version ${joinFeature.url.version} (supported versions: ${JOIN_VERSIONS.versions().join(', ')})`);
97
105
  }
98
- return [coreFeatures, joinSpec];
106
+
107
+ const contextFeature = coreFeatures.getByIdentity(ContextSpecDefinition.identity);
108
+ let contextSpec = undefined;
109
+ if (contextFeature) {
110
+ contextSpec = CONTEXT_VERSIONS.find(contextFeature.url.version);
111
+ if (!contextSpec) {
112
+ throw ERRORS.INVALID_FEDERATION_SUPERGRAPH.err(
113
+ `Invalid supergraph: uses unsupported context spec version ${contextFeature.url.version} (supported versions: ${CONTEXT_VERSIONS.versions().join(', ')})`);
114
+ }
115
+ }
116
+
117
+ const costFeature = coreFeatures.getByIdentity(costIdentity);
118
+ let costSpec = undefined;
119
+ if (costFeature) {
120
+ costSpec = COST_VERSIONS.find(costFeature.url.version);
121
+ if (!costSpec) {
122
+ throw ERRORS.INVALID_FEDERATION_SUPERGRAPH.err(
123
+ `Invalid supergraph: uses unsupported cost spec version ${costFeature.url.version} (supported versions: ${COST_VERSIONS.versions().join(', ')})`);
124
+ }
125
+ }
126
+ return [coreFeatures, joinSpec, contextSpec, costSpec];
99
127
  }
100
128
 
101
129
  export function isFed1Supergraph(supergraph: Schema): boolean {
package/src/values.ts CHANGED
@@ -135,8 +135,10 @@ export function valueEquals(a: any, b: any): boolean {
135
135
  if (Array.isArray(a)) {
136
136
  return Array.isArray(b) && arrayValueEquals(a, b) ;
137
137
  }
138
- if (typeof a === 'object') {
139
- return typeof b === 'object' && objectEquals(a, b);
138
+ // Note that typeof null === 'object', so we have to manually rule that out
139
+ // here.
140
+ if (a !== null && typeof a === 'object') {
141
+ return b !== null && typeof b === 'object' && objectEquals(a, b);
140
142
  }
141
143
  return a === b;
142
144
  }
@@ -224,8 +226,10 @@ function applyDefaultValues(value: any, type: InputType): any {
224
226
  if (fieldValue === undefined) {
225
227
  if (field.defaultValue !== undefined) {
226
228
  updated[field.name] = applyDefaultValues(field.defaultValue, field.type);
227
- } else if (isNonNullType(field.type)) {
228
- throw ERRORS.INVALID_GRAPHQL.err(`Field "${field.name}" of required type ${type} was not provided.`);
229
+ } else if (!isNonNullType(field.type)) {
230
+ updated[field.name] = null;
231
+ } else {
232
+ throw ERRORS.INVALID_GRAPHQL.err(`Required field "${field.name}" of type ${type} was not provided.`);
229
233
  }
230
234
  } else {
231
235
  updated[field.name] = applyDefaultValues(fieldValue, field.type);
@@ -249,8 +253,12 @@ export function withDefaultValues(value: any, argument: ArgumentDefinition<any>)
249
253
  throw buildError(`Cannot compute default value for argument ${argument} as the type is undefined`);
250
254
  }
251
255
  if (value === undefined) {
252
- if (argument.defaultValue) {
256
+ if (argument.defaultValue !== undefined) {
253
257
  return applyDefaultValues(argument.defaultValue, argument.type);
258
+ } else if (!isNonNullType(argument.type)) {
259
+ return null;
260
+ } else {
261
+ throw ERRORS.INVALID_GRAPHQL.err(`Required argument "${argument.coordinate}" was not provided.`);
254
262
  }
255
263
  }
256
264
  return applyDefaultValues(value, argument.type);