codex

package
v0.10.0 Latest Latest
Warning

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

Go to latest
Published: Jun 12, 2026 License: MIT Imports: 8 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

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, ...), ...)

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.

Functions

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 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"))

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: format.FromEnv has no type hints for any-typed fields and will pass the raw env var string through as-is.

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 using base64 standard encoding. Encoded values are strings; schema format is "byte".

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