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 ¶
- Variables
- func Downcast[A any, B any](v B) (A, error)
- func Must[T any](v T, err error) T
- type Codec
- func Any() Codec[any]
- 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 Eq[T comparable](base Codec[T], value T) Codec[T]
- func Float32() Codec[float32]
- func Float64() Codec[float64]
- 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 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 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 KeyError
- type TypeMismatchError
- type UnknownVariantError
- type UntaggedVariant
- type ValidationError
- type ValidationErrors
- 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.
Functions ¶
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 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"))
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: format.FromEnv has no type hints for any-typed fields and will pass the raw env var string through as-is.
func Bytes ¶
Bytes returns a Codec for []byte using base64 standard encoding. Encoded values are strings; schema format is "byte".
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 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 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 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 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 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 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 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