Skip to content
Encode and decode Go (golang) struct types via protocol buffers.
Go
Branch: master
Clone or download

Latest commit

Fetching latest commit…
Cannot retrieve the latest commit at this time.

Files

Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
LICENSE Create LICENSE Mar 2, 2020
README.md update limitations Mar 3, 2020
go.mod Encode/New seem to work, poorly tested at the moment Mar 2, 2020
go.sum Encode/New seem to work, poorly tested at the moment Mar 2, 2020
protostructure.go docs Mar 2, 2020
protostructure.pb.go Initial proto structure Mar 2, 2020
protostructure.proto add the go_package option for imports Mar 2, 2020
protostructure_test.go implement decoding slices and arrays Mar 3, 2020
type.go implement decoding slices and arrays Mar 3, 2020
type_test.go Add more tests, cleanup some docs Mar 2, 2020

README.md

protostructure Godoc

protostructure is a Go library for encoding and decoding a struct type over the wire.

This library is useful when you want to send arbitrary structures over protocol buffers for behavior such as configuration decoding (encoding/json, etc.), validation (using packages that use tags), etc. This works because we can reconstruct the struct type dynamically using reflect including any field tags.

This library only sends the structure of the struct, not the value. If you want to send the value, you should build your protocol buffer message in such a way that it encodes that somehow using something such as JSON.

Installation

Standard go get:

$ go get github.com/mitchellh/protostructure

Usage & Example

For usage and examples see the Godoc.

A quick code example is shown below using both the imaginary proto file and the Go code that uses it.

syntax = "proto3";
package myapp;
import "protostructure.proto";

// Response is an example response structure for an RPC endpoint.
message Response {
	protostructure.Struct config = 1;
}
type Config struct {
	Name string            `json:"name"`
	Meta map[string]string `json:"metadata"`
	Port []*Port           `json:"ports"`
}

type Port struct {
	Number uint `json:"number"`
	Desc   string `json:"desc"`
}

// You can encode the structure on one side:
message, err := protostructure.Encode(Config{})

// And you can use the structure on the other side. Imagine resp
// is populated using some protobuf RPC such as gRPC.
val, err := protostructure.New(resp.Config)
json.Unmarshal([]byte(`{
	"name": "example",
	"meta": { "env": "prod" },
	"ports": [
		{ "number": 8080 },
		{ "number": 8100, desc: "backup" },
	]
}`, val)

// val now holds the same structure dynamically. You can pair with other
// libraries such as https://github.com/go-playground/validator to also
// send validation using this library.

Limitations

There are several limitations on the structures that can be encoded. Some of these limitations are fixable but the effort hasn't been put in while others are fundamental due to the limitations of Go currently:

  • Circular references are not allowed between any struct types.
  • Embedded structs are not supported
  • Methods are not preserved, and therefore interface implementation is not known. This is also an important detail because custom callbacks such as UnmarshalJSON may not work properly.
  • Field types cannot be: interfaces, channels, functions
  • Certain stdlib types such as time.Time currently do not encode well.

But... why?

The real world use case that led to the creation of this library was to facilitate decoding and validating configuration for plugins via go-plugin, a plugin system for Go that communicates using gRPC.

The plugins for this particular program have dynamic configuration structures that were decoded using an encoding/json-like interface (struct tags) and validated using go-playground/validator which also uses struct tags. Using protostructure, we can send the configuration structure across the wire, decode and validate the configuration in the host process, and report more rich errors that way.

Another reason we wanted to ship the config structure vs. ship the config is because the actual language we are using for configuration is HCL which supports things like function calls, logic, and more and shipping that runtime across is much, much more difficult.

This was extracted into a separate library because the ability to encode a Go structure (particulary to include tags) seemed more generally useful, although rare.

You can’t perform that action at this time.