Documentation
¶
Overview ¶
Package format bridges Codec[T] to concrete serialization formats.
A [Codec][T] works with an intermediate representation (map[string]any) that is format-agnostic. Format wraps that intermediate layer so the same codec can read and write multiple wire formats without any changes to the codec itself.
File I/O — Read, Write, Update, Patch, PatchEncoded ¶
File[T] is a declarative typed file descriptor. It supports five operations:
- File.Read — full decode (reads entire file into T)
- File.Write — full encode (overwrites file with T); use when you already have the decoded value
- File.Update — typed read-modify-write: fn(T) T; use when you need the latest file state first
- File.Patch — partial field update (map[string]any); unknown fields dropped
- PatchEncoded — typed partial update via a separate patch codec (free function); fields in patchCodec but NOT in the file codec are preserved in the output
Field survival rules for Patch and PatchEncoded ¶
Every write operation filters output through its codec. The rules differ:
// Patch: only file-codec fields survive; unknown keys in the patch map are dropped
configFile.Patch(nil, map[string]any{"port": 9090}, opts)
// → file codec fields updated/re-written; "port" updated; unknown keys dropped
// PatchEncoded: patchCodec fields survive even if not in the file codec
format.PatchEncoded(configFile, nil, patchCodec, patchValue, opts)
// → file codec fields updated/re-written; patchCodec fields written (even extra ones)
Field survival summary:
Field in file codec + field in patch map/patchCodec → updated ✓ Field in file codec + absent from patch → preserved ✓ Field in patchCodec only (not in file codec) → written by PatchEncoded ✓ Field in neither codec → dropped by both Patch and PatchEncoded
Key rule: use PatchEncoded to intentionally add new fields to a file by declaring them in the patch codec. Use File.Patch with an explicit map[string]any when unknown keys should be silently discarded.
Patch and PatchEncoded are supported only for map-based formats (JSON, YAML, TOML, New). Check Format.IsPatchable before calling either when the format is not known at compile time.
All file error types implement slog.LogValuer for structured logging:
var encErr format.FileEncodeError
if errors.As(err, &encErr) {
slog.Warn("encode failed", "error", encErr) // structured output via LogValue()
}
Embedded format codecs — JSON/YAML/TOML within a string field ¶
Some APIs and protocols store a serialised document inside a string field (CloudEvents data-as-string, database JSONB via REST, Kafka message headers, device-twin configuration). EmbeddedJSON, EmbeddedYAML, and EmbeddedTOML return a codex.Codec[T] that treats the wire string as a nested document:
// Wire: {"event":"user.created","payload":"{\"id\":\"123\",\"name\":\"Alice\"}"}
var eventCodec = codex.Struct[Event](
codex.RequiredField("event", codex.String(), ...),
codex.RequiredField("payload", format.EmbeddedJSON(userCodec), ...),
)
// Compose with File[T] — the outer format handles the file bytes;
// EmbeddedJSON handles the string-to-struct field conversion.
var eventFile = format.NewFile("events/user.json", format.JSON(eventCodec))
event, err := eventFile.Read(nil, format.FileOptions{})
Decode: wire string → format unmarshal → inner.Decode → T Encode: inner.Encode → format marshal → wire string
Format parse failures return EmbeddedDecodeError{Format, Err}; marshal failures return EmbeddedEncodeError{Format, Err}. Both implement slog.LogValuer. Inner codec validation errors propagate unchanged.
Built-in formats ¶
Text-based formats (JSON, YAML, TOML) pass through the map[string]any intermediate:
format.JSON(codec) // application/json format.YAML(codec) // application/yaml format.TOML(codec) // application/toml
Binary formats ¶
Binary formats bypass the intermediate and operate on the typed value directly:
Gob — uses encoding/gob framing. Suitable for Go-to-Go communication and binary caching. NOT suitable for writing files that must be readable by other tools (image viewers, PDF readers), because Gob adds its own framing bytes.
Binary — writes and reads []byte as-is, without any encoding. Suitable for raw binary file I/O (PNG, JPEG, PDF, WAV…) and HTTP binary bodies. Unlike Gob, Binary produces files that any tool understanding the underlying format can open.
Choosing between Binary and Gob ¶
- Use Binary when the file must be byte-identical to the original format (PNG, PDF…).
- Use Gob for internal Go-to-Go serialization where framing overhead is acceptable.
Custom formats ¶
For formats not covered by the built-ins, use:
- New — map-based intermediate (CBOR, MessagePack, XML, …)
- NewTyped — typed T directly, without map[string]any (templ HTML, Protobuf, image.Image)
- NewStreamed — streams to io.Writer without buffering (SSR streaming, chunked exports)
Binary is convenience sugar over NewTyped[[]byte] with identity marshal/unmarshal. Use NewTyped directly when T ≠ []byte or when marshal/unmarshal perform real encoding.
Index ¶
- Variables
- func EmbeddedJSON[T any](inner codex.Codec[T]) codex.Codec[T]
- func EmbeddedTOML[T any](inner codex.Codec[T]) codex.Codec[T]
- func EmbeddedYAML[T any](inner codex.Codec[T]) codex.Codec[T]
- func FromEnv[T any](c codex.Codec[T], prefix string) (T, error)
- func FromEnvVar[T any](key string, c codex.Codec[T]) (T, error)
- func PatchEncoded[T, P any](fh File[T], vars map[string]string, patchCodec codex.Codec[P], patch P, ...) error
- type EmbeddedDecodeError
- type EmbeddedEncodeError
- type EnvVarError
- type File
- func (fh File[T]) BuildPath(vars map[string]string) (string, error)
- func (fh File[T]) Patch(vars map[string]string, patch map[string]any, opts FileOptions) error
- func (fh File[T]) PathParamSchemas() map[string]schema.Schema
- func (fh File[T]) Read(vars map[string]string, opts FileOptions) (T, error)
- func (fh File[T]) Update(vars map[string]string, fn func(T) T, opts FileOptions) error
- func (fh File[T]) ValidatePathVars(vars map[string]string) error
- func (fh File[T]) Write(vars map[string]string, v T, opts FileOptions) error
- type FileDecodeError
- type FileEncodeError
- type FileOpt
- type FileOptions
- type FilePatchNotSupportedError
- type FilePathParam
- type FilePathParamError
- type FileReadError
- type FileWriteError
- type Format
- func Binary(c codex.Codec[[]byte]) Format[[]byte]
- func Gob[T any](c codex.Codec[T]) Format[T]
- func JSON[T any](c codex.Codec[T]) Format[T]
- func New[T any](c codex.Codec[T], marshal func(any) ([]byte, error), ...) Format[T]
- func NewStreamed[T any](c codex.Codec[T], marshalTo func(T, io.Writer) error, ...) Format[T]
- func NewTyped[T any](c codex.Codec[T], marshal func(T) ([]byte, error), ...) Format[T]
- func TOML[T any](c codex.Codec[T]) Format[T]
- func YAML[T any](c codex.Codec[T]) Format[T]
- func (f Format[T]) ContentType() string
- func (f Format[T]) IsPatchable() bool
- func (f Format[T]) IsStreamable() bool
- func (f Format[T]) Marshal(v T) ([]byte, error)
- func (f Format[T]) MarshalTo(v T, w io.Writer) error
- func (f Format[T]) Schema() schema.Schema
- func (f Format[T]) Unmarshal(data []byte) (T, error)
- func (f Format[T]) Validate(v T) error
- func (f Format[T]) WithContentType(ct string) Format[T]
- type MissingFilePathVarError
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrNotStreamable = errors.New("format: not streamable — use NewStreamed to create a streaming format")
ErrNotStreamable is returned by Format.MarshalTo when the format was not created with NewStreamed and therefore does not support streaming output.
Functions ¶
func EmbeddedJSON ¶ added in v0.11.0
EmbeddedJSON returns a codex.Codec[T] where the wire type is a JSON-encoded string. The inner codec's constraints and schema run on the decoded value.
Decode path: JSON string → json.Unmarshal → map[string]any → inner.Decode → T Encode path: T → inner.Encode → map[string]any → json.Marshal → JSON string
This pattern is common in systems where a structured value is serialised into a string field: CloudEvents data-as-string, database JSONB via REST APIs, Kafka message headers, and similar double-encoded formats.
On format parse failure, returns EmbeddedDecodeError{Format:"json"}. On marshal failure, returns EmbeddedEncodeError{Format:"json"}. Codec validation errors from inner propagate unchanged.
Example — CloudEvents-style event with a typed payload in a string field:
var eventCodec = codex.Struct[Event](
codex.RequiredField("type", codex.String(), ...),
codex.RequiredField("payload", format.EmbeddedJSON(userCodec), ...),
)
// Wire: {"type":"user.created","payload":"{\"id\":\"123\",\"name\":\"Alice\"}"}
// Go: Event{Type:"user.created", Payload:User{ID:"123", Name:"Alice"}}
func EmbeddedTOML ¶ added in v0.11.0
EmbeddedTOML returns a codex.Codec[T] where the wire type is a TOML-encoded string. The inner codec's constraints run on the decoded value.
Decode path: TOML string → toml.Decode → map[string]any → inner.Decode → T Encode path: T → inner.Encode → map[string]any → toml.Encode → TOML string
TOML integers decode as int64 and TOML floats as float64 — both are handled correctly by the built-in codex primitives.
Note: TOML requires all keys to be strings and does not support top-level arrays. Use EmbeddedTOML with struct codecs (not SliceOf).
On format parse failure, returns EmbeddedDecodeError{Format:"toml"}. On marshal failure, returns EmbeddedEncodeError{Format:"toml"}.
func EmbeddedYAML ¶ added in v0.11.0
EmbeddedYAML returns a codex.Codec[T] where the wire type is a YAML-encoded string. The inner codec's constraints run on the decoded value.
Decode path: YAML string → yaml.Unmarshal → map[string]any → inner.Decode → T Encode path: T → inner.Encode → map[string]any → yaml.Marshal → YAML string
YAML integers decode as int and YAML floats as float64 — both are handled correctly by the built-in codex primitives (Int, Float64, etc.).
On format parse failure, returns EmbeddedDecodeError{Format:"yaml"}. On marshal failure, returns EmbeddedEncodeError{Format:"yaml"}.
func FromEnv ¶ added in v0.3.0
FromEnv loads T from environment variables using schema-driven type coercion.
Naming convention: strings.ToUpper(prefix + field_name). Underscores in field names are preserved:
field "log_level" + prefix "APP_" → "APP_LOG_LEVEL" field "db" + prefix "APP_" → recurse with prefix "APP_DB_" nested field "host" → "APP_DB_HOST"
Supported types (determined from the codec's schema):
flat primitives — direct env var, string coerced by schema type
nested structs — prefix expansion (APP_DB_HOST) OR JSON object (APP_DB='{"host":"..."}')
slices — comma-separated (APP_TAGS=a,b,c) OR JSON array (APP_TAGS='["a","b","c"]')
StringMap — JSON object only (APP_LABELS='{"k":"v"}')
Nullable[T] — absent = nil; present = coerce as inner type
JSON detection: when a field's env var is set and the value starts with '{' or '[' matching the field's schema type, it is parsed as JSON. JSON takes precedence over prefix expansion and comma-split when both would apply (e.g. APP_DB='{...}' takes priority over APP_DB_HOST=...).
Silently skipped: TaggedUnion, slices of objects.
Errors are returned as codex.ValidationErrors. Parse errors (an env var is set but its value cannot be coerced to the field's type) are collected and returned before the codec's Decode runs. Missing required fields and constraint violations are reported by Decode in the same error shape.
func FromEnvVar ¶ added in v0.11.0
FromEnvVar loads a single typed value from one environment variable.
The codec's schema determines the string coercion (integer, number, boolean, string). All Refine constraints run after coercion — the same rules apply as in any codec Decode call.
Returns EnvVarError wrapping a codex.ValidationErrors when coercion or constraint validation fails. Returns the zero value of T when the variable is not set. Use errors.As to inspect the structured error:
port, err := format.FromEnvVar("APP_PORT", codex.Int().Refine(validate.RangeInt(1, 65535)))
if err != nil {
var envErr format.EnvVarError
if errors.As(err, &envErr) {
slog.Warn("env var invalid", "key", envErr.Key, "cause", envErr.Err)
}
}
func PatchEncoded ¶ added in v0.11.0
func PatchEncoded[T, P any](fh File[T], vars map[string]string, patchCodec codex.Codec[P], patch P, opts FileOptions) error
PatchEncoded encodes patch using patchCodec and merges the result into the existing file, preserving fields not covered by either codec.
PatchEncoded is a free function (not a method on File) because Go methods on a generic type cannot introduce additional type parameters. P is the patch type — a struct that contains only the fields you want to update. T is the full file type; it does not need to match P.
Field survival rules ¶
- Fields in the file codec (T): re-written with their current values and validated.
- Fields in patchCodec (P) but NOT in the file codec: written to the file as-is. These are validated by patchCodec before being merged, so they are safe to persist.
- Fields in the existing file that are in neither codec: dropped (no codec validates them).
This makes PatchEncoded the right tool for intentionally adding new fields to a file — declare them in the patch codec and they will be persisted.
Declare separate codecs for the file type and the patch type:
type AppConfig struct { Port int; LogLevel string; MaxWorkers int }
var configFile = format.NewFile("config.json", format.JSON(appConfigCodec))
// Patch type — only patchable fields; may include fields not in AppConfig
type AppConfigPatch struct { Port int; LogLevel string; NewFeatureFlag bool }
var configPatchCodec = codex.Struct[AppConfigPatch](
codex.RequiredField("port",
codex.Int().Refine(validate.RangeInt(1, 65535)),
func(p AppConfigPatch) int { return p.Port },
func(p *AppConfigPatch, v int) { p.Port = v },
),
codex.RequiredField("log_level",
codex.String().Refine(validate.OneOf("debug", "info", "warn", "error")),
func(p AppConfigPatch) string { return p.LogLevel },
func(p *AppConfigPatch, v string) { p.LogLevel = v },
),
codex.RequiredField("new_feature_flag",
codex.Bool(),
func(p AppConfigPatch) bool { return p.NewFeatureFlag },
func(p *AppConfigPatch, v bool) { p.NewFeatureFlag = v },
),
)
// new_feature_flag is not in AppConfig but IS in patchCodec — it will be written
err = format.PatchEncoded(configFile, nil, configPatchCodec,
AppConfigPatch{Port: 9090, LogLevel: "debug", NewFeatureFlag: true},
format.FileOptions{Observer: obs},
)
PatchEncoded returns FilePatchNotSupportedError when the encoded intermediate is not a map[string]any (scalar or slice patch codec, or Gob/Binary format).
Errors:
- FilePathParamError / MissingFilePathVarError — path variable validation (no I/O)
- FileEncodeError — patchCodec.Encode(patch) fails (Refine constraint violation)
- FilePatchNotSupportedError — encoded intermediate is not map[string]any, or format not patchable
- FileReadError — os.ReadFile failure
- FileDecodeError — merged result fails fh's codec constraints for known fields
- FileWriteError — os.WriteFile failure
Types ¶
type EmbeddedDecodeError ¶ added in v0.11.0
type EmbeddedDecodeError struct {
// Format is the wire format that failed to parse ("json", "yaml", "toml").
Format string
// Err is the underlying parse error from the format library.
Err error
}
EmbeddedDecodeError is returned by EmbeddedJSON, EmbeddedYAML, and EmbeddedTOML when the string value cannot be parsed as the expected format. Codec validation errors from the inner codec propagate unchanged.
var dec format.EmbeddedDecodeError
if errors.As(err, &dec) {
slog.Warn("embedded field parse failed", "error", dec)
}
func (EmbeddedDecodeError) Error ¶ added in v0.11.0
func (e EmbeddedDecodeError) Error() string
func (EmbeddedDecodeError) LogValue ¶ added in v0.11.0
func (e EmbeddedDecodeError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type EmbeddedEncodeError ¶ added in v0.11.0
type EmbeddedEncodeError struct {
// Format is the wire format that failed to marshal ("json", "yaml", "toml").
Format string
// Err is the underlying marshal error from the format library.
Err error
}
EmbeddedEncodeError is returned by EmbeddedJSON, EmbeddedYAML, and EmbeddedTOML when the Go value cannot be marshalled to the target format string.
var enc format.EmbeddedEncodeError
if errors.As(err, &enc) {
slog.Warn("embedded field marshal failed", "error", enc)
}
func (EmbeddedEncodeError) Error ¶ added in v0.11.0
func (e EmbeddedEncodeError) Error() string
func (EmbeddedEncodeError) LogValue ¶ added in v0.11.0
func (e EmbeddedEncodeError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type EnvVarError ¶ added in v0.11.0
type EnvVarError struct {
// Key is the environment variable name (e.g. "APP_PORT").
Key string
// Err is the underlying coercion or validation error.
// Typically wraps [codex.ValidationErrors].
Err error
}
EnvVarError is returned by FromEnvVar when coercion or codec validation fails for a single environment variable.
Use errors.As to extract the key and structured cause:
var envErr format.EnvVarError
if errors.As(err, &envErr) {
slog.Warn("env var invalid", "key", envErr.Key, "cause", envErr.Err)
stats.ReportErrors(obs, "env", envErr.Err)
}
func (EnvVarError) Error ¶ added in v0.11.0
func (e EnvVarError) Error() string
type File ¶ added in v0.11.0
type File[T any] struct { // Template is the original path template (with {varName} placeholders). Template string // contains filtered or unexported fields }
File is a declarative typed file descriptor: a path template, a wire format, and optional per-variable codecs. It bundles everything needed to read, write, and update a file in one reusable value.
File mirrors the declare-once pattern of api/rest.Route and api/events.Channel:
- NewFile declares the file descriptor as a value — no side effects.
- File.Read, File.Write, File.Update perform the I/O.
- File.BuildPath substitutes variables and validates without any I/O.
For static paths (no template variables), pass nil for vars in all methods.
Typical usage:
// Declare once — share across functions and packages.
var configFile = format.NewFile("config.toml", format.TOML(configCodec))
// Read
cfg, err := configFile.Read(nil, format.FileOptions{Observer: obs})
// Update (read-modify-write)
err = configFile.Update(nil, func(c Config) Config {
c.Port = 9090
return c
}, format.FileOptions{Observer: obs})
// Template path with variable validation
var measurementFile = format.NewFile("data/{date}/{sensorID}.json",
format.JSON(measurementCodec),
format.FilePathParam{Name: "date"}.WithCodec(codex.String().Refine(validate.Date)),
format.FilePathParam{Name: "sensorID"}.WithCodec(codex.String().Refine(validate.UUID)),
)
path, err := measurementFile.BuildPath(map[string]string{
"date": "2024-01-15",
"sensorID": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
})
// path == "data/2024-01-15/f47ac10b-58cc-4372-a567-0e02b2c3d479.json"
func NewFile ¶ added in v0.11.0
NewFile creates a File descriptor from a path template, a wire format, and optional FilePathParam values.
NewFile is infallible — it only captures the spec. Validation of template variable names against registered params runs at File.BuildPath time.
Example ¶
package main
import (
"fmt"
"os"
"github.com/DaniDeer/go-codex/codex"
"github.com/DaniDeer/go-codex/format"
)
func main() {
// Declare a typed file descriptor once — no I/O at declaration time.
// The format (JSON here) and path template are captured in the value.
type Config struct {
Host string
Port int
}
cfgCodec := codex.Struct[Config](
codex.RequiredField("host", codex.String(),
func(c Config) string { return c.Host },
func(c *Config, v string) { c.Host = v },
),
codex.RequiredField("port", codex.Int(),
func(c Config) int { return c.Port },
func(c *Config, v int) { c.Port = v },
),
)
// Static path — pass nil for vars on every call.
path := os.TempDir() + "/example-config.json"
cfgFile := format.NewFile(path, format.JSON(cfgCodec))
// Write — encodes + validates before writing.
_ = cfgFile.Write(nil, Config{Host: "localhost", Port: 8080}, format.FileOptions{})
// Read — reads + decodes + validates constraints.
cfg, _ := cfgFile.Read(nil, format.FileOptions{})
fmt.Printf("host=%s port=%d\n", cfg.Host, cfg.Port)
}
Output: host=localhost port=8080
func (File[T]) BuildPath ¶ added in v0.11.0
BuildPath substitutes {varName} placeholders with the values in vars and validates each against its registered FilePathParam.Codec. Returns the concrete file path on success.
All template variables must be present in vars; missing variables return a MissingFilePathVarError. Values are validated before substitution; codec failures return a FilePathParamError.
When vars is nil or empty and the template has no placeholders, BuildPath returns the template unchanged.
func (File[T]) Patch ¶ added in v0.11.0
Patch reads the existing file, deep-patches its intermediate representation with the provided patch map, validates the result through the codec, and writes it back.
patch follows JSON Merge Patch semantics (RFC 7396): keys present in patch overwrite the corresponding fields in the file; fields absent from patch are preserved through the read phase but dropped when the merged value is re-encoded through the file's codec. Only fields the codec knows about survive in the written file — unknown fields are silently dropped.
To intentionally write fields not declared in the file's codec, use PatchEncoded with a patch codec that declares those fields explicitly.
Patch is supported only for map-based formats: JSON, YAML, TOML, and formats created with New. Returns FilePatchNotSupportedError before any I/O for Gob, Binary, NewTyped, and NewStreamed formats — check Format.IsPatchable upfront when the format type is not known at compile time.
Errors:
- FilePathParamError / MissingFilePathVarError — path variable validation failure (no I/O)
- FilePatchNotSupportedError — format does not use a map[string]any intermediate (no I/O)
- FileReadError — os.ReadFile failure
- FileDecodeError — patched result fails codec constraint validation
- FileEncodeError — encode failure after patch
- FileWriteError — os.WriteFile failure
func (File[T]) PathParamSchemas ¶ added in v0.11.0
PathParamSchemas returns a map from template variable name to the codec's schema.Schema for each FilePathParam that has a FilePathParam.Codec set. Parameters without a codec are omitted. Returns an empty map when no params have codecs registered.
Use this for documentation generation or spec tooling that needs the schema of each path variable (e.g. emitting a machine-readable description of an API that reads or writes files with templated paths).
func (File[T]) Read ¶ added in v0.11.0
func (fh File[T]) Read(vars map[string]string, opts FileOptions) (T, error)
Read builds the concrete path from vars, reads the file, and decodes its contents using the file's format codec.
Errors:
- FilePathParamError / MissingFilePathVarError — path variable validation failure (no I/O)
- FileReadError — os.ReadFile failure
- FileDecodeError — format decode/validation failure (wraps codex.ValidationErrors)
func (File[T]) Update ¶ added in v0.11.0
func (fh File[T]) Update(vars map[string]string, fn func(T) T, opts FileOptions) error
Update reads the file at the path built from vars, applies fn to the decoded value, then writes the result back. It is equivalent to Read followed by Write.
Use Update when you need the current file contents to decide what to write — for example, incrementing a counter or conditionally modifying a field. If you already have the decoded value in memory, use File.Write directly to avoid an unnecessary re-read.
Errors: see File.Read and File.Write.
func (File[T]) ValidatePathVars ¶ added in v0.11.0
ValidatePathVars validates the variable values in vars against their registered codecs without building the concrete path. Returns the first codec failure as a FilePathParamError, or MissingFilePathVarError for absent variables.
func (File[T]) Write ¶ added in v0.11.0
func (fh File[T]) Write(vars map[string]string, v T, opts FileOptions) error
Write builds the concrete path from vars, encodes v, and writes it to the file. The file is created if it does not exist, or truncated and overwritten if it does.
Errors:
- FilePathParamError / MissingFilePathVarError — path variable validation failure (no I/O)
- FileEncodeError — format encode/validation failure
- FileWriteError — os.WriteFile failure
type FileDecodeError ¶ added in v0.11.0
FileDecodeError is returned by File.Read when format decoding or codec validation fails after a successful os.ReadFile.
The wrapped error is typically a codex.ValidationErrors — use errors.As to extract per-field details.
func (FileDecodeError) Error ¶ added in v0.11.0
func (e FileDecodeError) Error() string
func (FileDecodeError) LogValue ¶ added in v0.11.0
func (e FileDecodeError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type FileEncodeError ¶ added in v0.11.0
FileEncodeError is returned by File.Write when format encoding or codec validation fails before any write to the filesystem.
func (FileEncodeError) Error ¶ added in v0.11.0
func (e FileEncodeError) Error() string
func (FileEncodeError) LogValue ¶ added in v0.11.0
func (e FileEncodeError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type FileOpt ¶ added in v0.11.0
type FileOpt interface {
// contains filtered or unexported methods
}
FileOpt is the sealed option interface for NewFile.
type FileOptions ¶ added in v0.11.0
type FileOptions struct {
// Observer, when non-nil, receives per-operation lifecycle events.
// [stats.FileObserver.RecordFileRead] is called after every Read or Update
// (read phase). [stats.FileObserver.RecordFileWrite] is called after every
// Write or Update (write phase). Per-field decode/encode errors are reported
// via [stats.Observer.RecordValidationError] with location "file".
//
// The observer is type-asserted to [stats.FileObserver] — existing Observer
// implementations need not implement FileObserver. Defaults to
// [stats.NoopObserver] when nil.
Observer stats.Observer
// Perm is the file permission used when creating a new file.
// Defaults to 0644 when zero.
Perm os.FileMode
// Context is an optional context for TraceObserver span parent propagation.
// When non-nil, file operations create child spans under the trace span
// carried by this context. When nil (default), spans use [context.Background]
// and become root spans.
Context context.Context
}
FileOptions configures the behaviour of File.Read, File.Write, and File.Update.
type FilePatchNotSupportedError ¶ added in v0.11.0
type FilePatchNotSupportedError struct {
// Path is the concrete file path after template substitution.
Path string
}
FilePatchNotSupportedError is returned by File.Patch when the file's format does not use a [map[string]any] intermediate. Only JSON, YAML, TOML, and formats created with New support patching.
Use errors.As to extract the path:
var patchErr format.FilePatchNotSupportedError
if errors.As(err, &patchErr) {
slog.Warn("patch not supported", "error", patchErr)
}
func (FilePatchNotSupportedError) Error ¶ added in v0.11.0
func (e FilePatchNotSupportedError) Error() string
func (FilePatchNotSupportedError) LogValue ¶ added in v0.11.0
func (e FilePatchNotSupportedError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type FilePathParam ¶ added in v0.11.0
type FilePathParam struct {
// Name is the placeholder name (without braces) in the path template.
// e.g. for template "data/{date}/{sensor}.json", Name is "date" or "sensor".
Name string
// Description enriches the documentation for this path variable.
Description string
// Codec validates the variable value at [File.BuildPath] and [File.Read]/
// [File.Write]/[File.Update] time. When non-nil, the codec's schema is
// available via [File.PathParamSchemas]. Nil means no runtime validation.
Codec *codex.Codec[string]
}
FilePathParam describes a {varName} placeholder in a File path template. It mirrors [TopicParam] and [PathParam] — no Required field because every template variable must always be present.
FilePathParam implements the FileOpt interface: pass it directly to NewFile.
func (FilePathParam) WithCodec ¶ added in v0.11.0
func (p FilePathParam) WithCodec(c codex.Codec[string]) FilePathParam
WithCodec sets the validation codec and returns the updated FilePathParam. Use this instead of setting Codec directly:
format.FilePathParam{Name: "date"}.WithCodec(codex.String().Refine(validate.Date))
type FilePathParamError ¶ added in v0.11.0
type FilePathParamError struct {
Name string // placeholder name (without braces)
Value string // the value that failed validation
Err error // underlying constraint or codec error
}
FilePathParamError is returned by File.BuildPath when a {varName} value fails its FilePathParam.Codec validation.
Use errors.As to extract the failing variable name and value:
var paramErr format.FilePathParamError
if errors.As(err, ¶mErr) {
slog.Warn("file path var rejected",
"param", paramErr.Name,
"value", paramErr.Value,
"cause", paramErr.Err,
)
}
func (FilePathParamError) Error ¶ added in v0.11.0
func (e FilePathParamError) Error() string
func (FilePathParamError) LogValue ¶ added in v0.11.0
func (e FilePathParamError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type FileReadError ¶ added in v0.11.0
FileReadError is returned by File.Read when os.ReadFile fails.
Use errors.As to extract the path and underlying OS error.
func (FileReadError) Error ¶ added in v0.11.0
func (e FileReadError) Error() string
func (FileReadError) LogValue ¶ added in v0.11.0
func (e FileReadError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type FileWriteError ¶ added in v0.11.0
FileWriteError is returned by File.Write when os.WriteFile fails after successful encoding.
func (FileWriteError) Error ¶ added in v0.11.0
func (e FileWriteError) Error() string
func (FileWriteError) LogValue ¶ added in v0.11.0
func (e FileWriteError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type Format ¶
type Format[T any] struct { // contains filtered or unexported fields }
Format binds a Codec[T] to a specific serialization format. Use JSON, YAML, TOML, Gob, or Binary to construct one. For formats that operate on the typed value directly (e.g. HTML rendering), use NewTyped. For streaming output (write to io.Writer without buffering), use NewStreamed.
func Binary ¶ added in v0.11.0
Binary returns a Format that reads and writes raw binary data.
It is equivalent to NewTyped[[]byte] with identity marshal and validate+identity unmarshal — bytes are stored and read exactly as they are, without any encoding. Unlike Gob (which adds framing bytes), Binary writes raw bytes that any tool understanding the underlying format (PNG viewer, PDF reader, etc.) can open.
The codec validates the []byte value via [Codec.Refine] constraints on both paths:
- Write: constraints run before the bytes are written; failure returns format.FileEncodeError (via format.File).
- Read: constraints run after the bytes are read; failure returns format.FileDecodeError (via format.File).
ContentType defaults to "application/octet-stream"; override with Format.WithContentType.
For typed binary formats where the Go type is not []byte (e.g. image.Image, Protobuf structs) or for write-only formats, use NewTyped directly.
Typical usage:
var pngSignature = []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}
var pngFile = format.NewFile(
"images/{name}.png",
format.Binary(
codex.Bytes().
Refine(validate.MaxBytes(5*1024*1024)).
Refine(validate.HasPrefix(pngSignature)),
).WithContentType("image/png"),
format.FilePathParam{Name: "name"},
)
func Gob ¶ added in v0.9.0
Gob returns a Format[T] that serialises T using encoding/gob.
Unlike JSON, YAML, and TOML — which pass a map[string]any intermediate through the codec — Gob encodes and decodes the typed value directly. Codec constraints are enforced on both marshal (before encoding) and unmarshal (after decoding).
Suitable for internal Go-to-Go communication: forge pipelines, MQTT between Go services, and binary caching. Not suitable for REST content negotiation, human-readable output, or cross-language interoperability.
Requirements and limitations:
- All struct fields that gob encodes must be exported; unexported fields are silently skipped.
- For interface values, register concrete types with encoding/gob.Register before use.
- "application/gob" is a conventional content type; it is not an IANA-registered MIME type.
- Observability is handled by the adapter layer — no special configuration needed.
ContentType is "application/gob".
func JSON ¶
JSON returns a Format that reads and writes JSON. ContentType is "application/json".
Example ¶
package main
import (
"fmt"
"github.com/DaniDeer/go-codex/codex"
"github.com/DaniDeer/go-codex/format"
)
func main() {
type Item struct {
Name string
Price float64
}
itemCodec := codex.Struct[Item](
codex.RequiredField("name", codex.String(),
func(i Item) string { return i.Name },
func(i *Item, v string) { i.Name = v },
),
codex.RequiredField("price", codex.Float64(),
func(i Item) float64 { return i.Price },
func(i *Item, v float64) { i.Price = v },
),
)
j := format.JSON(itemCodec)
// Marshal a Go value to JSON bytes.
data, _ := j.Marshal(Item{Name: "Widget", Price: 9.99})
fmt.Println(string(data))
// Unmarshal JSON bytes back to the typed value.
item, _ := j.Unmarshal(data)
fmt.Printf("%s: %.2f\n", item.Name, item.Price)
}
Output: {"name":"Widget","price":9.99} Widget: 9.99
func New ¶
func New[T any](c codex.Codec[T], marshal func(any) ([]byte, error), unmarshal func([]byte) (any, error)) Format[T]
New creates a Format from a codec and custom marshal/unmarshal functions. Use this to integrate formats not covered by the built-in constructors. ContentType is empty by default; call Format.WithContentType to set it.
func NewStreamed ¶ added in v0.8.0
func NewStreamed[T any](c codex.Codec[T], marshalTo func(T, io.Writer) error, unmarshal func([]byte) (T, error), contentType string) Format[T]
NewStreamed creates a Format where responses are written directly to an io.Writer without buffering to a []byte intermediate. This enables chunked or streaming responses for large payloads (HTML pages, JSON arrays, CSV exports).
The codec is used for validation: Format.MarshalTo runs all Refine constraints on the value before calling marshalTo, so invalid data is rejected before any bytes are written.
unmarshal is used for Format.Unmarshal (reading streaming formats is rarely needed; pass a function that returns an error when not applicable).
Use Format.IsStreamable to detect streaming formats. The adapter calls Format.MarshalTo instead of Format.Marshal and writes response headers before streaming, so partial output is never flushed on validation failure.
Example — streaming a templ component without buffering:
streamFmt := format.NewStreamed(
propsCodec,
func(props Props, w io.Writer) error {
return component(props).Render(context.Background(), w)
},
func([]byte) (Props, error) {
var zero Props
return zero, errors.New("HTML is not decodable")
},
"text/html; charset=utf-8",
)
func NewTyped ¶ added in v0.8.0
func NewTyped[T any](c codex.Codec[T], marshal func(T) ([]byte, error), unmarshal func([]byte) (T, error), contentType string) Format[T]
NewTyped creates a Format where the marshal and unmarshal functions operate on the typed value directly, rather than on the intermediate representation. The codec is still used for validation: Format.Marshal runs all Refine constraints on the value before calling marshal, and Format.Validate works as normal.
Use this when the wire format cannot be represented via a map[string]any intermediate — for example, rendering HTML via a templ component:
htmlFormat := format.NewTyped(
propsCodec,
func(props Props) ([]byte, error) {
var buf bytes.Buffer
err := component(props).Render(context.Background(), &buf)
return buf.Bytes(), err
},
func([]byte) (Props, error) {
var zero Props
return zero, errors.New("HTML is not decodable")
},
"text/html; charset=utf-8",
)
func (Format[T]) ContentType ¶ added in v0.8.0
ContentType returns the MIME type associated with this format (e.g. "application/json"). Empty string means the format has no registered content type.
func (Format[T]) IsPatchable ¶ added in v0.11.0
IsPatchable reports whether File.Patch is supported for this format. Only map-based formats (JSON, YAML, TOML, and formats created with New) return true — they use a [map[string]any] intermediate that can be partially overridden. Gob, Binary, NewTyped, and NewStreamed formats return false.
func (Format[T]) IsStreamable ¶ added in v0.8.0
IsStreamable reports whether this format supports streaming output via Format.MarshalTo. Streaming formats are created with NewStreamed.
func (Format[T]) Marshal ¶
Marshal encodes v to bytes using the codec and then the format serializer. If the format was created with NewTyped, the codec validates v first and then the typed marshal function is called directly (bypassing the intermediate). For streaming formats created with NewStreamed, use Format.MarshalTo instead.
func (Format[T]) MarshalTo ¶ added in v0.8.0
MarshalTo validates v via the codec and then writes the serialized form directly to w without buffering to a []byte intermediate. Use this with formats created via NewStreamed; call Format.IsStreamable first. Returns ErrNotStreamable if the format has no streaming marshal function. Validation errors are returned before any bytes are written to w.
func (Format[T]) Unmarshal ¶
Unmarshal deserializes data into an intermediate and then decodes it via the codec. If the format was created with NewTyped, the typed unmarshal function is used directly.
func (Format[T]) Validate ¶
Validate checks v against the codec's constraints without serializing to bytes. It delegates to Codec.Validate — see its documentation for the rationale.
func (Format[T]) WithContentType ¶ added in v0.8.0
WithContentType returns a copy of the format with the given MIME content type set. Use this when registering custom formats for content negotiation.
type MissingFilePathVarError ¶ added in v0.11.0
type MissingFilePathVarError struct {
Name string // placeholder name (without braces)
}
MissingFilePathVarError is returned by File.BuildPath when a {varName} placeholder has no corresponding entry in the vars map.
Use errors.As to extract the missing variable name:
var missingErr format.MissingFilePathVarError
if errors.As(err, &missingErr) {
slog.Warn("missing file path variable", "param", missingErr.Name)
}
func (MissingFilePathVarError) Error ¶ added in v0.11.0
func (e MissingFilePathVarError) Error() string
func (MissingFilePathVarError) LogValue ¶ added in v0.11.0
func (e MissingFilePathVarError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.