deep

package module
v5.5.0 Latest Latest
Warning

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

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

README

deep: Type-Safe Diff, Patch, Clone and Sync for Go

deep is a comprehensive Go library for comparing, cloning, patching and synchronizing complex data structures. It combines code generation (fast, reflection-free implementations for your types) with a reflection engine (which handles everything else, including unexported fields and cyclic structures) behind one uniform API.

Key Features

  • Four core operations: Diff, Apply, Equal, Clone — one call each, for any type.
  • Hybrid architecture: deep-gen generates optimized fast paths; a reflection engine transparently handles types without generated code, unexported fields, cycles, and exotic shapes.
  • Compile-time safety: type-safe field selectors replace brittle string paths.
  • Data-oriented patches: a patch is a flat, serializable list of operations — portable, mergeable, reversible.
  • Conditional patching: guards and per-operation conditions travel inside the patch.
  • Strict mode: optimistic-concurrency checks that verify expected old values before writing.
  • Standard interop: RFC 6902 JSON Patch import/export, including strict checks as test ops.
  • First-class CRDTs: CRDT[T] wrapper with hybrid logical clocks, plus LWW, Text, Counter, Set and Map convergent types.

Quick Start

go get github.com/brunoga/deep/v5
1. Define your model
type User struct {
    ID    int            `json:"id"`
    Name  string         `json:"name"`
    Roles []string       `json:"roles"`
    Score map[string]int `json:"score"`
}
//go:generate go run github.com/brunoga/deep/v5/cmd/deep-gen -type=User .
go generate ./...

This writes user_deep.go next to your source. Commit it. Everything works without this step too — the reflection engine picks up any type automatically — generation just makes it much faster (see benchmarks below).

3. Use the API
import deep "github.com/brunoga/deep/v5"

u1 := User{ID: 1, Name: "Alice", Roles: []string{"user"}}
u2 := User{ID: 1, Name: "Bob", Roles: []string{"user", "admin"}}

// Compare two values; get a patch describing the changes.
patch, err := deep.Diff(u1, u2)

// Apply a patch.
err = deep.Apply(&u1, patch)

// Deep equality and deep copy.
same := deep.Equal(u1, u2)
clone := deep.Clone(u1)

Benchmarks

Generated code vs the reflection engine, on the same five-field struct (nested struct, slice, map). Reproduce with go test -bench 'Generated|Reflection' -benchmem .:

Operation Reflection Generated Speedup
Diff 3,121 ns/op (77 allocs) 546 ns/op (16 allocs) 5.7×
Apply 1,476 ns/op (49 allocs) 80 ns/op (2 allocs) 18.5×
Equal 245 ns/op (5 allocs) 100 ns/op (2 allocs) 2.4×
Clone 986 ns/op (15 allocs) 188 ns/op (5 allocs) 5.3×

Deep copy compared with other clone libraries, same struct:

Library ns/op allocs/op Unexported fields Cyclic structures
deep (generated) 168 5 — (¹)
barkimedes/go-deepcopy 740 16 silently zeroed
deep (reflection) 981 15
mitchellh/copystructure 3,149 91 silently zeroed stack overflow

¹ Generated Clone assumes acyclic data; cyclic values are the reflection engine's territory.

Numbers from an Intel Core Ultra 9 285K; relative ordering is what matters. The competitor benchmarks live out-of-tree so this module stays dependency-free.

Core Operations

  • deep.Diff(a, b) (Patch[T], error) — computes the operations turning a into b. Changed chan/func values diff to a whole-value replace that shares the reference; the error return covers values the reflection engine cannot process.
  • deep.Apply(&target, patch, opts...) error — applies a patch. Individual operation failures are collected: the returned error is an *ApplyError whose Unwrap() []error yields every failure; the remaining operations still apply. Pass deep.WithLogger(l) to route log operations to a specific *slog.Logger.
  • deep.Equal(a, b) bool — deep equality, including unexported fields and cyclic values.
  • deep.Clone(v) T — deep copy. The reflection path handles unexported fields and cycles; non-nil chan and func values are cloned as nil.

All four automatically dispatch to generated methods when they exist and fall back to reflection when they don't — including per-operation: a generated Patch method hands any operation it does not model (slice indexing, move/copy, strict map entries) to the reflection engine.

Type-Safe Selectors

