@@ -31,7 +31,6 @@ import {
31
31
CompositeType ,
32
32
Subgraphs ,
33
33
JOIN_VERSIONS ,
34
- INACCESSIBLE_VERSIONS ,
35
34
NamedSchemaElement ,
36
35
errorCauses ,
37
36
isObjectType ,
@@ -69,7 +68,6 @@ import {
69
68
CoreSpecDefinition ,
70
69
FeatureVersion ,
71
70
FEDERATION_VERSIONS ,
72
- InaccessibleSpecDefinition ,
73
71
LinkDirectiveArgs ,
74
72
sourceIdentity ,
75
73
FeatureUrl ,
@@ -81,6 +79,10 @@ import {
81
79
isNullableType ,
82
80
isFieldDefinition ,
83
81
Post20FederationDirectiveDefinition ,
82
+ DirectiveCompositionSpecification ,
83
+ FeatureDefinition ,
84
+ CoreImport ,
85
+ inaccessibleIdentity ,
84
86
} from "@apollo/federation-internals" ;
85
87
import { ASTNode , GraphQLError , DirectiveLocation } from "graphql" ;
86
88
import {
@@ -345,6 +347,12 @@ interface OverrideArgs {
345
347
label ?: string ;
346
348
}
347
349
350
+ interface MergedDirectiveInfo {
351
+ definition : DirectiveDefinition ;
352
+ argumentsMerger ?: ArgumentMerger ;
353
+ staticArgumentTransform ?: StaticArgumentsTransform ;
354
+ }
355
+
348
356
class Merger {
349
357
readonly names : readonly string [ ] ;
350
358
readonly subgraphsSchema : readonly Schema [ ] ;
@@ -353,7 +361,8 @@ class Merger {
353
361
readonly merged : Schema = new Schema ( ) ;
354
362
readonly subgraphNamesToJoinSpecName : Map < string , string > ;
355
363
readonly mergedFederationDirectiveNames = new Set < string > ( ) ;
356
- readonly mergedFederationDirectiveInSupergraph = new Map < string , { definition : DirectiveDefinition , argumentsMerger ?: ArgumentMerger , staticArgumentTransform ?: StaticArgumentsTransform } > ( ) ;
364
+ readonly mergedFederationDirectiveInSupergraphByDirectiveName =
365
+ new Map < string , MergedDirectiveInfo > ( ) ;
357
366
readonly enumUsages = new Map < string , EnumTypeUsage > ( ) ;
358
367
private composeDirectiveManager : ComposeDirectiveManager ;
359
368
private mismatchReporter : MismatchReporter ;
@@ -364,7 +373,7 @@ class Merger {
364
373
} [ ] ;
365
374
private joinSpec : JoinSpecDefinition ;
366
375
private linkSpec : CoreSpecDefinition ;
367
- private inaccessibleSpec : InaccessibleSpecDefinition ;
376
+ private inaccessibleDirectiveInSupergraph ?: DirectiveDefinition ;
368
377
private latestFedVersionUsed : FeatureVersion ;
369
378
private joinDirectiveIdentityURLs = new Set < string > ( ) ;
370
379
private schemaToImportNameToFeatureUrl = new Map < Schema , Map < string , FeatureUrl > > ( ) ;
@@ -375,7 +384,6 @@ class Merger {
375
384
this . latestFedVersionUsed = this . getLatestFederationVersionUsed ( ) ;
376
385
this . joinSpec = JOIN_VERSIONS . getMinimumRequiredVersion ( this . latestFedVersionUsed ) ;
377
386
this . linkSpec = LINK_VERSIONS . getMinimumRequiredVersion ( this . latestFedVersionUsed ) ;
378
- this . inaccessibleSpec = INACCESSIBLE_VERSIONS . getMinimumRequiredVersion ( this . latestFedVersionUsed ) ;
379
387
this . fieldsWithFromContext = this . getFieldsWithFromContextDirective ( ) ;
380
388
this . fieldsWithOverride = this . getFieldsWithOverrideDirective ( ) ;
381
389
@@ -470,59 +478,127 @@ class Merger {
470
478
assert ( errors . length === 0 , "We shouldn't have errors adding the join spec to the (still empty) supergraph schema" ) ;
471
479
472
480
const directivesMergeInfo = collectCoreDirectivesToCompose ( this . subgraphs ) ;
473
- for ( const mergeInfo of directivesMergeInfo ) {
474
- this . validateAndMaybeAddSpec ( mergeInfo ) ;
475
- }
476
-
481
+ this . validateAndMaybeAddSpecs ( directivesMergeInfo ) ;
477
482
return this . joinSpec . populateGraphEnum ( this . merged , this . subgraphs ) ;
478
483
}
479
484
480
- private validateAndMaybeAddSpec ( { url, name, definitionsPerSubgraph, compositionSpec} : CoreDirectiveInSubgraphs ) {
481
- // Not composition specification means that it shouldn't be composed.
482
- if ( ! compositionSpec ) {
483
- return ;
484
- }
485
-
486
- let nameInSupergraph : string | undefined ;
487
- for ( const subgraph of this . subgraphs ) {
488
- const directive = definitionsPerSubgraph . get ( subgraph . name ) ;
489
- if ( ! directive ) {
490
- continue ;
485
+ private validateAndMaybeAddSpecs ( directivesMergeInfo : CoreDirectiveInSubgraphs [ ] ) {
486
+ const supergraphInfoByIdentity = new Map <
487
+ string ,
488
+ {
489
+ specInSupergraph : FeatureDefinition ;
490
+ directives : {
491
+ nameInFeature : string ;
492
+ nameInSupergraph : string ;
493
+ compositionSpec : DirectiveCompositionSpecification ;
494
+ } [ ] ;
491
495
}
496
+ > ;
492
497
493
- if ( ! nameInSupergraph ) {
494
- nameInSupergraph = directive . name ;
495
- } else if ( nameInSupergraph !== directive . name ) {
496
- this . mismatchReporter . reportMismatchError (
497
- ERRORS . LINK_IMPORT_NAME_MISMATCH ,
498
- `The "@${ name } " directive (from ${ url } ) is imported with mismatched name between subgraphs: it is imported as ` ,
499
- directive ,
500
- sourcesFromArray ( this . subgraphs . values ( ) . map ( ( s ) => definitionsPerSubgraph . get ( s . name ) ) ) ,
501
- ( def ) => `"@${ def . name } "` ,
502
- ) ;
498
+ for ( const { url, name, definitionsPerSubgraph, compositionSpec} of directivesMergeInfo ) {
499
+ // No composition specification means that it shouldn't be composed.
500
+ if ( ! compositionSpec ) {
503
501
return ;
504
502
}
503
+
504
+ let nameInSupergraph : string | undefined ;
505
+ for ( const subgraph of this . subgraphs ) {
506
+ const directive = definitionsPerSubgraph . get ( subgraph . name ) ;
507
+ if ( ! directive ) {
508
+ continue ;
509
+ }
510
+
511
+ if ( ! nameInSupergraph ) {
512
+ nameInSupergraph = directive . name ;
513
+ } else if ( nameInSupergraph !== directive . name ) {
514
+ this . mismatchReporter . reportMismatchError (
515
+ ERRORS . LINK_IMPORT_NAME_MISMATCH ,
516
+ `The "@${ name } " directive (from ${ url } ) is imported with mismatched name between subgraphs: it is imported as ` ,
517
+ directive ,
518
+ sourcesFromArray ( this . subgraphs . values ( ) . map ( ( s ) => definitionsPerSubgraph . get ( s . name ) ) ) ,
519
+ ( def ) => `"@${ def . name } "` ,
520
+ ) ;
521
+ return ;
522
+ }
523
+ }
524
+
525
+ // If we get here with `nameInSupergraph` unset, it means there is no usage for the directive at all and we
526
+ // don't bother adding the spec to the supergraph.
527
+ if ( nameInSupergraph ) {
528
+ const specInSupergraph = compositionSpec . supergraphSpecification ( this . latestFedVersionUsed ) ;
529
+ let supergraphInfo = supergraphInfoByIdentity . get ( specInSupergraph . url . identity ) ;
530
+ if ( supergraphInfo ) {
531
+ assert (
532
+ specInSupergraph . url . equals ( supergraphInfo . specInSupergraph . url ) ,
533
+ `Spec ${ specInSupergraph . url } directives disagree on version for supergraph` ,
534
+ ) ;
535
+ } else {
536
+ supergraphInfo = {
537
+ specInSupergraph,
538
+ directives : [ ] ,
539
+ } ;
540
+ supergraphInfoByIdentity . set ( specInSupergraph . url . identity , supergraphInfo ) ;
541
+ }
542
+ supergraphInfo . directives . push ( {
543
+ nameInFeature : name ,
544
+ nameInSupergraph,
545
+ compositionSpec,
546
+ } ) ;
547
+ }
505
548
}
506
549
507
- // If we get here with `nameInSupergraph` unset, it means there is no usage for the directive at all and we
508
- // don't bother adding the spec to the supergraph.
509
- if ( nameInSupergraph ) {
510
- const specInSupergraph = compositionSpec . supergraphSpecification ( this . latestFedVersionUsed ) ;
511
- const errors = this . linkSpec . applyFeatureAsLink ( this . merged , specInSupergraph , specInSupergraph . defaultCorePurpose , [ { name, as : name === nameInSupergraph ? undefined : nameInSupergraph } ] , ) ;
512
- assert ( errors . length === 0 , "We shouldn't have errors adding the join spec to the (still empty) supergraph schema" ) ;
513
- const feature = this . merged ?. coreFeatures ?. getByIdentity ( specInSupergraph . url . identity ) ;
550
+ for ( const { specInSupergraph, directives } of supergraphInfoByIdentity . values ( ) ) {
551
+ const imports : CoreImport [ ] = [ ] ;
552
+ for ( const { nameInFeature, nameInSupergraph } of directives ) {
553
+ const defaultNameInSupergraph = CoreFeature . directiveNameInSchemaForCoreArguments (
554
+ specInSupergraph . url ,
555
+ specInSupergraph . url . name ,
556
+ [ ] ,
557
+ nameInFeature ,
558
+ ) ;
559
+ if ( nameInSupergraph !== defaultNameInSupergraph ) {
560
+ imports . push ( nameInFeature === nameInSupergraph
561
+ ? { name : `@${ nameInFeature } ` }
562
+ : { name : `@${ nameInFeature } ` , as : `@${ nameInSupergraph } ` }
563
+ ) ;
564
+ }
565
+ }
566
+ const errors = this . linkSpec . applyFeatureToSchema (
567
+ this . merged ,
568
+ specInSupergraph ,
569
+ undefined ,
570
+ specInSupergraph . defaultCorePurpose ,
571
+ imports ,
572
+ ) ;
573
+ assert (
574
+ errors . length === 0 ,
575
+ "We shouldn't have errors adding the join spec to the (still empty) supergraph schema"
576
+ ) ;
577
+ const feature = this . merged . coreFeatures ?. getByIdentity ( specInSupergraph . url . identity ) ;
514
578
assert ( feature , 'Should have found the feature we just added' ) ;
515
- const argumentsMerger = compositionSpec . argumentsMerger ?. call ( null , this . merged , feature ) ;
516
- if ( argumentsMerger instanceof GraphQLError ) {
517
- // That would mean we made a mistake in the declaration of a hard-coded directive, so we just throw right away so this can be caught and corrected.
518
- throw argumentsMerger ;
519
- }
520
- this . mergedFederationDirectiveNames . add ( nameInSupergraph ) ;
521
- this . mergedFederationDirectiveInSupergraph . set ( name , {
522
- definition : this . merged . directive ( nameInSupergraph ) ! ,
523
- argumentsMerger,
524
- staticArgumentTransform : compositionSpec . staticArgumentTransform ,
525
- } ) ;
579
+ for ( const { nameInFeature, nameInSupergraph, compositionSpec } of directives ) {
580
+ const argumentsMerger = compositionSpec . argumentsMerger ?. call ( null , this . merged , feature ) ;
581
+ if ( argumentsMerger instanceof GraphQLError ) {
582
+ // That would mean we made a mistake in the declaration of a hard-coded directive,
583
+ // so we just throw right away so this can be caught and corrected.
584
+ throw argumentsMerger ;
585
+ }
586
+ this . mergedFederationDirectiveNames . add ( nameInSupergraph ) ;
587
+ this . mergedFederationDirectiveInSupergraphByDirectiveName . set ( nameInSupergraph , {
588
+ definition : this . merged . directive ( nameInSupergraph ) ! ,
589
+ argumentsMerger,
590
+ staticArgumentTransform : compositionSpec . staticArgumentTransform ,
591
+ } ) ;
592
+ // If we encounter the @inaccessible directive, we need to record its
593
+ // definition so certain merge validations that care about @inaccessible
594
+ // can act accordingly.
595
+ if (
596
+ specInSupergraph . identity === inaccessibleIdentity
597
+ && nameInFeature === specInSupergraph . url . name
598
+ ) {
599
+ this . inaccessibleDirectiveInSupergraph = this . merged . directive ( nameInSupergraph ) ! ;
600
+ }
601
+ }
526
602
}
527
603
}
528
604
@@ -2464,8 +2540,8 @@ class Merger {
2464
2540
this . recordAppliedDirectivesToMerge ( valueSources , value ) ;
2465
2541
this . addJoinEnumValue ( valueSources , value ) ;
2466
2542
2467
- const inaccessibleInSupergraph = this . mergedFederationDirectiveInSupergraph . get ( this . inaccessibleSpec . inaccessibleDirectiveSpec . name ) ;
2468
- const isInaccessible = inaccessibleInSupergraph && value . hasAppliedDirective ( inaccessibleInSupergraph . definition ) ;
2543
+ const isInaccessible = this . inaccessibleDirectiveInSupergraph
2544
+ && value . hasAppliedDirective ( this . inaccessibleDirectiveInSupergraph ) ;
2469
2545
// The merging strategy depends on the enum type usage:
2470
2546
// - if it is _only_ used in position of Input type, we merge it with an "intersection" strategy (like other input types/things).
2471
2547
// - if it is _only_ used in position of Output type, we merge it with an "union" strategy (like other output types/things).
@@ -2562,8 +2638,6 @@ class Merger {
2562
2638
}
2563
2639
2564
2640
private mergeInput ( inputSources : Sources < InputObjectType > , dest : InputObjectType ) {
2565
- const inaccessibleInSupergraph = this . mergedFederationDirectiveInSupergraph . get ( this . inaccessibleSpec . inaccessibleDirectiveSpec . name ) ;
2566
-
2567
2641
// Like for other inputs, we add all the fields found in any subgraphs initially as a simple mean to have a complete list of
2568
2642
// field to iterate over, but we will remove those that are not in all subgraphs.
2569
2643
const added = this . addFieldsShallow ( inputSources , dest ) ;
@@ -2572,7 +2646,8 @@ class Merger {
2572
2646
// compatibility between definitions and 2) we actually want to see if the result is marked inaccessible or not and it makes
2573
2647
// that easier.
2574
2648
this . mergeInputField ( subgraphFields , destField ) ;
2575
- const isInaccessible = inaccessibleInSupergraph && destField . hasAppliedDirective ( inaccessibleInSupergraph . definition ) ;
2649
+ const isInaccessible = this . inaccessibleDirectiveInSupergraph
2650
+ && destField . hasAppliedDirective ( this . inaccessibleDirectiveInSupergraph ) ;
2576
2651
// Note that if the field is manually marked @inaccessible , we can always accept it to be inconsistent between subgraphs since
2577
2652
// it won't be exposed in the API, and we don't hint about it because we're just doing what the user is explicitely asking.
2578
2653
if ( ! isInaccessible && someSources ( subgraphFields , field => ! field ) ) {
@@ -2840,8 +2915,7 @@ class Merger {
2840
2915
// is @inaccessible , which is necessary to exist in the supergraph for EnumValues to properly
2841
2916
// determine whether the fact that a value is both input / output will matter
2842
2917
private recordAppliedDirectivesToMerge ( sources : Sources < SchemaElement < any , any > > , dest : SchemaElement < any , any > ) {
2843
- const inaccessibleInSupergraph = this . mergedFederationDirectiveInSupergraph . get ( this . inaccessibleSpec . inaccessibleDirectiveSpec . name ) ;
2844
- const inaccessibleName = inaccessibleInSupergraph ?. definition . name ;
2918
+ const inaccessibleName = this . inaccessibleDirectiveInSupergraph ?. name ;
2845
2919
const names = this . gatherAppliedDirectiveNames ( sources ) ;
2846
2920
2847
2921
if ( inaccessibleName && names . has ( inaccessibleName ) ) {
@@ -2905,7 +2979,7 @@ class Merger {
2905
2979
return ;
2906
2980
}
2907
2981
2908
- const directiveInSupergraph = this . mergedFederationDirectiveInSupergraph . get ( name ) ;
2982
+ const directiveInSupergraph = this . mergedFederationDirectiveInSupergraphByDirectiveName . get ( name ) ;
2909
2983
2910
2984
if ( dest . schema ( ) . directive ( name ) ?. repeatable ) {
2911
2985
// For repeatable directives, we simply include each application found but with exact duplicates removed
@@ -2945,7 +3019,7 @@ class Merger {
2945
3019
if ( differentApplications . length === 1 ) {
2946
3020
dest . applyDirective ( name , differentApplications [ 0 ] . arguments ( false ) ) ;
2947
3021
} else {
2948
- const info = this . mergedFederationDirectiveInSupergraph . get ( name ) ;
3022
+ const info = this . mergedFederationDirectiveInSupergraphByDirectiveName . get ( name ) ;
2949
3023
if ( info && info . argumentsMerger ) {
2950
3024
const mergedArguments = Object . create ( null ) ;
2951
3025
const applicationsArguments = differentApplications . map ( ( a ) => a . arguments ( true ) ) ;
0 commit comments