Documentation ¶
Overview ¶
Package protein is an encoding/decoding library for Protobuf that comes with schema-versioning and runtime-decoding capabilities.
It has diverse use-cases, including but not limited to:
- setting up schema registries
- decoding Protobuf payloads without the need to know their schema at compile-time
- identifying & preventing applicative bugs and data corruption issues
- creating custom-made container formats for on-disk storage
- ...and more!
Package protein is a generated protocol buffer package.
It is generated from these files:
github.com/znly/protein/protobuf/protobuf_payload.proto github.com/znly/protein/protobuf/protobuf_schema.proto
It has these top-level messages:
ProtobufPayload ProtobufSchema
Example ¶
This example demonstrates the use the Protein package in order to: initialize a `Transcoder`, sniff the local protobuf schemas from memory, synchronize the local schema-database with a remote datastore (here `redis`), use a `Transcoder` to encode & decode protobuf payloads using an already known schema, use a `Transcoder` to decode protobuf payloads without any prior knowledge of their schema.
// A local `redis` server must be up & running for this example to work: // // $ docker run -p 6379:6379 --name my-redis --rm redis:3.2 redis-server // open up a new `redis` connection pool redisURI := os.Getenv("PROT_REDIS_URI") p := &redis.Pool{ Dial: func() (redis.Conn, error) { return redis.DialURL(redisURI) }, } defer p.Close() /* INITIALIZATION */ // initialize a `Transcoder` that is transparently kept in-sync with // a `redis` datatore. trc, err := NewTranscoder( // this context defines the timeout & deadline policies when pushing // schemas to the local `redis`; i.e. it is forwarded to the // `TranscoderSetter` function that is passed below context.Background(), // the schemas found in memory will be versioned using a MD5 hash // algorithm, prefixed by the 'PROT-' string protoscan.MD5, "PROT-", // configure the `Transcoder` to push every protobuf schema it can find // in memory into the specified `redis` connection pool TranscoderOptSetter(NewTranscoderSetterRedis(p)), // configure the `Transcoder` to query the given `redis` connection pool // when it cannot find a specific protobuf schema in its local cache TranscoderOptGetter(NewTranscoderGetterRedis(p))) if err != nil { panic(err) } // At this point, the local `redis` datastore should contain all the // protobuf schemas known to the `Transcoder`, as defined by their respective // versioning hashes: // // $ docker run -it --link my-redis:redis --rm redis:3.2 redis-cli -h redis -p 6379 -c KEYS '*' // // 1) "PROT-31c64ad1c6476720f3afee6881e6f257" // 2) "PROT-56b347c6c212d3176392ab9bf5bb21ee" // 3) "PROT-c2dbc910081a372f31594db2dc2adf72" // 4) "PROT-09595a7e58d28b081d967b69cb00e722" // 5) "PROT-05dc5bd440d980600ecc3f1c4a8e315d" // 6) "PROT-8cbb4e79fdeadd5f0ff0971bbf7de31e" // ... etc ... /* ENCODING */ // create a simple object to be serialized ts, _ := types.TimestampProto(time.Now()) obj := &test.TestSchemaXXX{ Ids: map[int32]string{ 42: "the-answer", 666: "the-devil", }, Ts: *ts, } // wrap the object and its versioning metadata within a `ProtobufPayload` // object, then serialize the bundle as a protobuf binary blob payload, err := trc.Encode(obj) if err != nil { log.Fatal(err) } /* DECODING */ var myObj test.TestSchemaXXX // /!\ This will fail in cryptic ways since vanilla protobuf is unaware // of how Protein bundles the versioning metadata within the payload... // Don't do this! _ = proto.Unmarshal(payload, &myObj) // this will properly unbundle the data from the metadata before // unmarshalling the payload err = trc.DecodeAs(payload, &myObj) if err != nil { log.Fatal(err) } fmt.Println("A:", myObj.Ids[42]) // prints the answer! /* RUNTIME-DECODING */ // empty the content of the local cache of protobuf schemas in order to // make sure that the `Transcoder` will have to lazily fetch the schema // and its dependencies from the `redis` datastore during the decoding trc.sm = NewSchemaMap() // the `Transcoder` will do a lot of stuff behind the scenes so it can // successfully decode the payload: // 1. the versioning metadata is extracted from the payload // 2. the corresponding schema as well as its dependencies are lazily // fetched from the `redis` datastore (using the `TranscoderGetter` that // was passed to the constructor) // 3. a structure-type definition is created from these schemas using Go's // reflection APIs, with the right protobuf tags & hints for the protobuf // deserializer to do its thing // 4. an instance of this structure is created, then the payload is // unmarshalled into it myRuntimeObj, err := trc.Decode(context.Background(), payload) if err != nil { log.Fatal(err) } myRuntimeIDs := myRuntimeObj.Elem().FieldByName("IDs") fmt.Println("B:", myRuntimeIDs.MapIndex(reflect.ValueOf(int32(666)))) // prints the devil!
Output: A: the-answer B: the-devil
Index ¶
- Variables
- func CreateStructType(schemaUID string, sm *SchemaMap) (reflect_raw.Type, error)
- type ProtobufPayload
- func (*ProtobufPayload) Descriptor() ([]byte, []int)
- func (m *ProtobufPayload) GetPayload() []byte
- func (m *ProtobufPayload) GetSchemaUID() string
- func (this *ProtobufPayload) GoString() string
- func (*ProtobufPayload) ProtoMessage()
- func (m *ProtobufPayload) Reset()
- func (m *ProtobufPayload) String() string
- type ProtobufSchema
- func (*ProtobufSchema) Descriptor() ([]byte, []int)
- func (m *ProtobufSchema) GetDeps() map[string]string
- func (m *ProtobufSchema) GetDescr() isProtobufSchema_Descr
- func (m *ProtobufSchema) GetEnum() *google_protobuf.EnumDescriptorProto
- func (m *ProtobufSchema) GetFQName() string
- func (m *ProtobufSchema) GetMessage() *google_protobuf.DescriptorProto
- func (m *ProtobufSchema) GetSchemaUID() string
- func (this *ProtobufSchema) GoString() string
- func (*ProtobufSchema) ProtoMessage()
- func (m *ProtobufSchema) Reset()
- func (m *ProtobufSchema) String() string
- func (*ProtobufSchema) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, ...)
- type ProtobufSchema_Enum
- type ProtobufSchema_Message
- type SchemaMap
- type Transcoder
- func (t *Transcoder) Decode(ctx context.Context, payload []byte) (reflect.Value, error)
- func (t *Transcoder) DecodeAs(payload []byte, msg proto.Message) error
- func (t *Transcoder) Encode(msg proto.Message, fqName ...string) ([]byte, error)
- func (t *Transcoder) FQName(ctx context.Context, schemaUID string) string
- func (t *Transcoder) GetAndUpsert(ctx context.Context, schemaUID string) (map[string]*ProtobufSchema, error)
- func (t *Transcoder) LoadState(path string) error
- func (t *Transcoder) SaveState(path string) error
- type TranscoderDeserializer
- type TranscoderGetter
- type TranscoderOpt
- type TranscoderSerializer
- type TranscoderSetter
- type TranscoderSetterMulti
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // TranscoderOptGetter is used to configure the `TranscoderGetter` used by // the `Transcoder`. // See `TranscoderGetter` documentation for more information. TranscoderOptGetter = func(getter TranscoderGetter) TranscoderOpt { return func(trc *Transcoder) { trc.getter = getter } } // TranscoderOptSetter is used to configure the `TranscoderSetter` used by // the `Transcoder`. // See `TranscoderSetter` documentation for more information. TranscoderOptSetter = func(setter TranscoderSetter) TranscoderOpt { return func(trc *Transcoder) { trc.setter = setter } } // TranscoderOptSetterMulti is used to configure the `TranscoderSetterMulti` // used by the `Transcoder`. // See `TranscoderSetterMulti` documentation for more information. TranscoderOptSetterMulti = func(setterM TranscoderSetterMulti) TranscoderOpt { return func(trc *Transcoder) { trc.setterMulti = setterM } } // TranscoderOptSerializer is used to configure the `TranscoderSerializer` // used by the `Transcoder`. // See `TranscoderSerializer` documentation for more information. TranscoderOptSerializer = func(serializer TranscoderSerializer) TranscoderOpt { return func(trc *Transcoder) { trc.serializer = serializer } } // TranscoderOptDeserializer is used to configure the `TranscoderDeserializer` // used by the `Transcoder`. // See `TranscoderDeserializer` documentation for more information. TranscoderOptDeserializer = func(deserializer TranscoderDeserializer) TranscoderOpt { return func(trc *Transcoder) { trc.deserializer = deserializer } } )
Functions ¶
func CreateStructType ¶
func CreateStructType(schemaUID string, sm *SchemaMap) (reflect_raw.Type, error)
CreateStructType constructs a new structure-type definition at runtime from a `ProtobufSchema` tree.
This newly-created structure embeds all the necessary tags & hints for the protobuf SDK to deserialize payloads into it. I.e., it allows for runtime-decoding of protobuf payloads.
This is a complex and costly operation, it is strongly recommended to cache the result like the `Transcoder` does.
It requires the new `reflect` APIs provided by Go 1.7+.
Example ¶
This simple example demonstrates how to use the protostruct API in order to create a structure-type at runtime for a given protobuf schema, specified by its fully-qualified name.
// sniff all of local protobuf schemas and store them in a `SchemaMap` sm, err := ScanSchemas(protoscan.SHA256, "PROT-") if err != nil { zap.L().Fatal(err.Error()) } // create a structure-type definition for the '.test.TestSchemaXXX' // protobuf schema structType, err := CreateStructType( sm.GetByFQName(".test.TestSchemaXXX").SchemaUID, sm) if err != nil { zap.L().Fatal(err.Error()) } // pretty-print the resulting structure-type structType = Clean(structType) // remove tags to ease reading b, err := format.Source( // gofmt []byte(fmt.Sprintf("type TestSchemaXXX %s", structType))) if err != nil { zap.L().Fatal(err.Error()) } fmt.Println(string(b))
Output: type TestSchemaXXX struct { SchemaUID string FQNames []string Weathers []int32 TSStd time.Time DurStd []*time.Duration Deps map[string]*struct { Key string Value string } IDs map[int32]string TS struct { Seconds int64 Nanos int32 } Ots *struct { TS *struct { Seconds int64 Nanos int32 } } Nss []struct { Key string Value string } }
Types ¶
type ProtobufPayload ¶
type ProtobufPayload struct { // SchemaUID is the unique, deterministic & versioned identifier of the // `payload`'s schema. SchemaUID string `protobuf:"bytes,1,opt,name=schema_uid,json=schemaUid,proto3" json:"schema_uid,omitempty"` // Payload is the actual, marshaled protobuf payload. Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"` }
ProtobufPayload is a protobuf payload annotated with the unique versioning identifier of its schema.
This allows a `ProtobufPayload` to be decoded at runtime using Protein's `Transcoder`.
See `ScanSchemas`'s documentation for more information.
func (*ProtobufPayload) Descriptor ¶
func (*ProtobufPayload) Descriptor() ([]byte, []int)
func (*ProtobufPayload) GetPayload ¶
func (m *ProtobufPayload) GetPayload() []byte
func (*ProtobufPayload) GetSchemaUID ¶
func (m *ProtobufPayload) GetSchemaUID() string
func (*ProtobufPayload) GoString ¶
func (this *ProtobufPayload) GoString() string
func (*ProtobufPayload) ProtoMessage ¶
func (*ProtobufPayload) ProtoMessage()
func (*ProtobufPayload) Reset ¶
func (m *ProtobufPayload) Reset()
func (*ProtobufPayload) String ¶
func (m *ProtobufPayload) String() string
type ProtobufSchema ¶
type ProtobufSchema struct { // SchemaUID is the unique, deterministic & versioned identifier of this // schema. SchemaUID string `protobuf:"bytes,1,opt,name=schema_uid,json=schemaUid,proto3" json:"schema_uid,omitempty"` // FQName is the fully-qualified name of this schema, // e.g. `.google.protobuf.Timestamp`. FQName string `protobuf:"bytes,2,opt,name=fq_name,json=fqName,proto3" json:"fq_name,omitempty"` // Descriptor is either a Message or an Enum protobuf descriptor. // // Types that are valid to be assigned to Descr: // *ProtobufSchema_Message // *ProtobufSchema_Enum Descr isProtobufSchema_Descr `protobuf_oneof:"descr"` // Deps contains every direct and indirect dependencies that this schema // relies on. // // Key: the dependency's `schemaUID` // Value: the dependency's fully-qualified name Deps map[string]string `` /* 132-byte string literal not displayed */ }
ProtobufSchema is a versioned protobuf Message or Enum descriptor that can be used to decode `ProtobufPayload`s at runtime.
See `ScanSchemas`'s documentation for more information.
func (*ProtobufSchema) Descriptor ¶
func (*ProtobufSchema) Descriptor() ([]byte, []int)
func (*ProtobufSchema) GetDeps ¶
func (m *ProtobufSchema) GetDeps() map[string]string
func (*ProtobufSchema) GetDescr ¶
func (m *ProtobufSchema) GetDescr() isProtobufSchema_Descr
func (*ProtobufSchema) GetEnum ¶
func (m *ProtobufSchema) GetEnum() *google_protobuf.EnumDescriptorProto
func (*ProtobufSchema) GetFQName ¶
func (m *ProtobufSchema) GetFQName() string
func (*ProtobufSchema) GetMessage ¶
func (m *ProtobufSchema) GetMessage() *google_protobuf.DescriptorProto
func (*ProtobufSchema) GetSchemaUID ¶
func (m *ProtobufSchema) GetSchemaUID() string
func (*ProtobufSchema) GoString ¶
func (this *ProtobufSchema) GoString() string
func (*ProtobufSchema) ProtoMessage ¶
func (*ProtobufSchema) ProtoMessage()
func (*ProtobufSchema) Reset ¶
func (m *ProtobufSchema) Reset()
func (*ProtobufSchema) String ¶
func (m *ProtobufSchema) String() string
func (*ProtobufSchema) XXX_OneofFuncs ¶
func (*ProtobufSchema) 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.
type ProtobufSchema_Enum ¶
type ProtobufSchema_Enum struct {
Enum *google_protobuf.EnumDescriptorProto `protobuf:"bytes,31,opt,name=enm,oneof"`
}
func (*ProtobufSchema_Enum) GoString ¶
func (this *ProtobufSchema_Enum) GoString() string
type ProtobufSchema_Message ¶
type ProtobufSchema_Message struct {
Message *google_protobuf.DescriptorProto `protobuf:"bytes,30,opt,name=msg,oneof"`
}
func (*ProtobufSchema_Message) GoString ¶
func (this *ProtobufSchema_Message) GoString() string
type SchemaMap ¶
type SchemaMap struct {
// contains filtered or unexported fields
}
SchemaMap is a thread-safe mapping & reverse-mapping of `ProtobufSchema`s.
It atomically maintains two data-structures in parallel: a map of schemaUIDs to `ProtobufSchema`s, and a map of fully-qualified schema names to schemaUIDs.
The `SchemaMap` is the main data-structure behind a `Transcoder`, used to store and retrieve every `ProtobufSchema`s that have been cached locally.
func LoadSchemas ¶ added in v1.2.1
func LoadSchemas(fileDescriptorProtos map[string][]byte, hasher protoscan.Hasher, hashPrefix string, failOnDuplicate ...bool, ) (*SchemaMap, error)
LoadSchemas is the exact same thing as `ScanSchemas` except for the fact that it uses the given set of user-specified `fileDescriptorProtos` instead of scanning for matching symbols.
See `ScanSchemas`'s documentation for more information.
func NewSchemaMap ¶
func NewSchemaMap() *SchemaMap
NewSchemaMap returns a new SchemaMap with its maps & locks pre-allocated.
func ScanSchemas ¶
func ScanSchemas( hasher protoscan.Hasher, hashPrefix string, failOnDuplicate ...bool, ) (*SchemaMap, error)
ScanSchemas retrieves every protobuf schema instanciated by any of the currently loaded protobuf libraries (e.g. `golang/protobuf`, `gogo/protobuf`...), computes the dependency graphs that link them, builds the `ProtobufSchema` objects then returns a new `SchemaMap` filled with all those schemas.
A `ProtobufSchema` is a data-structure that holds a protobuf descriptor as well as a map of all its dependencies' schemaUIDs. A schemaUID uniquely & deterministically identifies a protobuf schema based on its descriptor and all of its dependencies' descriptors. It essentially is the versioned identifier of a schema. For more information, see `ProtobufSchema`'s documentation as well as the `protoscan` implementation, especially `descriptor_tree.go`.
The specified `hasher` is the actual function used to compute these schemaUIDs, see the `protoscan.Hasher` documentation for more information. The `hashPrefix` string will be preprended to the resulting hash that was computed via the `hasher` function. E.g. by passing `protoscan.MD5` as a `Hasher` and `PROT-` as a `hashPrefix`, the resulting schemaUIDs will be of the form 'PROT-<MD5hex>'.
As a schema and/or its dependencies follow their natural evolution, each and every historic version of them will thus have been stored with their own unique identifiers.
`failOnDuplicate` is an optional parameter that defaults to true; have a look at `ScanSchemas` implementation to understand what it does and when (if ever) would you need to set it to false instead.
Finally, have a look at the `protoscan` sub-packages as a whole for more information about how all of this machinery works; the code is heavily documented.
Example ¶
This example demonstrates how to use Protein's `ScanSchemas` function in order to sniff all the locally instanciated schemas into a `SchemaMap`, then walk over this map in order to print each schema as well as its dependencies.
// sniff local protobuf schemas into a `SchemaMap` using a MD5 hasher, // and prefixing each resulting UID with 'PROT-' sm, err := ScanSchemas(protoscan.MD5, "PROT-") if err != nil { zap.L().Fatal(err.Error()) } // walk over the map to print the schemas and their respective dependencies var output string sm.ForEach(func(ps *ProtobufSchema) error { // discard schemas not orginating from protein's test package if !strings.HasPrefix(ps.GetFQName(), ".test") { return nil } output += fmt.Sprintf("[%s] %s\n", ps.GetSchemaUID(), ps.GetFQName()) for uid, name := range ps.GetDeps() { // discard dependencies not orginating from protein's test package if strings.HasPrefix(name, ".test") { output += fmt.Sprintf( "[%s] depends on: [%s] %s\n", ps.GetSchemaUID(), uid, name, ) } } return nil }) // sort rows so the output stays deterministic rows := strings.Split(output, "\n") sort.Strings(rows) for i, r := range rows { // prettying if strings.Contains(r, "depends on") { rows[i] = "\t" + strings.Join(strings.Split(r, " ")[1:], " ") } } output = strings.Join(rows, "\n") fmt.Println(output)
Output: [PROT-048ddab197df688302a76296293ba101] .test.OtherTestSchemaXXX [PROT-1ed0887b99e22551676141f133ee3813] .test.TestSchemaXXX depends on: [PROT-048ddab197df688302a76296293ba101] .test.OtherTestSchemaXXX depends on: [PROT-393cb6dc1b4fc350cf10ca99f429301d] .test.TestSchemaXXX.WeatherType depends on: [PROT-6926276ca6306966d1a802c3b8f75298] .test.TestSchemaXXX.IdsEntry depends on: [PROT-c43da9745d68bd3cb97dc0f4905f3279] .test.TestSchemaXXX.NestedEntry depends on: [PROT-f6be24770f6e8d5edc8ef12c94a23010] .test.TestSchemaXXX.DepsEntry [PROT-393cb6dc1b4fc350cf10ca99f429301d] .test.TestSchemaXXX.WeatherType [PROT-3fecf73710581dfb3f46718988b9316e] .test.TestSchema.GhostType [PROT-4f6928d2737ba44dac0e3df123f80284] .test.TestSchema.DepsEntry [PROT-6926276ca6306966d1a802c3b8f75298] .test.TestSchemaXXX.IdsEntry [PROT-8b244a1a35e88f1e1aad8915dd603021] .test.TestSchema depends on: [PROT-3fecf73710581dfb3f46718988b9316e] .test.TestSchema.GhostType depends on: [PROT-4f6928d2737ba44dac0e3df123f80284] .test.TestSchema.DepsEntry [PROT-c43da9745d68bd3cb97dc0f4905f3279] .test.TestSchemaXXX.NestedEntry [PROT-f6be24770f6e8d5edc8ef12c94a23010] .test.TestSchemaXXX.DepsEntry depends on: [PROT-c43da9745d68bd3cb97dc0f4905f3279] .test.TestSchemaXXX.NestedEntry
func (*SchemaMap) Add ¶
func (sm *SchemaMap) Add(schemas map[string]*ProtobufSchema) *SchemaMap
Add walks over the given map of `schemas` and add them to the `SchemaMap` while making sure to atomically maintain both the internal map and reverse-map.
func (*SchemaMap) ForEach ¶
func (sm *SchemaMap) ForEach(f func(s *ProtobufSchema) error) error
ForEach applies the specified function `f` to every entry in the `SchemaMap`.
ForEach is guaranteed to see a consistent view of the internal mapping.
func (*SchemaMap) GetByFQName ¶
func (sm *SchemaMap) GetByFQName(fqName string) *ProtobufSchema
GetByFQName returns the first `ProtobufSchema`s that matches the specified fully-qualified name (e.g. `.google.protobuf.timestamp`).
If more than one version of the same schema are stored in the `SchemaMap`, a fully-qualified name will naturally point to several distinct schemaUIDs. When this happens, `GetByFQName` will always return the first one to had been inserted in the map.
This is thread-safe.
func (*SchemaMap) GetByUID ¶
func (sm *SchemaMap) GetByUID(schemaUID string) *ProtobufSchema
GetByUID returns the `ProtobufSchema` associated with the specified `schemaUID`.
This is thread-safe.
type Transcoder ¶
type Transcoder struct {
// contains filtered or unexported fields
}
A Transcoder is a protobuf encoder/decoder with schema-versioning as well as runtime-decoding capabilities.
func NewTranscoder ¶
func NewTranscoder(ctx context.Context, hasher protoscan.Hasher, hashPrefix string, opts ...TranscoderOpt, ) (*Transcoder, error)
NewTranscoder returns a new `Transcoder`.
See `ScanSchemas`'s documentation for more information regarding the use of `hasher` and `hashPrefix`.
See `TranscoderOpt`'s documentation for the list of available options.
The given context is passed to the user-specified `TranscoderSetter`, if any.
func NewTranscoderFromSchemaMap ¶ added in v1.2.1
func NewTranscoderFromSchemaMap(ctx context.Context, sm *SchemaMap, opts ...TranscoderOpt, ) (*Transcoder, error)
NewTranscoderFromSchemaMap returns a new `Transcoder` backed by the user-specified `sm` schema-map. This is reserved for advanced usages.
When using the vanilla `NewTranscoder` constructor, the schema-map is internally computed using the `ScanSchemas` function of the protoscan API. This constructor allows the developer to build this map themselves when needed; more often than not, this is achieved by using the `LoadSchemas` function from the protoscan API.
func (*Transcoder) Decode ¶
Decode decodes the given protein-encoded `payload` into a dynamically generated structure-type.
It is used when you need to work with protein-encoded data in a completely agnostic way (e.g. when you merely know the respective names of the fields you're interested in, such as a generic data-enricher for example).
When decoding a specific version of a schema for the first-time in the lifetime of a `Transcoder`, a structure-type must be created from the dependency tree of this schema. This is a costly operation that involves a lot of reflection, see `CreateStructType` documentation for more information. Fortunately, the resulting structure-type is cached so that it can be freely re-used by later calls to `Decode`; i.e. you pay the price only once.
Also, when trying to decode a specific schema for the first-time, `Decode` might not have all of the dependencies directly available in its local `SchemaMap`, in which case it will call the user-defined `TranscoderGetter` in the hope that it might return these missing dependencies. This user-defined function may or may not do some kind of I/O; the given context will be passed to it.
Once again, this price is paid only once.
func (*Transcoder) DecodeAs ¶
func (t *Transcoder) DecodeAs(payload []byte, msg proto.Message) error
DecodeAs decodes the given protein-encoded `payload` into the specified protobuf `Message` using the standard protobuf methods, thus bypassing all of the runtime-decoding and schema versioning machinery.
It is very often used when you need to work with protein-encoded data in a non-agnostic way (i.e. when you know beforehand how you want to decode and interpret the data).
`DecodeAs` basically adds zero overhead compared to a straightforward `proto.Unmarshal` call.
`DecodeAs` never does any kind of I/O.
func (*Transcoder) Encode ¶
Encode bundles the given protobuf `Message` and its associated versioning metadata together within a `ProtobufPayload`, marshals it all together in a byte-slice then returns the result.
`Encode` needs the message's fully-qualified name in order to reverse-lookup its schemaUID (i.e. its versioning hash).
In order to find this name, it will look at different places until either one of those does return a result or none of them does, in which case the encoding will fail. In search order, those places are: 1. first, the `fqName` parameter is checked; if it isn't set, then 2. the `golang/protobuf` package is queried for the FQN; if it isn't available there then 3. finally, the `gogo/protobuf` package is queried too, as a last resort.
Note that a single fully-qualified name might point to multiple schemaUIDs if multiple versions of that schema are currently available in the `SchemaMap`. When this happens, the first schemaUID from the list will be used, which corresponds to the first version of the schema to have ever been added to the local `SchemaMap` (i.e. the oldest one).
func (*Transcoder) FQName ¶ added in v1.3.2
func (t *Transcoder) FQName(ctx context.Context, schemaUID string) string
FQName returns the fully-qualified name of the protobuf schema associated with `schemaUID`.
Iff this schema cannot be found in the local cache, it'll try and fetch it from the remote registry via a call to `GetAndUpsert`.
An empty string is returned if the schema is found neither locally nor remotely.
func (*Transcoder) GetAndUpsert ¶ added in v1.1.0
func (t *Transcoder) GetAndUpsert( ctx context.Context, schemaUID string, ) (map[string]*ProtobufSchema, error)
GetAndUpsert retrieves the `ProtobufSchema` associated with the specified `schemaUID`, plus all of its direct & indirect dependencies.
The retrieval process is done in two steps:
- First, the root schema, as identified by `schemaUID`, is fetched from the local `SchemaMap`; if it cannot be found in there, it'll try to retrieve it via the user-defined `TranscoderGetter`, as passed to the constructor of the `Transcoder`. If it cannot be found in there either, then a schema-not-found error is returned.
- Second, this exact same process is applied for every direct & indirect dependency of the root schema. Once again, a schema-not-found error is returned if one or more dependency couldn't be found (the returned error does indicate which of them).
The `ProtobufSchema`s found during this process are both: - added to the local `SchemaMap` so that they don't need to be searched for ever again during the lifetime of this `Transcoder`, and - returned to the caller as flattened map.
func (*Transcoder) LoadState ¶ added in v1.5.0
func (t *Transcoder) LoadState(path string) error
LoadState loads the state of the Transcoder from disk. The current state is not overwritten, it is merely appended to.
The on-disk format is the following:
PROT-xxx:::base64(schema1)\nPROT-xxx:::base64(schema2)\n...
This can be useful in situations such as shell implementations or CLI tools, where you don't want to re-fetch all the schemas you depend on every restart. This is in absolutely no way designed with performance in mind.
func (*Transcoder) SaveState ¶ added in v1.5.0
func (t *Transcoder) SaveState(path string) error
SaveState saves the current state of the Transcoder to disk.
The on-disk format is the following:
PROT-xxx:::base64(schema1)\nPROT-xxx:::base64(schema2)\n...
This can be useful in situations such as shell implementations or CLI tools, where you don't want to re-fetch all the schemas you depend on every restart. This is in absolutely no way designed with performance in mind.
type TranscoderDeserializer ¶
type TranscoderDeserializer func(payload []byte, ps *ProtobufSchema) error
A TranscoderDeserializer is used to deserialize the payloads returned by a `TranscoderGetter` into a `ProtobufSchema`. See `TranscoderGetter` documentation for more information.
The default `TranscoderDeserializer` unwraps the schema from its `ProtobufPayload` wrapper; i.e. it uses Protein's decoding to decode the schema.
type TranscoderGetter ¶
A TranscoderGetter is called by the `Transcoder` when it cannot find a specific `schemaUID` in its local `SchemaMap`.
The function returns a byte-slice that will be deserialized into a `ProtobufSchema` by a `TranscoderDeserializer` (see below).
A `TranscoderGetter` is typically used to fetch `ProtobufSchema`s from a remote data-store. To that end, several ready-to-use implementations are provided by this package for different protocols: memcached, redis & CQL (i.e. cassandra). See `transcoder_helpers.go` for more information.
The default `TranscoderGetter` always returns a not-found error.
func NewTranscoderGetterCassandra ¶
func NewTranscoderGetterCassandra(s *gocql.Session, table, keyCol, dataCol string, ) TranscoderGetter
NewTranscoderGetterCassandra returns a `TranscoderGetter` suitable for querying a binary blob from a cassandra-compatible store.
The <table> column-family is expected to have (at least) the following columns:
TABLE ( <keyCol> ascii, <dataCol> blob, PRIMARY KEY (<keyCol>))
The given context is forwarded to `gocql`.
func NewTranscoderGetterMemcached ¶
func NewTranscoderGetterMemcached(c *memcache.Client) TranscoderGetter
NewTranscoderGetterMemcached returns a `TranscoderGetter` suitable for querying a binary blob from a memcached-compatible store.
The specified context will be ignored.
func NewTranscoderGetterRedis ¶
func NewTranscoderGetterRedis(p *redis.Pool) TranscoderGetter
NewTranscoderGetterRedis returns a `TranscoderGetter` suitable for querying a binary blob from a redis-compatible store.
The specified context will be ignored.
type TranscoderOpt ¶
type TranscoderOpt func(trc *Transcoder)
A TranscoderOpt is passed to the `Transcoder` constructor to configure various options.
type TranscoderSerializer ¶
type TranscoderSerializer func(ps *ProtobufSchema) ([]byte, error)
A TranscoderSerializer is used to serialize `ProtobufSchema`s before passing them to a `TranscoderSetter`. See `TranscoderSetter` documentation for more information.
The default `TranscoderSerializer` wraps the schema within a `ProtobufPayload`; i.e. it uses Protein's encoding to encode the schema.
type TranscoderSetter ¶
A TranscoderSetter is called by the `Transcoder` for every schema that it can find in memory.
The function receives a byte-slice that corresponds to a `ProtobufSchema` which has been previously serialized by a `TranscoderSerializer` (see below).
A `TranscoderSetter` is typically used to push the local `ProtobufSchema`s sniffed from memory into a remote data-store. To that end, several ready-to-use implementations are provided by this package for different protocols: memcached, redis & CQL (i.e. cassandra). See `transcoder_helpers.go` for more information.
The default `TranscoderSetter` is a no-op.
func NewTranscoderSetterCassandra ¶
func NewTranscoderSetterCassandra(s *gocql.Session, table, keyCol, dataCol string, ) TranscoderSetter
NewTranscoderSetterCassandra returns a `TranscoderSetter` suitable for setting a binary blob into a redis-compatible store.
The <table> column-family is expected to have (at least) the following columns:
TABLE ( <keyCol> ascii, <dataCol> blob, PRIMARY KEY (<keyCol>))
The given context is forwarded to `gocql`.
func NewTranscoderSetterMemcached ¶
func NewTranscoderSetterMemcached(c *memcache.Client) TranscoderSetter
NewTranscoderSetterMemcached returns a `TranscoderSetter` suitable for setting a binary blob into a memcached-compatible store.
The specified context will be ignored.
func NewTranscoderSetterRedis ¶
func NewTranscoderSetterRedis(p *redis.Pool) TranscoderSetter
NewTranscoderSetterRedis returns a `TranscoderSetter` suitable for setting a binary blob into a redis-compatible store.
The specified context will be ignored.
type TranscoderSetterMulti ¶ added in v1.8.0
type TranscoderSetterMulti func( ctx context.Context, schemaUIDs []string, payloads [][]byte, ) error
A TranscoderSetterMulti is called by the `Transcoder` at the end of the initial schema scan in order to publish all the newly found schemas at once.
If a `TranscoderSetterMulti` is set-up, it will take precedence over any vanilla `TranscoderSetter` that might also be configured. In case of failure, the client will fallback to the simple `TranscoderSetter`, if any.
The function receives a slice of byte-slices that corresponds to all the `ProtobufSchema`s that were found in memory, and which have been previously serialized by a `TranscoderSerializer` (see below).
A `TranscoderSetterMulti` is typically used to push the local `ProtobufSchema`s sniffed from memory into a remote data-store. To that end, several ready-to-use implementations are provided by this package for different protocols: redis. See `transcoder_helpers.go` for more information.
Unlike its vanilla `TranscoderSetter` counterpart, this -Multi version guarantees a single round-trip to the remote database, independently of the number of schemas that were fetched from memory; i.e. it guarantees constant latencies.
The default `TranscoderSetterMulti` fallbacks to the simple `TranscoderSetter`.
func NewTranscoderSetterMultiRedis ¶ added in v1.8.0
func NewTranscoderSetterMultiRedis(p *redis.Pool) TranscoderSetterMulti
NewTranscoderSetterMultiRedis returns a `TranscoderSetterMulti` suitable for setting a set of binary blobs into a redis-compatible store.
The specified context will be ignored.
Source Files ¶
Directories ¶
Path | Synopsis |
---|---|
Package failure lists all the possible errors that can be returned either by `protein` or any of its sub-packages.
|
Package failure lists all the possible errors that can be returned either by `protein` or any of its sub-packages. |
protobuf
|
|
test
Package test is a generated protocol buffer package.
|
Package test is a generated protocol buffer package. |
Package protoscan provides the necessary tools & APIs to find, extract, version and build the dependency trees of all the protobuf schemas that have been instanciated by one or more protobuf library (golang/protobuf, gogo/protobuf...).
|
Package protoscan provides the necessary tools & APIs to find, extract, version and build the dependency trees of all the protobuf schemas that have been instanciated by one or more protobuf library (golang/protobuf, gogo/protobuf...). |