@@ -36,6 +36,7 @@ internal sealed class FederationTypeInterceptor : TypeInterceptor
36
36
BindingFlags . Static | BindingFlags . Public ) ! ;
37
37
38
38
private readonly List < ObjectType > _entityTypes = [ ] ;
39
+ private readonly List < InterfaceType > _entityInterfaceTypes = [ ] ;
39
40
private readonly Dictionary < Uri , HashSet < string > > _imports = [ ] ;
40
41
private IDescriptorContext _context = null ! ;
41
42
private ITypeInspector _typeInspector = null ! ;
@@ -79,6 +80,25 @@ public override void OnAfterInitialize(
79
80
objectType ,
80
81
objectTypeCfg ,
81
82
discoveryContext ) ;
83
+ return ;
84
+ }
85
+
86
+ if ( discoveryContext . Type is InterfaceType interfaceType &&
87
+ configuration is InterfaceTypeConfiguration interfaceTypeCfg )
88
+ {
89
+ ApplyMethodLevelReferenceResolvers (
90
+ interfaceType ,
91
+ interfaceTypeCfg ) ;
92
+
93
+ AddToUnionIfHasTypeLevelKeyDirective (
94
+ interfaceType ,
95
+ interfaceTypeCfg ) ;
96
+
97
+ AggregatePropertyLevelKeyDirectives (
98
+ interfaceType ,
99
+ interfaceTypeCfg ,
100
+ discoveryContext ) ;
101
+ return ;
82
102
}
83
103
}
84
104
@@ -345,12 +365,24 @@ public override void OnAfterMakeExecutable(
345
365
{
346
366
CompleteExternalFieldSetters ( type , typeCfg ) ;
347
367
CompleteReferenceResolver ( typeCfg ) ;
368
+ return ;
369
+ }
370
+
371
+ if ( completionContext . Type is InterfaceType interfaceType &&
372
+ configuration is InterfaceTypeConfiguration interfaceTypeCfg )
373
+ {
374
+ CompleteExternalFieldSetters ( interfaceType , interfaceTypeCfg ) ;
375
+ CompleteReferenceResolver ( interfaceTypeCfg ) ;
376
+ return ;
348
377
}
349
378
}
350
379
351
380
private void CompleteExternalFieldSetters ( ObjectType type , ObjectTypeConfiguration typeCfg )
352
381
=> ExternalSetterExpressionHelper . TryAddExternalSetter ( type , typeCfg ) ;
353
382
383
+ private void CompleteExternalFieldSetters ( InterfaceType type , InterfaceTypeConfiguration typeCfg )
384
+ => ExternalSetterExpressionHelper . TryAddExternalSetter ( type , typeCfg ) ;
385
+
354
386
private void CompleteReferenceResolver ( ObjectTypeConfiguration typeCfg )
355
387
{
356
388
var resolvers = typeCfg . Features . Get < List < ReferenceResolverConfiguration > > ( ) ;
@@ -398,6 +430,53 @@ private void CompleteReferenceResolver(ObjectTypeConfiguration typeCfg)
398
430
}
399
431
}
400
432
433
+ private void CompleteReferenceResolver ( InterfaceTypeConfiguration typeCfg )
434
+ {
435
+ var resolvers = typeCfg . Features . Get < List < ReferenceResolverConfiguration > > ( ) ;
436
+
437
+ if ( resolvers is null )
438
+ {
439
+ return ;
440
+ }
441
+
442
+ if ( resolvers . Count == 1 )
443
+ {
444
+ typeCfg . Features . Set ( new ReferenceResolver ( resolvers [ 0 ] . Resolver ) ) ;
445
+ }
446
+ else
447
+ {
448
+ var expressions = new Stack < ( Expression Condition , Expression Execute ) > ( ) ;
449
+ var context = Expression . Parameter ( typeof ( IResolverContext ) ) ;
450
+
451
+ foreach ( var resolverDef in resolvers )
452
+ {
453
+ Expression required = Expression . Constant ( resolverDef . Required ) ;
454
+ Expression resolver = Expression . Constant ( resolverDef . Resolver ) ;
455
+ Expression condition = Expression . Call ( s_matches , context , required ) ;
456
+ Expression execute = Expression . Call ( s_execute , context , resolver ) ;
457
+ expressions . Push ( ( condition , execute ) ) ;
458
+ }
459
+
460
+ Expression current = Expression . Call ( s_invalid , context ) ;
461
+ var variable = Expression . Variable ( typeof ( ValueTask < object ? > ) ) ;
462
+
463
+ while ( expressions . Count > 0 )
464
+ {
465
+ var expression = expressions . Pop ( ) ;
466
+ current = Expression . IfThenElse (
467
+ expression . Condition ,
468
+ Expression . Assign ( variable , expression . Execute ) ,
469
+ current ) ;
470
+ }
471
+
472
+ current = Expression . Block ( [ variable ] , current , variable ) ;
473
+
474
+ typeCfg . Features . Set (
475
+ new ReferenceResolver (
476
+ Expression . Lambda < FieldResolverDelegate > ( current , context ) . Compile ( ) ) ) ;
477
+ }
478
+ }
479
+
401
480
private void AddServiceTypeToQueryType (
402
481
ITypeCompletionContext completionContext ,
403
482
TypeSystemConfiguration ? definition )
@@ -409,7 +488,7 @@ private void AddServiceTypeToQueryType(
409
488
410
489
var objectTypeCfg = ( ObjectTypeConfiguration ) definition ! ;
411
490
objectTypeCfg . Fields . Add ( ServerFields . CreateServiceField ( _context ) ) ;
412
- if ( _entityTypes . Count > 0 )
491
+ if ( _entityTypes . Count > 0 || _entityInterfaceTypes . Count > 0 )
413
492
{
414
493
objectTypeCfg . Fields . Add ( ServerFields . CreateEntitiesField ( _context ) ) ;
415
494
}
@@ -452,6 +531,43 @@ private void ApplyMethodLevelReferenceResolvers(
452
531
descriptor . CreateConfiguration ( ) ;
453
532
}
454
533
534
+ private void ApplyMethodLevelReferenceResolvers (
535
+ InterfaceType interfaceType ,
536
+ InterfaceTypeConfiguration interfaceTypeCfg )
537
+ {
538
+ if ( interfaceType . RuntimeType == typeof ( object ) )
539
+ {
540
+ return ;
541
+ }
542
+
543
+ var descriptor = InterfaceTypeDescriptor . From ( _context , interfaceTypeCfg ) ;
544
+
545
+ // Static methods won't end up in the schema as fields.
546
+ // The default initialization system only considers instance methods,
547
+ // so we have to handle the attributes for those manually.
548
+ var potentiallyUnregisteredReferenceResolvers = interfaceType . RuntimeType
549
+ . GetMethods ( BindingFlags . Static | BindingFlags . Public ) ;
550
+
551
+ foreach ( var possibleReferenceResolver in potentiallyUnregisteredReferenceResolvers )
552
+ {
553
+ if ( ! possibleReferenceResolver . IsDefined ( typeof ( ReferenceResolverAttribute ) ) )
554
+ {
555
+ continue ;
556
+ }
557
+
558
+ foreach ( var attribute in possibleReferenceResolver . GetCustomAttributes ( true ) )
559
+ {
560
+ if ( attribute is ReferenceResolverAttribute casted )
561
+ {
562
+ casted . TryConfigure ( _context , descriptor , possibleReferenceResolver ) ;
563
+ }
564
+ }
565
+ }
566
+
567
+ // This seems to re-detect the entity resolver and save it into the context data.
568
+ descriptor . CreateConfiguration ( ) ;
569
+ }
570
+
455
571
private void AddToUnionIfHasTypeLevelKeyDirective (
456
572
ObjectType objectType ,
457
573
ObjectTypeConfiguration objectTypeCfg )
@@ -469,6 +585,23 @@ private void AddToUnionIfHasTypeLevelKeyDirective(
469
585
}
470
586
}
471
587
588
+ private void AddToUnionIfHasTypeLevelKeyDirective (
589
+ InterfaceType interfaceType ,
590
+ InterfaceTypeConfiguration interfaceTypeCfg )
591
+ {
592
+ if ( interfaceTypeCfg . Directives . FirstOrDefault ( d => d . Value is KeyDirective ) is { } keyDirective &&
593
+ ( ( KeyDirective ) keyDirective . Value ) . Resolvable )
594
+ {
595
+ _entityInterfaceTypes . Add ( interfaceType ) ;
596
+ return ;
597
+ }
598
+
599
+ if ( interfaceTypeCfg . Fields . Any ( f => f . Features . TryGet ( out KeyMarker ? key ) && key . Resolvable ) )
600
+ {
601
+ _entityInterfaceTypes . Add ( interfaceType ) ;
602
+ }
603
+ }
604
+
472
605
private void AggregatePropertyLevelKeyDirectives (
473
606
ObjectType objectType ,
474
607
ObjectTypeConfiguration objectTypeCfg ,
@@ -532,6 +665,69 @@ private void AggregatePropertyLevelKeyDirectives(
532
665
}
533
666
}
534
667
668
+ private void AggregatePropertyLevelKeyDirectives (
669
+ InterfaceType interfaceType ,
670
+ InterfaceTypeConfiguration interfaceTypeCfg ,
671
+ ITypeDiscoveryContext discoveryContext )
672
+ {
673
+ // if we find key markers on our fields, we need to construct the key directive
674
+ // from the annotated fields.
675
+ var foundMarkers = interfaceTypeCfg . Fields . Any ( f => f . Features . TryGet ( out KeyMarker ? _ ) ) ;
676
+
677
+ if ( ! foundMarkers )
678
+ {
679
+ return ;
680
+ }
681
+
682
+ IReadOnlyList < InterfaceFieldConfiguration > fields = interfaceTypeCfg . Fields ;
683
+ var fieldSet = new StringBuilder ( ) ;
684
+ bool ? resolvable = null ;
685
+
686
+ foreach ( var fieldDefinition in fields )
687
+ {
688
+ if ( fieldDefinition . Features . TryGet ( out KeyMarker ? key ) )
689
+ {
690
+ if ( resolvable is null )
691
+ {
692
+ resolvable = key . Resolvable ;
693
+ }
694
+ else if ( resolvable != key . Resolvable )
695
+ {
696
+ throw Key_FieldSet_ResolvableMustBeConsistent ( fieldDefinition . Member ! ) ;
697
+ }
698
+
699
+ if ( fieldSet . Length > 0 )
700
+ {
701
+ fieldSet . Append ( ' ' ) ;
702
+ }
703
+
704
+ fieldSet . Append ( fieldDefinition . Name ) ;
705
+ }
706
+ }
707
+
708
+ // add the key directive with the dynamically generated field set.
709
+ AddKeyDirective ( interfaceTypeCfg , fieldSet . ToString ( ) , resolvable ?? true ) ;
710
+
711
+ // register dependency to the key directive so that it is completed before
712
+ // we complete this type.
713
+ foreach ( var directiveDefinition in interfaceTypeCfg . Directives )
714
+ {
715
+ discoveryContext . Dependencies . Add (
716
+ new TypeDependency (
717
+ directiveDefinition . Type ,
718
+ TypeDependencyFulfilled . Completed ) ) ;
719
+
720
+ discoveryContext . Dependencies . Add ( new ( directiveDefinition . Type ) ) ;
721
+ }
722
+
723
+ if ( resolvable ?? true )
724
+ {
725
+ // since this type has now a key directive we also need to add this type to
726
+ // the _Entity union type provided that the key is resolvable.
727
+ _entityInterfaceTypes . Add ( interfaceType ) ;
728
+ }
729
+ }
730
+
535
731
private void AddMemberTypesToTheEntityUnionType (
536
732
ITypeCompletionContext completionContext ,
537
733
TypeSystemConfiguration ? definition )
@@ -556,4 +752,15 @@ private void AddKeyDirective(
556
752
new KeyDirective ( fieldSet , resolvable ) ,
557
753
_keyDirectiveReference ) ) ;
558
754
}
755
+
756
+ private void AddKeyDirective (
757
+ InterfaceTypeConfiguration interfaceTypeCfg ,
758
+ string fieldSet ,
759
+ bool resolvable )
760
+ {
761
+ interfaceTypeCfg . Directives . Add (
762
+ new DirectiveConfiguration (
763
+ new KeyDirective ( fieldSet , resolvable ) ,
764
+ _keyDirectiveReference ) ) ;
765
+ }
559
766
}
0 commit comments