Skip to content

Commit 701ea8d

Browse files
feat: add composite type support for field resolvers (#1341)
This PR improves the existing functionality for field resolvers. Currently field resolvers do not support returning any form of composite types (interfaces and unions). This feature will be provided in this PR. The planner will now determine the correct selection set and identify, whether the field selections are part of an inline fragment. The calling logic will always inline all fragments provided to the datasource, therefore we do not need to handle fragments and spreads. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Resolver-driven execution planning with nested field-resolver support and enhanced composite-type handling; new GraphQL fields/types (product mascotRecommendation/stockStatus/productDetails, category mascot/categoryStatus/metrics, TestContainer/TestDetails, animal/owner/breed data) and matching RPCs. * **Tests** * Large expansion of test coverage for nested resolvers, composite/interface/union scenarios, federation/entity lookups, execution plan generation, stack utilities, lookups, mappings, and nullable-field cases. * **Chores** * Updated mock services, RPC mappings/protos, test helpers, and internal utilities to support the above. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai --> ## Checklist - [ ] I have discussed my proposed changes in an issue and have received approval to proceed. - [ ] I have followed the coding standards of the project. - [ ] Tests or benchmarks have been added or updated. <!-- Please add any additional information or context regarding your changes here. --> --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 37152c1 commit 701ea8d

28 files changed

+13993
-5214
lines changed

v2/pkg/ast/ast_selection.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,18 @@ func (d *Document) SelectionSetHasFieldSelectionWithExactName(set int, name []by
212212
return false, InvalidRef
213213
}
214214

215+
// SelectionSetFieldRefs returns a list of field refs in the selection set.
216+
// It traverses through the field selections of the selection set and returns the actual field refs.
217+
func (d *Document) SelectionSetFieldRefs(set int) (refs []int) {
218+
for _, selectionRef := range d.SelectionSets[set].SelectionRefs {
219+
if d.Selections[selectionRef].Kind == SelectionKindField {
220+
refs = append(refs, d.Selections[selectionRef].Ref)
221+
}
222+
}
223+
return
224+
}
225+
226+
// SelectionSetFieldSelections returns a list of field selection refs in the selection set.
215227
func (d *Document) SelectionSetFieldSelections(set int) (refs []int) {
216228
for _, selectionRef := range d.SelectionSets[set].SelectionRefs {
217229
if d.Selections[selectionRef].Kind == SelectionKindField {

v2/pkg/astvisitor/visitor.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4021,6 +4021,7 @@ func (w *Walker) FieldDefinitionDirectiveArgumentValueByName(field int, directiv
40214021
}
40224022

40234023
// InRootField returns true if the current field is a root field.
4024+
// This helper function can be used in EnterField and LeaveField.
40244025
func (w *Walker) InRootField() bool {
40254026
return w.CurrentKind == ast.NodeKindField &&
40264027
len(w.Ancestors) == 2 &&

v2/pkg/engine/datasource/grpc_datasource/compiler.go

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,9 @@ func (p *RPCCompiler) buildProtoMessageWithContext(inputMessage Message, rpcMess
604604
val := contextList.NewElement()
605605
valMsg := val.Message()
606606
for fieldName, value := range data {
607-
p.setMessageValue(valMsg, fieldName, value)
607+
if err := p.setMessageValue(valMsg, fieldName, value); err != nil {
608+
return nil, err
609+
}
608610
}
609611

610612
contextList.Append(val)
@@ -625,10 +627,10 @@ func (p *RPCCompiler) buildProtoMessageWithContext(inputMessage Message, rpcMess
625627
if err != nil {
626628
return nil, err
627629
}
628-
629-
// Set the context and args fields
630-
p.setMessageValue(rootMessage, contextSchemaField.Name, protoref.ValueOfList(contextList))
631-
p.setMessageValue(rootMessage, argsRPCField.Name, protoref.ValueOfMessage(args))
630+
// Set the args field
631+
if err := p.setMessageValue(rootMessage, argsRPCField.Name, protoref.ValueOfMessage(args)); err != nil {
632+
return nil, err
633+
}
632634

633635
return rootMessage, nil
634636
}
@@ -736,7 +738,7 @@ func (p *RPCCompiler) resolveDataForPath(messsage protoref.Message, path ast.Pat
736738
switch fd.Kind() {
737739
case protoref.MessageKind:
738740
if fd.IsList() {
739-
return []protoref.Value{field}
741+
return []protoref.Value{protoref.ValueOfList(field.List())}
740742
}
741743

742744
return p.resolveDataForPath(field.Message(), path[1:])
@@ -835,8 +837,33 @@ func (p *RPCCompiler) newEmptyListMessageByName(msg protoref.Message, name strin
835837
return msg.Mutable(msg.Descriptor().Fields().ByName(protoref.Name(name))).List()
836838
}
837839

838-
func (p *RPCCompiler) setMessageValue(message protoref.Message, fieldName string, value protoref.Value) {
840+
func (p *RPCCompiler) setMessageValue(message protoref.Message, fieldName string, value protoref.Value) error {
841+
fd := message.Descriptor().Fields().ByName(protoref.Name(fieldName))
842+
if fd == nil {
843+
return fmt.Errorf("field %s not found in message %s", fieldName, message.Descriptor().Name())
844+
}
845+
846+
// If we are setting a list value here, we need to create a copy of the list
847+
// because the field descriptor is included in the type check, so we cannot asign it using `Set` directly.
848+
if fd.IsList() {
849+
list := message.Mutable(fd).List()
850+
source, ok := value.Interface().(protoref.List)
851+
if !ok {
852+
return fmt.Errorf("value is not a list")
853+
}
854+
855+
p.copyListValues(source, list)
856+
return nil
857+
}
858+
839859
message.Set(message.Descriptor().Fields().ByName(protoref.Name(fieldName)), value)
860+
return nil
861+
}
862+
863+
func (p *RPCCompiler) copyListValues(source protoref.List, destination protoref.List) {
864+
for i := range source.Len() {
865+
destination.Append(source.Get(i))
866+
}
840867
}
841868

842869
// buildProtoMessage recursively builds a protobuf message from an RPCMessage definition

0 commit comments

Comments
 (0)