Selectors turn field accessors into JSON Pointer paths at compile time — no string literals to typo:

namePath  := deep.Field(func(u *User) *string         { return &u.Name })
scorePath := deep.Field(func(u *User) *map[string]int { return &u.Score })
rolesPath := deep.Field(func(u *User) *[]string       { return &u.Roles })

deep.At(rolesPath, 0)            // "/roles/0"    — slice element
deep.MapKey(scorePath, "power")  // "/score/power" — map value
namePath.String()                // "/name"

Selectors work for unexported fields too — expose an accessor method returning the field's address and pass it to Field.

Map keys are RFC 6901-escaped automatically (/~1, ~~0). Building paths by hand? Use deep.EscapePathKey / deep.UnescapePathKey.

Building Patches

Diff derives patches from state; the builder constructs them explicitly:

patch := deep.Edit(&user).
    With(
        deep.Set(namePath, "Alice Smith"),            // replace
        deep.Add(deep.MapKey(scorePath, "power"), 100),
        deep.Remove(deep.MapKey(scorePath, "legacy")),
        deep.Move(oldPath, newPath),
        deep.Copy(srcPath, dstPath),
    ).
    Log("update applied").              // structured log line during Apply
    Guard(deep.Eq(statusPath, "paid")). // whole patch is a no-op unless this holds
    Build()

Edit's argument is used only for type inference; the builder produces a standalone Patch[T], not a live view.

Conditions

Conditions are data — they serialize with the patch and are enforced wherever it is applied, including remote peers.

Comparisons: Eq, Ne, Gt, Ge, Lt, Le • Membership: In • Structure: Exists, Type ("string", "number", "boolean", "object", "array", "null") • Text: Matches (regexp) • Combinators: And, Or, Not.

Patch-level guard — all-or-nothing, for state-machine transitions:

patch := deep.Edit(&order).
    With(deep.Set(statusPath, "shipped")).
    Guard(deep.Eq(statusPath, "paid")).
    Build()

Per-operation conditions — individual ops skip independently:

patch := deep.Edit(&invoice).
    With(deep.Set(paidAtPath, time.Now())).
    With(deep.Set(feePath, 25.0).If(deep.Gt(balancePath, 0.0))).
    With(deep.Set(notePath, "").Unless(deep.Exists(notePath))).
    Build()

A condition evaluating to false skips its operation (or, for a guard, the patch — Apply returns an error so the caller knows it did not run). A condition that cannot be evaluated — malformed value, bad path — is an error, not a silent skip.

Struct Tags

Tag Effect
json:"name" Field appears in paths as /name (the Go field name is accepted on apply as well)
json:"-" or deep:"-" Invisible to deep: skipped by Diff, Equal and Clone; operations targeting it are silently ignored
deep:"readonly" Operations targeting the field fail with an error (log operations are still allowed)
deep:"atomic" Diffed as a single whole-value replace — no per-field/per-element operations
deep:"key" Marks a slice element's identity field: the slice diffs by key (add/remove/modify per element) instead of by index, so reordering produces no operations
type Item struct {
    SKU string `deep:"key" json:"sku"`
    Qty int    `json:"qty"`
}
type Inventory struct {
    Items []Item `json:"items"` // diffs as "/items/<sku>", order-insensitive
}

Embedded fields are addressed by their type name (/Meta/version for an embedded Meta), matching how Go names them.

Strict Mode (Optimistic Concurrency)

strict := patch.AsStrict()
err := deep.Apply(&target, strict) // fails if any Old value no longer matches

Every replace/remove verifies the operation's Old value against the current state before writing. Diff fills Old automatically. Strictness survives JSON Patch interop: ToJSONPatch emits an RFC 6902 test op before each checked operation, and ParseJSONPatch folds them back into Old + Strict.

Merging Patches

merged := deep.Merge(base, other, resolver) // resolver may be nil: other wins

Operations are deduplicated by path; on conflict a custom ConflictResolver (Resolve(path string, local, remote any) any) decides, and the output is sorted by path for determinism.

Patch Utilities

patch.IsEmpty()       // no operations?
patch.Reverse()       // undo patch (swaps Old/New, add↔remove; log ops are dropped)
patch.WithGuard(cond) // returns a copy with a patch-level guard
patch.String()        // human-readable summary

