STET

agent_patch

runs/2026-02-28__13-38-10__gpt-5-4/flux-pr-1155/flux-pr-1155.1-of-1.2026-02-28__13-38-10__gpt-5-4/agent-logs/agent.patch

22628 bytes

Back to adjudication
diff --git a/tmp/agent-patch-flux-pr-1155.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/datasource/graphql_datasource/configuration.go b/app/v2/pkg/engine/datasource/graphql_datasource/configuration.go
index f876776..fe219cb 100644
--- a/tmp/agent-patch-flux-pr-1155.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/datasource/graphql_datasource/configuration.go
+++ b/app/v2/pkg/engine/datasource/graphql_datasource/configuration.go
@@ -96,7 +96,7 @@ func (c *Configuration) FederationConfiguration() *FederationConfiguration {
 }
 
 func (c *Configuration) IsGRPC() bool {
-	return c.grpc != nil
+	return c.grpc != nil && !c.grpc.Disabled
 }
 
 type SingleTypeField struct {
diff --git a/tmp/agent-patch-flux-pr-1155.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/datasource/graphql_datasource/graphql_datasource.go b/app/v2/pkg/engine/datasource/graphql_datasource/graphql_datasource.go
index 1d45769..4697264 100644
--- a/tmp/agent-patch-flux-pr-1155.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/datasource/graphql_datasource/graphql_datasource.go
+++ b/app/v2/pkg/engine/datasource/graphql_datasource/graphql_datasource.go
@@ -317,7 +317,8 @@ func (p *Planner[T]) createInputForQuery() (input, operation []byte) {
 }
 
 func (p *Planner[T]) ConfigureFetch() resolve.FetchConfiguration {
-	if p.config.fetch == nil && p.config.grpc == nil {
+	grpcEnabled := p.config.grpc != nil && !p.config.grpc.Disabled
+	if p.config.fetch == nil && !grpcEnabled {
 		p.stopWithError(errors.WithStack(errors.New("ConfigureFetch: fetch and grpc configuration is empty")))
 		return resolve.FetchConfiguration{}
 	}
@@ -353,8 +354,13 @@ func (p *Planner[T]) ConfigureFetch() resolve.FetchConfiguration {
 
 	dataSource = &Source{httpClient: p.fetchClient}
 
-	if p.config.grpc != nil {
+	if grpcEnabled {
 		var err error
+		grpcClient, err := p.resolveGRPCClient()
+		if err != nil {
+			p.stopWithError(errors.WithStack(fmt.Errorf("failed to resolve gRPC client: %w", err)))
+			return resolve.FetchConfiguration{}
+		}
 
 		opDocument, opReport := astparser.ParseGraphqlDocumentBytes(operation)
 		if opReport.HasErrors() {
@@ -362,7 +368,7 @@ func (p *Planner[T]) ConfigureFetch() resolve.FetchConfiguration {
 			return resolve.FetchConfiguration{}
 		}
 
-		dataSource, err = grpcdatasource.NewDataSource(p.grpcClient, grpcdatasource.DataSourceConfig{
+		dataSource, err = grpcdatasource.NewDataSource(grpcClient, grpcdatasource.DataSourceConfig{
 			Operation:  &opDocument,
 			Definition: p.config.schemaConfiguration.upstreamSchemaAst,
 			Mapping:    p.config.grpc.Mapping,
@@ -389,6 +395,23 @@ func (p *Planner[T]) ConfigureFetch() resolve.FetchConfiguration {
 	}
 }
 
+func (p *Planner[T]) resolveGRPCClient() (grpc.ClientConnInterface, error) {
+	if p.config.grpc != nil {
+		if p.config.grpc.Client != nil {
+			return p.config.grpc.Client, nil
+		}
+		if p.config.grpc.ClientFactory != nil {
+			return p.config.grpc.ClientFactory(context.Background())
+		}
+	}
+
+	if p.grpcClient != nil {
+		return p.grpcClient, nil
+	}
+
+	return nil, fmt.Errorf("grpc client is required")
+}
+
 func (p *Planner[T]) shouldSelectSingleEntity() bool {
 	return p.dataSourcePlannerConfig.HasRequiredFields() &&
 		p.dataSourcePlannerConfig.PathType == plan.PlannerPathObject
@@ -1736,10 +1759,6 @@ func NewFactoryGRPC(executionContext context.Context, grpcClient grpc.ClientConn
 		return nil, fmt.Errorf("execution context is required")
 	}
 
-	if grpcClient == nil {
-		return nil, fmt.Errorf("grpc client is required")
-	}
-
 	return &Factory[Configuration]{
 		executionContext: executionContext,
 		grpcClient:       grpcClient,
diff --git a/app/v2/pkg/engine/datasource/graphql_datasource/grpc_factory_test.go b/app/v2/pkg/engine/datasource/graphql_datasource/grpc_factory_test.go
new file mode 100644
index 0000000..f9979a3
--- /dev/null
+++ b/app/v2/pkg/engine/datasource/graphql_datasource/grpc_factory_test.go
@@ -0,0 +1,57 @@
+package graphql_datasource
+
+import (
+	"context"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	grpcdatasource "github.com/wundergraph/graphql-go-tools/v2/pkg/engine/datasource/grpc_datasource"
+	"google.golang.org/grpc"
+)
+
+type testGRPCClient struct{}
+
+func (t *testGRPCClient) Invoke(context.Context, string, any, any, ...grpc.CallOption) error {
+	return nil
+}
+
+func (t *testGRPCClient) NewStream(context.Context, *grpc.StreamDesc, string, ...grpc.CallOption) (grpc.ClientStream, error) {
+	return nil, nil
+}
+
+func TestNewFactoryGRPC_AllowsDatasourceProvidedClient(t *testing.T) {
+	factory, err := NewFactoryGRPC(context.Background(), nil)
+	require.NoError(t, err)
+	require.NotNil(t, factory)
+}
+
+func TestPlannerResolveGRPCClient_UsesDatasourceFactory(t *testing.T) {
+	expectedClient := &testGRPCClient{}
+	planner := &Planner[Configuration]{
+		config: Configuration{
+			grpc: &grpcdatasource.GRPCConfiguration{
+				ClientFactory: func(context.Context) (grpc.ClientConnInterface, error) {
+					return expectedClient, nil
+				},
+			},
+		},
+	}
+
+	client, err := planner.resolveGRPCClient()
+	require.NoError(t, err)
+	require.Same(t, expectedClient, client)
+}
+
+func TestConfigurationIsGRPC_IgnoresDisabledGRPCDatasource(t *testing.T) {
+	schemaConfig, err := NewSchemaConfiguration("type Query { hello: String }", nil)
+	require.NoError(t, err)
+
+	cfg, err := NewConfiguration(ConfigurationInput{
+		GRPC: &grpcdatasource.GRPCConfiguration{
+			Disabled: true,
+		},
+		SchemaConfiguration: schemaConfig,
+	})
+	require.NoError(t, err)
+	require.False(t, cfg.IsGRPC())
+}
diff --git a/tmp/agent-patch-flux-pr-1155.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/datasource/grpc_datasource/compiler.go b/app/v2/pkg/engine/datasource/grpc_datasource/compiler.go
index ed8b887..8defa41 100644
--- a/tmp/agent-patch-flux-pr-1155.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/datasource/grpc_datasource/compiler.go
+++ b/app/v2/pkg/engine/datasource/grpc_datasource/compiler.go
@@ -319,6 +319,7 @@ type Invocation struct {
 // Compile processes an RPCExecutionPlan and builds protobuf messages from JSON data
 // based on the compiled schema.
 func (p *RPCCompiler) Compile(executionPlan *RPCExecutionPlan, inputData gjson.Result) ([]Invocation, error) {
+	p.report.Reset()
 	invocations := make([]Invocation, 0, len(executionPlan.Calls))
 
 	for _, call := range executionPlan.Calls {
@@ -397,21 +398,7 @@ func (p *RPCCompiler) buildProtoMessage(inputMessage Message, rpcMessage *RPCMes
 
 		// Handle repeated fields (arrays/lists)
 		if field.Repeated {
-			// Get a mutable reference to the list field
-			list := message.Mutable(fd.ByName(protoref.Name(field.Name))).List()
-
-			// Extract the array elements from the JSON data
-			elements := data.Get(rpcField.JSONPath).Array()
-			if len(elements) == 0 {
-				continue
-			}
-
-			// Process each element and append to the list
-			for _, element := range elements {
-				fieldMsg := p.buildProtoMessage(p.doc.Messages[field.MessageRef], rpcField.Message, element)
-				list.Append(protoref.ValueOfMessage(fieldMsg))
-			}
-
+			p.populateRepeatedField(message, inputMessage, field, rpcField, data)
 			continue
 		}
 
@@ -472,11 +459,63 @@ func (p *RPCCompiler) setValueForKind(kind DataType, data gjson.Result) protoref
 		return protoref.ValueOfFloat64(data.Float())
 	case DataTypeBool:
 		return protoref.ValueOfBool(data.Bool())
+	case DataTypeBytes:
+		return protoref.ValueOfBytes([]byte(data.String()))
 	}
 
 	return protoref.Value{}
 }
 
+func (p *RPCCompiler) populateRepeatedField(message *dynamicpb.Message, inputMessage Message, field Field, rpcField *RPCField, data gjson.Result) {
+	list := message.Mutable(inputMessage.Desc.Fields().ByName(protoref.Name(field.Name))).List()
+	fieldData := data.Get(rpcField.JSONPath)
+	if !fieldData.Exists() || fieldData.Type == gjson.Null {
+		return
+	}
+
+	elements := fieldData.Array()
+	if len(elements) == 0 && (len(fieldData.Raw) == 0 || fieldData.Raw[0] != '[') {
+		elements = []gjson.Result{fieldData}
+	}
+
+	if field.MessageRef >= 0 {
+		for _, element := range elements {
+			fieldMsg := p.buildProtoMessage(p.doc.Messages[field.MessageRef], rpcField.Message, element)
+			list.Append(protoref.ValueOfMessage(fieldMsg))
+		}
+
+		return
+	}
+
+	if field.Type == DataTypeEnum {
+		enum, ok := p.doc.EnumByName(rpcField.EnumName)
+		if !ok {
+			p.report.AddInternalError(fmt.Errorf("enum %s not found in document", rpcField.EnumName))
+			return
+		}
+
+		for _, element := range elements {
+			matched := false
+			for _, enumValue := range enum.Values {
+				if enumValue.GraphqlValue == element.String() {
+					list.Append(protoref.ValueOfEnum(protoref.EnumNumber(enumValue.Number)))
+					matched = true
+					break
+				}
+			}
+			if !matched {
+				p.report.AddInternalError(fmt.Errorf("enum value %q not found in document for enum %s", element.String(), rpcField.EnumName))
+			}
+		}
+
+		return
+	}
+
+	for _, element := range elements {
+		list.Append(p.setValueForKind(field.Type, element))
+	}
+}
+
 // parseEnum extracts information from a protobuf enum descriptor.
 func (p *RPCCompiler) parseEnum(e protoref.EnumDescriptor, mapping *GRPCMapping) Enum {
 	var enumValueMappings []EnumValueMapping
diff --git a/tmp/agent-patch-flux-pr-1155.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/datasource/grpc_datasource/compiler_test.go b/app/v2/pkg/engine/datasource/grpc_datasource/compiler_test.go
index 5e098c3..fd251e1 100644
--- a/tmp/agent-patch-flux-pr-1155.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/datasource/grpc_datasource/compiler_test.go
+++ b/app/v2/pkg/engine/datasource/grpc_datasource/compiler_test.go
@@ -6,6 +6,7 @@ import (
 	"github.com/stretchr/testify/require"
 	"github.com/tidwall/gjson"
 	"github.com/wundergraph/graphql-go-tools/v2/pkg/grpctest"
+	protoref "google.golang.org/protobuf/reflect/protoreflect"
 )
 
 // Complete valid protobuf definition with service and message definitions
@@ -291,3 +292,69 @@ func TestBuildProtoMessage(t *testing.T) {
 	require.Equal(t, 1, len(invocations))
 
 }
+
+func TestBuildProtoMessage_WithRepeatedScalarField(t *testing.T) {
+	compiler, err := NewProtoCompiler(grpctest.MustProtoSchema(t), nil)
+	require.NoError(t, err)
+
+	request := RPCMessage{
+		Name: "QueryCalculateTotalsRequest",
+		Fields: []RPCField{
+			{
+				Name:     "orders",
+				TypeName: string(DataTypeMessage),
+				JSONPath: "orders",
+				Repeated: true,
+				Message: &RPCMessage{
+					Name: "OrderInput",
+					Fields: []RPCField{
+						{Name: "order_id", TypeName: string(DataTypeString), JSONPath: "orderId"},
+						{Name: "customer_name", TypeName: string(DataTypeString), JSONPath: "customerName"},
+						{
+							Name:     "lines",
+							TypeName: string(DataTypeMessage),
+							JSONPath: "lines",
+							Repeated: true,
+							Message: &RPCMessage{
+								Name: "OrderLineInput",
+								Fields: []RPCField{
+									{Name: "product_id", TypeName: string(DataTypeString), JSONPath: "productId"},
+									{Name: "quantity", TypeName: string(DataTypeInt32), JSONPath: "quantity"},
+									{Name: "modifiers", TypeName: string(DataTypeString), JSONPath: "modifiers", Repeated: true},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	data := gjson.Parse(`{
+		"orders": [{
+			"orderId": "order-1",
+			"customerName": "Jane Smith",
+			"lines": [{
+				"productId": "product-1",
+				"quantity": 2,
+				"modifiers": ["gift-wrap", "fragile"]
+			}]
+		}]
+	}`)
+
+	message := compiler.buildProtoMessage(compiler.doc.MessageByName("QueryCalculateTotalsRequest"), &request, data)
+	require.NotNil(t, message)
+
+	orders := message.Get(message.Descriptor().Fields().ByName(protoref.Name("orders"))).List()
+	require.Equal(t, 1, orders.Len())
+
+	order := orders.Get(0).Message()
+	lines := order.Get(order.Descriptor().Fields().ByName(protoref.Name("lines"))).List()
+	require.Equal(t, 1, lines.Len())
+
+	line := lines.Get(0).Message()
+	modifiers := line.Get(line.Descriptor().Fields().ByName(protoref.Name("modifiers"))).List()
+	require.Equal(t, 2, modifiers.Len())
+	require.Equal(t, "gift-wrap", modifiers.Get(0).String())
+	require.Equal(t, "fragile", modifiers.Get(1).String())
+}
diff --git a/tmp/agent-patch-flux-pr-1155.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/datasource/grpc_datasource/configuration.go b/app/v2/pkg/engine/datasource/grpc_datasource/configuration.go
index 4dbd48b..3867ba6 100644
--- a/tmp/agent-patch-flux-pr-1155.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/datasource/grpc_datasource/configuration.go
+++ b/app/v2/pkg/engine/datasource/grpc_datasource/configuration.go
@@ -1,5 +1,11 @@
 package grpcdatasource
 
+import (
+	"context"
+
+	"google.golang.org/grpc"
+)
+
 type (
 	// RPCConfigMap is a map of RPC names to RPC configurations
 	RPCConfigMap map[string]RPCConfig
@@ -29,9 +35,14 @@ type EnumValueMapping struct {
 	TargetValue string
 }
 
+type GRPCClientFactory func(context.Context) (grpc.ClientConnInterface, error)
+
 type GRPCConfiguration struct {
-	Mapping  *GRPCMapping
-	Compiler *RPCCompiler
+	Disabled      bool
+	Mapping       *GRPCMapping
+	Compiler      *RPCCompiler
+	Client        grpc.ClientConnInterface
+	ClientFactory GRPCClientFactory
 }
 
 // RPCConfig defines the configuration for a specific RPC operation
diff --git a/tmp/agent-patch-flux-pr-1155.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/datasource/grpc_datasource/grpc_datasource.go b/app/v2/pkg/engine/datasource/grpc_datasource/grpc_datasource.go
index 940d501..710e37d 100644
--- a/tmp/agent-patch-flux-pr-1155.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/datasource/grpc_datasource/grpc_datasource.go
+++ b/app/v2/pkg/engine/datasource/grpc_datasource/grpc_datasource.go
@@ -17,7 +17,9 @@ import (
 	"github.com/wundergraph/graphql-go-tools/v2/pkg/engine/datasource/httpclient"
 	"github.com/wundergraph/graphql-go-tools/v2/pkg/engine/resolve"
 	"google.golang.org/grpc"
+	"google.golang.org/grpc/status"
 	protoref "google.golang.org/protobuf/reflect/protoreflect"
+	"google.golang.org/protobuf/types/dynamicpb"
 )
 
 // Verify DataSource implements the resolve.DataSource interface
@@ -95,6 +97,9 @@ func (d *DataSource) Load(ctx context.Context, input []byte, out *bytes.Buffer)
 		if err != nil {
 			return err
 		}
+		if responseJSON == nil || responseJSON.Type() == astjson.TypeNull {
+			continue
+		}
 
 		root, _, err = astjson.MergeValues(root, responseJSON)
 		if err != nil {
@@ -123,7 +128,10 @@ func (d *DataSource) LoadWithFiles(ctx context.Context, input []byte, files []*h
 
 func (d *DataSource) marshalResponseJSON(arena *astjson.Arena, message *RPCMessage, data protoref.Message) (*astjson.Value, error) {
 	if message == nil {
-		return nil, nil
+		return arena.NewNull(), nil
+	}
+	if data == nil || !data.IsValid() {
+		return arena.NewNull(), nil
 	}
 
 	root := arena.NewObject()
@@ -162,12 +170,10 @@ func (d *DataSource) marshalResponseJSON(arena *astjson.Arena, message *RPCMessa
 			root.Set(field.JSONPath, arr)
 			list := data.Get(fd).List()
 			for i := 0; i < list.Len(); i++ {
-				message := list.Get(i).Message()
-				value, err := d.marshalResponseJSON(arena, field.Message, message)
+				value, err := d.marshalListItemJSON(arena, &field, fd, list.Get(i))
 				if err != nil {
 					return nil, err
 				}
-
 				arr.SetArrayItem(i, value)
 			}
 
@@ -176,12 +182,18 @@ func (d *DataSource) marshalResponseJSON(arena *astjson.Arena, message *RPCMessa
 
 		if fd.Kind() == protoref.MessageKind {
 			msg := data.Get(fd).Message()
+			if msg == nil || !msg.IsValid() {
+				msg = dynamicpb.NewMessage(fd.Message())
+			}
 			value, err := d.marshalResponseJSON(arena, field.Message, msg)
 			if err != nil {
 				return nil, err
 			}
 
 			if field.JSONPath == "" {
+				if value == nil || value.Type() == astjson.TypeNull {
+					continue
+				}
 				root, _, err = astjson.MergeValues(root, value)
 				if err != nil {
 					return nil, err
@@ -193,47 +205,58 @@ func (d *DataSource) marshalResponseJSON(arena *astjson.Arena, message *RPCMessa
 			continue
 		}
 
-		d.setJSONValue(arena, root, field.JSONPath, data, fd)
+		root.Set(field.JSONPath, d.protoValueToJSONValue(arena, fd, data.Get(fd)))
 	}
 
 	return root, nil
 }
 
-func (d *DataSource) setJSONValue(arena *astjson.Arena, root *astjson.Value, name string, data protoref.Message, fd protoref.FieldDescriptor) {
+func (d *DataSource) marshalListItemJSON(arena *astjson.Arena, field *RPCField, fd protoref.FieldDescriptor, value protoref.Value) (*astjson.Value, error) {
+	if fd.Kind() == protoref.MessageKind {
+		msg := value.Message()
+		if msg == nil || !msg.IsValid() {
+			msg = dynamicpb.NewMessage(fd.Message())
+		}
+		return d.marshalResponseJSON(arena, field.Message, msg)
+	}
+
+	return d.protoValueToJSONValue(arena, fd, value), nil
+}
+
+func (d *DataSource) protoValueToJSONValue(arena *astjson.Arena, fd protoref.FieldDescriptor, value protoref.Value) *astjson.Value {
 	switch fd.Kind() {
 	case protoref.BoolKind:
-		boolValue := data.Get(fd).Bool()
+		boolValue := value.Bool()
 		if boolValue {
-			root.Set(name, arena.NewTrue())
-		} else {
-			root.Set(name, arena.NewFalse())
+			return arena.NewTrue()
 		}
+		return arena.NewFalse()
 	case protoref.StringKind:
-		root.Set(name, arena.NewString(data.Get(fd).String()))
+		return arena.NewString(value.String())
 	case protoref.Int32Kind, protoref.Int64Kind:
-		root.Set(name, arena.NewNumberInt(int(data.Get(fd).Int())))
+		return arena.NewNumberString(fmt.Sprintf("%d", value.Int()))
 	case protoref.Uint32Kind, protoref.Uint64Kind:
-		root.Set(name, arena.NewNumberString(fmt.Sprintf("%d", data.Get(fd).Uint())))
+		return arena.NewNumberString(fmt.Sprintf("%d", value.Uint()))
 	case protoref.FloatKind, protoref.DoubleKind:
-		root.Set(name, arena.NewNumberFloat64(data.Get(fd).Float()))
+		return arena.NewNumberFloat64(value.Float())
 	case protoref.BytesKind:
-		root.Set(name, arena.NewStringBytes(data.Get(fd).Bytes()))
+		return arena.NewStringBytes(value.Bytes())
 	case protoref.EnumKind:
 		enumDesc := fd.Enum()
-		enumValueDesc := enumDesc.Values().ByNumber(data.Get(fd).Enum())
+		enumValueDesc := enumDesc.Values().ByNumber(value.Enum())
 		if enumValueDesc == nil {
-			root.Set(name, arena.NewNull())
-			return
+			return arena.NewNull()
 		}
 
 		graphqlValue, ok := d.mapping.ResolveEnumValue(string(enumDesc.Name()), string(enumValueDesc.Name()))
 		if !ok {
-			root.Set(name, arena.NewNull())
-			return
+			return arena.NewNull()
 		}
 
-		root.Set(name, arena.NewString(graphqlValue))
+		return arena.NewString(graphqlValue)
 	}
+
+	return arena.NewNull()
 }
 
 func writeErrorBytes(err error) []byte {
@@ -244,6 +267,12 @@ func writeErrorBytes(err error) []byte {
 
 	errorItem := a.NewObject()
 	errorItem.Set("message", a.NewString(err.Error()))
+	if st, ok := status.FromError(err); ok {
+		extensions := a.NewObject()
+		extensions.Set("grpcCode", a.NewString(st.Code().String()))
+		extensions.Set("grpcStatusCode", a.NewNumberInt(int(st.Code())))
+		errorItem.Set("extensions", extensions)
+	}
 	errorArray.SetArrayItem(0, errorItem)
 
 	return errorRoot.MarshalTo(nil)
diff --git a/tmp/agent-patch-flux-pr-1155.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/datasource/grpc_datasource/grpc_datasource_test.go b/app/v2/pkg/engine/datasource/grpc_datasource/grpc_datasource_test.go
index a14f5a1..3b76ee5 100644
--- a/tmp/agent-patch-flux-pr-1155.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/datasource/grpc_datasource/grpc_datasource_test.go
+++ b/app/v2/pkg/engine/datasource/grpc_datasource/grpc_datasource_test.go
@@ -425,6 +425,8 @@ func Test_DataSource_Load_WithGrpcError(t *testing.T) {
 
 	// Verify the error message
 	require.Contains(t, response.Errors[0].Message, "user not found: error-user")
+	require.Contains(t, responseJson, `"grpcCode":"NotFound"`)
+	require.Contains(t, responseJson, `"grpcStatusCode":5`)
 }
 
 func TestMarshalResponseJSON(t *testing.T) {
@@ -493,6 +495,72 @@ func TestMarshalResponseJSON(t *testing.T) {
 	require.Equal(t, `{"_entities":[{"__typename":"Product","id":"123","name_different":"test","price_different":123.45}]}`, responseJSON.String())
 }
 
+func TestMarshalResponseJSON_WithRepeatedScalarFields(t *testing.T) {
+	protoSchema := `
+syntax = "proto3";
+package product.v1;
+
+service ProductService {
+  rpc QueryScalars(QueryScalarsRequest) returns (QueryScalarsResponse) {}
+}
+
+message QueryScalarsRequest {}
+
+message QueryScalarsResponse {
+  repeated string tags = 1;
+  repeated int32 quantities = 2;
+  Nested nested = 3;
+}
+
+message Nested {
+  string name = 1;
+}
+`
+
+	compiler, err := NewProtoCompiler(protoSchema, nil)
+	require.NoError(t, err)
+
+	responseDesc := compiler.doc.MessageByName("QueryScalarsResponse").Desc
+	responseMessage := dynamicpb.NewMessage(responseDesc)
+	responseMessage.Mutable(responseDesc.Fields().ByName("tags")).List().Append(protoref.ValueOfString("a"))
+	responseMessage.Mutable(responseDesc.Fields().ByName("tags")).List().Append(protoref.ValueOfString("b"))
+	responseMessage.Mutable(responseDesc.Fields().ByName("quantities")).List().Append(protoref.ValueOfInt32(3))
+	responseMessage.Mutable(responseDesc.Fields().ByName("quantities")).List().Append(protoref.ValueOfInt32(5))
+
+	response := RPCMessage{
+		Name: "QueryScalarsResponse",
+		Fields: []RPCField{
+			{Name: "tags", TypeName: string(DataTypeString), JSONPath: "tags", Repeated: true},
+			{Name: "quantities", TypeName: string(DataTypeInt32), JSONPath: "quantities", Repeated: true},
+			{
+				Name:     "nested",
+				TypeName: string(DataTypeMessage),
+				JSONPath: "nested",
+				Message: &RPCMessage{
+					Name: "Nested",
+					Fields: []RPCField{
+						{Name: "name", TypeName: string(DataTypeString), JSONPath: "name"},
+					},
+				},
+			},
+		},
+	}
+
+	ds := &DataSource{}
+	arena := astjson.Arena{}
+	responseJSON, err := ds.marshalResponseJSON(&arena, &response, responseMessage)
+	require.NoError(t, err)
+	require.Equal(t, `{"tags":["a","b"],"quantities":[3,5],"nested":{"name":""}}`, responseJSON.String())
+}
+
+func TestMarshalResponseJSON_WithNilMessage(t *testing.T) {
+	ds := &DataSource{}
+	arena := astjson.Arena{}
+	responseJSON, err := ds.marshalResponseJSON(&arena, &RPCMessage{Name: "Ignored"}, nil)
+	require.NoError(t, err)
+	require.Equal(t, astjson.TypeNull, responseJSON.Type())
+}
+
 // TODO test interface types
 // Test_DataSource_Load_WithAnimalInterface tests the datasource with Animal interface types (Cat/Dog)
 // using a bufconn connection to the mock service