Documentation
¶
Overview ¶
Package merge layers one configuration value over another, and deep-copies one, from the struct definitions themselves.
It exists to delete a category of code: hand-written `if o.X != "" { s.X = o.X }` chains, hand-written emptiness tests, and hand-written field-by-field copies. Each of those has to be updated whenever a field is added, and each is a place a field can be silently forgotten. Structural merging cannot forget a field, so adding one to a spec needs no merge code at all.
The default behaviour is:
- scalars and strings — the override wins when it is set (a zero value means "unset", so an override can turn a bool on but never off);
- slices — replaced wholesale when the override's is non-empty, because a list of tools means exactly those tools;
- maps — merged key-wise, because a map is a set of independent settings;
- structs, including those behind a pointer — merged field by field, so setting one sub-field does not erase its siblings.
A single field can name an exception the structure cannot express, with a tag:
Pre []string `merge:"append"` // the base's elements, then the override's Allow []string `merge:"append,unique"` // as append, with repeats dropped Ports []Port `json:"ports" patchStrategy:"merge" patchMergeKey:"port"` // a Kubernetes strategic merge
A list carrying Kubernetes' own patch tags merges exactly as a Kubernetes strategic merge patch applies — the override's list is the patch and the base's the object it lands on — using k8s.io/apimachinery's implementation, ported into merge/strategicpatch. Elements sharing a merge key merge field by field (and by their own patch tags, recursively), elements only the override names are added, and elements only the base holds are kept; a list of scalars under patchStrategy "merge" becomes the union. Elements cross into the patch through their JSON encoding, so JSON decides what an element says: a field without omitempty always speaks and so always wins, and a field JSON does not encode is not carried.
Tags are read on struct fields reached through structs and through pointers that are non-nil on both sides. A tag that cannot mean what it says — append on something that is not a list, unique over a non-comparable element type, a rule this package does not define, a patch strategy on something that is not a list or beside a merge rule, a merge key with no strategy, an element without its merge key — panics rather than being ignored, because a silently ignored tag reads exactly like an honoured one.
Policy names the exceptions that belong to a whole type rather than to one field, including the types whose merge is a domain rule rather than a structural one and which therefore merge themselves.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Apply ¶
Apply returns base with override's set fields layered on top, per the package default and policy's exceptions. Neither argument is mutated, and the result shares no mutable memory with either — a merged spec can be edited without reaching back into the config it inherited from.
T must be a struct, map or slice type; anything else is a caller error and panics, as does a mergo failure, which cannot occur for a well-formed T since both operands are the same concrete type by construction.
Types ¶
type Policy ¶
type Policy struct {
// Replace lists types that are indivisible: when the override's value is set
// it replaces the base's wholesale instead of being merged into. A pointer
// type counts as set when it is non-nil — which is how an explicit
// *float64(0) or *bool(false) survives a merge that would otherwise read the
// pointed-at zero as "unset" and drop it.
//
// Name a type by its zero value: merge.Policy{Replace: []any{(*float64)(nil)}}.
Replace []any
// Replace, and Clone copies the reference instead of walking it. Registries,
// catalogs and caches belong here — deep-copying one is both wasteful and
// wrong, because consumers rely on pointer identity.
Shared []any
// Merger lists types that merge themselves. Each must declare the method
// `Merge(T) T` on the type named; Apply calls base.Merge(override) and takes
// the result instead of walking the type. It is where a merge rule that is a
// domain decision rather than a structural one lives: a list that accumulates
// across configuration layers instead of being replaced, or two fields that
// only mean anything when they move together.
//
// The method always runs — a nil slice or map destination is materialised
// first, so "the base had none" reaches Merge as an empty value rather than
// silently bypassing it. A pointer type therefore cannot be a Merger (a nil
// pointer has nothing to call the method on), and naming a type without that
// exact method panics rather than quietly reverting to structural merging.
Merger []any
}
Policy declares the exceptions to structural merging for one family of types. The zero Policy is valid and means "no exceptions".
Directories
¶
| Path | Synopsis |
|---|---|
|
internal/json
Package json is forked from the Go standard library to enable us to find the field of a struct that a given JSON key maps to.
|
Package json is forked from the Go standard library to enable us to find the field of a struct that a given JSON key maps to. |