Serialization

Native JSONPatch[T] marshals directly; compact keys keep the wire format small (k=kind, p=path, f=from, o=old, n=new, if/un=conditions):

{"ops":[{"k":2,"p":"/name","o":"Alice","n":"Bob"}],"strict":true}

RFC 6902 JSON Patch — for interop with other tooling:

jsonData, err := patch.ToJSONPatch()
// [{"op":"replace","path":"/name","value":"Bob"}]
restored, err := deep.ParseJSONPatch[User](jsonData)

Deep extensions: a leading test op on / with an "if" key carries the patch guard, per-op conditions ride as "if"/"unless" keys, log is a non-standard op, and strict Old checks map to standard test ops.

Note: after any JSON round-trip, numbers in Old/New are float64 (standard Go JSON behavior). Generated code coerces numerics automatically; be aware of it when inspecting operations directly.

Observability

Embed log operations to emit structured trace messages during Apply — request-scoped loggers, test capture, tracing, all without touching your model types:

patch := deep.Edit(&u).
    Log("starting update").
    With(deep.Set(namePath, "Alice Smith")).
    Log("update complete").
    Build()

logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
deep.Apply(&u, patch, deep.WithLogger(logger))
// {"level":"INFO","msg":"deep log","message":"starting update","path":"/"}
// {"level":"INFO","msg":"deep log","message":"update complete","path":"/"}

Without WithLogger, slog.Default() is used.

deep-gen

deep-gen -type=TypeA,TypeB [-output file.go] [dir]

Generates Patch, Diff, Equal and Clone methods (plus internal helpers) for the named struct types. Output defaults to <firsttype>_deep.go in the target directory.

Rules worth knowing:

  • Every referenced struct needs generated code too. If Doc has a Detail field (direct, embedded, pointer, or as a slice/map element), Detail must be listed in -type or generated by another run over the same package. Multiple runs per package are supported.
  • Types from other packages (time.Time, generic instantiations, interfaces) are handled through deep.Equal/deep.Clone, which dispatch to the type's own methods when present. A small set of stdlib value types (time.Time, time.Duration, …) stays on a fast assignment path.
  • What generation can't express falls back to reflection — per operation, transparently: channels/funcs, slice-index paths, move/copy, strict map-entry checks, conditions on nested paths.
  • Generated files never need hand-editing and are safe to regenerate; deep-gen ignores its own previous output while parsing.

CRDTs and Synchronization

The crdt package builds multi-writer synchronization on top of patches, ordered by hybrid logical clocks (crdt/hlc).

CRDT[T] — wraps any type in a concurrency-safe, convergent container:

nodeA := crdt.NewCRDT(GameState{}, "node-a")
nodeB := crdt.NewCRDT(GameState{}, "node-b")

delta := nodeA.Edit(func(s *GameState) { s.Score = 10 }) // timestamped Delta[T]
nodeB.ApplyDelta(delta)                                  // idempotent, causally ordered

nodeA.Merge(nodeB)     // full-state merge, LWW per path
state := nodeA.View()  // snapshot copy

CRDT[T] is JSON-serializable (state + clock + per-path metadata survive the round-trip).

Undo/redoReverse applies a delta's inverse locally and returns a fresh-timestamped undo delta, safe to propagate; reversing the undo redoes:

delta := node.Edit(func(d *Doc) { d.Title = "Draft" })
undo := node.Reverse(delta)
redo := node.Reverse(undo)

Field-level types:

type Document struct {
    Title   crdt.LWW[string] // last-write-wins register: Set(v, ts)
    Content crdt.Text        // collaborative text: Insert/Delete/String, MergeTextRuns
}

Standalone convergent containerscrdt.Counter (increment/decrement), crdt.Set[T] (add-wins set) and crdt.Map[K,V] (LWW map), each with a commutative, idempotent Merge.

crdt/hlcClock (per-node: Now, Update, Reserve, SetLatest) and HLC timestamps (Compare, After) giving a total order across nodes without synchronized wall clocks.

Architecture

A patch is a flat operation list[]Operation with JSON Pointer paths — rather than a recursive tree. That makes patches trivially serializable, cheap to iterate, and composable (merging is stateless). Application is a hybrid: generated applyOperation methods handle the common shapes at native speed and report anything else as unhandled, at which point the reflection engine — which understands every Go shape, unexported fields included — takes over for that one operation. Both paths implement the same semantics; divergence is treated as a bug.

