deepproto

package module
v1.3.0 Latest Latest
Warning

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

Go to latest
Published: Sep 5, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

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

func FromProto[T any](w *wire.Patch) (deep.Patch[T], error)

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.

func ToProto added in v1.2.0

func ToProto[T any](p deep.Patch[T]) (*wire.Patch, error)

ToProto converts a patch into its protobuf envelope.

Types

This section is empty.

Directories

Path Synopsis
internal
genmodel
Code generated by deep-gen.
Code generated by deep-gen.

Jump to

Keyboard shortcuts

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