codex

package
v0.12.0 Latest Latest
Warning

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

Go to latest
Published: Jul 29, 2026 License: MIT Imports: 9 Imported by: 0

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

Examples

Constants

This section is empty.

Variables

View Source
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.

View Source
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

func Downcast[A any, B any](v B) (A, error)

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

func Must[T any](v T, err error) T

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

func Must2[A, B any](a A, b B, err error) (A, B)

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.

var Empty Codec[struct{}] = Struct[struct{}]()

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

func Any() Codec[any]

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

func Base64() Codec[[]byte]

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 Bool

func Bool() Codec[bool]

Bool returns a Codec for the bool type.

func Bytes

func Bytes() Codec[[]byte]

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:

func Date

func Date() Codec[time.Time]

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

func Duration() Codec[time.Duration]

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

func Either2[A, B any](ca Codec[A], cb Codec[B]) Codec[Either[A, B]]

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

func Float32() Codec[float32]

Float32 returns a Codec for the float32 type. The schema uses format "float" to document the reduced precision.

func Float64

func Float64() Codec[float64]

Float64 returns a Codec for the float64 type.

func HexColor added in v0.12.0

func HexColor() Codec[Color]

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 Int

func Int() Codec[int]

Int returns a Codec for the int type.

func Int32 added in v0.3.0

func Int32() Codec[int32]

Int32 returns a Codec for the int32 type. The schema uses format "int32" to document the reduced range.

func Int64

func Int64() Codec[int64]

Int64 returns a Codec for the int64 type.

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

func MapCodecSafe[A, B any](
	c Codec[A],
	to func(A) B,
	from func(B) (A, error),
) Codec[B]

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

func Nullable[T any](inner Codec[T]) Codec[*T]

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

func Pure[T any](value T) Codec[T]

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 SliceOf

func SliceOf[T any](elem Codec[T]) Codec[[]T]

SliceOf returns a Codec for a slice of T, using elem to encode/decode each element.

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 String

func String() Codec[string]

String returns a Codec for the string type.

func StringMap

func StringMap[V any](value Codec[V]) Codec[map[string]V]

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

func Time() Codec[time.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

func Uint() Codec[uint]

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

func Uint64() Codec[uint64]

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

func (c Codec[T]) New(v T) (T, error)

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

func (c Codec[T]) RefineFunc(fn func(T) error) Codec[T]

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

func (c Codec[T]) Validate(v T) error

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

func (c Codec[T]) WithDeprecated() Codec[T]

WithDeprecated returns a new Codec marked as deprecated. Deprecated fields are rendered with "deprecated: true" in generated schemas.

func (Codec[T]) WithDescription

func (c Codec[T]) WithDescription(desc string) Codec[T]

WithDescription returns a new Codec with Schema.Description set to desc.

func (Codec[T]) WithExample added in v0.3.0

func (c Codec[T]) WithExample(v any) Codec[T]

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.

func (Codec[T]) WithTitle

func (c Codec[T]) WithTitle(title string) Codec[T]

WithTitle returns a new Codec with Schema.Title set to title.

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

type ElementError struct {
	Index int
	Err   error
}

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

type KeyError struct {
	Key string
	Err error
}

KeyError wraps a decode error at a specific map key.

func (KeyError) Error

func (e KeyError) Error() string

func (KeyError) LogValue

func (e KeyError) LogValue() slog.Value

LogValue implements slog.LogValuer for structured logging.

func (KeyError) Unwrap

func (e KeyError) Unwrap() error

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

type UntaggedVariant[T any] struct {
	Name  string
	Codec Codec[T]
}

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

type VarEncodeTypeError struct {
	Field string
	Got   string // fmt.Sprintf("%T", val)
}

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

Jump to

Keyboard shortcuts

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