Examples

Every directory under examples/ is a runnable program (go run ./examples/<name>) built around one concept.

Core operations and patches

Example Concept
config_manager Diff, apply, and roll back with Reverse
state_management An undo stack built from reverse patches
nested_structs Nested and embedded struct paths; targeted field updates
slice_paths At for positional slice elements; how slice diffs are shaped
keyed_inventory deep:"key" — order-insensitive, identity-based slice diffs
struct_map_keys Non-string map keys in paths
move_copy_ops Move and Copy operations
multi_error Error collection and ApplyError unwrapping

Tags, conditions, and safety

Example Concept
atomic_config deep:"readonly" enforcement and deep:"atomic" whole-value updates
ignored_fields json:"-" / deep:"-" — keeping secrets out of patches
policy_engine Patch-level Guard with composed conditions
conditional_ops Per-operation If / Unless inside one patch
concurrent_updates Strict mode as optimistic locking
three_way_merge Merge with a custom ConflictResolver
reflection_fallback Unexported fields and cyclic structures

Transport and interop

Example Concept
json_interop Native JSON, RFC 6902 export, and ParseJSONPatch ingest
http_patch_api A patch-driven HTTP PATCH endpoint
websocket_sync Broadcasting state deltas to clients
audit_logging Diffs as an audit trail, plus OpLog tracing

CRDTs

Example Concept
crdt_sync CRDT[T] delta exchange and convergence
crdt_undo_redo Distributed undo/redo via Reverse
crdt_containers Counter, Set and Map
lww_fields Per-field LWW[T] registers resolving a write conflict
text_sync Collaborative text with crdt.Text

License

Apache 2.0

Documentation

Overview

Package deep provides high-performance, type-safe deep diff, copy, equality, and patch-apply operations for Go values.

Architecture

Deep operates on Patch values — flat, serializable lists of Operation records describing changes between two values of the same type. The four core operations are:

  • Diff computes the patch from a to b.
  • Apply applies a patch to a target pointer.
  • Equal reports whether two values are deeply equal.
  • Clone returns a deep copy of a value.

Code Generation

For production use, run deep-gen to generate reflection-free implementations of all four operations for your types:

//go:generate go run github.com/brunoga/deep/v5/cmd/deep-gen -type=MyType .

Generated code is 4–14x faster than the reflection fallback and is used automatically — no API changes required. The reflection engine remains as a transparent fallback for types without generated code.

Patch Construction

Patches can be computed via Diff or built manually with Edit. Typed operation constructors (Set, Add, Remove, Move, Copy) return an Op value that can be passed to Builder.With for a fluent, type-safe chain:

patch := deep.Edit(&user).
    With(
        deep.Set(nameField, "Alice"),
        deep.Set(ageField, 30).If(deep.Gt(ageField, 0)),
    ).
    Guard(deep.Gt(ageField, 18)).
    Build()

Field creates type-safe path selectors from struct field accessors. At and MapKey extend paths into slices and maps with full type safety.

Conditions

Per-operation guards are attached to Op values via Op.If and Op.Unless. A global patch guard is set via Builder.Guard or Patch.WithGuard. Conditions are serializable and survive JSON round-trips.

Causality and CRDTs

The [crdt] sub-package provides [crdt.LWW], a generic Last-Write-Wins register; [crdt.CRDT], a concurrency-safe wrapper for any type; and [crdt.Text], a convergent collaborative text type.

Serialization

Patch marshals to/from JSON natively. Patch.ToJSONPatch and ParseJSONPatch interoperate with RFC 6902 JSON Patch (with deep extensions for conditions and log operations).

Index

Constants

View Source
const (
	OpAdd     = engine.OpAdd
	OpRemove  = engine.OpRemove
	OpReplace = engine.OpReplace
	OpMove    = engine.OpMove
	OpCopy    = engine.OpCopy
	OpLog     = engine.OpLog
)

Variables

This section is empty.

Functions

func And

func And(conds ...*condition.Condition) *condition.Condition

And combines multiple conditions with logical AND.

func Apply

func Apply[T any](target *T, p Patch[T], opts ...ApplyOption) error

