protostructure

package module
v0.0.0-...-f93c309 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 21, 2022 License: MIT Imports: 4 Imported by: 1

README

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/hashicorp/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.

Documentation

Overview

Package protostructure provides a mechanism for encoding and decoding a struct _type_ using protocol buffers. To be clear: this encodes the _type_ and not the _value_.

Most importantly, this lets you do things such as transferring a struct that supports JSON decoding across a protobuf RPC, and then decoding a JSON value directly into it since you have access to things such as struct tags from the remote end.

For a pure JSON use case, it may make sense to instead send the JSON rather than send the struct type. There are other scenarios where sending the type is easier and this library facilitates those use cases.

The primary functions you want to look at are "Encode" and "New".

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func New

func New(s *Struct) (result interface{}, err error)

New returns a pointer to an allocated struct for the structure given or an error if there are any invalid fields.

This interface{} value can be used directly in functions such as json.Unmarshal, or it can be inspected further as necessary.

Types

type Container

type Container struct {
	// kind must be one of: array, map, ptr, slice
	Kind uint32 `protobuf:"varint,1,opt,name=kind,proto3" json:"kind,omitempty"`
	// elem is the type of the element of this container
	Elem *Type `protobuf:"bytes,2,opt,name=elem,proto3" json:"elem,omitempty"`
	// key is the type of the key, only if kind == map
	Key *Type `protobuf:"bytes,3,opt,name=key,proto3" json:"key,omitempty"`
	// count is the number of elements, only if kind == array
	Count                int32    `protobuf:"varint,4,opt,name=count,proto3" json:"count,omitempty"`
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}

Container represents any "container" type such as a sliec, array, map, etc.

func (*Container) Descriptor

func (*Container) Descriptor() ([]byte, []int)

func (*Container) GetCount

func (m *Container) GetCount() int32

func (*Container) GetElem

func (m *Container) GetElem() *Type

func (*Container) GetKey

func (m *Container) GetKey() *Type

func (*Container) GetKind

func (m *Container) GetKind() uint32

func (*Container) ProtoMessage

func (*Container) ProtoMessage()

func (*Container) Reset

func (m *Container) Reset()

func (*Container) String

func (m *Container) String() string

func (*Container) XXX_DiscardUnknown

func (m *Container) XXX_DiscardUnknown()

func (*Container) XXX_Marshal

func (m *Container) XXX_Marshal(b []byte, deterministic bool) ([]byte, error)

func (*Container) XXX_Merge

func (dst *Container) XXX_Merge(src proto.Message)

func (*Container) XXX_Size

func (m *Container) XXX_Size() int

func (*Container) XXX_Unmarshal

func (m *Container) XXX_Unmarshal(b []byte) error

type Primitive

type Primitive struct {
	// kind is the reflect.Kind value for this primitive. This MUST be
	// a primitive value. For example, reflect.Ptr would be invalid here.
	Kind                 uint32   `protobuf:"varint,1,opt,name=kind,proto3" json:"kind,omitempty"`
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}

Primitive is a primitive type such as int, bool, etc.

func (*Primitive) Descriptor

func (*Primitive) Descriptor() ([]byte, []int)

func (*Primitive) GetKind

func (m *Primitive) GetKind() uint32

func (*Primitive) ProtoMessage

func (*Primitive) ProtoMessage()

func (*Primitive) Reset

func (m *Primitive) Reset()

func (*Primitive) String

func (m *Primitive) String() string

func (*Primitive) XXX_DiscardUnknown

func (m *Primitive) XXX_DiscardUnknown()

func (*Primitive) XXX_Marshal

func (m *Primitive) XXX_Marshal(b []byte, deterministic bool) ([]byte, error)

func (*Primitive) XXX_Merge

func (dst *Primitive) XXX_Merge(src proto.Message)

func (*Primitive) XXX_Size

func (m *Primitive) XXX_Size() int

func (*Primitive) XXX_Unmarshal

func (m *Primitive) XXX_Unmarshal(b []byte) error

type Struct

type Struct struct {
	// fields is the list of fields in the struct
	Fields               []*Struct_Field `protobuf:"bytes,1,rep,name=fields,proto3" json:"fields,omitempty"`
	XXX_NoUnkeyedLiteral struct{}        `json:"-"`
	XXX_unrecognized     []byte          `json:"-"`
	XXX_sizecache        int32           `json:"-"`
}

Struct represents a struct type.

This has the following limitations:

  • Circular references are not allowed between any struct types
  • Embedded structs are not supported
  • Methods are not preserved

func Encode

func Encode(s interface{}) (*Struct, error)

Encode converts a struct to a *Struct which implements proto.Message and can therefore be sent over the wire. Note that only the _structure_ of the struct is encoded and NOT any fields values.

