-
Notifications
You must be signed in to change notification settings - Fork 158
Expand file tree
/
Copy pathexecution_plan.go
More file actions
1643 lines (1378 loc) · 54 KB
/
execution_plan.go
File metadata and controls
1643 lines (1378 loc) · 54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package grpcdatasource
import (
"errors"
"fmt"
"slices"
"strings"
"github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
"github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor"
"github.com/wundergraph/graphql-go-tools/v2/pkg/engine/plan"
"github.com/wundergraph/graphql-go-tools/v2/pkg/internal/unsafebytes"
)
const (
// knownTypeOptionalFieldValueName is the name of the field that is used to wrap optional scalar values
// in a message as protobuf scalar types are not nullable.
knownTypeOptionalFieldValueName = "value"
// fieldResolverDirectiveName is the name of the directive that is used to configure the resolver context.
fieldResolverDirectiveName = "connect__fieldResolver"
// requiresDirectiveName specifies the name of the @requires federation directive.
requiresDirectiveName = "requires"
// typenameFieldName is the name of the field that is used to store the typename of the object.
typenameFieldName = "__typename"
)
const (
// resultFieldName is the name of the field that is used to store the result of the RPC call.
resultFieldName = "result"
// contextFieldName is the name of the field that is used to store the context of the RPC call.
contextFieldName = "context"
// fieldArgsFieldName is the name of the field that is used to store the field arguments of the RPC call.
fieldArgsFieldName = "field_args"
// requiresArgumentsFieldName is the name of the field that is used to store the required fields arguments of the RPC call.
requiresArgumentsFieldName = "fields"
)
// OneOfType represents the type of a oneof field in a protobuf message.
// It can be either an interface or a union type.
type OneOfType uint8
// OneOfType constants define the different types of oneof fields.
const (
// OneOfTypeNone represents no oneof type (default/zero value)
OneOfTypeNone OneOfType = iota
// OneOfTypeInterface represents an interface type oneof field
OneOfTypeInterface
// OneOfTypeUnion represents a union type oneof field
OneOfTypeUnion
)
// FieldName returns the corresponding field name for the OneOfType.
// For interfaces, it returns "instance", for unions it returns "value".
// Returns an empty string for invalid or unknown types.
func (o OneOfType) FieldName() string {
switch o {
case OneOfTypeInterface:
return "instance"
case OneOfTypeUnion:
return "value"
}
return ""
}
// RPCExecutionPlan represents a plan for executing one or more RPC calls
// to gRPC services. It defines the sequence of calls and their dependencies.
type RPCExecutionPlan struct {
// Calls is a list of gRPC calls that are executed in the same group
Calls []RPCCall
// TODO add mapping to the execution plan
// instead of the planner and the compiler?
}
// CallKind is the type of call operation to perform.
type CallKind uint8
const (
// CallKindStandard is a basic fetch operation.
CallKindStandard CallKind = iota
// CallKindEntity is a fetch operation for entities.
CallKindEntity
// CallKindResolve is a fetch operation for resolving field values.
CallKindResolve
// CallKindRequired is a fetch operation which is similar to Resolve, but it can be executed in parallel.
// Required fields are indicated by the @requires federation directive.
CallKindRequired
)
// RPCCall represents a single call to a gRPC service method.
// It contains all the information needed to make the call and process the response.
type RPCCall struct {
// ID indicates the expected index of the call in the execution plan
ID int
// Kind of call, used to decide how to execute the call
// This is used to identify the call type and execution behaviour.
Kind CallKind
// DependentCalls is a list of calls that must be executed before this call
DependentCalls []int
// ServiceName is the name of the gRPC service to call
ServiceName string
// MethodName is the name of the method on the service to call
MethodName string
// Request contains the message structure for the gRPC request
Request RPCMessage
// Response contains the message structure for the gRPC response
Response RPCMessage
// ResponsePath is the path to the response in the JSON response
ResponsePath ast.Path
// RequestedEntityType is the type of the entity that is being requested
RequestedEntityType string
}
// RPCMessage represents a gRPC message structure for requests and responses.
// It defines the structure of the message including all its fields.
type RPCMessage struct {
// Name is the name of the message type in the protobuf definition
Name string
// Fields is a list of fields in the message
Fields RPCFields
// FragmentFields are field selections based on inline fragments
FragmentFields RPCFieldSelectionSet
// OneOfType indicates the type of the oneof field
OneOfType OneOfType
// MemberTypes provides the names of the types that are implemented by the Interface or Union
MemberTypes []string
}
// IsOneOf checks if the message is a oneof field.
func (r *RPCMessage) IsOneOf() bool {
switch r.OneOfType {
case OneOfTypeInterface, OneOfTypeUnion:
return true
}
return false
}
// SelectValidTypes returns the valid types for a given type name.
func (r *RPCMessage) SelectValidTypes(typeName string) []string {
if r.Name == typeName {
return []string{r.Name}
}
// If we have an interface or union type, we need to select the provided type as well.
return []string{r.Name, typeName}
}
// RPCFieldSelectionSet is a map of field selections based on inline fragments
type RPCFieldSelectionSet map[string]RPCFields
// Add adds a field selection set to the map
func (r RPCFieldSelectionSet) Add(fragmentName string, field ...RPCField) {
r[fragmentName] = append(r[fragmentName], field...)
}
// SelectFieldsForTypes returns the fields for the given valid types.
// It also makes sure to deduplicate the fields.
func (r RPCFieldSelectionSet) SelectFieldsForTypes(validTypes []string) RPCFields {
fieldSet := make(map[string]struct{})
fields := make(RPCFields, 0)
for _, typeName := range validTypes {
lookupFields, ok := r[typeName]
if !ok {
continue
}
for _, field := range lookupFields {
if _, found := fieldSet[field.AliasOrPath()]; found {
continue
}
fieldSet[field.AliasOrPath()] = struct{}{}
fields = append(fields, field)
}
}
return fields
}
// RPCField represents a single field in a gRPC message.
// It contains all information required to extract data from GraphQL variables
// and construct the appropriate protobuf field.
type RPCField struct {
// Alias can be used to rename the field in the request message
// This is needed to make sure that during the json composition,
// the field names match the GraphQL request naming.
Alias string
// Repeated indicates if the field is a repeated field (array/list)
Repeated bool
// Name is the name of the field as defined in the protobuf message
Name string
// ProtoTypeName is the name of the type of the field in the protobuf definition
ProtoTypeName DataType
// JSONPath either holds the path to the variable definition for the request message,
// or defines the name of the response field in the message.
JSONPath string
// ResolvePath is used to resolve values from another message.
ResolvePath ast.Path
// EnumName is the name of the enum if the field is an enum type
EnumName string
// StaticValue is the static value of the field
StaticValue string
// Optional indicates if the field is optional
Optional bool
// IsListType indicates if the field is a list wrapper type
IsListType bool
// ListMetadata contains the metadata for the list type
ListMetadata *ListMetadata
// Message represents the nested message type definition for complex fields.
// This enables recursive construction of nested protobuf message structures.
Message *RPCMessage
}
// ListMetadata contains the metadata for the list type
type ListMetadata struct {
// NestingLevel is the nesting level of the list type
NestingLevel int
// LevelInfo contains the metadata for each nesting level of the list
LevelInfo []LevelInfo
}
// LevelInfo contains the metadata for the list type
type LevelInfo struct {
// Optional indicates if the field is optional
Optional bool
}
// ToOptionalTypeMessage returns a message that wraps the scalar value in a message
// as protobuf scalar types are not nullable.
func (r *RPCField) ToOptionalTypeMessage(protoName string) *RPCMessage {
if r == nil {
return nil
}
return &RPCMessage{
Name: protoName,
Fields: RPCFields{
RPCField{
Name: knownTypeOptionalFieldValueName,
JSONPath: r.JSONPath,
ProtoTypeName: r.ProtoTypeName,
Repeated: r.Repeated,
EnumName: r.EnumName,
},
},
}
}
// AliasOrPath returns the alias of the field if it exists, otherwise it returns the JSONPath.
func (r *RPCField) AliasOrPath() string {
if r.Alias != "" {
return r.Alias
}
return r.JSONPath
}
// IsOptionalScalar checks if the field is an optional scalar value.
func (r *RPCField) IsOptionalScalar() bool {
return r.Optional && r.ProtoTypeName != DataTypeMessage
}
// RPCFields is a list of RPCFields that provides helper methods
// for working with collections of fields.
type RPCFields []RPCField
// ByName returns a field by its name from the collection of fields.
// Returns nil if no field with the given name exists.
func (r RPCFields) ByName(name string) *RPCField {
for _, field := range r {
if field.Name == name {
return &field
}
}
return nil
}
// Exists checks if a field with the given name and alias exists in the collection of fields.
func (r RPCFields) Exists(name, alias string) bool {
for _, field := range r {
if field.Name == name && field.Alias == alias {
return true
}
}
return false
}
// Last returns the last element of r or nil if r has no elements.
func (r RPCFields) Last() *RPCField {
if len(r) == 0 {
return nil
}
return &r[len(r)-1]
}
func (r *RPCExecutionPlan) String() string {
var result strings.Builder
result.WriteString("RPCExecutionPlan:\n")
for _, call := range r.Calls {
fmt.Fprintf(&result, " Call %d:\n", call.ID)
if len(call.DependentCalls) > 0 {
result.WriteString(" DependentCalls: [")
for k, depID := range call.DependentCalls {
if k > 0 {
result.WriteString(", ")
}
fmt.Fprintf(&result, "%d", depID)
}
result.WriteString("]\n")
} else {
result.WriteString(" DependentCalls: []\n")
}
fmt.Fprintf(&result, " Service: %s\n", call.ServiceName)
fmt.Fprintf(&result, " Method: %s\n", call.MethodName)
result.WriteString(" Request:\n")
formatRPCMessage(&result, call.Request, 8)
result.WriteString(" Response:\n")
formatRPCMessage(&result, call.Response, 8)
}
return result.String()
}
type PlanVisitor interface {
PlanOperation(operation, definition *ast.Document) (*RPCExecutionPlan, error)
}
// NewPlanner returns a new PlanVisitor instance.
//
// The planner is responsible for creating an RPCExecutionPlan from a given
// GraphQL operation. It is used by the engine to execute operations against
// gRPC services.
func NewPlanner(subgraphName string, mapping *GRPCMapping, federationConfigs plan.FederationFieldConfigurations) (PlanVisitor, error) {
if mapping == nil {
return nil, fmt.Errorf("mapping is required")
}
if len(federationConfigs) > 0 {
return newRPCPlanVisitorFederation(rpcPlanVisitorConfig{
subgraphName: subgraphName,
mapping: mapping,
federationConfigs: federationConfigs,
}), nil
}
return newRPCPlanVisitor(rpcPlanVisitorConfig{
subgraphName: subgraphName,
mapping: mapping,
federationConfigs: federationConfigs,
}), nil
}
// formatRPCMessage formats an RPCMessage and adds it to the string builder with the specified indentation
func formatRPCMessage(sb *strings.Builder, message RPCMessage, indent int) {
visited := make(map[*RPCMessage]struct{})
formatRPCMessageVisited(sb, message, indent, visited)
}
func formatRPCMessageVisited(sb *strings.Builder, message RPCMessage, indent int, visited map[*RPCMessage]struct{}) {
indentStr := strings.Repeat(" ", indent)
fmt.Fprintf(sb, "%sName: %s\n", indentStr, message.Name)
fmt.Fprintf(sb, "%sFields:\n", indentStr)
for _, field := range message.Fields {
fmt.Fprintf(sb, "%s - Name: %s\n", indentStr, field.Name)
fmt.Fprintf(sb, "%s TypeName: %s (%d)\n", indentStr, field.ProtoTypeName.String(), field.ProtoTypeName)
fmt.Fprintf(sb, "%s Repeated: %v\n", indentStr, field.Repeated)
fmt.Fprintf(sb, "%s JSONPath: %s\n", indentStr, field.JSONPath)
fmt.Fprintf(sb, "%s ResolvePath: %s\n", indentStr, field.ResolvePath.String())
if field.Message == nil {
return
}
fmt.Fprintf(sb, "%s Message:\n", indentStr)
if _, seen := visited[field.Message]; seen {
fmt.Fprintf(sb, "%s <recursive: %s>\n", indentStr, field.Message.Name)
continue
}
visited[field.Message] = struct{}{}
formatRPCMessageVisited(sb, *field.Message, indent+6, visited)
}
}
type rpcPlanningContext struct {
operation *ast.Document
definition *ast.Document
mapping *GRPCMapping
visitedInputTypes map[string]*RPCMessage
}
// newRPCPlanningContext creates a new RPCPlanningContext.
func newRPCPlanningContext(operation *ast.Document, definition *ast.Document, mapping *GRPCMapping) *rpcPlanningContext {
return &rpcPlanningContext{
operation: operation,
definition: definition,
mapping: mapping,
visitedInputTypes: make(map[string]*RPCMessage, len(definition.InputObjectTypeDefinitions)),
}
}
// toDataType converts an ast.Type to a DataType.
// It handles the different type kinds and non-null types.
func (r *rpcPlanningContext) toDataType(t *ast.Type) DataType {
switch t.TypeKind {
case ast.TypeKindNamed:
return r.parseGraphQLType(t)
case ast.TypeKindList:
return r.toDataType(&r.definition.Types[t.OfType])
case ast.TypeKindNonNull:
return r.toDataType(&r.definition.Types[t.OfType])
}
return DataTypeUnknown
}
// parseGraphQLType parses an ast.Type and returns the corresponding DataType.
// It handles the different type kinds and non-null types.
func (r *rpcPlanningContext) parseGraphQLType(t *ast.Type) DataType {
dt := r.definition.Input.ByteSlice(t.Name)
// Retrieve the node to check the kind
node, found := r.definition.NodeByName(dt)
if !found {
return fromGraphQLType(dt)
}
// For non-scalar types, return the corresponding DataType
switch node.Kind {
case ast.NodeKindInterfaceTypeDefinition:
fallthrough
case ast.NodeKindUnionTypeDefinition:
fallthrough
case ast.NodeKindObjectTypeDefinition, ast.NodeKindInputObjectTypeDefinition:
return DataTypeMessage
case ast.NodeKindEnumTypeDefinition:
return DataTypeEnum
default:
return fromGraphQLType(dt)
}
}
// resolveRPCMethodMapping resolves the RPC method mapping for a given operation type and operation field name.
func (r *rpcPlanningContext) resolveRPCMethodMapping(operationType ast.OperationType, operationFieldName string) (RPCConfig, error) {
if r.mapping == nil {
return RPCConfig{}, nil
}
var rpcConfig RPCConfig
var ok bool
switch operationType {
case ast.OperationTypeQuery:
rpcConfig, ok = r.mapping.QueryRPCs[operationFieldName]
case ast.OperationTypeMutation:
rpcConfig, ok = r.mapping.MutationRPCs[operationFieldName]
case ast.OperationTypeSubscription:
rpcConfig, ok = r.mapping.SubscriptionRPCs[operationFieldName]
}
if !ok {
return RPCConfig{}, nil
}
// We require all fields to be present when defining a mapping for the operation
if rpcConfig.RPC == "" {
return RPCConfig{}, fmt.Errorf("no rpc method name mapping found for operation %s", operationFieldName)
}
if rpcConfig.Request == "" {
return RPCConfig{}, fmt.Errorf("no request message name mapping found for operation %s", operationFieldName)
}
if rpcConfig.Response == "" {
return RPCConfig{}, fmt.Errorf("no response message name mapping found for operation %s", operationFieldName)
}
return rpcConfig, nil
}
// enterNestedField handles descending into a nested response message
// when entering a selection set. inlineFragmentRef identifies the inline fragment
// that directly contains the field being descended into (ast.InvalidRef if none).
// Returns true if it descended into a nested message, false if there was nothing
// to descend into.
func (r *rpcPlanningContext) enterNestedField(info *planningInfo, enclosingTypeNode ast.Node, selectionSetRef, inlineFragmentRef int) bool {
lastField := r.lastResponseField(info.currentResponseMessage, inlineFragmentRef)
if lastField == nil {
return false
}
if lastField.Message == nil {
lastField.Message = r.newMessageFromSelectionSet(enclosingTypeNode, selectionSetRef)
}
info.responseMessageAncestors = append(info.responseMessageAncestors, info.currentResponseMessage)
info.currentResponseMessage = lastField.Message
return true
}
// lastResponseField returns a pointer to the last field (or fragment field) of the message,
// or nil if there are no fields.
func (r *rpcPlanningContext) lastResponseField(msg *RPCMessage, inlineFragmentRef int) *RPCField {
if inlineFragmentRef == ast.InvalidRef {
return msg.Fields.Last()
}
inlineFragmentName := r.operation.InlineFragmentTypeConditionNameString(inlineFragmentRef)
return msg.FragmentFields[inlineFragmentName].Last()
}
// leaveNestedField pops the response message ancestors when leaving a selection set.
func (r *rpcPlanningContext) leaveNestedField(info *planningInfo) {
if len(info.responseMessageAncestors) > 0 {
info.currentResponseMessage = info.responseMessageAncestors[len(info.responseMessageAncestors)-1]
info.responseMessageAncestors = info.responseMessageAncestors[:len(info.responseMessageAncestors)-1]
}
}
// newMessageFromSelectionSet creates a new message from the enclosing type node and the selection set reference.
func (r *rpcPlanningContext) newMessageFromSelectionSet(enclosingTypeNode ast.Node, selectSetRef int) *RPCMessage {
message := &RPCMessage{
Name: enclosingTypeNode.NameString(r.definition),
Fields: make(RPCFields, 0, len(r.operation.SelectionSets[selectSetRef].SelectionRefs)),
}
return message
}
func (r *rpcPlanningContext) findResolverFieldMapping(typeName, fieldName string) string {
resolveConfig := r.mapping.FindResolveTypeFieldMapping(typeName, fieldName)
if resolveConfig == nil {
return fieldName
}
return resolveConfig.FieldMappingData.TargetName
}
// resolveFieldMapping resolves the field mapping for a field.
// This applies both for complex types in the input and for all fields in the response.
func (r *rpcPlanningContext) resolveFieldMapping(typeName, fieldName string) string {
if grpcFieldName, ok := r.mapping.FindFieldMapping(typeName, fieldName); ok {
return grpcFieldName
}
return fieldName
}
// resolveFieldArgumentMapping resolves the field argument mapping for a given type name, field name and argument name.
func (r *rpcPlanningContext) resolveFieldArgumentMapping(typeName, fieldName, argumentName string) string {
if grpcFieldName, ok := r.mapping.FindFieldArgumentMapping(typeName, fieldName, argumentName); ok {
return grpcFieldName
}
return argumentName
}
// typeIsNullableOrNestedList checks if a type is nullable or a nested list.
func (r *rpcPlanningContext) typeIsNullableOrNestedList(typeRef int) bool {
if !r.definition.TypeIsNonNull(typeRef) && r.definition.TypeIsList(typeRef) {
return true
}
if r.definition.TypeNumberOfListWraps(typeRef) > 1 {
return true
}
return false
}
// createListMetadata creates a list metadata for a given type reference.
func (r *rpcPlanningContext) createListMetadata(typeRef int) (*ListMetadata, error) {
nestingLevel := r.definition.TypeNumberOfListWraps(typeRef)
md := &ListMetadata{
NestingLevel: nestingLevel,
LevelInfo: make([]LevelInfo, nestingLevel),
}
for i := 0; i < nestingLevel; i++ {
md.LevelInfo[i] = LevelInfo{
Optional: !r.definition.TypeIsNonNull(typeRef),
}
typeRef = r.definition.ResolveNestedListOrListType(typeRef)
if typeRef == ast.InvalidRef {
return nil, fmt.Errorf("unable to resolve underlying list type for ref: %d", typeRef)
}
}
return md, nil
}
// buildField builds a field from a field definition.
// It handles lists, enums, and other types.
func (r *rpcPlanningContext) buildField(parentTypeName string, fieldDef int, fieldName, fieldAlias string) (RPCField, error) {
fieldDefType := r.definition.FieldDefinitionType(fieldDef)
typeName := r.toDataType(&r.definition.Types[fieldDefType])
field := RPCField{
Name: r.resolveFieldMapping(parentTypeName, fieldName),
Alias: fieldAlias,
Optional: !r.definition.TypeIsNonNull(fieldDefType),
JSONPath: fieldName,
ProtoTypeName: typeName,
}
if r.definition.TypeIsList(fieldDefType) {
switch {
// for nullable or nested lists we need to build a wrapper message
// Nullability is handled by the datasource during the execution.
case r.typeIsNullableOrNestedList(fieldDefType):
md, err := r.createListMetadata(fieldDefType)
if err != nil {
return field, err
}
field.ListMetadata = md
field.IsListType = true
default:
// For non-nullable single lists we can directly use the repeated syntax in protobuf.
field.Repeated = true
}
}
if typeName == DataTypeEnum {
field.EnumName = r.definition.FieldDefinitionTypeNameString(fieldDef)
}
if fieldName == typenameFieldName {
field.StaticValue = parentTypeName
}
return field, nil
}
// createRPCFieldFromFieldArgument builds an RPCField from an input value definition.
// It handles scalar, enum, and input object types.
// If the type is an input object type, a message is created and added to the field.
func (r *rpcPlanningContext) createRPCFieldFromFieldArgument(fieldArg fieldArgument) (RPCField, error) {
argDef := r.definition.InputValueDefinitions[fieldArg.argumentDefinitionRef]
argName := r.definition.Input.ByteSliceString(argDef.Name)
underlyingTypeNode, found := r.definition.ResolveNodeFromTypeRef(argDef.Type)
if !found {
return RPCField{}, fmt.Errorf("unable to resolve underlying type node for argument %s", argName)
}
var (
fieldMessage *RPCMessage
err error
dt = DataTypeMessage
)
// only scalar, enum and input object types are supported
switch underlyingTypeNode.Kind {
case ast.NodeKindScalarTypeDefinition, ast.NodeKindEnumTypeDefinition:
dt = r.toDataType(&r.definition.Types[argDef.Type])
case ast.NodeKindInputObjectTypeDefinition:
// If the type is an input object type, a message is created and added to the field.
if fieldMessage, err = r.buildMessageFromInputObjectType(&underlyingTypeNode); err != nil {
return RPCField{}, err
}
default:
return RPCField{}, fmt.Errorf("unsupported type: %s", underlyingTypeNode.Kind)
}
parentTypeName := fieldArg.parentTypeNode.NameString(r.definition)
fieldName := r.definition.FieldDefinitionNameString(fieldArg.fieldDefinitionRef)
mappedName := r.resolveFieldArgumentMapping(parentTypeName, fieldName, argName)
field, err := r.buildInputMessageField(
argDef.Type,
mappedName,
fieldArg.jsonPath,
dt,
)
if err != nil {
return RPCField{}, err
}
field.Message = fieldMessage
return field, nil
}
// buildMessageFromInputObjectType builds a message from an input object type definition.
func (r *rpcPlanningContext) buildMessageFromInputObjectType(node *ast.Node) (*RPCMessage, error) {
if node.Kind != ast.NodeKindInputObjectTypeDefinition {
return nil, fmt.Errorf("unable to build message from input object type definition - incorrect type: %s", node.Kind)
}
typeName := node.NameString(r.definition)
// If we've already started building this type, return the in-progress message
// pointer to break the recursion cycle. The message's fields are populated by
// the caller that first entered this type, so the pointer will be complete once
// the top-level call returns.
if existing, ok := r.visitedInputTypes[typeName]; ok {
return existing, nil
}
inputObjectDefinition := r.definition.InputObjectTypeDefinitions[node.Ref]
message := &RPCMessage{
Name: typeName,
Fields: make(RPCFields, 0, len(inputObjectDefinition.InputFieldsDefinition.Refs)),
}
// Register the message before recursing into fields so that recursive
// references resolve to this same pointer.
r.visitedInputTypes[typeName] = message
for _, inputFieldRef := range inputObjectDefinition.InputFieldsDefinition.Refs {
field, err := r.buildMessageFieldFromInputValueDefinition(inputFieldRef, node)
if err != nil {
return nil, err
}
message.Fields = append(message.Fields, field)
}
return message, nil
}
// buildMessageFieldFromInputValueDefinition builds an RPCField from an input value definition.
func (r *rpcPlanningContext) buildMessageFieldFromInputValueDefinition(ivdRef int, node *ast.Node) (RPCField, error) {
inputValueDef := r.definition.InputValueDefinitions[ivdRef]
inputValueDefType := r.definition.Types[inputValueDef.Type]
// We need to resolve the underlying type to determine whether we are building a nested message or a scalar type.
underlyingTypeNode, found := r.definition.ResolveNodeFromTypeRef(inputValueDef.Type)
if !found {
return RPCField{}, fmt.Errorf("unable to resolve underlying type node for input value definition %s", r.definition.Input.ByteSliceString(inputValueDef.Name))
}
var (
fieldMessage *RPCMessage
err error
)
// If the type is an input object type, we need to build a nested message.
dt := DataTypeMessage
switch underlyingTypeNode.Kind {
case ast.NodeKindInputObjectTypeDefinition:
fieldMessage, err = r.buildMessageFromInputObjectType(&underlyingTypeNode)
if err != nil {
return RPCField{}, err
}
default:
dt = r.toDataType(&inputValueDefType)
}
fieldName := r.definition.Input.ByteSliceString(inputValueDef.Name)
mappedName := r.resolveFieldMapping(node.NameString(r.definition), fieldName)
field, err := r.buildInputMessageField(inputValueDef.Type, mappedName, fieldName, dt)
if err != nil {
return RPCField{}, err
}
field.Message = fieldMessage
return field, nil
}
// buildInputMessageField builds an RPCField from an input value definition.
// It handles scalar, enum and list types.
func (r *rpcPlanningContext) buildInputMessageField(typeRef int, fieldName, jsonPath string, dt DataType) (RPCField, error) {
field := RPCField{
Name: fieldName,
Optional: !r.definition.TypeIsNonNull(typeRef),
ProtoTypeName: dt,
JSONPath: jsonPath,
}
if r.definition.TypeIsList(typeRef) {
switch {
// for nullable or nested lists we need to build a wrapper message
// Nullability is handled by the datasource during the execution.
case r.typeIsNullableOrNestedList(typeRef):
md, err := r.createListMetadata(typeRef)
if err != nil {
return field, err
}
field.ListMetadata = md
field.IsListType = true
default:
// For non-nullable single lists we can directly use the repeated syntax in protobuf.
field.Repeated = true
}
}
if dt == DataTypeEnum {
field.EnumName = r.definition.ResolveTypeNameString(typeRef)
}
return field, nil
}
// buildFieldMessage builds a message from a field definition.
// It handles complex and composite types.
func (r *rpcPlanningContext) buildFieldMessage(fieldTypeNode ast.Node, fieldRef int) (*RPCMessage, error) {
field := r.operation.Fields[fieldRef]
if !field.HasSelections {
return nil, fmt.Errorf("unable to build field message: field %s has no selections", r.operation.FieldAliasOrNameString(fieldRef))
}
fieldRefs := make([]int, 0)
inlineFragmentRefs := make([]int, 0)
selections := r.operation.SelectionSets[field.SelectionSet].SelectionRefs
for i := range selections {
selection := r.operation.Selections[selections[i]]
switch selection.Kind {
case ast.SelectionKindField:
fieldRefs = append(fieldRefs, selection.Ref)
case ast.SelectionKindInlineFragment:
inlineFragmentRefs = append(inlineFragmentRefs, selection.Ref)
}
}
message := &RPCMessage{
Name: fieldTypeNode.NameString(r.definition),
}
if compositeType := r.getCompositeType(fieldTypeNode); compositeType != OneOfTypeNone {
memberTypes, err := r.getMemberTypes(fieldTypeNode)
if err != nil {
return nil, err
}
message.MemberTypes = memberTypes
message.OneOfType = compositeType
}
for _, inlineFragmentRef := range inlineFragmentRefs {
selectionSetRef, ok := r.operation.InlineFragmentSelectionSet(inlineFragmentRef)
if !ok {
continue
}
typeName := r.operation.InlineFragmentTypeConditionNameString(inlineFragmentRef)
inlineFragmentTypeNode, found := r.definition.NodeByNameStr(typeName)
if !found {
return nil, fmt.Errorf("unable to resolve type node for inline fragment %s", typeName)
}
fields, err := r.buildCompositeFields(inlineFragmentTypeNode, fragmentSelection{
typeName: typeName,
selectionSetRef: selectionSetRef,
})
if err != nil {
return nil, err
}
if message.FragmentFields == nil {
message.FragmentFields = make(RPCFieldSelectionSet)
}
message.FragmentFields.Add(typeName, fields...)
}
for _, fieldRef := range fieldRefs {
fieldDefRef, found := r.definition.NodeFieldDefinitionByName(fieldTypeNode, r.operation.FieldNameBytes(fieldRef))
if !found {
return nil, fmt.Errorf("unable to build required field: field definition not found for field %s", r.operation.FieldNameString(fieldRef))
}
if r.isFieldResolver(fieldDefRef, false) {
continue
}
field, err := r.buildResolverField(fieldTypeNode, fieldRef, fieldDefRef)
if err != nil {
return nil, err
}
message.Fields = append(message.Fields, field)
}
return message, nil
}
// resolveServiceName resolves the service name for a given subgraph name.
func (r *rpcPlanningContext) resolveServiceName(subgraphName string) string {
if r.mapping == nil || r.mapping.Service == "" {
return subgraphName
}
return r.mapping.Service
}
type resolverField struct {
id int
callerRef int
parentTypeNode ast.Node
fieldRef int
fieldDefinitionTypeRef int
fieldsSelectionSetRef int
responsePath ast.Path
contextPath ast.Path
contextFields []contextField
fieldArguments []fieldArgument
fragmentSelections []fragmentSelection
fragmentType OneOfType
listNestingLevel int
memberTypes []string
}
type fragmentSelection struct {
typeName string
selectionSetRef int
}
// enterResolverCompositeSelectionSet handles logic when entering a composite selection set for a given field resolver.
// It appends the inline fragment selections to the resolved field and sets the fragment type.
func (r *rpcPlanningContext) enterResolverCompositeSelectionSet(oneOfType OneOfType, selectionSetRef int, resolvedField *resolverField) {
resolvedField.fieldsSelectionSetRef = ast.InvalidRef
resolvedField.fragmentType = oneOfType
// In case of an interface we can select individual fields from the interface without having to use an inline fragment.
if len(r.operation.SelectionSetFieldRefs(selectionSetRef)) > 0 {
resolvedField.fieldsSelectionSetRef = selectionSetRef
}
inlineFragSelections := r.operation.SelectionSetInlineFragmentSelections(selectionSetRef)
if len(inlineFragSelections) == 0 {
return
}
for _, inlineFragSelectionRef := range inlineFragSelections {
inlineFragRef := r.operation.Selections[inlineFragSelectionRef].Ref
inlinFragSelectionSetRef, ok := r.operation.InlineFragmentSelectionSet(inlineFragRef)
if !ok {
continue
}
resolvedField.fragmentSelections = append(resolvedField.fragmentSelections, fragmentSelection{
typeName: r.operation.InlineFragmentTypeConditionNameString(inlineFragRef),
selectionSetRef: inlinFragSelectionSetRef,
})
}
}
// isFieldResolver checks if a field is a field resolver.
func (r *rpcPlanningContext) isFieldResolver(fieldDefRef int, isRootField bool) bool {
if isRootField || fieldDefRef == ast.InvalidRef {
return false
}
return r.definition.FieldDefinitionHasArgumentsDefinitions(fieldDefRef) || r.hasFieldResolverDirective(fieldDefRef)
}
// hasFieldResolverDirective checks if a field has a field resolver directive.
func (r *rpcPlanningContext) hasFieldResolverDirective(fieldDefRef int) bool {
return r.definition.FieldDefinitionHasNamedDirective(fieldDefRef, fieldResolverDirectiveName)
}
// isRequiredField checks if a field is a required field.
func (r *rpcPlanningContext) isRequiredField(fieldDefRef int) bool {
return r.definition.FieldDefinitionHasNamedDirective(fieldDefRef, requiresDirectiveName)
}
// getCompositeType checks whether the node is an interface or union type.
// It returns OneOfTypeNone for non-composite types.
func (r *rpcPlanningContext) getCompositeType(node ast.Node) OneOfType {
switch node.Kind {
case ast.NodeKindInterfaceTypeDefinition:
return OneOfTypeInterface
case ast.NodeKindUnionTypeDefinition:
return OneOfTypeUnion
default:
return OneOfTypeNone
}
}
func (r *rpcPlanningContext) getMemberTypes(node ast.Node) ([]string, error) {
switch node.Kind {
case ast.NodeKindInterfaceTypeDefinition:
memberTypes, ok := r.definition.InterfaceTypeDefinitionImplementedByObjectWithNames(node.Ref)
if !ok {
return nil, fmt.Errorf("interface type %s is not implemented by any object", r.definition.InterfaceTypeDefinitionNameString(node.Ref))
}
return memberTypes, nil
case ast.NodeKindUnionTypeDefinition:
memberTypes, ok := r.definition.UnionTypeDefinitionMemberTypeNames(node.Ref)
if !ok {
return nil, fmt.Errorf("union type %s is not defined", r.definition.UnionTypeDefinitionNameString(node.Ref))
}
return memberTypes, nil
default:
return nil, fmt.Errorf("invalid node kind: %s", node.Kind)