goprotocol-buffersgrpc-go

How to send google.protobuf.Struct data in a GoLang test case?


I'm writing my first API endpoint in GoLang using GRPC/proto-buffers. I'm rather new to GoLang. Below is the file I'm writing for my test case(s)

package my_package

import (
    "context"
    "testing"

    "github.com/stretchr/testify/require"

    "google.golang.org/protobuf/types/known/structpb"
    "github.com/MyTeam/myproject/cmd/eventstream/setup"
    v1handler "github.com/MyTeam/myproject/internal/handlers/myproject/v1"
    v1interface "github.com/MyTeam/myproject/proto/.gen/go/myteam/myproject/v1"
)

func TestEndpoint(t *testing.T) {
    conf := &setup.Config{}

    // Initialize our API handlers
    myhandler := v1handler.New(&v1handler.Config{})

    t.Run("Success", func(t *testing.T) {

        res, err := myhandler.Endpoint(context.Background(), &v1interface.EndpointRequest{
            Data: &structpb.Struct{},
        })
        require.Nil(t, err)

        // Assert we got what we want.
        require.Equal(t, "Ok", res.Text)
    })


}

This is how the EndpointRequest object is defined in the v1.go file included above:

// An v1 interface Endpoint Request object.
message EndpointRequest {
  // data can be a complex object.
  google.protobuf.Struct data = 1;
}

This seems to work.

But now, I want to do something slightly different. In my test case, instead of sending an empty data object, I would like to send a map/dictionary with key/value pairs A: "B", C: "D". How can I do it? If I replace Data: &structpb.Struct{} with Data: &structpb.Struct{A: "B", C: "D"}, I get the compiler errors:

invalid field name "A" in struct initializer
invalid field name "C" in struct initializer 

Solution

  • The way you are initializing Data means that you are expecting the following:

    type Struct struct {
        A string
        C string
    }
    

    However, structpb.Struct is defined as follows:

    type Struct struct {   
        // Unordered map of dynamically typed values.
        Fields map[string]*Value `protobuf:"bytes,1,rep,name=fields,proto3" json:"fields,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
        // contains filtered or unexported fields
    }
    

    Clearly there's a bit of a mismatch there. You need to initialize the Fields map of the struct and use the correct way to set Value fields. The equivalent to the code you showed would be:

    Data: &structpb.Struct{
        Fields: map[string]*structpb.Value{
            "A": &structpb.Value{
                Kind: &structpb.Value_StringValue{
                    StringValue: "B",
                },
            },
            "C": &structpb.Value{
                Kind: &structpb.Value_StringValue{
                    StringValue: "D",
                },
            },
        },
    }