Documentation
¶
Overview ¶
Package deepproto makes deep operate correctly on protocol buffer messages.
Pointing the generic machinery at protoc-generated structs is not merely suboptimal, it is wrong: a message's Go struct carries the proto runtime's internal state, so field-by-field equality reports two equal messages unequal the moment one has been marshaled, a diff emits operations for the runtime's bookkeeping, and a reflection-made clone crashes the runtime on its next Marshal.
Register installs a deep.TypeFamily claiming every proto.Message type. Inside that boundary the proto runtime's own machinery is used — proto.Equal, proto.Clone, protoreflect for diffing and applying, protojson on the wire — and outside it nothing changes: messages sit in ordinary structs, patches carry ordinary operations, and paths address message fields by their protojson names.
func main() {
deepproto.Register()
...
}
Example ¶
The scenario this package exists for: rows stored as serialized protobuf, updated with conditional patches applied where the data lives. The store unmarshals the blob, applies the operations whose conditions still hold, and marshals it back — the flow that, without Register, corrupted messages and reported phantom conflicts.
package main
import (
"fmt"
deepproto "github.com/brunoga/deep/proto"
deep "github.com/brunoga/deep/v6"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/structpb"
)
func main() {
deepproto.Register()
// A row, as it sits in the database: proto bytes.
original, _ := structpb.NewStruct(map[string]any{
"title": "Kettle",
"price": 1999.0,
})
blob, _ := proto.Marshal(original)
// A client's patch, e.g. decoded from a request body. The path addresses
// the message by its protojson field names; the condition is the client's
// stated assumption.
patch := deep.Patch[*structpb.Struct]{Operations: []deep.Operation{{
Kind: deep.OpReplace,
Path: "/fields/price/numberValue",
Old: 1999.0,
New: 2499.0,
}}, Strict: true}
// The write, inside the transaction: load, apply, store.
row := &structpb.Struct{}
if err := proto.Unmarshal(blob, row); err != nil {
panic(err)
}
res, err := deep.ApplyWithResult(&row, patch)
if err != nil {
fmt.Println("rejected:", err)
return
}
fmt.Println("applied:", res.AllApplied())
blob, _ = proto.Marshal(row)
// The next reader sees the committed change.
next := &structpb.Struct{}
_ = proto.Unmarshal(blob, next)
fmt.Println("price:", next.Fields["price"].GetNumberValue())
// A writer whose assumption no longer holds is rejected — the strict
// check compares against what the row now says.
stale := patch // still expects 1999
if _, err := deep.ApplyWithResult(&next, stale); err != nil {
fmt.Println("stale writer rejected")
}
}
Output: applied: true price: 2499 stale writer rejected
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func FromProto ¶ added in v1.2.0
FromProto converts a protobuf envelope back into a patch. Values come back as deep.RawValue and decode at apply time, against the type of the field each operation addresses.
func Register ¶
func Register()
Register installs the protobuf type family. Call it once, during initialisation, before the first deep operation touches a message. Calling it again is a no-op.
func RegisterListKey ¶ added in v1.2.0
func RegisterListKey(fieldFullName, keyField string)
RegisterListKey declares that the repeated message field fieldFullName — "package.Message.field" — is keyed by the element field keyField, named by its protojson name. Register during initialisation, alongside Register.
Types ¶
This section is empty.