merge

package
v1.54.0 Latest Latest
Warning

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

Go to latest
Published: Jul 29, 2026 License: Apache-2.0 Imports: 3 Imported by: 0

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

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 — 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

func Apply[T any](base, override T, policy Policy) T

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.

func Clone

func Clone[T any](v T, policy Policy) T

Clone returns a deep copy of v. Interfaces, functions and channels are copied by reference — they carry behaviour rather than configuration — as are the types named in policy.Shared. Unexported fields are copied as they stand.

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

	// Shared lists types that are referenced, not owned: Apply assigns them like
	// 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".

func (Policy) With

func (p Policy) With(other Policy) Policy

With returns the union of two policies, so a nested type's policy can be composed into its container's rather than restated.

Jump to

Keyboard shortcuts

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