Documentation
¶
Overview ¶
Package encoding turns values into bytes and back, in a content type chosen by configuration rather than by the call site.
It is not an HTTP package. HTTP was its first consumer and ServerEncoderDecoder still speaks in http.ResponseWriter and *http.Request, but the rest of the surface is transport-free and is meant to be used by anything that needs to encode data — queue payloads, cache entries, database columns, files on disk. Prefer it to calling json.Marshal directly, so that the content type stays one decision made in one place.
Picking a type to depend on ¶
The interfaces are layered so a caller can ask for the narrowest thing that does its job:
- Marshaler renders a value as bytes.
- Unmarshaler parses bytes into a value.
- Codec is both, plus the content type being spoken.
- ClientEncoder is a Codec that also streams, via io.Writer and io.Reader.
Depend on Marshaler or Codec unless a transport is genuinely part of the job.
For one-off use there are package-level helpers that build an encoder for you: Encode and Decode return errors, MustEncode and MustDecode panic, and each has a JSON-pinned variant (EncodeJSON, DecodeJSON, and so on) for callers whose wire format is fixed rather than configurable.
This package does not alter the marshaler's output ¶
Every encode path routes through one byte-oriented marshaler per content type, so EncodeJSON(v) returns exactly what json.Marshal(v) returns. In particular no trailing newline is appended — the streaming encoders in the standard library add one, and this package deliberately does not use them for that reason.
That is the whole of the claim: nothing is added, removed, or reordered on the way out. It is not a promise that a value has one canonical encoding. Some marshalers do not offer that — CBOR does not sort map keys, so encoding the same map twice can produce different bytes — and no caller here needs it. What is guaranteed is the round trip: bytes produced by one content type decode back into the value they came from. Code that wants a stable digest should hash the bytes it stored rather than re-encoding the value to compare.
Index ¶
- Constants
- Variables
- func Decode(data []byte, ct ContentType, dest any) error
- func DecodeJSON(data []byte, dest any) error
- func Encode(data any, ct ContentType) ([]byte, error)
- func EncodeJSON(data any) ([]byte, error)
- func MustDecode(data []byte, ct ContentType, dest any)
- func MustDecodeJSON(data []byte, dest any)
- func MustEncode(data any, ct ContentType) []byte
- func MustEncodeJSON(data any) []byte
- func MustJSONIntoReader(data any) io.Reader
- func RegisterServerEncoderDecoder(i do.Injector)
- type ClientEncoder
- type Codec
- type Config
- type ContentType
- type Encoder
- func (e *Encoder) ContentType() string
- func (e *Encoder) Encode(ctx context.Context, dest io.Writer, data any) error
- func (e *Encoder) EncodeReader(ctx context.Context, data any) (io.Reader, error)
- func (e *Encoder) Marshal(ctx context.Context, v any) ([]byte, error)
- func (e *Encoder) Unmarshal(ctx context.Context, data []byte, v any) error
- type EncoderDecoder
- func (e *EncoderDecoder) DecodeBytes(ctx context.Context, data []byte, dest any) error
- func (e *EncoderDecoder) DecodeRequest(ctx context.Context, req *http.Request, v any) error
- func (e *EncoderDecoder) EncodeResponseWithStatus(ctx context.Context, res http.ResponseWriter, v any, statusCode int)
- func (e *EncoderDecoder) MustEncode(ctx context.Context, v any) []byte
- func (e *EncoderDecoder) MustEncodeJSON(ctx context.Context, v any) []byte
- func (e *EncoderDecoder) RespondWithData(ctx context.Context, res http.ResponseWriter, v any)
- type Marshaler
- type Option
- type ServerEncoderDecoder
- type Unmarshaler
Constants ¶
const (
// ContentTypeHeaderKey is the HTTP standard header name for content type.
ContentTypeHeaderKey = "Content-type"
)
Variables ¶
var ContentTypes = []ContentType{ ContentTypeJSON, ContentTypeXML, ContentTypeTOML, ContentTypeYAML, ContentTypeCBOR, ContentTypeEmoji, }
ContentTypes are every content type this package supports, in no significant order.
var ErrUnsupportedContentType = platformerrors.New("unsupported content type")
ErrUnsupportedContentType is returned when a media type does not name one of the encodings this package implements.
Functions ¶
func Decode ¶
func Decode(data []byte, ct ContentType, dest any) error
Decode parses data in the given encoding into dest.
The zero ContentType means JSON, so callers with nothing to say about the encoding can pass it and get the obvious default.
func DecodeJSON ¶
DecodeJSON decodes JSON data into dest.
func Encode ¶
func Encode(data any, ct ContentType) ([]byte, error)
Encode renders data in the given encoding, defaulting to JSON. It is the error-returning counterpart of Decode, and the entry point to reach for when something outside an HTTP handler needs bytes.
func EncodeJSON ¶
EncodeJSON JSON encodes a piece of data.
func MustDecode ¶
func MustDecode(data []byte, ct ContentType, dest any)
MustDecode decodes a given piece of data from a given encoding into dest, panicking on failure.
func MustDecodeJSON ¶
MustDecodeJSON decodes JSON data into dest, panicking on failure.
func MustEncode ¶
func MustEncode(data any, ct ContentType) []byte
MustEncode encodes a given piece of data to a given encoding, panicking on failure.
func MustEncodeJSON ¶
MustEncodeJSON JSON encodes a piece of data.
func MustJSONIntoReader ¶
MustJSONIntoReader JSON encodes a piece of data.
func RegisterServerEncoderDecoder ¶
RegisterServerEncoderDecoder registers a ContentType and ServerEncoderDecoder with the injector.
Types ¶
type ClientEncoder ¶
type ClientEncoder interface {
Codec
Encode(ctx context.Context, dest io.Writer, v any) error
EncodeReader(ctx context.Context, data any) (io.Reader, error)
}
ClientEncoder is a Codec that can also stream. The streaming halves are separated out because they are the parts tied to a transport; a caller that only needs bytes should ask for Marshaler or Codec instead.
type Codec ¶
type Codec interface {
Marshaler
Unmarshaler
ContentType() string
}
Codec is the transport-free pair, plus the content type it speaks. Prefer it over ClientEncoder wherever io.Writer and io.Reader are not part of the job.
type Config ¶
type Config struct {
ContentType string `env:"CONTENT_TYPE" json:"contentType,omitempty" yaml:"contentType,omitempty"`
// contains filtered or unexported fields
}
Config configures input/output encoding for the service.
type ContentType ¶
type ContentType string
ContentType is a media type this package can encode and decode.
It is a string-backed value type: comparable with ==, usable as a map key, printable, and with no pointer identity to get wrong. The zero value is the empty ContentType, which no constructor in this package returns — an unknown media type is reported as ErrUnsupportedContentType rather than silently standing in for JSON.
const ( // ContentTypeJSON selects JSON encoding. ContentTypeJSON ContentType = contentTypeJSON // ContentTypeXML selects XML encoding. ContentTypeXML ContentType = contentTypeXML // ContentTypeTOML selects TOML encoding. ContentTypeTOML ContentType = contentTypeTOML // ContentTypeYAML selects YAML encoding. ContentTypeYAML ContentType = contentTypeYAML // ContentTypeCBOR selects CBOR encoding (RFC 8949) — the binary option, // smaller than JSON on the wire and readable outside Go. Struct tags carry // over: a field with no cbor tag falls back to its json tag. ContentTypeCBOR ContentType = contentTypeCBOR // ContentTypeEmoji selects Ecoji-over-gob encoding. ContentTypeEmoji ContentType = contentTypeEmoji )
func NewContentType ¶
func NewContentType(cfg Config) (ContentType, error)
NewContentType resolves the ContentType named by a Config.
An unrecognized content type is an error rather than a silent fall back to JSON: a typo in configuration should stop startup, not quietly change the wire format of every response.
func ParseContentType ¶
func ParseContentType(val string) (ContentType, error)
ParseContentType resolves a media type — with or without parameters, in any case — to the ContentType that names it.
It returns ErrUnsupportedContentType for anything it does not implement, including the empty string. Callers that want a default must say so; this package will not choose one for them.
func (ContentType) String ¶
func (c ContentType) String() string
String returns the media type as it appears in a Content-Type header.
func (ContentType) Valid ¶
func (c ContentType) Valid() bool
Valid reports whether c is one of the content types this package implements.
type Encoder ¶
type Encoder struct {
// contains filtered or unexported fields
}
Encoder is our concrete implementation of ClientEncoder, speaking one ContentType for its whole life. It is exported, and returned by NewClientEncoder, so a caller can depend on the encoder it built rather than on the ClientEncoder seam.
func NewClientEncoder ¶
func NewClientEncoder(encoding ContentType, opts ...Option) *Encoder
NewClientEncoder provides a ClientEncoder.
It takes an already-resolved ContentType rather than a string, so the place to turn configuration into one is ParseContentType, which reports an unrecognized media type. An encoder built on a hand-made ContentType this package does not implement returns ErrUnsupportedContentType from every operation; it does not fall back to JSON.
func (*Encoder) ContentType ¶
func (*Encoder) EncodeReader ¶
type EncoderDecoder ¶
type EncoderDecoder struct {
// contains filtered or unexported fields
}
EncoderDecoder is our concrete implementation of ServerEncoderDecoder, speaking one ContentType for its whole life. It is exported, and returned by NewServerEncoderDecoder, so a caller can depend on the encoder it built rather than on the ServerEncoderDecoder seam.
func NewServerEncoderDecoder ¶
func NewServerEncoderDecoder(contentType ContentType, opts ...Option) *EncoderDecoder
NewServerEncoderDecoder provides a ServerEncoderDecoder.
As with NewClientEncoder, an unsupported ContentType is reported from every operation as ErrUnsupportedContentType rather than silently served as JSON. Resolve configuration through ParseContentType, which refuses it up front.
func (*EncoderDecoder) DecodeBytes ¶
DecodeBytes decodes bytes into values.
func (*EncoderDecoder) DecodeRequest ¶
DecodeRequest decodes request bodies into values.
func (*EncoderDecoder) EncodeResponseWithStatus ¶
func (e *EncoderDecoder) EncodeResponseWithStatus(ctx context.Context, res http.ResponseWriter, v any, statusCode int)
EncodeResponseWithStatus encodes responses and writes the provided status to the response.
func (*EncoderDecoder) MustEncode ¶
func (e *EncoderDecoder) MustEncode(ctx context.Context, v any) []byte
MustEncode encodes data or else.
func (*EncoderDecoder) MustEncodeJSON ¶
func (e *EncoderDecoder) MustEncodeJSON(ctx context.Context, v any) []byte
func (*EncoderDecoder) RespondWithData ¶
func (e *EncoderDecoder) RespondWithData(ctx context.Context, res http.ResponseWriter, v any)
RespondWithData encodes successful responses with data.
type Marshaler ¶
Marshaler renders a value as bytes in one content type. It is the smallest thing most callers need, and it carries no transport: anything that has to turn a value into bytes — a queue payload, a cache entry, a database column — should depend on this rather than on ClientEncoder.
type Option ¶
type Option func(*options)
Option configures the encoders this package constructs. The zero configuration works: an absent logger logs nowhere and an absent tracer provider traces nowhere.
func WithPillars ¶
func WithPillars(p *observability.Pillars) Option
WithPillars attaches a logger and tracer provider in one go, for the common case where a caller has already built them together. A nil Pillars attaches nothing.
The metrics provider a Pillars also carries is dropped, because this package records no instruments: encoding is a translation between a value and bytes, and the thing worth counting is whatever asked for the translation. Callers that want an encode counted instrument the operation around it.
It is applied in order with the individual options, so a caller can hand over its pillars and then override one of them.
func WithTracerProvider ¶
WithTracerProvider attaches a tracer provider, enabling spans on every encode and decode.
type ServerEncoderDecoder ¶
type ServerEncoderDecoder interface {
RespondWithData(ctx context.Context, res http.ResponseWriter, val any)
EncodeResponseWithStatus(ctx context.Context, res http.ResponseWriter, val any, statusCode int)
DecodeRequest(ctx context.Context, req *http.Request, dest any) error
DecodeBytes(ctx context.Context, payload []byte, dest any) error
MustEncode(ctx context.Context, v any) []byte
MustEncodeJSON(ctx context.Context, v any) []byte
}
ServerEncoderDecoder is an interface that allows for multiple implementations of HTTP response formats.