Apply applies a Patch to a target pointer. v5 prioritizes the generated Patch method but falls back to reflection if needed.

Note: when a Patch has been serialized to JSON and decoded, numeric values in Operation.Old and Operation.New will be float64 regardless of the original type. This affects strict-mode Old-value checks.

func ApplyOpReflection added in v5.2.0

func ApplyOpReflection[T any](target *T, op Operation, logger *slog.Logger) error

ApplyOpReflection applies a single Operation to target using reflection.

This is intended for use by code generated by deep-gen as a fallback for operations the generated fast-path does not handle (e.g. slice index or map key paths). It is not part of the stable user-facing API; prefer Apply for normal use.

func Clone

func Clone[T any](v T) T

Clone returns a deep copy of v.

func Eq

func Eq[T, V any](p Path[T, V], val V) *condition.Condition

Eq creates an equality condition.

func Equal

func Equal[T any](a, b T) bool

Equal returns true if a and b are deeply equal.

func EscapePathKey added in v5.4.0

func EscapePathKey(key string) string

EscapePathKey escapes a map key or keyed-slice key for use as a JSON Pointer token (RFC 6901): "~" becomes "~0" and "/" becomes "~1". Generated code uses it when building operation paths; it is also useful when constructing paths by hand.

func Exists

func Exists[T, V any](p Path[T, V]) *condition.Condition

Exists creates a condition that checks if a path exists.

func Ge

func Ge[T, V any](p Path[T, V], val V) *condition.Condition

Ge creates a greater-than-or-equal condition.

func Gt

func Gt[T, V any](p Path[T, V], val V) *condition.Condition

Gt creates a greater-than condition.

func In

func In[T, V any](p Path[T, V], vals []V) *condition.Condition

In creates a condition that checks if a value is in a list.

func Le

func Le[T, V any](p Path[T, V], val V) *condition.Condition

Le creates a less-than-or-equal condition.

func Lt

func Lt[T, V any](p Path[T, V], val V) *condition.Condition

Lt creates a less-than condition.

func Matches

func Matches[T, V any](p Path[T, V], regex string) *condition.Condition

Matches creates a regex condition.

func Ne

func Ne[T, V any](p Path[T, V], val V) *condition.Condition

Ne creates a non-equality condition.

func Not

Not inverts a condition.

func Or

func Or(conds ...*condition.Condition) *condition.Condition

Or combines multiple conditions with logical OR.

func Type

func Type[T, V any](p Path[T, V], typeName string) *condition.Condition

Type creates a type-check condition.

func UnescapePathKey added in v5.4.0

func UnescapePathKey(token string) string

UnescapePathKey reverses EscapePathKey, turning a JSON Pointer token back into the original key.

Types

type ApplyError

type ApplyError struct {
	Errors []error
}

ApplyError represents one or more errors that occurred during patch application.

func (*ApplyError) Error

func (e *ApplyError) Error() string

func (*ApplyError) Unwrap

func (e *ApplyError) Unwrap() []error

Unwrap implements the errors.Join interface, allowing errors.Is and errors.As to inspect individual errors within the ApplyError.

type ApplyOption

type ApplyOption func(*applyConfig)

ApplyOption configures the behaviour of Apply.

func WithLogger

func WithLogger(l *slog.Logger) ApplyOption

WithLogger sets the slog.Logger used for OpLog operations within a single Apply call. If not provided, slog.Default is used.

type Builder

type Builder[T any] struct {
	// contains filtered or unexported fields
}

Builder constructs a Patch via a fluent chain.

func Edit

func Edit[T any](_ *T) *Builder[T]

Edit returns a Builder for constructing a Patch[T]. The target argument is used only for type inference and is not stored; the builder produces a standalone Patch, not a live view of the target.

func (*Builder[T]) Build

func (b *Builder[T]) Build() Patch[T]

Build assembles and returns the completed Patch.

func (*Builder[T]) Guard

func (b *Builder[T]) Guard(c *condition.Condition) *Builder[T]

Guard sets the global guard condition on the patch. If Guard has already been called, the new condition is ANDed with the existing one rather than replacing it — calling Guard twice is equivalent to Guard(And(c1, c2)).

func (*Builder[T]) Log

func (b *Builder[T]) Log(msg string) *Builder[T]

Log appends a log operation.

func (*Builder[T]) With

func (b *Builder[T]) With(ops ...Op) *Builder[T]

With appends one or more operations to the patch being built. Obtain operations from the typed constructors Set, Add, Remove, Move, and Copy; per-operation conditions can be attached with Op.If and Op.Unless before passing here.

type ConflictResolver

type ConflictResolver interface {
	Resolve(path string, local, remote any) any
}

ConflictResolver defines how to resolve merge conflicts.

type Op

type Op struct {
	// contains filtered or unexported fields
}

Op is a pending patch operation. Obtain one from Set, Add, Remove, Move, or Copy; attach per-operation conditions with Op.If or Op.Unless before passing to Builder.With.

func Add

func Add[T, V any](p Path[T, V], val V) Op

Add returns a type-safe add (insert) operation.

func Copy

func Copy[T, V any](from, to Path[T, V]) Op

Copy returns a type-safe copy operation that duplicates the value at from to to. Both paths must share the same value type V.

func Move

func Move[T, V any](from, to Path[T, V]) Op

Move returns a type-safe move operation that relocates the value at from to to. Both paths must share the same value type V.

func Remove

func Remove[T, V any](p Path[T, V]) Op

Remove returns a type-safe remove operation.

func Set

func Set[T, V any](p Path[T, V], val V) Op

Set returns a type-safe replace operation.

func (Op) If

func (o Op) If(c *condition.Condition) Op

If attaches a condition that must hold for this operation to be applied.

func (Op) Unless

func (o Op) Unless(c *condition.Condition) Op

Unless attaches a condition that must NOT hold for this operation to be applied.

type OpKind

type OpKind = engine.OpKind

OpKind represents the type of operation in a patch.

type Operation

type Operation = engine.Operation

Operation is an alias for the internal engine operation type.

Note: after JSON round-trip, numeric Old/New values become float64.

type Patch

type Patch[T any] struct {

	// Guard is a global Condition that must be satisfied before any operation
	// in this patch is applied. Set via WithGuard or Builder.Guard.
	Guard *condition.Condition `json:"cond,omitempty"`

	// Operations is a flat list of changes.
	Operations []Operation `json:"ops"`

	// Strict mode enables Old value verification.
	Strict bool `json:"strict,omitempty"`
	// contains filtered or unexported fields
}

Patch is a pure data structure representing a set of changes to type T. It is designed to be easily serializable and manipulatable.

func Diff

func Diff[T any](a, b T) (Patch[T], error)

Diff compares two values and returns a Patch describing the changes from a to b. Generated types (produced by deep-gen) dispatch to a reflection-free implementation. For other types, Diff falls back to the reflection engine. Changed chan and func values diff to a whole-value replace that shares the reference; the error return covers values the engine cannot process.

func Merge

func Merge[T any](base, other Patch[T], r ConflictResolver) Patch[T]

Merge combines two patches into a single patch, resolving conflicts. Operations are deduplicated by path. When both patches modify the same path, r.Resolve is called if r is non-nil; otherwise other's operation wins over base. The output operations are sorted by path for deterministic ordering.

func ParseJSONPatch

func ParseJSONPatch[T any](data []byte) (Patch[T], error)

ParseJSONPatch parses a JSON Patch document (RFC 6902 plus deep extensions) back into a Patch[T]. This is the inverse of Patch.ToJSONPatch().

Wire conventions:

  • A leading {"op":"test","path":"/","if":<predicate>} entry is interpreted as the global Patch.Guard rather than a regular test op, mirroring what ToJSONPatch emits. To round-trip a regular test op at "/", attach it via Builder rather than serialising it as the document's first entry.
  • A {"op":"test","path":<p>,"value":<v>} entry immediately followed by a replace or remove at the same path becomes that operation's Old value and marks the patch strict, mirroring what ToJSONPatch emits for a strict patch.
  • Any other test op — and any unknown op — is dropped: the operation model has no general test kind.

func (Patch[T]) AsStrict

func (p Patch[T]) AsStrict() Patch[T]

AsStrict returns a new patch with strict mode enabled. When strict mode is on, every Replace and Remove operation verifies the current value matches Op.Old before applying; mismatches return an error.

func (Patch[T]) IsEmpty

func (p Patch[T]) IsEmpty() bool

IsEmpty reports whether the patch contains no operations.

