goprotocol-buffersenvoyproxy

How does the protoregistry.GlobalTypes load the types?


I am trying to unmarshal an envoy config from JSON to a Go proto struct. The JSON has an Any field with type information. I know that protojson somehow magically determines the proto definitions in the binary. In the case below I get an error.

Error:          Expected nil, but got: &errors.prefixError{s:"(line 23:46): unable to resolve \"type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog\": \"not found\""}
package play

import (
    "testing"

    "github.com/stretchr/testify/require"
    "google.golang.org/protobuf/encoding/protojson"

    listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
    // _ "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/file/v3"
)

var listnerJSONWithAccessLog = `
{
    "name": "lis1",
    "address": {
        "socket_address": {
            "address": "127.0.0.1",
            "port_value": 9999
        }
    },
    "filter_chains": [
        {
            "filters": [
                {
                    "name": "envoy.filters.network.http_connection_manager",
                    "typed_config": {
                        "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
                        "codec_type": "AUTO",
                        "stat_prefix": "hcm_prefix",
                        "access_log": [
                            {
                                "name": "envoy.access_loggers.file",
                                "typed_config": {
                                    "@type": "type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog",
                                    "path": "/dev/stdout"
                                }
                            }
                        ],
                        "http_filters": [
                            {
                                "name": "envoy.filters.http.router",
                                "typed_config": {
                                    "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
                                }
                            }
                        ],
                        "route_config": {
                            "name": "lis1_route_config",
                            "virtual_hosts": [
                                {
                                    "name": "lis1_virtual_host",
                                    "domains": [
                                        "*"
                                    ],
                                    "routes": [
                                        {
                                            "match": {
                                                "prefix": "/direct"
                                            },
                                            "direct_response": {
                                                "status": 200,
                                                "body": {
                                                    "inline_string": "hello"
                                                }
                                            }
                                        }
                                    ]
                                }
                            ]
                        }
                    }
                }
            ]
        }
    ]
}

`

func TestJSONToPB(t *testing.T) {
    t.Run("json to pb", func(t *testing.T) {
        var lis listener.Listener
        err := protojson.Unmarshal([]byte(listnerJSONWithAccessLog), &lis)
        require.Nil(t, err)
    })
}

Uncommenting the line with the //_ github... fixes the issue. What is the correct way to do this? Do I need to add an empty import like this for every possible struct that I am trying to unmarshal?


Solution

  • You need the protoreflect.MessageDescriptor for the named message, envoy.extensions.access_loggers.file.v3.FileAccessLog in your case.

    You see you can get that from FileAccessLog.ProtoReflect (or the deprecated FileAccessLog.Descriptor directly), but you'll need a FileAccessLog for that. And that you can get via reflection, which of course fails when the structure doesn't exist in the binary.

    So you need to get the information from somewhere. Reflection is usually a good and convenient way to do this. You can just import the package as _, because you won't use it directly, but still dependent on it.