Documentation
¶
Overview ¶
Package codex is the public API for go-codex: a self-documenting codec library for Go.
A Codec[T] is a single value that simultaneously describes how to encode, decode, and document a type. Write the codec once; derive JSON, YAML, TOML, OpenAPI schemas, AsyncAPI schemas, and more from the same definition — no struct tags, no reflection, no code generation.
Core type ¶
Codec[T] bundles three functions in one value:
- Encode: transforms a Go value into an intermediate (e.g. map[string]any for JSON)
- Decode: transforms the intermediate back to T, running all constraints
- Schema: carries the data shape and constraints as a schema.Schema value
Primitive codecs ¶
Use these to build up more complex codecs:
codex.String() // string codex.Int() // int codex.Float64() // float64 codex.Bool() // bool codex.Time() // time.Time ↔ RFC 3339 string codex.Any() // any
Binary codecs — Bytes vs Base64 ¶
Both Bytes and Base64 work with []byte in Go, but serialize differently:
codex.Bytes() // raw []byte pass-through; schema format "binary" (OpenAPI binary body) codex.Base64() // base64 string encoding; schema format "byte" (OpenAPI base64 field)
Use Bytes for binary file I/O and HTTP binary request/response bodies — the wire representation is the raw bytes themselves. Combine with [format.Binary] and [validate.HasPrefix] for magic-byte validation:
pngSignature := []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}
pngCodec := codex.Bytes().
Refine(validate.MaxBytes(5 * 1024 * 1024)).
Refine(validate.HasPrefix(pngSignature))
Use Base64 when the binary data is embedded inside a JSON document as a base64-encoded string field (e.g. an "avatar" field in a user profile):
codex.OptionalField("avatar",
codex.Base64().Refine(validate.MaxBytes(65536)).
WithDescription("Profile image (base64, max 64 KiB)."),
...
)
Struct codecs ¶
Build struct codecs with RequiredField and OptionalField:
var UserCodec = codex.Struct[User](
codex.RequiredField("name",
codex.String().Refine(validate.NonEmptyString).WithDescription("Display name."),
func(u User) string { return u.Name },
func(u *User, v string) { u.Name = v },
),
codex.RequiredField("email",
codex.String().Refine(validate.Email),
func(u User) string { return u.Email },
func(u *User, v string) { u.Email = v },
),
)
Constraints ¶
Add constraints with Codec.Refine. Constraints run on both Encode and Decode:
var AgeCodec = codex.Int().
Refine(validate.RangeInt(0, 150)).
WithTitle("Age").
WithDescription("Age in years.")
Constraint violations return structured ValidationErrors that are fully inspectable with errors.As.
Composition ¶
Codecs compose — build complex codecs from simpler ones:
// Define a field codec once, reuse across multiple structs.
var emailField = codex.String().Refine(validate.Email).
WithDescription("Email address.")
var UserCodec = codex.Struct[User]( codex.RequiredField("email", emailField, ...), ...)
var ProfileCodec = codex.Struct[Profile](codex.RequiredField("email", emailField, ...), ...)
Key composing constructors:
- SliceOf — homogeneous array
- StringMap — map[string]V
- Map — map[K]V with validated keys
- EntrySlice — JSON/YAML/TOML object where key+value are merged into a single element type
- Nullable — optional pointer *T
- TaggedUnion — discriminated union
- UntaggedUnion — structural union (first-match decode)
EntrySlice is particularly useful when the object key carries domain meaning:
var containersCodec = codex.EntrySlice(
containerKeyCodec, // validates + strips prefix from wire key
moduleCodec, // decodes value
func(name string, m ModuleConfig) Container {
return Container{Name: name, Image: m.Image, Status: m.Status}
},
func(c Container) (string, ModuleConfig) {
return c.Name, ModuleConfig{Image: c.Image, Status: c.Status}
},
)
// Codec[[]Container] — no post-processing needed
Smart constructors ¶
Use Codec.New to validate at construction time:
email, err := emailCodec.New(Email("user@example.com"))
// err != nil if the email is invalid
Use Must for package-level validated constants:
var defaultUser = codex.Must(usernameCodec.New(Username("guest")))
Further reading ¶
- [validate] — reusable constraints (Email, UUID, URL, ranges, …)
- [format] — format bridges (JSON, YAML, TOML, Gob, streaming)
- api/rest — REST API builder using codecs
- api/events — event channel builder using codecs
- [forge] — governed computation pipeline using codecs
Index ¶
- Variables
- func DecodeVars[T any](target *T, vars map[string]string, fields ...FieldCodec[T]) error
- func Downcast[A any, B any](v B) (A, error)
- func EncodeVars[T any](v T, fields ...FieldCodec[T]) (map[string]string, error)
- func Must[T any](v T, err error) T
- func Must2[A, B any](a A, b B, err error) (A, B)
- type Codec
- func Any() Codec[any]
- func Base64() Codec[[]byte]
- func Bool() Codec[bool]
- func Bytes() Codec[[]byte]
- func Date() Codec[time.Time]
- func Duration() Codec[time.Duration]
- func Either2[A, B any](ca Codec[A], cb Codec[B]) Codec[Either[A, B]]
- func EntrySlice[K comparable, V, R any](keyCodec Codec[K], valueCodec Codec[V], merge func(K, V) R, ...) Codec[[]R]
- func Eq[T comparable](base Codec[T], value T) Codec[T]
- func Float32() Codec[float32]
- func Float64() Codec[float64]
- func HexColor() Codec[Color]
- func Int() Codec[int]
- func Int32() Codec[int32]
- func Int64() Codec[int64]
- func Map[K comparable, V any](keyCodec Codec[K], valueCodec Codec[V]) Codec[map[K]V]
- func MapCodecSafe[A, B any](c Codec[A], to func(A) B, from func(B) (A, error)) Codec[B]
- func MapCodecValidated[A, B any](ca Codec[A], cb Codec[B], to func(A) (B, error), from func(B) (A, error)) Codec[B]
- func Nullable[T any](inner Codec[T]) Codec[*T]
- func Pure[T any](value T) Codec[T]
- func SliceOf[T any](elem Codec[T]) Codec[[]T]
- func StrictStruct[T any](fields ...FieldCodec[T]) Codec[T]
- func String() Codec[string]
- func StringMap[V any](value Codec[V]) Codec[map[string]V]
- func Struct[T any](fields ...FieldCodec[T]) Codec[T]
- func TaggedUnion[T any](tag string, variants map[string]Codec[T], ...) Codec[T]
- func Time() Codec[time.Time]
- func Uint() Codec[uint]
- func Uint64() Codec[uint64]
- func UntaggedUnion[T any](which func(T) int, variants ...UntaggedVariant[T]) Codec[T]
- func (c Codec[T]) New(v T) (T, error)
- func (c Codec[T]) Refine(cons ...Constraint[T]) Codec[T]
- func (c Codec[T]) RefineFunc(fn func(T) error) Codec[T]
- func (c Codec[T]) Validate(v T) error
- func (c Codec[T]) WithDeprecated() Codec[T]
- func (c Codec[T]) WithDescription(desc string) Codec[T]
- func (c Codec[T]) WithExample(v any) Codec[T]
- func (c Codec[T]) WithTitle(title string) Codec[T]
- type Color
- type Constraint
- type ConstraintError
- type Either
- type EitherError
- type ElementError
- type Field
- func DefaultField[T, F any](name string, codec Codec[F], defaultVal F, get func(T) F, set func(*T, F)) Field[T, F]
- func OptionalField[T, F any](name string, codec Codec[F], get func(T) F, set func(*T, F)) Field[T, F]
- func RequiredField[T, F any](name string, codec Codec[F], get func(T) F, set func(*T, F)) Field[T, F]
- type FieldCodec
- type InvalidColorError
- type KeyError
- type TypeMismatchError
- type UnknownVariantError
- type UntaggedVariant
- type ValidationError
- type ValidationErrors
- type VarEncodeTypeError
- type VariantError
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrMissingField = errors.New("missing required field")
ErrMissingField is returned when a required struct field is absent from the input. Use errors.Is to check for this sentinel.
var ErrUnknownField = errors.New("unknown field")
ErrUnknownField is returned by StrictStruct-built codecs when the input contains a key not declared by any field — the "additionalProperties: false" case. Use errors.Is to check for this sentinel. Like ErrMissingField, the offending field name is carried by the wrapping ValidationError.Field, not by this sentinel itself.
Functions ¶
func DecodeVars ¶ added in v0.12.0
func DecodeVars[T any](target *T, vars map[string]string, fields ...FieldCodec[T]) error
DecodeVars decodes each named field in fields from vars into target, mutating only those fields — any other fields already set on *target are left untouched. This is a PARTIAL merge, unlike Struct's Decode, which builds an entirely new T from one JSON object.
fields are declared with the SAME RequiredField/OptionalField/ DefaultField constructors already used for Struct — no new declaration API. A field's Codec must accept a string value on Decode (e.g. String()... or MapCodecSafe(String()..., ...) for a typed field like int or time.Time) since vars is always string-keyed/ string-valued (path segments, topic segments, header/query/cookie values, and file path segments are all strings at the wire level).
RequiredField vars that are absent from vars return ValidationErrors containing ErrMissingField; OptionalField/DefaultField vars that are absent are skipped/defaulted exactly as in Struct. Codec validation failures are collected the same way — DecodeVars never stops at the first error; every field is attempted, and every failure is reported.
var req GetUserReq
err := codex.DecodeVars(&req, map[string]string{"id": r.PathValue("id")},
codex.RequiredField("id", codex.String().Refine(validate.UUID),
func(r GetUserReq) string { return r.ID },
func(r *GetUserReq, v string) { r.ID = v }))
Example ¶
package main
import (
"fmt"
"github.com/DaniDeer/go-codex/codex"
"github.com/DaniDeer/go-codex/validate"
)
func main() {
type GetUserReq struct{ ID string }
idField := codex.RequiredField("id", codex.String().Refine(validate.UUID),
func(r GetUserReq) string { return r.ID },
func(r *GetUserReq, v string) { r.ID = v })
var req GetUserReq
vars := map[string]string{"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"}
if err := codex.DecodeVars(&req, vars, idField); err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(req.ID)
}
Output: f47ac10b-58cc-4372-a567-0e02b2c3d479
func Downcast ¶
Downcast attempts to cast a value of type B to type A. Useful for tagged unions where variants share a common interface.
func EncodeVars ¶ added in v0.12.0
func EncodeVars[T any](v T, fields ...FieldCodec[T]) (map[string]string, error)
EncodeVars extracts each named field in fields from v using its Get function and Codec, producing a map[string]string. This replaces hand-written varsFor func(T) map[string]string closures used by every adapter's SinkAdapter/IOAdapter/SourceAdapter constructor (adapters/file, adapters/redis, adapters/mqtt, adapters/mqtt5, adapters/zeromq) — call it FROM inside the closure the adapter expects:
varsFor := func(r SensorReading) map[string]string {
return codex.Must(codex.EncodeVars(r, sensorIDField))
}
Returns VarEncodeTypeError if any field's Codec.Encode does not produce a string — a caller programming error (an unsuitable codec was attached to a var field), not a runtime data error.
Example ¶
package main
import (
"fmt"
"github.com/DaniDeer/go-codex/codex"
"github.com/DaniDeer/go-codex/validate"
)
func main() {
type SensorReading struct{ SensorID string }
sensorIDField := codex.RequiredField("sensorID", codex.String().Refine(validate.NonEmptyString),
func(r SensorReading) string { return r.SensorID },
func(r *SensorReading, v string) { r.SensorID = v })
reading := SensorReading{SensorID: "sensor-42"}
vars, err := codex.EncodeVars(reading, sensorIDField)
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(vars["sensorID"])
}
Output: sensor-42
func Must ¶
Must returns v if err is nil, and panics with err otherwise.
It follows the same convention as template.Must and regexp.MustCompile: use it to wrap any (T, error) call where failure is a programming error, not a recoverable runtime condition.
Typical uses include package-level validated constants and test data setup:
var defaultEmail = codex.Must(emailCodec.New(Email("noreply@example.com")))
got := codex.Must(emailCodec.Decode("user@example.com"))
func Must2 ¶ added in v0.12.0
Must2 is Must for a (A, B, error) triple — e.g. the port + handle pair returned by ports' protocol-named convenience constructors (ports.NewRestPort, ports.NewReqReplyPort, ports.NewMCPPort, ...):
Readings, readingsHandle := codex.Must2(ports.NewRestPort[Req, Resp](
"rest/readings", reqCodec, respCodec, pattern, opts))
Types ¶
type Codec ¶
type Codec[T any] struct { Encode func(T) (any, error) Decode func(any) (T, error) Schema schema.Schema }
Codec encodes values of type T to an intermediate representation, decodes that representation back to T, and describes the schema.
Empty is a ready-made Codec for routes and SSE streams that carry no request body. Use it as the reqCodec argument to api/rest.NewRoute and api/rest.NewSSERoute for GET, DELETE, and other body-less routes — no per-file empty struct or codec needed.
handle, err := rest.NewRoute[struct{}, User]("GET", "/users/{id}",
codex.Empty, userCodec, rest.RouteMeta{OperationID: "getUser"},
).Register(b)
func Any ¶ added in v0.3.0
Any returns a Codec[any] that passes values through without modification. Encode and Decode are identity functions; no type checking or coercion is applied. The schema is empty ({}) which means "accepts any value" in JSON Schema terms.
Typical uses: extension fields, opaque config blobs, dynamic JSON passed through.
Note: config.FromEnv has no type hints for any-typed fields and will pass the raw env var string through as-is.
func Base64 ¶ added in v0.11.0
Base64 returns a Codec for []byte using standard base64 encoding. Encoded values are base64 strings; schema format is "byte" (OpenAPI base64 convention).
Use Base64 when embedding binary data inside a JSON document (e.g. an "avatar" field). For raw binary data in file I/O or HTTP binary bodies, use Bytes instead.
Errors:
- TypeMismatchError — Decode receives a non-string value
- A plain error — the string is not valid base64
- ConstraintError — a Refine constraint is violated (when constraints are added)
func Bytes ¶
Bytes returns a Codec for []byte that passes raw bytes through without encoding. Schema format is "binary" (OpenAPI binary body convention for raw HTTP bodies and file I/O).
Use Bytes for binary file I/O (format.Binary) and HTTP binary request/response bodies. For base64-encoded fields inside JSON documents, use Base64 instead.
Errors:
- TypeMismatchError — Decode receives a value that is not []byte
- ConstraintError — a Refine constraint is violated (when constraints are added)
func Date ¶
Date returns a Codec for time.Time using date-only encoding (2006-01-02). The time component is ignored on encode. Decoded values have time set to midnight UTC. Schema format is "date".
func Duration ¶ added in v0.3.0
Duration returns a Codec for time.Duration. Values encode to the standard Go duration string (e.g. "1h30m5s") and decode from the same format.
func Either2 ¶ added in v0.3.0
Either2 returns a Codec[Either[A, B]] that tries ca first, then cb.
Decode strategy:
- Try ca.Decode(v). If it succeeds, return Either{Left: &a}.
- Otherwise try cb.Decode(v). If it succeeds, return Either{Right: &b}.
- If both fail, return EitherError listing both errors.
Encode strategy:
- If Left != nil, use ca.Encode(*Left).
- Otherwise use cb.Encode(*Right).
Schema: {oneOf: [schemaA, schemaB]}
func EntrySlice ¶ added in v0.11.0
func EntrySlice[K comparable, V, R any]( keyCodec Codec[K], valueCodec Codec[V], merge func(K, V) R, split func(R) (K, V), ) Codec[[]R]
EntrySlice[K, V, R any] decodes a JSON/YAML/TOML object by merging each entry's decoded key and value into a single element of type R. The result is Codec[[]R].
keyCodec decodes each string key on the wire into K and validates it. valueCodec decodes each object value into V. merge combines a decoded (K, V) pair into R — called on decode. It is infallible: (K, V) are already validated by their codecs. Chain Codec.RefineFunc on the result if you need cross-field constraints on R. split extracts (K, V) from R — called on encode. keyCodec.Encode(K) must produce a string; if it does not, the entry is reported as KeyError.
Typical use case — a JSON object whose key encodes a domain identifier:
{"properties.desired.modules.cv-writer": {"image": "...", "status": "running"}}
With EntrySlice, this decodes directly into []Container where Container.Name is extracted from the key by the key codec (e.g. via MapCodecValidated to strip a prefix).
All key errors are reported as KeyError{Key, Err}. The order of elements in the decoded slice is non-deterministic (JSON/YAML/TOML object key order is not guaranteed). Sort the slice after decode if order matters.
Wire format compatibility: works with JSON, YAML (quoted keys), and TOML (quoted table headers). TOML bare dotted keys (e.g. [properties.desired]) produce nested map objects, not flat string keys — use quoted headers instead.
Schema: identical to Map — an object schema with propertyNames for the key constraint and additionalProperties for the value schema.
Example ¶
package main
import (
"encoding/json"
"fmt"
"strings"
"github.com/DaniDeer/go-codex/codex"
)
func main() {
type ModuleConfig struct {
Image string
Status string
}
type Container struct {
Name string
Image string
Status string
}
const prefix = "modules."
keyCodec := codex.MapCodecSafe(
codex.String(),
func(fullKey string) string { return strings.TrimPrefix(fullKey, prefix) },
func(name string) (string, error) { return prefix + name, nil },
)
valueCodec := codex.Struct[ModuleConfig](
codex.RequiredField("image", codex.String(),
func(m ModuleConfig) string { return m.Image },
func(m *ModuleConfig, v string) { m.Image = v },
),
codex.RequiredField("status", codex.String(),
func(m ModuleConfig) string { return m.Status },
func(m *ModuleConfig, v string) { m.Status = v },
),
)
c := codex.EntrySlice(
keyCodec,
valueCodec,
func(name string, m ModuleConfig) Container {
return Container{Name: name, Image: m.Image, Status: m.Status}
},
func(c Container) (string, ModuleConfig) {
return c.Name, ModuleConfig{Image: c.Image, Status: c.Status}
},
)
raw := map[string]any{
"modules.writer": map[string]any{"image": "registry/writer:1.0", "status": "running"},
}
containers, _ := c.Decode(raw)
data, _ := json.Marshal(containers)
fmt.Println(string(data))
}
Output: [{"Name":"writer","Image":"registry/writer:1.0","Status":"running"}]
func Eq ¶ added in v0.3.0
func Eq[T comparable](base Codec[T], value T) Codec[T]
Eq wraps base with a constraint that only accepts value. Decode: base decodes the wire value, then equality is checked. Encode: equality is checked before encoding via base.
Using a base codec handles wire-type coercion: Eq(Int(), 42) correctly accepts the JSON number 42 (arrives as float64) because Int() converts it first.
The schema is inherited from base with Enum set to [value].
func Float32 ¶ added in v0.3.0
Float32 returns a Codec for the float32 type. The schema uses format "float" to document the reduced precision.
func HexColor ¶ added in v0.12.0
HexColor returns a Codec for Color using CSS Color Level 4 hex notation: "#RGB", "#RRGGBB", "#RGBA", or "#RRGGBBAA" (case-insensitive on decode; the leading "#" is required). Encode always emits a canonical lowercase form — "#rrggbb" when A is 255 (fully opaque), "#rrggbbaa" otherwise — never the 3/4-digit shorthand, regardless of the shorthand used on decode.
Example ¶
package main
import (
"fmt"
"github.com/DaniDeer/go-codex/codex"
)
func main() {
c := codex.HexColor()
v, err := c.Decode("#1E90FF")
if err != nil {
fmt.Println("decode error:", err)
return
}
fmt.Printf("R=%d G=%d B=%d A=%d\n", v.R, v.G, v.B, v.A)
enc, _ := c.Encode(v)
fmt.Println(enc)
// 3-digit shorthand expands, and Encode always emits the canonical form.
short, _ := c.Decode("#f00")
enc2, _ := c.Encode(short)
fmt.Println(enc2)
}
Output: R=30 G=144 B=255 A=255 #1e90ff #ff0000
func Int32 ¶ added in v0.3.0
Int32 returns a Codec for the int32 type. The schema uses format "int32" to document the reduced range.
func Map ¶ added in v0.8.0
func Map[K comparable, V any](keyCodec Codec[K], valueCodec Codec[V]) Codec[map[K]V]
Map returns a Codec for map[K]V, using keyCodec to validate/encode/decode map keys and valueCodec for values. K must encode to a string — JSON and YAML require string map keys.
Key errors are reported as KeyError{Key, Err}. Non-string key encoding produces a clear error. The generated schema uses "propertyNames" for the key constraint and "additionalProperties" for the value schema.
func MapCodecSafe ¶
MapCodecSafe creates a new Codec[B] from Codec[A] using two mapping functions. from is the encode direction and must always succeed. to is the decode direction and may fail.
func MapCodecValidated ¶
func MapCodecValidated[A, B any]( ca Codec[A], cb Codec[B], to func(A) (B, error), from func(B) (A, error), ) Codec[B]
MapCodecValidated creates a Codec[B] from Codec[A] and Codec[B] using two fallible mapping functions.
Both directions may return an error. After mapping to B in the decode direction, cb.Validate is called to enforce all Refine constraints defined on cb. The resulting codec carries cb's schema.
Use MapCodecValidated when the mapping itself can fail and the target type B has its own validation constraints expressed via Refine. For a simpler case where only the encode direction can fail and no post-mapping validation is needed, use MapCodecSafe.
func Nullable ¶
Nullable wraps inner to produce a Codec[*T] that treats nil as JSON null. The generated schema inherits all fields from inner and sets Nullable to true.
func Pure ¶ added in v0.3.0
Pure returns a Codec[T] that always decodes to value regardless of the wire input, and always encodes value regardless of the Go value passed to Encode.
Use for fields that must always carry a fixed value in the encoded form — for example, a protocol specversion field ("1.0") or a derived field set automatically.
The schema is {enum: [value]} to communicate the fixed value to documentation tools.
func StrictStruct ¶ added in v0.12.0
func StrictStruct[T any](fields ...FieldCodec[T]) Codec[T]
StrictStruct is Struct, but Decode additionally rejects any input key not declared by fields — the JSON Schema "additionalProperties: false" semantics. Encode is unchanged: "unknown field" only has meaning on the decode (external input) direction. Use StrictStruct when unrecognized keys should be treated as errors (e.g. catching a typo'd field name) instead of silently ignored, which is Struct's default (forward-compatible) behavior.
strictOrderCodec := codex.StrictStruct[Order](
codex.RequiredField("id", codex.String(), ...),
// ...
)
_, err := strictOrderCodec.Decode(map[string]any{"id": "x", "totall": 9.99})
// err: field "totall": unknown field (ErrUnknownField) — likely a typo for "total"
Strictness is NOT viral/recursive: a nested Struct field inside a StrictStruct-declared outer struct stays non-strict unless that nested codec is ALSO declared via StrictStruct — opt in at each nesting level independently, exactly like Required/Optional/Default are declared independently at each level.
Unknown-key errors are collected alongside normal per-field errors (missing required fields, constraint failures) in one pass — a request with both a missing required field AND a typo'd key reports both, not just one.
func StringMap ¶
StringMap returns a Codec for map[string]V, using value to encode/decode each entry. The generated schema is an object with additionalProperties set to the value codec's schema.
func Struct ¶
func Struct[T any](fields ...FieldCodec[T]) Codec[T]
Struct builds a Codec[T] by composing field codecs. Schema is built eagerly.
func TaggedUnion ¶
func TaggedUnion[T any]( tag string, variants map[string]Codec[T], selectVariant func(T) (string, error), ) Codec[T]
TaggedUnion builds a Codec[T] for a discriminated union identified by a tag field.
func Time ¶
Time returns a Codec for time.Time using RFC 3339 (ISO 8601) encoding. Values are normalized to UTC on encode. Schema format is "date-time".
func Uint ¶ added in v0.3.0
Uint returns a Codec for the uint type. The schema sets minimum: 0 to document the non-negative constraint.
func Uint64 ¶ added in v0.3.0
Uint64 returns a Codec for the uint64 type. The schema sets minimum: 0 to document the non-negative constraint.
func UntaggedUnion ¶ added in v0.3.0
func UntaggedUnion[T any](which func(T) int, variants ...UntaggedVariant[T]) Codec[T]
UntaggedUnion builds a Codec[T] that tries each variant in order during decode.
Decode strategy: try variants in order; first success wins. If all fail, return EitherError listing all branch errors.
Encode strategy: which(v) returns the index (0-based) of the variant to use.
Schema: {oneOf: [...variant schemas...]} — no discriminator field.
Use TaggedUnion when your values carry a type discriminator field. Use UntaggedUnion when the shape alone distinguishes variants.
func (Codec[T]) New ¶
New validates v and returns it if all constraints pass.
It is a single-call smart constructor: call New to create a validated instance of T without separating construction from validation. On success it returns (v, nil); on failure it returns (zero, err) where err contains the first constraint that failed.
New delegates to Validate internally, so the same Refine constraints and encode-direction checks apply.
func (Codec[T]) Refine ¶
func (c Codec[T]) Refine(cons ...Constraint[T]) Codec[T]
Refine wraps the codec with one or more constraints checked during both Encode and Decode. Constraints are applied in order; the first failing constraint stops evaluation. Encode validates after serialising (field errors surface before cross-field constraints); Decode validates after parsing. If a constraint's Schema is non-nil, it is applied to the codec's schema. Calling Refine with no arguments returns the codec unchanged.
func (Codec[T]) RefineFunc ¶ added in v0.3.0
RefineFunc wraps the codec with a constraint expressed as a function returning an error. Encode validates after serialising (underlying field errors surface before cross-field constraints); Decode validates after parsing. If fn returns nil the value passes; if fn returns an error it becomes a ConstraintError.
This is the idiomatic way to add cross-field constraints to a struct codec:
var rangeCodec = codex.Struct[DateRange](...).
RefineFunc(func(r DateRange) error {
if !r.End.After(r.Start) {
return errors.New("end must be after start")
}
return nil
})
func (Codec[T]) Validate ¶
Validate checks v against all Refine constraints by encoding it and decoding it back. Both directions now run constraints, so an invalid value fails at the Encode step. This is equivalent to calling Encode followed by Decode.
func (Codec[T]) WithDeprecated ¶ added in v0.3.0
WithDeprecated returns a new Codec marked as deprecated. Deprecated fields are rendered with "deprecated: true" in generated schemas.
func (Codec[T]) WithDescription ¶
WithDescription returns a new Codec with Schema.Description set to desc.
func (Codec[T]) WithExample ¶ added in v0.3.0
WithExample returns a new Codec with Schema.Example set to v. The example value appears in generated schemas (OpenAPI, AsyncAPI) to illustrate expected input for documentation purposes.
type Color ¶ added in v0.12.0
type Color struct {
R, G, B, A uint8
}
Color is a straight (non-premultiplied) RGB(A) color value. Component values are 0-255; A defaults to 255 (fully opaque) when a hex string omits the alpha channel.
Color is intentionally NOT image/color.RGBA — that stdlib type's R/G/B/A are alpha-premultiplied, a different semantic than hex notation's straight channels, and reusing it would silently produce wrong values for any partially-transparent color.
type Constraint ¶
type Constraint[T any] struct { Name string Check func(T) bool Message func(T) string Schema func(schema.Schema) schema.Schema // optional: mutates schema when Refine is applied }
Constraint is a named validation predicate applied during decoding.
The optional Schema field annotates the codec's schema when the constraint is applied via Refine. Set it to propagate constraint metadata (e.g. minimum length, numeric bounds) into the schema for renderers such as render/openapi. Leaving Schema nil is a no-op and keeps all existing constraints unchanged.
type ConstraintError ¶
type ConstraintError struct {
Name string // constraint identifier
Message string // human-readable failure description
}
ConstraintError is returned when a Refine constraint check fails during Decode. Name identifies the constraint (e.g. "minLen(3)"); Message describes the failure.
func (ConstraintError) Error ¶
func (e ConstraintError) Error() string
func (ConstraintError) LogValue ¶
func (e ConstraintError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type Either ¶ added in v0.3.0
type Either[A, B any] struct { Left *A Right *B }
Either holds exactly one of two values. Left and Right are mutually exclusive: a value decoded from the left branch sets Left to a non-nil pointer and leaves Right nil, and vice versa.
Use a type switch or check Left/Right directly:
switch {
case e.Left != nil:
// handle *e.Left (type A)
case e.Right != nil:
// handle *e.Right (type B)
}
type EitherError ¶ added in v0.3.0
type EitherError struct {
Errors []error
}
EitherError is returned when all branches of an Either2 or UntaggedUnion codec fail to decode. Errors contains one error per branch in order.
func (EitherError) Error ¶ added in v0.3.0
func (e EitherError) Error() string
func (EitherError) LogValue ¶ added in v0.3.0
func (e EitherError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
func (EitherError) Unwrap ¶ added in v0.3.0
func (e EitherError) Unwrap() []error
Unwrap returns all branch errors for errors.Is/As traversal.
type ElementError ¶
ElementError wraps a decode error at a specific slice index.
func (ElementError) Error ¶
func (e ElementError) Error() string
func (ElementError) LogValue ¶
func (e ElementError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
func (ElementError) Unwrap ¶
func (e ElementError) Unwrap() error
type Field ¶
type Field[T any, F any] struct { Name string Codec Codec[F] Get func(T) F Set func(*T, F) Required bool // Default holds the field's default value. A non-nil pointer means the field // has a declared default; nil means no default. A pointer is used to // distinguish "no default" from a zero-value default. Default *F }
Field describes a single struct field and its codec.
func DefaultField ¶ added in v0.3.0
func DefaultField[T, F any](name string, codec Codec[F], defaultVal F, get func(T) F, set func(*T, F)) Field[T, F]
DefaultField is a shorthand for Field with Required set to false and a documented default value. When the field is absent during decode, defaultVal is used automatically. The default appears in generated schemas as "default".
func OptionalField ¶
func OptionalField[T, F any](name string, codec Codec[F], get func(T) F, set func(*T, F)) Field[T, F]
OptionalField is a shorthand for Field with Required set to false. The intent is explicit at the call site — no boolean flag needed.
func RequiredField ¶
func RequiredField[T, F any](name string, codec Codec[F], get func(T) F, set func(*T, F)) Field[T, F]
RequiredField is a shorthand for Field with Required set to true. The intent is explicit at the call site — no boolean flag needed.
Example ¶
package main
import (
"fmt"
"github.com/DaniDeer/go-codex/codex"
)
func main() {
type User struct {
Name string
Email string
}
// Define the codec once — encode, decode, validate, and schema from one value.
userCodec := codex.Struct[User](
codex.RequiredField("name", codex.String(),
func(u User) string { return u.Name },
func(u *User, v string) { u.Name = v },
),
codex.RequiredField("email", codex.String(),
func(u User) string { return u.Email },
func(u *User, v string) { u.Email = v },
),
)
// Decode from intermediate representation (map[string]any).
user, err := userCodec.Decode(map[string]any{"name": "Alice", "email": "alice@example.com"})
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Printf("%s <%s>\n", user.Name, user.Email)
// Missing required field returns a structured error.
_, err = userCodec.Decode(map[string]any{"name": "Bob"})
fmt.Println(err != nil)
}
Output: Alice <alice@example.com> true
type FieldCodec ¶ added in v0.12.0
type FieldCodec[T any] interface { // contains filtered or unexported methods }
FieldCodec is the sealed interface implemented by Field (via RequiredField/OptionalField/DefaultField) that Struct composes to build a full object codec. Its methods are unexported — only this package can produce values satisfying it — but the interface NAME is exported so other packages can name it in their own signatures (e.g. to hold a slice of heterogeneous per-field declarations, as DecodeVars and EncodeVars do, or as api/rest's merge-capable Param constructors do to bridge a declared Field into a route's automatic request merge).
type InvalidColorError ¶ added in v0.12.0
type InvalidColorError struct {
Value string // the raw string that failed to parse
}
InvalidColorError is returned when HexColor's Decode receives a string that is not valid CSS Color Level 4 hex notation.
func (InvalidColorError) Error ¶ added in v0.12.0
func (e InvalidColorError) Error() string
func (InvalidColorError) LogValue ¶ added in v0.12.0
func (e InvalidColorError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type KeyError ¶
KeyError wraps a decode error at a specific map key.
type TypeMismatchError ¶
type TypeMismatchError struct {
Expected string // e.g. "object", "array", "string"
Got string // e.g. "int", "bool"
}
TypeMismatchError is returned when a codec receives a value of an unexpected type. Expected names the required type; Got names the actual type received.
func (TypeMismatchError) Error ¶
func (e TypeMismatchError) Error() string
func (TypeMismatchError) LogValue ¶
func (e TypeMismatchError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type UnknownVariantError ¶
type UnknownVariantError struct {
Tag string // discriminator field name
Variant string // unrecognised tag value
}
UnknownVariantError is returned when a tagged union receives a tag value that does not match any registered variant. Tag is the discriminator field name; Variant is the unrecognised tag value.
func (UnknownVariantError) Error ¶
func (e UnknownVariantError) Error() string
func (UnknownVariantError) LogValue ¶
func (e UnknownVariantError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type UntaggedVariant ¶ added in v0.3.0
UntaggedVariant pairs a name (used in schema documentation) with a Codec[T]. The name appears in the oneOf schema to identify the branch but is NOT added to the encoded value — unlike TaggedUnion which writes a discriminator field.
type ValidationError ¶
type ValidationError struct {
Field string // name of the field that failed
Err error // underlying constraint or missing-field error
}
ValidationError is a single field-level validation failure returned from struct Decode.
func (ValidationError) Error ¶
func (e ValidationError) Error() string
func (ValidationError) LogValue ¶
func (e ValidationError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
func (ValidationError) Unwrap ¶
func (e ValidationError) Unwrap() error
type ValidationErrors ¶
type ValidationErrors []ValidationError
ValidationErrors is a collection of field-level validation errors. It implements the error interface; callers can use errors.As to extract it. Unwrap returns the individual errors as a []error slice for errors.Is/As traversal.
func (ValidationErrors) Error ¶
func (ve ValidationErrors) Error() string
func (ValidationErrors) LogValue ¶
func (ve ValidationErrors) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging. Each field name is the slog key; its value is the underlying error (which invokes LogValue on types like ConstraintError, preserving nested structure).
func (ValidationErrors) Unwrap ¶
func (ve ValidationErrors) Unwrap() []error
Unwrap returns the individual ValidationError values as a []error slice, enabling errors.Is and errors.As to traverse the full list.
type VarEncodeTypeError ¶ added in v0.12.0
VarEncodeTypeError is returned by EncodeVars when a field's Codec.Encode does not produce a string value. Attaching an unsuitable codec (e.g. Int directly, instead of a string-wire-wrapped codec built via MapCodecSafe) to a var field is a caller programming error, not a runtime data error — vars maps are always string-keyed/string-valued (path segments, topic segments, header/query/cookie values, and file path segments are all strings at the wire level).
func (VarEncodeTypeError) Error ¶ added in v0.12.0
func (e VarEncodeTypeError) Error() string
func (VarEncodeTypeError) LogValue ¶ added in v0.12.0
func (e VarEncodeTypeError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type VariantError ¶
type VariantError struct {
Tag string // discriminator field name
Variant string // matched variant value
Err error // underlying encode or decode failure
}
VariantError is returned when a known tagged-union variant fails to encode or decode. Tag is the discriminator field name; Variant is the matched variant value. Err is always non-nil; use UnknownVariantError for unrecognised tag values.
func (VariantError) Error ¶
func (e VariantError) Error() string
func (VariantError) LogValue ¶
func (e VariantError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
func (VariantError) Unwrap ¶
func (e VariantError) Unwrap() error