diff --git a/execution/engine/execution_engine_grpc_test.go b/execution/engine/execution_engine_grpc_test.go index 94bf2f3f6b..3a58ba841a 100644 --- a/execution/engine/execution_engine_grpc_test.go +++ b/execution/engine/execution_engine_grpc_test.go @@ -1432,4 +1432,23 @@ func TestGRPCSubgraphExecution(t *testing.T) { require.Contains(t, response, `"teamsByProject":`) require.Contains(t, response, `"favoriteCategories":`) }) + + t.Run("should handle empty and nullable list items", func(t *testing.T) { + operation := graphql.Request{ + OperationName: "EmptyAndNullableListItems", + Query: `query EmptyAndNullableListItems { + author { + id + authorGroups { + id + name + } + } + }`, + } + + response, err := executeOperation(t, conn, operation, withGRPCMapping(mapping.DefaultGRPCMapping())) + require.NoError(t, err) + require.Equal(t, `{"data":{"author":{"id":"author-default","authorGroups":[[{"id":"group-auth-1","name":"Team Lead Alpha"},{"id":"group-auth-2","name":"Senior Dev Beta"}],[{"id":"group-auth-3","name":"Junior Dev Gamma"}],[],null]}}}`, response) + }) } diff --git a/v2/pkg/engine/datasource/grpc_datasource/grpc_datasource.go b/v2/pkg/engine/datasource/grpc_datasource/grpc_datasource.go index 626b4a69f2..26d18b9163 100644 --- a/v2/pkg/engine/datasource/grpc_datasource/grpc_datasource.go +++ b/v2/pkg/engine/datasource/grpc_datasource/grpc_datasource.go @@ -299,11 +299,12 @@ func (d *DataSource) traverseList(level int, arena *astjson.Arena, current *astj msg := data.Get(fd).Message() if !msg.IsValid() { + // If the message is not valid we can either return null if the list is nullable or an error if it is non nullable. if md.LevelInfo[level].Optional { return arena.NewNull(), nil } - return arena.NewArray(), nil + return arena.NewArray(), fmt.Errorf("cannot add null item to response for non nullable list") } fd = msg.Descriptor().Fields().ByNumber(1) @@ -328,11 +329,9 @@ func (d *DataSource) traverseList(level int, arena *astjson.Arena, current *astj list := msg.Get(fd).List() if !list.IsValid() { - if md.LevelInfo[level].Optional { - return arena.NewNull(), nil - } - - return arena.NewNull(), fmt.Errorf("cannot add null item to response for non nullable list") + // If the list is not valid, we return an empty array here as the + // nullabilty is checked on the outer List wrapper type. + return arena.NewArray(), nil } for i := 0; i < list.Len(); i++ { diff --git a/v2/pkg/grpctest/mockservice.go b/v2/pkg/grpctest/mockservice.go index 54f4283f4a..2454a811f7 100644 --- a/v2/pkg/grpctest/mockservice.go +++ b/v2/pkg/grpctest/mockservice.go @@ -1628,6 +1628,10 @@ func (s *MockService) QueryAuthor(ctx context.Context, in *productv1.QueryAuthor {Id: "group-auth-3", Name: "Junior Dev Gamma"}, }, }}, + // empty list + {List: &productv1.ListOfUser_List{}}, + // null item + nil, }, }, },