View all results

Value Encoders

When a session finishes, Ferret encodes the result value into bytes using a codec selected by content type. The Output struct carries the encoded bytes and their MIME type:

example.go
read-only
output, err := session.Run(ctx) if err != nil { log.Fatal(err) } fmt.Println(output.ContentType) // "application/json" fmt.Println(string(output.Content))

Built-in codecs

Two codecs are registered by default:

Content type Format Package
application/json JSON (default) pkg/encoding/json
application/vnd.msgpack MessagePack pkg/encoding/msgpack

Selecting the output format

Set the content type when creating a session:

example.go
read-only
session, err := plan.NewSession(ctx, ferret.WithOutputContentType("application/vnd.msgpack"), ) if err != nil { log.Fatal(err) } output, err := session.Run(ctx) if err != nil { log.Fatal(err) } fmt.Println(output.ContentType) // application/vnd.msgpack

If no content type is set, the session defaults to application/json.

The Codec interface

A codec combines encoding and decoding behind a single content type:

example.go
read-only
type Codec interface { ContentType() string Encoder Decoder }

Encoder

example.go
read-only
type Encoder interface { Encode(value runtime.Value) ([]byte, error) EncodeWith() EncoderConfigurer }

Encode converts a runtime value to bytes. EncodeWith returns a configurer for attaching hooks (see below).

Decoder

example.go
read-only
type Decoder interface { Decode(data []byte) (runtime.Value, error) DecodeWith() DecoderConfigurer }

Decode converts bytes back into a runtime value. DecodeWith returns a configurer for attaching hooks.

Encoder and decoder hooks

Hooks let you intercept encoding and decoding without replacing the codec. The configurer chain builds a new encoder or decoder with hooks attached:

example.go
read-only
encoder := codec.EncodeWith(). PreHook(func(value runtime.Value) error { // runs before encoding return nil }). PostHook(func(value runtime.Value, err error) error { // runs after encoding return nil }). Encoder()

Hook types

Hook Signature When it runs
PreEncoderHook func(value runtime.Value) error Before encoding a value
PostEncoderHook func(value runtime.Value, err error) error After encoding; receives the encode error
PreDecoderHook func(data []byte) error Before decoding bytes
PostDecoderHook func(data []byte, err error) error After decoding; receives the decode error

Multiple hooks of the same type run in registration order. If any hook returns an error, processing stops.

The Registry

The encoding.Registry stores codecs by MIME-normalized content type:

example.go
read-only
// Create a registry seeded with codecs registry := encoding.NewRegistry(jsonCodec, msgpackCodec) // Or start empty and register individually registry := encoding.NewEmptyRegistry() registry.Register(myCodec)

Registry methods

Method Returns Purpose
Register(codec) error Store a codec by its content type
Codec(contentType) Codec, error Look up a full codec
Encoder(contentType) Encoder, error Look up an encoder
Decoder(contentType) Decoder, error Look up a decoder
Clone() *Registry Create an independent copy

Content types are normalized using MIME media type parsing, so application/json and application/json; charset=utf-8 resolve to the same codec.

Registering codecs on the engine

Add or override a single codec:

example.go
read-only
engine, err := ferret.New( ferret.WithEncodingCodec("text/csv", myCSVCodec), )

Replace the entire registry:

example.go
read-only
registry := encoding.NewRegistry(jsonCodec, myCSVCodec) engine, err := ferret.New( ferret.WithEncodingRegistry(registry), )

When you use WithEncodingRegistry, only the codecs in the provided registry are available. The default JSON and MessagePack codecs are not included unless you add them yourself.

Complete example

A custom codec that encodes values as plain text:

example.go
read-only
package main import ( "context" "fmt" "log" "github.com/MontFerret/ferret/v2" "github.com/MontFerret/ferret/v2/pkg/encoding" "github.com/MontFerret/ferret/v2/pkg/runtime" "github.com/MontFerret/ferret/v2/pkg/source" ) type plainTextCodec struct{} func (plainTextCodec) ContentType() string { return "text/plain" } func (plainTextCodec) Encode(value runtime.Value) ([]byte, error) { return []byte(value.String()), nil } func (c plainTextCodec) EncodeWith() encoding.EncoderConfigurer { return &plainTextEncoderConfigurer{codec: c} } func (plainTextCodec) Decode(data []byte) (runtime.Value, error) { return runtime.NewString(string(data)), nil } func (c plainTextCodec) DecodeWith() encoding.DecoderConfigurer { return &plainTextDecoderConfigurer{codec: c} } type plainTextEncoderConfigurer struct { codec plainTextCodec pre []encoding.PreEncoderHook post []encoding.PostEncoderHook } func (e *plainTextEncoderConfigurer) PreHook(hook encoding.PreEncoderHook) encoding.EncoderConfigurer { if hook != nil { e.pre = append(e.pre, hook) } return e } func (e *plainTextEncoderConfigurer) PostHook(hook encoding.PostEncoderHook) encoding.EncoderConfigurer { if hook != nil { e.post = append(e.post, hook) } return e } func (e *plainTextEncoderConfigurer) Encoder() encoding.Encoder { return e.codec } type plainTextDecoderConfigurer struct { codec plainTextCodec pre []encoding.PreDecoderHook post []encoding.PostDecoderHook } func (d *plainTextDecoderConfigurer) PreHook(hook encoding.PreDecoderHook) encoding.DecoderConfigurer { if hook != nil { d.pre = append(d.pre, hook) } return d } func (d *plainTextDecoderConfigurer) PostHook(hook encoding.PostDecoderHook) encoding.DecoderConfigurer { if hook != nil { d.post = append(d.post, hook) } return d } func (d *plainTextDecoderConfigurer) Decoder() encoding.Decoder { return d.codec } func main() { engine, err := ferret.New( ferret.WithEncodingCodec("text/plain", plainTextCodec{}), ) if err != nil { log.Fatal(err) } defer engine.Close() plan, err := engine.Compile( context.Background(), source.NewAnonymous(`RETURN "Hello, Ferret!"`), ) if err != nil { log.Fatal(err) } defer plan.Close() session, err := plan.NewSession( context.Background(), ferret.WithOutputContentType("text/plain"), ) if err != nil { log.Fatal(err) } defer session.Close() output, err := session.Run(context.Background()) if err != nil { log.Fatal(err) } fmt.Println(output.ContentType) // text/plain fmt.Println(string(output.Content)) // Hello, Ferret! }

Next steps