func (Patch[T]) Reverse

func (p Patch[T]) Reverse() Patch[T]

Reverse returns a new patch that undoes the changes in this patch.

func (Patch[T]) String

func (p Patch[T]) String() string

String returns a human-readable summary of the patch operations.

func (Patch[T]) ToJSONPatch

func (p Patch[T]) ToJSONPatch() ([]byte, error)

ToJSONPatch returns a JSON Patch representation compatible with RFC 6902 and the github.com/brunoga/jsonpatch extensions.

A strict patch (see Patch.AsStrict) emits a test operation carrying the expected Old value before each replace and remove — RFC 6902's native way of expressing precondition checks — so strictness survives the round-trip through ParseJSONPatch.

func (Patch[T]) WithGuard

func (p Patch[T]) WithGuard(c *condition.Condition) Patch[T]

WithGuard returns a new patch with the global guard condition set.

type Path

type Path[T, V any] struct {
	// contains filtered or unexported fields
}

Path represents a type-safe path to a field of type V within type T.

func At

func At[T any, S ~[]E, E any](p Path[T, S], i int) Path[T, E]

At returns a type-safe path to the element at index i within a slice field.

func Field

func Field[T, V any](s func(*T) *V) Path[T, V]

Field creates a new type-safe path from a selector function.

func MapKey

func MapKey[T any, M ~map[K]V, K comparable, V any](p Path[T, M], k K) Path[T, V]

MapKey returns a type-safe path to the value at key k within a map field. Keys are RFC 6901-escaped so values containing '/' or '~' navigate correctly.

func (Path[T, V]) String

func (p Path[T, V]) String() string

String returns the string representation of the path. Paths built from a selector resolve lazily; the result is cached per selector function so repeated calls are O(1) after the first.

Directories

Path Synopsis
cmd
deep-gen command
Command deep-gen generates reflection-free Patch, Diff, Equal and Clone methods for the types named by -type.
Command deep-gen generates reflection-free Patch, Diff, Equal and Clone methods for the types named by -type.
Package crdt provides Conflict-free Replicated Data Types (CRDTs) built on top of the deep patch engine.
Package crdt provides Conflict-free Replicated Data Types (CRDTs) built on top of the deep patch engine.
hlc
Package hlc implements a Hybrid Logical Clock (HLC) for distributed causality tracking.
Package hlc implements a Hybrid Logical Clock (HLC) for distributed causality tracking.
examples
atomic_config command
Code generated by deep-gen.
Code generated by deep-gen.
audit_logging command
Code generated by deep-gen.
Code generated by deep-gen.
concurrent_updates command
Code generated by deep-gen.
Code generated by deep-gen.
conditional_ops command
Code generated by deep-gen.
Code generated by deep-gen.
config_manager command
Code generated by deep-gen.
Code generated by deep-gen.
crdt_containers command
crdt_sync command
crdt_undo_redo command
http_patch_api command
Code generated by deep-gen.
Code generated by deep-gen.
ignored_fields command
Code generated by deep-gen.
Code generated by deep-gen.
json_interop command
Code generated by deep-gen.
Code generated by deep-gen.
keyed_inventory command
Code generated by deep-gen.
Code generated by deep-gen.
lww_fields command
move_copy_ops command
multi_error command
Code generated by deep-gen.
Code generated by deep-gen.
nested_structs command
Code generated by deep-gen.
Code generated by deep-gen.
policy_engine command
Code generated by deep-gen.
Code generated by deep-gen.
slice_paths command
Code generated by deep-gen.
Code generated by deep-gen.
state_management command
Code generated by deep-gen.
Code generated by deep-gen.
struct_map_keys command
Code generated by deep-gen.
Code generated by deep-gen.
text_sync command
three_way_merge command
Code generated by deep-gen.
Code generated by deep-gen.
websocket_sync command
Code generated by deep-gen.
Code generated by deep-gen.
internal
testmodels
Code generated by deep-gen.
Code generated by deep-gen.
testmodels/external
Code generated by deep-gen.
Code generated by deep-gen.
testmodels/multirun
Package multirun holds two types generated by two separate deep-gen runs over the same package — the documented workflow for splitting generation.
Package multirun holds two types generated by two separate deep-gen runs over the same package — the documented workflow for splitting generation.
testmodels/shapes
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