Encoding has a number of limitations:

  • Circular references are not allowed between any struct types
  • Embedded structs are not supported
  • Methods are not preserved
  • Field types cannot be: interfaces, channels, functions

func (*Struct) Descriptor

func (*Struct) Descriptor() ([]byte, []int)

func (*Struct) GetFields

func (m *Struct) GetFields() []*Struct_Field

func (*Struct) ProtoMessage

func (*Struct) ProtoMessage()

func (*Struct) Reset

func (m *Struct) Reset()

func (*Struct) String

func (m *Struct) String() string

func (*Struct) XXX_DiscardUnknown

func (m *Struct) XXX_DiscardUnknown()

func (*Struct) XXX_Marshal

func (m *Struct) XXX_Marshal(b []byte, deterministic bool) ([]byte, error)

func (*Struct) XXX_Merge

func (dst *Struct) XXX_Merge(src proto.Message)

func (*Struct) XXX_Size

func (m *Struct) XXX_Size() int

func (*Struct) XXX_Unmarshal

func (m *Struct) XXX_Unmarshal(b []byte) error

type Struct_Field

type Struct_Field struct {
	Name                 string   `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"`
	PkgPath              string   `protobuf:"bytes,2,opt,name=PkgPath,proto3" json:"PkgPath,omitempty"`
	Tag                  string   `protobuf:"bytes,3,opt,name=Tag,proto3" json:"Tag,omitempty"`
	Type                 *Type    `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"`
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}

Field is a field type. See reflect.StructField in the Go stdlib since the fields in this message match that almost exactly.

func (*Struct_Field) Descriptor

func (*Struct_Field) Descriptor() ([]byte, []int)

func (*Struct_Field) GetName

func (m *Struct_Field) GetName() string

func (*Struct_Field) GetPkgPath

func (m *Struct_Field) GetPkgPath() string

func (*Struct_Field) GetTag

func (m *Struct_Field) GetTag() string

func (*Struct_Field) GetType

func (m *Struct_Field) GetType() *Type

func (*Struct_Field) ProtoMessage

func (*Struct_Field) ProtoMessage()

func (*Struct_Field) Reset

func (m *Struct_Field) Reset()

func (*Struct_Field) String

func (m *Struct_Field) String() string

func (*Struct_Field) XXX_DiscardUnknown

func (m *Struct_Field) XXX_DiscardUnknown()

func (*Struct_Field) XXX_Marshal

func (m *Struct_Field) XXX_Marshal(b []byte, deterministic bool) ([]byte, error)

func (*Struct_Field) XXX_Merge

func (dst *Struct_Field) XXX_Merge(src proto.Message)

func (*Struct_Field) XXX_Size

func (m *Struct_Field) XXX_Size() int

func (*Struct_Field) XXX_Unmarshal

func (m *Struct_Field) XXX_Unmarshal(b []byte) error

type Type

type Type struct {
	// Types that are valid to be assigned to Type:
	//	*Type_Primitive
	//	*Type_Container
	//	*Type_Struct
	Type                 isType_Type `protobuf_oneof:"type"`
	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
	XXX_unrecognized     []byte      `json:"-"`
	XXX_sizecache        int32       `json:"-"`
}

Type represents a Go type.

func (*Type) Descriptor

func (*Type) Descriptor() ([]byte, []int)

func (*Type) GetContainer

func (m *Type) GetContainer() *Container

func (*Type) GetPrimitive

func (m *Type) GetPrimitive() *Primitive

func (*Type) GetStruct

func (m *Type) GetStruct() *Struct

func (*Type) GetType

func (m *Type) GetType() isType_Type

func (*Type) ProtoMessage

func (*Type) ProtoMessage()

func (*Type) Reset

func (m *Type) Reset()

func (*Type) String

func (m *Type) String() string

func (*Type) XXX_DiscardUnknown

func (m *Type) XXX_DiscardUnknown()

func (*Type) XXX_Marshal

func (m *Type) XXX_Marshal(b []byte, deterministic bool) ([]byte, error)

func (*Type) XXX_Merge

func (dst *Type) XXX_Merge(src proto.Message)

func (*Type) XXX_OneofFuncs

func (*Type) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{})

XXX_OneofFuncs is for the internal use of the proto package.

func (*Type) XXX_Size

func (m *Type) XXX_Size() int

func (*Type) XXX_Unmarshal

func (m *Type) XXX_Unmarshal(b []byte) error

type Type_Container

type Type_Container struct {
	Container *Container `protobuf:"bytes,2,opt,name=container,proto3,oneof"`
}

type Type_Primitive

type Type_Primitive struct {
	Primitive *Primitive `protobuf:"bytes,1,opt,name=primitive,proto3,oneof"`
}

type Type_Struct

type Type_Struct struct {
	Struct *Struct `protobuf:"bytes,3,opt,name=struct,proto3,oneof"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL