Skip to content

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.