Codecs¶
A codec defines how a Go value becomes bytes and back:
package codecs
import "github.com/dmundt/go-cask/cas"
// Codec is the two-method serialization contract for one type.
type Codec[T any] interface {
Encode(v T) ([]byte, error)
Decode(data []byte) (T, error)
}
// The contract above and the shipped cas.Codec accept exactly each other's
// implementations, so this listing cannot drift from the real interface.
var (
_ cas.Codec[string] = Codec[string](nil)
_ Codec[string] = cas.Codec[string](nil)
)
The contract is a round trip: Decode(Encode(v)) == v for every storable
value.
Shipped codecs¶
| Package | Format | Notes |
|---|---|---|
cas/codec/json |
JSON | readable, portable, the usual default |
cas/codec/gob |
Go encoding/gob |
Go-only; not a stable cross-language format |
cas/codec/binary |
compact binary | caller-supplied encode/decode functions |
cas/codec/cbor |
compact CBOR subset | caller-supplied encode/decode functions |
cas/codec/gzip, cas/codec/zlib, cas/codec/flate |
compression wrappers | wrap an inner codec; no encryption wrapper ships |
Why codecs are separate¶
The storage layer stores bytes; the codec decides how a domain object becomes those bytes. That keeps the core generic and leaves representation choices — JSON for readability, a compact format for size — entirely with the caller.
Composing codecs¶
A compression wrapper takes an inner codec and re-encodes its output:
package codecs
import (
"github.com/dmundt/go-cask/cas/codec/gzip"
jsoncodec "github.com/dmundt/go-cask/cas/codec/json"
)
type Note struct {
Text string `json:"text"`
}
// gzip wraps any inner Codec[T]: Encode runs the inner codec first and then
// compresses the result, and Decode reverses the order.
var compressed = gzip.New(jsoncodec.New[*Note]())
compressed.Encode runs the inner codec first, then compresses the result;
Decode reverses the order. Because the digest covers the final envelope,
changing the codec stack changes the digest of otherwise-identical values —
see the custom codec recipe for a worked
example.
Codec identity¶
A codec may implement the optional cas.CodecNamer interface to declare the
format it produces, and the store writes that tag into every envelope:
package codecs
import "github.com/dmundt/go-cask/cas"
// CodecNamer declares the wire format a codec produces.
type CodecNamer interface {
CodecName() string // "json", "gzip+json", or "" for none
}
// The contract above and the shipped cas.CodecNamer accept exactly each
// other's implementations.
var (
_ cas.CodecNamer = CodecNamer(nil)
_ CodecNamer = cas.CodecNamer(nil)
)
Store.Get compares the stored tag with its own codec's tag before decoding
and reports cas.ErrCodecMismatch when both are present and differ, so
swapping the codec behind a type is a reported format change instead of a
decode failure — no type major bump required. The tags are declared, never
derived from the Go type or the payload; a codec without the interface writes
no tag and reads any tag without complaint. See
object format for the full
rule.