Documentation
¶
Overview ¶
Package hal implements the Hypertext Application Language (HAL) for JSON.
It provides a mechanism to wrap existing Go structs in an envelope that injects standard HAL "_links" and "_embedded" fields without requiring changes to the underlying data models.
The package relies on a central or instance-based registry to map Go types to link generation functions. It supports CURIEs (Compact URIs) and strict mode validation to ensure type safety during link generation.
All methods on Instance are safe for concurrent use.
Package hal provides a high-performance HAL (Hypertext Application Language) implementation.
Overview ¶
go-hal is a zero-reflection, allocation-efficient HAL serialization library for Go. It wraps your Go structs in HAL Envelopes, injecting _links and _embedded metadata during JSON serialization without intermediate allocations.
Key Features ¶
- Zero reflection at runtime (type-safe closures)
- Pre-computed links for maximum performance
- Full HAL spec compliance (draft-kelly-json-hal)
- Dual-mode: global singleton or isolated instances
Quick Start ¶
hal.Register(func(ctx context.Context, u *User) []hal.Link {
return []hal.Link{{Rel: "self", Href: fmt.Sprintf("/users/%d", u.ID)}}
})
env := hal.Wrap(ctx, &User{ID: 1, Name: "Alice"})
json.Marshal(env) // => {"id":1,"name":"Alice","_links":{"self":{"href":"/users/1"}}}
Index ¶
- Variables
- func GetVersion() string
- func Register[T any](gen func(context.Context, *T) []Link)
- func RegisterCurie(prefix, href string)
- func RegisterInstance[T any](i *Instance, gen func(context.Context, *T) []Link)
- func RegisterStatic(i *Instance, target any, links []Link)
- type CollectionPage
- type Envelope
- type Generator
- type Instance
- func (i *Instance) Collection(ctx context.Context, items any, total int, selfLink Link) *CollectionPage
- func (i *Instance) RegisterCurie(prefix, href string)
- func (i *Instance) RegisterInstance(gen any)
- func (i *Instance) RegisteredTypes() []reflect.Type
- func (i *Instance) Wrap(ctx context.Context, data any) *Envelope
- func (i *Instance) WrapPrecomputed(_ context.Context, data any, linksJSON []byte) *Envelope
- func (i *Instance) WrapRaw(data any) *Envelope
- type InstanceOption
- type Link
- type PrecomputedLinks
- type VersionInfo
Constants ¶
This section is empty.
Variables ¶
var DefaultInstance = New()
DefaultInstance is the global singleton registry used by package-level functions. If you need isolated registries for testing, create your own with New().
Functions ¶
func GetVersion ¶
func GetVersion() string
GetVersion returns the version string (e.g., "v1.0.0" or "v1.0.0-beta")
func Register ¶
Register registers a generator function for type T on the DefaultInstance. The generator is called at runtime to produce links for wrapped resources.
Type Requirements ¶
- T must be a pointer type (e.g., *User not User)
- Generators are registered for the pointee type
Example ¶
hal.Register(func(ctx context.Context, u *User) []Link {
return []Link{
{Rel: "self", Href: fmt.Sprintf("/users/%d", u.ID)},
{Rel: "edit", Href: fmt.Sprintf("/users/%d/edit", u.ID)},
}
})
func RegisterCurie ¶
func RegisterCurie(prefix, href string)
RegisterCurie registers a CURIE (Compact URI) prefix on the DefaultInstance. CURIEs allow shortened link relations like "acme:widget" that expand to full URIs.
Example ¶
hal.RegisterCurie("acme", "https://docs.example.com/rels/{rel}")
func RegisterInstance ¶
RegisterInstance binds a strongly-typed generator function to the provided Instance. The generator will be invoked whenever Wrap is called with a value of type *T.
func RegisterStatic ¶
RegisterStatic registers pre-computed links for a type. Links are serialized once at registration time, providing ~60% performance improvement over runtime generator calls. Best for static links that don't change per request.
Performance ¶
Pre-computed links bypass generator invocation and JSON serialization on every request. This is optimal for links like "self" hrefs that only depend on the resource ID.
Example ¶
inst := hal.New()
hal.RegisterStatic(inst, &User{}, []hal.Link{
{Rel: "self", Href: "/users"},
})
// At runtime - uses pre-computed links automatically
env := inst.Wrap(ctx, &User{ID: 42})
Types ¶
type CollectionPage ¶
type CollectionPage struct {
Links map[string]any `json:"_links"`
Embedded map[string]any `json:"_embedded"`
Count int `json:"count"`
Total int `json:"total,omitempty"`
}
CollectionPage represents a standard HAL collection response. It includes navigation links, embedded items, and pagination metadata.
func Collection ¶
Collection creates a CollectionPage using the DefaultInstance.
type Envelope ¶
type Envelope struct {
Data any // The user's struct
// contains filtered or unexported fields
}
Envelope is the container for your data with HAL metadata. It wraps your Go struct and injects _links and _embedded during JSON serialization.
To create an Envelope:
// Using global instance
env := hal.Wrap(ctx, &MyStruct{...})
// Using isolated instance
inst := hal.New()
env := inst.Wrap(ctx, &MyStruct{...})
The Envelope implements json.Marshaler and will inject HAL metadata automatically.
func Wrap ¶
Wrap wraps data in a HAL Envelope using the DefaultInstance. The envelope will inject _links during JSON serialization based on registered generators.
Example ¶
user := &User{ID: 42, Name: "Alice"}
env := hal.Wrap(context.Background(), user)
json.Marshal(env)
func (*Envelope) AddLink ¶
AddLink appends a link to the envelope. If a link with the same Relation (Rel) already exists, it is converted to a slice of links as per the HAL specification.
func (*Envelope) MarshalJSON ¶
MarshalJSON implements the json.Marshaler interface. It serializes the wrapped Data and splices in the HAL "_links" and "_embedded" fields into the resulting JSON object.
type Generator ¶
Generator defines a function that produces HAL links for a specific type. The function receives the data to generate links for and returns a slice of Link objects.
Example ¶
Generator: func(ctx context.Context, u *User) []Link {
return []Link{{Rel: "self", Href: fmt.Sprintf("/users/%d", u.ID)}}
}
type Instance ¶
type Instance struct {
// contains filtered or unexported fields
}
Instance maintains a registry of link generators and CURIE definitions. It is safe for concurrent use.
Creating an Instance ¶
// Basic instance inst := hal.New() // With strict mode inst := hal.New(hal.WithStrictMode())
func New ¶
func New(opts ...InstanceOption) *Instance
New creates a new HAL Instance.
The instance maintains its own registry of generators, allowing for isolated testing. Options can be provided to configure the instance:
inst := hal.New(hal.WithStrictMode())
func (*Instance) Collection ¶
func (i *Instance) Collection(ctx context.Context, items any, total int, selfLink Link) *CollectionPage
Collection wraps a slice of items into a HAL CollectionPage. It iterates over the items, wraps each one using the registered generators, and constructs the embedded items list.
This method panics if items is not a slice.
func (*Instance) RegisterCurie ¶
RegisterCurie adds a CURIE (Compact URI) mapping to the instance. These are used to shorten link relations in the output JSON.
func (*Instance) RegisterInstance ¶
RegisterInstance registers a generator using reflection. Deprecated: Use the generic function RegisterInstance[T] instead.
func (*Instance) RegisteredTypes ¶
RegisteredTypes returns a list of all Go types that have a generator registered. This is a Read-Only introspection hook useful for adapters (like OpenAPI).
func (*Instance) Wrap ¶
Wrap creates a HAL Envelope with computed links. It first checks for pre-computed links (via RegisterStatic), then falls back to generators. This is the primary method for wrapping data in HAL envelopes.
Automatic Optimization ¶
If RegisterStatic was called for the data's type, links are pre-computed and this method automatically uses them, providing ~60% better performance.
func (*Instance) WrapPrecomputed ¶
WrapPrecomputed wraps data with pre-serialized links JSON. This is the absolute fastest option - bypasses all generator calls.
Performance ¶
Provides ~65% better performance than Wrap by eliminating:
- Generator invocation
- JSON serialization of links
Input Format ¶
linksJSON must be a JSON object containing link relations:
{"self": {"href": "/users/42"}}
Example ¶
links := []byte(`{"self":{"href":"/users/42"}})
env := inst.WrapPrecomputed(ctx, &User{ID: 42}, links)
json.Marshal(env)
type InstanceOption ¶
type InstanceOption func(*Instance)
InstanceOption configures a new HAL Instance. Apply options when creating a new Instance:
inst := hal.New(hal.WithStrictMode())
func WithStrictMode ¶
func WithStrictMode() InstanceOption
WithStrictMode enables panic on generator mismatches. When enabled, hal.Wrap panics if:
- You pass a value type T but registered a generator for *T
- You pass a type with no registered generator
This helps catch type mismatch errors during development.
Example ¶
inst := hal.New(hal.WithStrictMode()) // panics on mismatches
type Link ¶
type Link struct {
Rel string `json:"-"` // Used for map keys, not serialized directly
Href string `json:"href"`
Templated bool `json:"templated,omitempty"`
Type string `json:"type,omitempty"`
Deprecation string `json:"deprecation,omitempty"` // OPTIONAL: link to deprecation info
Name string `json:"name,omitempty"`
Profile string `json:"profile,omitempty"` // OPTIONAL: URI hint about target
Title string `json:"title,omitempty"`
HrefLang string `json:"hreflang,omitempty"` // OPTIONAL: language of target
Method string `json:"method,omitempty"` // Non-standard: common hint for HTTP methods
}
Link represents a HAL Hypermedia link. Implements draft-kelly-json-hal Section 5 Link Object.
All fields (except Rel and Method) are compliant with the HAL specification. Rel is used internally as the map key and is not serialized. Method is a non-standard extension for HTTP method hints.
Example ¶
Link{
Rel: "self",
Href: "/users/123",
Templated: false,
Title: "Current User",
}
type PrecomputedLinks ¶
type PrecomputedLinks struct {
JSON []byte
}
PrecomputedLinks stores pre-computed links JSON
type VersionInfo ¶
VersionInfo holds version metadata
func GetVersionInfo ¶
func GetVersionInfo() VersionInfo
GetVersionInfo returns detailed version information