Documentation
¶
Overview ¶
Package binding provides request data binding for HTTP handlers.
The binding package maps values from various sources (query parameters, form data, JSON bodies, headers, cookies, path parameters) into Go structs using struct tags. It supports nested structs, slices, maps, pointers, custom types, default values, and type conversion.
Note: For validation (required fields, enum constraints, etc.), use the rivaas.dev/validation package separately after binding.
Quick Start ¶
The package provides both generic and non-generic APIs:
// Generic (preferred when type is known) user, err := binding.JSON[CreateUserRequest](body) // Non-generic (when type comes from variable) var user CreateUserRequest err := binding.JSONTo(body, &user)
Source-Specific Functions ¶
Each binding source has dedicated functions:
// Query parameters params, err := binding.Query[ListParams](r.URL.Query()) // Path parameters params, err := binding.Path[GetUserParams](pathParams) // Form data data, err := binding.Form[FormData](r.PostForm) // HTTP headers headers, err := binding.Header[RequestHeaders](r.Header) // Cookies session, err := binding.Cookie[SessionData](r.Cookies()) // JSON body user, err := binding.JSON[CreateUserRequest](body) // XML body user, err := binding.XML[CreateUserRequest](body)
Multi-Source Binding ¶
Bind from multiple sources using From* options:
req, err := binding.Bind[CreateOrderRequest](
binding.FromPath(pathParams),
binding.FromQuery(r.URL.Query()),
binding.FromHeader(r.Header),
binding.FromJSON(body),
)
Configuration ¶
Use functional options to customize binding behavior:
user, err := binding.JSON[User](body,
binding.WithUnknownFields(binding.UnknownError),
binding.WithMaxDepth(16),
)
Reusable Binder ¶
For shared configuration, create a Binder instance:
binder := binding.MustNew(
binding.WithConverter[uuid.UUID](uuid.Parse),
binding.WithTimeLayouts("2006-01-02", "01/02/2006"),
)
// Use across handlers
user, err := binder.JSON[CreateUserRequest](body)
params, err := binder.Query[ListParams](r.URL.Query())
Struct Tags ¶
The package uses struct tags to map values:
type Request struct {
// Query parameters
Page int `query:"page" default:"1"`
Limit int `query:"limit" default:"20"`
// Path parameters
UserID string `path:"user_id"`
// Headers
Auth string `header:"Authorization"`
// JSON body fields
Name string `json:"name"`
Email string `json:"email"`
// For validation, use the validation package
Status string `json:"status" validate:"required,oneof=active pending disabled"`
}
Supported Tag Types ¶
- query: URL query parameters (?name=value)
- path: URL path parameters (/users/:id)
- form: Form data (application/x-www-form-urlencoded)
- header: HTTP headers
- cookie: HTTP cookies
- json: JSON body fields
- xml: XML body fields
Additional Serialization Formats ¶
The following formats are available as sub-packages:
- rivaas.dev/binding/yaml: YAML support (gopkg.in/yaml.v3)
- rivaas.dev/binding/toml: TOML support (github.com/BurntSushi/toml)
- rivaas.dev/binding/msgpack: MessagePack support (github.com/vmihailenco/msgpack/v5)
- rivaas.dev/binding/proto: Protocol Buffers support (google.golang.org/protobuf)
Example with YAML:
import "rivaas.dev/binding/yaml" config, err := yaml.YAML[Config](body)
Example with TOML:
import "rivaas.dev/binding/toml" config, err := toml.TOML[Config](body)
Example with MessagePack:
import "rivaas.dev/binding/msgpack" msg, err := msgpack.MsgPack[Message](body)
Example with Protocol Buffers:
import "rivaas.dev/binding/proto" user, err := proto.Proto[*pb.User](body)
Special Tags ¶
- default:"value": Default value when field is not present
For validation constraints (required, enum, etc.), use the rivaas.dev/validation package with the `validate` struct tag.
Type Conversion ¶
Built-in support for common types:
- Primitives: string, int*, uint*, float*, bool
- Time: time.Time, time.Duration
- Network: net.IP, net.IPNet, url.URL
- Slices: []T for any supported T
- Maps: map[string]T for any supported T
- Pointers: *T for any supported T
- encoding.TextUnmarshaler implementations
Register custom converters:
binding.MustNew(
binding.WithConverter[uuid.UUID](uuid.Parse),
binding.WithConverter[decimal.Decimal](decimal.NewFromString),
)
Common Converter Patterns ¶
The package provides factory functions for common converter patterns:
## Custom Time Layouts
Use TimeConverter to parse times in non-standard formats:
binder := binding.MustNew(
binding.WithConverter(binding.TimeConverter(
"01/02/2006", // US format
"02/01/2006", // European format
"2006-01-02 15:04", // Custom datetime
)),
)
## Duration Aliases
Use DurationConverter to provide user-friendly duration names:
binder := binding.MustNew(
binding.WithConverter(binding.DurationConverter(map[string]time.Duration{
"fast": 100 * time.Millisecond,
"normal": 1 * time.Second,
"slow": 5 * time.Second,
"default": 30 * time.Second,
})),
)
// Query: ?timeout=fast -> 100ms
// Query: ?timeout=1h30m -> 1h30m (standard duration strings still work)
## Enum Validation
Use EnumConverter to validate against allowed values:
type Status string
const (
StatusActive Status = "active"
StatusPending Status = "pending"
StatusDisabled Status = "disabled"
)
binder := binding.MustNew(
binding.WithConverter(binding.EnumConverter(
StatusActive, StatusPending, StatusDisabled,
)),
)
// Query: ?status=ACTIVE -> StatusActive (case-insensitive)
// Query: ?status=unknown -> error
## Custom Boolean Values
Use BoolConverter to accept non-standard boolean representations:
binder := binding.MustNew(
binding.WithConverter(binding.BoolConverter(
[]string{"enabled", "active", "on"}, // truthy values
[]string{"disabled", "inactive", "off"}, // falsy values
)),
)
// Query: ?feature=enabled -> true
// Query: ?feature=off -> false
## Combining Multiple Converters
You can use multiple converter factories together:
binder := binding.MustNew(
binding.WithConverter(binding.TimeConverter("01/02/2006")),
binding.WithConverter(binding.DurationConverter(map[string]time.Duration{
"fast": 100 * time.Millisecond,
"slow": 5 * time.Second,
})),
binding.WithConverter(binding.EnumConverter(StatusActive, StatusInactive)),
binding.WithConverter(binding.BoolConverter(
[]string{"yes", "on"},
[]string{"no", "off"},
)),
)
## Third-Party Type Examples
For types from third-party packages, use WithConverter:
import (
"github.com/google/uuid"
"github.com/shopspring/decimal"
)
binder := binding.MustNew(
binding.WithConverter[uuid.UUID](uuid.Parse),
binding.WithConverter[decimal.Decimal](decimal.NewFromString),
)
Or with a custom wrapper for error handling:
binder := binding.MustNew(
binding.WithConverter[MyCustomType](func(s string) (MyCustomType, error) {
// Custom parsing logic
return parseMyCustomType(s)
}),
)
Error Handling ¶
Errors provide detailed context:
user, err := binding.JSON[User](body)
if err != nil {
var bindErr *binding.BindError
if errors.As(err, &bindErr) {
fmt.Printf("Field: %s, Source: %s, Value: %s\n",
bindErr.Field, bindErr.Source, bindErr.Value)
}
}
Collect all errors instead of failing on first:
user, err := binding.JSON[User](body, binding.WithAllErrors())
if err != nil {
var multi *binding.MultiError
if errors.As(err, &multi) {
for _, e := range multi.Errors {
// Handle each error
}
}
}
Observability ¶
Add hooks for monitoring:
binder := binding.MustNew(
binding.WithEvents(binding.Events{
FieldBound: func(name, tag string) {
log.Printf("Bound field %s from %s", name, tag)
},
Done: func(stats binding.Stats) {
log.Printf("Bound %d fields", stats.FieldsBound)
},
}),
)
Security Limits ¶
Built-in limits prevent resource exhaustion:
- MaxDepth: Maximum struct nesting depth (default: 32)
- MaxSliceLen: Maximum slice elements (default: 10,000)
- MaxMapSize: Maximum map entries (default: 1,000)
Configure limits:
binding.MustNew(
binding.WithMaxDepth(16),
binding.WithMaxSliceLen(1000),
binding.WithMaxMapSize(500),
)
Configuration Options ¶
The package provides extensive configuration through functional options:
## Security Limits
WithMaxDepth(n int) // Max struct nesting depth (default: 32) WithMaxSliceLen(n int) // Max slice elements (default: 10,000) WithMaxMapSize(n int) // Max map entries (default: 1,000)
## Unknown Fields
WithStrictJSON() // Convenience: fail on unknown fields WithUnknownFields(policy UnknownFieldPolicy) - UnknownIgnore: Ignore unknown fields (default) - UnknownWarn: Log warnings via events - UnknownError: Return error on unknown fields (same as WithStrictJSON)
## Slice Parsing
WithSliceMode(mode SliceParseMode) - SliceRepeat: Parse "tags=go&tags=rust" (default) - SliceCSV: Parse "tags=go,rust,python"
## Time Formats
WithTimeLayouts(layouts ...string) // Custom time.Time parsing formats // Default layouts exported as DefaultTimeLayouts: RFC3339, RFC3339Nano, DateOnly, DateTime // Extend defaults: WithTimeLayouts(append(DefaultTimeLayouts, "01/02/2006")...)
## Type Converters
WithConverter[T any](converter TypeConverter[T]) // Register custom type conversion function
## Error Handling
WithAllErrors() // Collect all errors instead of failing on first
## Observability
WithEvents(events Events) // Add hooks for monitoring - FieldBound: Called when field is bound - UnknownField: Called when unknown field detected - Done: Called when binding completes
## Key Normalization
WithKeyNormalizer(normalizer KeyNormalizer) // Custom key transformation for lookups
Advanced Tag Syntax ¶
## Tag Aliases
Provide multiple lookup names for a field:
type Request struct {
UserID int `query:"user_id,id"` // Looks for "user_id" or "id"
}
## Nested Structs
Use dot notation for nested fields:
type Address struct {
Street string `query:"street"`
City string `query:"city"`
}
type User struct {
Address Address `query:"address"`
}
// Query: ?address.street=123+Main&address.city=Boston
## Bracket Notation
Arrays can use bracket notation:
type Request struct {
Tags []string `query:"tags"`
}
// Both work: ?tags=go&tags=rust or ?tags[]=go&tags[]=rust
Streaming with io.Reader ¶
For large payloads, use Reader variants to avoid loading entire body into memory:
// JSON from reader user, err := binding.JSONReader[User](r.Body) // XML from reader doc, err := binding.XMLReader[Document](r.Body)
Reader variants are available for all body-based sources (JSON, XML, and sub-packages).
Performance Characteristics ¶
## Caching
Struct reflection information is cached automatically:
- First binding of a type: ~500ns overhead for reflection
- Subsequent bindings: ~50ns overhead (cache lookup)
- Cache is thread-safe and has no size limit
- Cache key includes both struct type and tag name
## Memory Allocation
- Query/Path/Form/Header/Cookie: Zero allocations for primitive types
- JSON/XML: Allocations depend on encoding/json and encoding/xml
- Nested structs: One allocation per nesting level
- Slices/Maps: Pre-allocated with capacity hints when possible
## Multi-Source Binding Precedence
When using Bind with multiple sources, later sources override earlier ones:
req, err := binding.Bind[Request](
binding.FromPath(pathParams), // Applied first
binding.FromQuery(r.URL.Query()), // Overrides path params
binding.FromJSON(body), // Overrides query params
)
This allows for flexible request handling where body data takes precedence over URL parameters.
Custom ValueGetter ¶
For simple map-based sources, use the convenience helpers:
// Single-value map
getter := binding.MapGetter(map[string]string{"name": "Alice", "age": "30"})
result, err := binding.RawInto[User](getter, "custom")
// Multi-value map (for slices)
getter := binding.MultiMapGetter(map[string][]string{"tags": {"go", "rust"}})
result, err := binding.RawInto[User](getter, "custom")
For more complex sources, implement the ValueGetter interface:
type CustomGetter struct {
data map[string]string
}
func (g *CustomGetter) Get(key string) string {
return g.data[key]
}
func (g *CustomGetter) GetAll(key string) []string {
if val, ok := g.data[key]; ok {
return []string{val}
}
return nil
}
func (g *CustomGetter) Has(key string) bool {
_, ok := g.data[key]
return ok
}
// Use with RawInto
result, err := binding.RawInto[MyStruct](&CustomGetter{data: myData}, "custom")
Alternatively, use GetterFunc for a function-based adapter:
getter := binding.GetterFunc(func(key string) ([]string, bool) {
if val, ok := myMap[key]; ok {
return []string{val}, true
}
return nil, false
})
Integration Examples ¶
## With net/http
func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
defer r.Body.Close()
user, err := binding.JSON[CreateUserRequest](body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Process user...
}
## With rivaas.dev/router
func CreateUserHandler(c *router.Context) {
user, err := binding.JSON[CreateUserRequest](c.Body())
if err != nil {
c.Error(err, http.StatusBadRequest)
return
}
c.JSON(http.StatusCreated, user)
}
## With rivaas.dev/app
func CreateUserHandler(c *app.Context) {
var user CreateUserRequest
if err := c.Bind(&user); err != nil {
return // Error automatically handled
}
c.JSON(http.StatusCreated, user)
}
Best Practices ¶
- Use generic API (JSON, Query, etc.) for compile-time type safety
- Create reusable Binder instances for shared configuration
- Set security limits (WithMaxDepth, WithMaxSliceLen, WithMaxMapSize)
- Use Reader variants for large payloads (>1MB)
- Use rivaas.dev/validation package for validation (required, enum, etc.)
- Add observability hooks with WithEvents for monitoring
- Collect all errors with WithAllErrors for better UX
Error Types ¶
The package provides detailed error types for different failure scenarios:
- BindError: Field-level binding errors with context
- UnknownFieldError: Unknown fields in strict JSON mode
- MultiError: Multiple errors collected with WithAllErrors
All error types implement standard error interfaces and integrate with rivaas.dev/errors for HTTP status code mapping.
Index ¶
- Constants
- Variables
- func AssertNoBindError(t *testing.T, err error)
- func Bind[T any](opts ...Option) (T, error)
- func BindTo(out any, opts ...Option) error
- func BindWith[T any](b *Binder, opts ...Option) (T, error)
- func BoolConverter(truthy, falsy []string) func(string) (bool, error)
- func Cookie[T any](cookies []*http.Cookie, opts ...Option) (T, error)
- func CookieTo(cookies []*http.Cookie, out any, opts ...Option) error
- func CookieWith[T any](b *Binder, cookies []*http.Cookie) (T, error)
- func DurationConverter(aliases map[string]time.Duration) func(string) (time.Duration, error)
- func EnumConverter[T ~string](allowed ...T) func(string) (T, error)
- func Form[T any](values url.Values, opts ...Option) (T, error)
- func FormTo(values url.Values, out any, opts ...Option) error
- func FormWith[T any](b *Binder, values url.Values) (T, error)
- func HasStructTag(t reflect.Type, tag string) bool
- func Header[T any](h http.Header, opts ...Option) (T, error)
- func HeaderTo(h http.Header, out any, opts ...Option) error
- func HeaderWith[T any](b *Binder, h http.Header) (T, error)
- func JSON[T any](body []byte, opts ...Option) (T, error)
- func JSONReader[T any](r io.Reader, opts ...Option) (T, error)
- func JSONReaderTo(r io.Reader, out any, opts ...Option) error
- func JSONReaderWith[T any](b *Binder, r io.Reader) (T, error)
- func JSONTo(body []byte, out any, opts ...Option) error
- func JSONWith[T any](b *Binder, body []byte) (T, error)
- func MustBind[T any](t *testing.T, getter ValueGetter, tag string, opts ...Option) T
- func MustBindForm[T any](t *testing.T, values url.Values, opts ...Option) T
- func MustBindJSON[T any](t *testing.T, jsonData string, opts ...Option) T
- func MustBindQuery[T any](t *testing.T, values url.Values, opts ...Option) T
- func MustWarmupCache(types ...any)
- func Path[T any](params map[string]string, opts ...Option) (T, error)
- func PathTo(params map[string]string, out any, opts ...Option) error
- func PathWith[T any](b *Binder, params map[string]string) (T, error)
- func Query[T any](values url.Values, opts ...Option) (T, error)
- func QueryTo(values url.Values, out any, opts ...Option) error
- func QueryWith[T any](b *Binder, values url.Values) (T, error)
- func Raw(getter ValueGetter, tag string, out any, opts ...Option) error
- func RawInto[T any](getter ValueGetter, tag string, opts ...Option) (T, error)
- func TimeConverter(layouts ...string) func(string) (time.Time, error)
- func WarmupCache(types ...any)
- func XML[T any](body []byte, opts ...Option) (T, error)
- func XMLReader[T any](r io.Reader, opts ...Option) (T, error)
- func XMLReaderTo(r io.Reader, out any, opts ...Option) error
- func XMLReaderWith[T any](b *Binder, r io.Reader) (T, error)
- func XMLTo(body []byte, out any, opts ...Option) error
- func XMLWith[T any](b *Binder, body []byte) (T, error)
- type BindError
- type Binder
- func (b *Binder) BindTo(out any, opts ...Option) error
- func (b *Binder) CookieTo(cookies []*http.Cookie, out any) error
- func (b *Binder) FormTo(values url.Values, out any) error
- func (b *Binder) HeaderTo(h http.Header, out any) error
- func (b *Binder) JSONReaderTo(r io.Reader, out any) error
- func (b *Binder) JSONTo(body []byte, out any) error
- func (b *Binder) PathTo(params map[string]string, out any) error
- func (b *Binder) QueryTo(values url.Values, out any) error
- func (b *Binder) XMLReaderTo(r io.Reader, out any) error
- func (b *Binder) XMLTo(body []byte, out any) error
- type CookieGetter
- type Events
- type FormGetter
- type GetterFunc
- type HeaderGetter
- type KeyNormalizer
- type Metadata
- type MultiError
- func (m *MultiError) Add(err *BindError)
- func (m *MultiError) Code() string
- func (m *MultiError) Details() any
- func (m *MultiError) Error() string
- func (m *MultiError) ErrorOrNil() error
- func (m *MultiError) HTTPStatus() int
- func (m *MultiError) HasErrors() bool
- func (m *MultiError) Unwrap() []error
- type Option
- func FromCookie(cookies []*http.Cookie) Option
- func FromForm(values url.Values) Option
- func FromGetter(getter ValueGetter, tag string) Option
- func FromHeader(h http.Header) Option
- func FromJSON(body []byte) Option
- func FromJSONReader(r io.Reader) Option
- func FromPath(params map[string]string) Option
- func FromQuery(values url.Values) Option
- func FromXML(body []byte) Option
- func FromXMLReader(r io.Reader) Option
- func WithAllErrors() Option
- func WithConverter[T any](fn func(string) (T, error)) Option
- func WithEvents(events Events) Option
- func WithIntBaseAuto() Option
- func WithJSONUseNumber() Option
- func WithKeyNormalizer(normalizer KeyNormalizer) Option
- func WithMaxDepth(depth int) Option
- func WithMaxMapSize(n int) Option
- func WithMaxSliceLen(n int) Option
- func WithSliceMode(mode SliceParseMode) Option
- func WithStrictJSON() Option
- func WithTimeLayouts(layouts ...string) Option
- func WithTypeConverter(targetType reflect.Type, converter TypeConverter) Option
- func WithUnknownFields(policy UnknownFieldPolicy) Option
- func WithXMLStrict() Option
- type PathGetter
- type QueryGetter
- type SliceParseMode
- type Source
- type Stats
- type TestValidator
- type TypeConverter
- type UnknownFieldError
- type UnknownFieldPolicy
- type ValueGetter
- func MapGetter(m map[string]string) ValueGetter
- func MultiMapGetter(m map[string][]string) ValueGetter
- func TestCookieGetter(t *testing.T, pairs ...string) ValueGetter
- func TestFormGetter(t *testing.T, pairs ...string) ValueGetter
- func TestHeaderGetter(t *testing.T, pairs ...string) ValueGetter
- func TestPathGetter(t *testing.T, pairs ...string) ValueGetter
- func TestQueryGetter(t *testing.T, pairs ...string) ValueGetter
- func TestQueryGetterMulti(t *testing.T, values map[string][]string) ValueGetter
Examples ¶
Constants ¶
const ( // DefaultMaxDepth is the default maximum nesting depth for structs and maps. // It prevents stack overflow from malicious deeply-nested payloads. DefaultMaxDepth = 32 // DefaultMaxMapSize is the default maximum number of map entries per field. // It prevents resource exhaustion from large map bindings. DefaultMaxMapSize = 1000 // DefaultMaxSliceLen is the default maximum number of slice elements per field. // It prevents memory exhaustion from large slice bindings. DefaultMaxSliceLen = 10_000 // DefaultMaxBodySize is the default maximum request body size (10 MiB). // This limit is enforced at the router layer, not in the binding package. DefaultMaxBodySize = 10 << 20 )
Security and resilience limits for binding operations.
const ( TagJSON = "json" // JSON struct tag TagQuery = "query" // Query parameter struct tag TagPath = "path" // URL path parameter struct tag TagForm = "form" // Form data struct tag TagHeader = "header" // HTTP header struct tag TagCookie = "cookie" // Cookie struct tag TagXML = "xml" // XML struct tag TagYAML = "yaml" // YAML struct tag TagTOML = "toml" // TOML struct tag TagMsgPack = "msgpack" // MessagePack struct tag TagProto = "proto" // Protocol Buffers struct tag )
Tag name constants for struct tags used in binding.
Variables ¶
var ( ErrUnsupportedContentType = errors.New("unsupported content type") ErrRequestBodyNil = errors.New("request body is nil") ErrOutMustBePointer = errors.New("out must be a pointer to struct") ErrOutPointerNil = errors.New("out pointer is nil") ErrInvalidIPAddress = errors.New("invalid IP address") ErrUnsupportedType = errors.New("unsupported type") ErrInvalidBooleanValue = errors.New("invalid boolean value") ErrEmptyTimeValue = errors.New("empty time value") ErrUnableToParseTime = errors.New("unable to parse time") ErrOnlyMapStringTSupported = errors.New("only map[string]T is supported") ErrInvalidBracketNotation = errors.New("invalid bracket notation in key") ErrMaxDepthExceeded = errors.New("exceeded maximum nesting depth") ErrSliceExceedsMaxLength = errors.New("slice exceeds max length") ErrMapExceedsMaxSize = errors.New("map exceeds max size") ErrInvalidStructTag = errors.New("invalid struct tag") ErrInvalidUUIDFormat = errors.New("invalid UUID format") ErrNoSourcesProvided = errors.New("no binding sources provided") )
Static errors for binding operations.
var DefaultTimeLayouts = []string{ time.RFC3339, time.RFC3339Nano, time.DateOnly, time.DateTime, "2006-01-02T15:04:05", }
DefaultTimeLayouts contains the default time parsing layouts used by binding. These are tried in order when parsing time values from strings. Use WithTimeLayouts to override or extend these defaults.
Example (extending defaults):
binding.WithTimeLayouts(append(binding.DefaultTimeLayouts, "01/02/2006")...)
Functions ¶
func AssertNoBindError ¶ added in v0.2.0
AssertNoBindError asserts that the error is nil. Provides a cleaner failure message than require.NoError for binding contexts.
Example:
err := binding.Raw(getter, binding.TagQuery, ¶ms) binding.AssertNoBindError(t, err)
func Bind ¶
Bind binds from one or more sources specified via From* options.
Example:
req, err := binding.Bind[CreateOrderRequest](
binding.FromPath(pathParams),
binding.FromQuery(r.URL.Query()),
binding.FromJSON(body),
)
Errors:
- ErrNoSourcesProvided: no binding sources provided via From* options
- ErrOutMustBePointer: T is not a struct type
- ErrMaxDepthExceeded: struct nesting exceeds maximum depth
- UnknownFieldError: when WithUnknownFields is UnknownError and unknown fields are present
- BindError: field-level binding errors with detailed context
- MultiError: when WithAllErrors is used and multiple errors occur
Example ¶
ExampleBind demonstrates multi-source binding.
package main
import (
"fmt"
"net/url"
"rivaas.dev/binding"
)
func main() {
type Request struct {
// From path parameters
UserID int `path:"user_id"`
// From query string
Page int `query:"page"`
Limit int `query:"limit"`
}
pathParams := map[string]string{"user_id": "456"}
query := url.Values{}
query.Set("page", "2")
query.Set("limit", "20")
req, err := binding.Bind[Request](
binding.FromPath(pathParams),
binding.FromQuery(query),
)
if err != nil {
_, _ = fmt.Printf("Error: %v\n", err)
return
}
_, _ = fmt.Printf("UserID: %d, Page: %d, Limit: %d\n", req.UserID, req.Page, req.Limit)
}
Output: UserID: 456, Page: 2, Limit: 20
func BindTo ¶ added in v0.2.0
BindTo binds from one or more sources specified via From* options.
Example:
var req CreateOrderRequest
err := binding.BindTo(&req,
binding.FromPath(pathParams),
binding.FromQuery(r.URL.Query()),
binding.FromJSON(body),
)
Example ¶
ExampleBindTo demonstrates non-generic multi-source binding.
package main
import (
"fmt"
"net/url"
"rivaas.dev/binding"
)
func main() {
type Request struct {
UserID int `path:"user_id"`
Page int `query:"page"`
}
pathParams := map[string]string{"user_id": "789"}
query := url.Values{}
query.Set("page", "3")
var req Request
err := binding.BindTo(&req,
binding.FromPath(pathParams),
binding.FromQuery(query),
)
if err != nil {
_, _ = fmt.Printf("Error: %v\n", err)
return
}
_, _ = fmt.Printf("UserID: %d, Page: %d\n", req.UserID, req.Page)
}
Output: UserID: 789, Page: 3
func BindWith ¶ added in v0.2.0
BindWith binds from one or more sources to type T using the Binder's config.
Example:
req, err := binding.BindWith[CreateOrderRequest](binder,
binding.FromPath(pathParams),
binding.FromQuery(r.URL.Query()),
binding.FromJSON(body),
)
func BoolConverter ¶ added in v0.6.0
BoolConverter creates a boolean converter with custom truthy and falsy values. This allows you to accept non-standard boolean representations beyond the default (true/false, yes/no, 1/0, on/off).
Example:
binder := binding.MustNew(
binding.WithConverter(binding.BoolConverter(
[]string{"enabled", "active", "on"}, // truthy values
[]string{"disabled", "inactive", "off"}, // falsy values
)),
)
The comparison is case-insensitive. Empty strings default to false.
func Cookie ¶ added in v0.2.0
Cookie binds cookies to type T.
Example:
session, err := binding.Cookie[SessionData](r.Cookies())
Errors:
- ErrOutMustBePointer: T is not a struct type
- ErrMaxDepthExceeded: struct nesting exceeds maximum depth
- BindError: field-level binding errors with detailed context
func CookieTo ¶ added in v0.2.0
CookieTo binds cookies to out.
Example:
var session SessionData err := binding.CookieTo(r.Cookies(), &session)
func CookieWith ¶ added in v0.2.0
CookieWith binds cookies to type T using the Binder's config.
Example:
session, err := binding.CookieWith[SessionData](binder, r.Cookies())
func DurationConverter ¶ added in v0.6.0
DurationConverter returns a converter for time.Duration with optional aliases. It supports both standard duration strings ("1h30m") and custom aliases.
This is useful for providing user-friendly duration names like "fast", "slow", or "default" that map to specific durations.
Example:
binder := binding.MustNew(
binding.WithConverter(binding.DurationConverter(map[string]time.Duration{
"fast": 100 * time.Millisecond,
"normal": 1 * time.Second,
"slow": 5 * time.Second,
"default": 30 * time.Second,
})),
)
The converter first checks aliases, then falls back to time.ParseDuration.
func EnumConverter ¶ added in v0.6.0
EnumConverter creates a converter that validates string values against a set of allowed values. It returns an error if the value is not in the allowed set.
This is useful for fields with a fixed set of valid values (enums, status codes, etc.).
Example:
type Status string
const (
StatusActive Status = "active"
StatusPending Status = "pending"
StatusDisabled Status = "disabled"
)
binder := binding.MustNew(
binding.WithConverter(binding.EnumConverter(
StatusActive,
StatusPending,
StatusDisabled,
)),
)
The comparison is case-insensitive for better UX.
func Form ¶ added in v0.2.0
Form binds form data to type T.
Example:
data, err := binding.Form[FormData](r.PostForm)
Errors:
- ErrOutMustBePointer: T is not a struct type
- ErrMaxDepthExceeded: struct nesting exceeds maximum depth
- ErrSliceExceedsMaxLength: slice length exceeds maximum
- ErrMapExceedsMaxSize: map size exceeds maximum
- BindError: field-level binding errors with detailed context
func FormTo ¶ added in v0.2.0
FormTo binds form data to out.
Example:
var data FormData err := binding.FormTo(r.PostForm, &data)
func FormWith ¶ added in v0.2.0
FormWith binds form data to type T using the Binder's config.
Example:
data, err := binding.FormWith[FormData](binder, r.PostForm)
func HasStructTag ¶
HasStructTag checks if any field in the struct has the given tag. It recursively checks embedded structs to determine if the tag is present anywhere in the type hierarchy.
This is useful for determining which binding sources should be used when binding from multiple sources with Bind or BindTo.
Parameters:
Returns true if any field (including in embedded structs) has the tag.
Example ¶
ExampleHasStructTag demonstrates checking if a struct has specific tags.
package main
import (
"fmt"
)
func main() {
type UserRequest struct {
ID int `path:"id"`
Name string `query:"name"`
Auth string `header:"Authorization"`
}
// Check at compile time which sources a struct uses
var req UserRequest
_ = req // Use the variable
// In real code, you'd use reflect.TypeOf:
// typ := reflect.TypeOf((*UserRequest)(nil)).Elem()
// hasPath := binding.HasStructTag(typ, binding.TagPath)
_, _ = fmt.Printf("UserRequest has multiple source tags\n")
}
Output: UserRequest has multiple source tags
func Header ¶ added in v0.2.0
Header binds HTTP headers to type T.
Example:
headers, err := binding.Header[RequestHeaders](r.Header)
func HeaderTo ¶ added in v0.2.0
HeaderTo binds HTTP headers to out.
Example:
var headers RequestHeaders err := binding.HeaderTo(r.Header, &headers)
func HeaderWith ¶ added in v0.2.0
HeaderWith binds HTTP headers to type T using the Binder's config.
Example:
headers, err := binding.HeaderWith[RequestHeaders](binder, r.Header)
func JSON ¶ added in v0.2.0
JSON binds JSON bytes to type T.
Example:
user, err := binding.JSON[CreateUserRequest](body)
// With options
user, err := binding.JSON[CreateUserRequest](body,
binding.WithUnknownFields(binding.UnknownError),
)
Errors:
- ErrOutMustBePointer: T is not a struct type
- ErrMaxDepthExceeded: struct nesting exceeds maximum depth
- UnknownFieldError: when WithUnknownFields is UnknownError and unknown fields are present
- BindError: field-level binding errors with detailed context
- MultiError: when WithAllErrors is used and multiple errors occur
Example ¶
ExampleJSON demonstrates binding from JSON body.
package main
import (
"fmt"
"rivaas.dev/binding"
)
func main() {
type User struct {
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
}
body := []byte(`{"name": "Charlie", "email": "[email protected]", "age": 35}`)
user, err := binding.JSON[User](body)
if err != nil {
_, _ = fmt.Printf("Error: %v\n", err)
return
}
_, _ = fmt.Printf("Name: %s, Email: %s, Age: %d\n", user.Name, user.Email, user.Age)
}
Output: Name: Charlie, Email: [email protected], Age: 35
Example (WithUnknownFields) ¶
ExampleJSON_withUnknownFields demonstrates strict JSON binding.
package main
import (
"fmt"
"rivaas.dev/binding"
)
func main() {
type User struct {
Name string `json:"name"`
}
// JSON with unknown field "extra"
body := []byte(`{"name": "Eve", "extra": "ignored"}`)
// Default: unknown fields are ignored
user, err := binding.JSON[User](body)
if err != nil {
_, _ = fmt.Printf("Error: %v\n", err)
return
}
_, _ = fmt.Printf("Name: %s\n", user.Name)
}
Output: Name: Eve
func JSONReader ¶ added in v0.2.0
JSONReader binds JSON from an io.Reader to type T.
Example:
user, err := binding.JSONReader[CreateUserRequest](r.Body)
Errors:
- ErrOutMustBePointer: T is not a struct type
- ErrMaxDepthExceeded: struct nesting exceeds maximum depth
- UnknownFieldError: when WithUnknownFields is UnknownError and unknown fields are present
- BindError: field-level binding errors with detailed context
func JSONReaderTo ¶ added in v0.2.0
JSONReaderTo binds JSON from an io.Reader to out.
Example:
var user CreateUserRequest err := binding.JSONReaderTo(r.Body, &user)
func JSONReaderWith ¶ added in v0.2.0
JSONReaderWith binds JSON from an io.Reader to type T using the Binder's config.
Example:
user, err := binding.JSONReaderWith[CreateUserRequest](binder, r.Body)
func JSONTo ¶ added in v0.2.0
JSONTo binds JSON bytes to out.
Example:
var user CreateUserRequest err := binding.JSONTo(body, &user)
func JSONWith ¶ added in v0.2.0
JSONWith binds JSON bytes to type T using the Binder's config.
Example:
user, err := binding.JSONWith[CreateUserRequest](binder, body)
func MustBind ¶ added in v0.2.0
MustBind is a test helper that binds and fails the test if binding fails. It's useful when binding must succeed for the test to proceed.
Example:
params := binding.MustBind[SearchParams](t, getter, binding.TagQuery)
func MustBindForm ¶ added in v0.2.0
MustBindForm is a test helper for form binding that fails if binding fails.
Example:
data := binding.MustBindForm[FormData](t, url.Values{"username": {"test"}})
func MustBindJSON ¶ added in v0.2.0
MustBindJSON is a test helper for JSON binding that fails if binding fails.
Example:
user := binding.MustBindJSON[User](t, `{"name":"John","age":30}`)
func MustBindQuery ¶ added in v0.2.0
MustBindQuery is a test helper for query binding that fails if binding fails.
Example:
params := binding.MustBindQuery[SearchParams](t, url.Values{"q": {"golang"}})
func MustWarmupCache ¶
func MustWarmupCache(types ...any)
MustWarmupCache is like WarmupCache but panics on invalid types. Use during application startup to validate struct tags at startup.
Example:
func init() {
binding.MustWarmupCache(
UserRequest{},
SearchParams{},
)
}
Parameters:
- types: Variadic list of struct instances to warm up
Panics if any type is invalid or has invalid struct tags.
func Path ¶ added in v0.2.0
Path binds URL path parameters to type T.
Example:
params, err := binding.Path[GetUserParams](pathParams)
Example ¶
ExamplePath demonstrates binding from path parameters.
package main
import (
"fmt"
"rivaas.dev/binding"
)
func main() {
type Params struct {
ID int `path:"id"`
Slug string `path:"slug"`
}
pathParams := map[string]string{
"id": "123",
"slug": "hello-world",
}
params, err := binding.Path[Params](pathParams)
if err != nil {
_, _ = fmt.Printf("Error: %v\n", err)
return
}
_, _ = fmt.Printf("ID: %d, Slug: %s\n", params.ID, params.Slug)
}
Output: ID: 123, Slug: hello-world
func PathTo ¶ added in v0.2.0
PathTo binds URL path parameters to out.
Example:
var params GetUserParams err := binding.PathTo(pathParams, ¶ms)
func PathWith ¶ added in v0.2.0
PathWith binds URL path parameters to type T using the Binder's config.
Example:
params, err := binding.PathWith[GetUserParams](binder, pathParams)
func Query ¶ added in v0.2.0
Query binds URL query parameters to type T.
Example:
params, err := binding.Query[ListParams](r.URL.Query())
Errors:
- ErrOutMustBePointer: T is not a struct type
- ErrMaxDepthExceeded: struct nesting exceeds maximum depth
- ErrSliceExceedsMaxLength: slice length exceeds maximum
- ErrMapExceedsMaxSize: map size exceeds maximum
- BindError: field-level binding errors with detailed context
Example ¶
ExampleQuery demonstrates basic binding from query parameters using generic API.
package main
import (
"fmt"
"net/url"
"rivaas.dev/binding"
)
func main() {
type Params struct {
Name string `query:"name"`
Age int `query:"age"`
Email string `query:"email"`
}
values := url.Values{}
values.Set("name", "Alice")
values.Set("age", "30")
values.Set("email", "[email protected]")
params, err := binding.Query[Params](values)
if err != nil {
_, _ = fmt.Printf("Error: %v\n", err)
return
}
_, _ = fmt.Printf("Name: %s, Age: %d, Email: %s\n", params.Name, params.Age, params.Email)
}
Output: Name: Alice, Age: 30, Email: [email protected]
Example (WithDefaults) ¶
ExampleQuery_withDefaults demonstrates binding with default values.
package main
import (
"fmt"
"net/url"
"rivaas.dev/binding"
)
func main() {
type Config struct {
Port int `query:"port" default:"8080"`
Host string `query:"host" default:"localhost"`
Debug bool `query:"debug" default:"false"`
LogLevel string `query:"log_level" default:"info"`
}
// Empty query string - defaults will be applied
values := url.Values{}
config, err := binding.Query[Config](values)
if err != nil {
_, _ = fmt.Printf("Error: %v\n", err)
return
}
_, _ = fmt.Printf("Port: %d, Host: %s, Debug: %v, LogLevel: %s\n", config.Port, config.Host, config.Debug, config.LogLevel)
}
Output: Port: 8080, Host: localhost, Debug: false, LogLevel: info
Example (WithOptions) ¶
ExampleQuery_withOptions demonstrates binding with custom options.
package main
import (
"fmt"
"net/url"
"rivaas.dev/binding"
)
func main() {
type Params struct {
Tags []string `query:"tags"`
}
values := url.Values{}
values.Set("tags", "go,rust,python")
// Use CSV mode for comma-separated values
params, err := binding.Query[Params](values,
binding.WithSliceMode(binding.SliceCSV),
)
if err != nil {
_, _ = fmt.Printf("Error: %v\n", err)
return
}
_, _ = fmt.Printf("Tags: %v\n", params.Tags)
}
Output: Tags: [go rust python]
func QueryTo ¶ added in v0.2.0
QueryTo binds URL query parameters to out.
Example:
var params ListParams err := binding.QueryTo(r.URL.Query(), ¶ms)
Example ¶
ExampleQueryTo demonstrates non-generic query binding.
package main
import (
"fmt"
"net/url"
"rivaas.dev/binding"
)
func main() {
type Params struct {
Name string `query:"name"`
Age int `query:"age"`
Email string `query:"email"`
}
values := url.Values{}
values.Set("name", "Bob")
values.Set("age", "25")
values.Set("email", "[email protected]")
var params Params
err := binding.QueryTo(values, ¶ms)
if err != nil {
_, _ = fmt.Printf("Error: %v\n", err)
return
}
_, _ = fmt.Printf("Name: %s, Age: %d, Email: %s\n", params.Name, params.Age, params.Email)
}
Output: Name: Bob, Age: 25, Email: [email protected]
func QueryWith ¶ added in v0.2.0
QueryWith binds URL query parameters to type T using the Binder's config.
Example:
params, err := binding.QueryWith[ListParams](binder, r.URL.Query())
func Raw ¶ added in v0.2.0
func Raw(getter ValueGetter, tag string, out any, opts ...Option) error
Raw binds values from a ValueGetter to out using the specified tag. This is the low-level binding function for custom sources.
For built-in sources, prefer the type-safe functions: Query, Path, Form, etc.
Example:
customGetter := &MyCustomGetter{...}
err := binding.Raw(customGetter, "custom", &result)
Errors:
- ErrOutMustBePointer: out is not a pointer to struct
- ErrMaxDepthExceeded: struct nesting exceeds maximum depth
- BindError: field-level binding errors with detailed context
func RawInto ¶ added in v0.2.0
func RawInto[T any](getter ValueGetter, tag string, opts ...Option) (T, error)
RawInto binds values from a ValueGetter to type T using the specified tag. This is the generic low-level binding function for custom sources.
Example:
result, err := binding.RawInto[MyType](customGetter, "custom")
Errors:
- ErrOutMustBePointer: T is not a struct type
- ErrMaxDepthExceeded: struct nesting exceeds maximum depth
- BindError: field-level binding errors with detailed context
func TimeConverter ¶ added in v0.6.0
TimeConverter returns a converter for time.Time with custom layouts. The converter tries each layout in order until one succeeds.
This is useful when you need to support time formats beyond the default layouts (RFC3339, DateOnly, DateTime, etc.).
Example:
binder := binding.MustNew(
binding.WithConverter(binding.TimeConverter(
"01/02/2006", // US format
"02/01/2006", // European format
"2006-01-02 15:04", // Custom datetime
)),
)
func WarmupCache ¶
func WarmupCache(types ...any)
WarmupCache pre-parses struct types to populate the type cache. Call this during application startup after defining your structs to populate the cache for known request types.
Invalid types are silently skipped. Use MustWarmupCache to panic on errors.
Example:
type UserRequest struct { ... }
type SearchParams struct { ... }
binding.WarmupCache(
UserRequest{},
SearchParams{},
)
Parameters:
- types: Variadic list of struct instances to warm up
func XML ¶ added in v0.2.0
XML binds XML bytes to type T.
Example:
user, err := binding.XML[CreateUserRequest](body)
Errors:
- ErrOutMustBePointer: T is not a struct type
- ErrMaxDepthExceeded: struct nesting exceeds maximum depth
- BindError: field-level binding errors with detailed context
func XMLReader ¶ added in v0.2.0
XMLReader binds XML from an io.Reader to type T.
Example:
user, err := binding.XMLReader[CreateUserRequest](r.Body)
Errors:
- ErrOutMustBePointer: T is not a struct type
- ErrMaxDepthExceeded: struct nesting exceeds maximum depth
- BindError: field-level binding errors with detailed context
func XMLReaderTo ¶ added in v0.2.0
XMLReaderTo binds XML from an io.Reader to out.
Example:
var user CreateUserRequest err := binding.XMLReaderTo(r.Body, &user)
func XMLReaderWith ¶ added in v0.2.0
XMLReaderWith binds XML from an io.Reader to type T using the Binder's config.
Example:
user, err := binding.XMLReaderWith[CreateUserRequest](binder, r.Body)
Types ¶
type BindError ¶
type BindError struct {
Field string // Field name that failed binding
Source Source // Binding source (typed)
Value string // The value that failed conversion
Type reflect.Type // Expected Go type
Reason string // Human-readable reason for failure
Err error // Underlying error
}
BindError represents a binding error with field-level context. It provides detailed information about which field failed, what value was provided, and what type was expected.
Use errors.As to check for BindError:
var bindErr *BindError
if errors.As(err, &bindErr) {
fmt.Printf("Field: %s, Source: %s\n", bindErr.Field, bindErr.Source)
}
func AssertBindError ¶ added in v0.2.0
AssertBindError checks if an error is a BindError with the expected field name. Returns the BindError if found, fails the test otherwise.
Example:
err := binding.Raw(getter, binding.TagQuery, ¶ms) bindErr := binding.AssertBindError(t, err, "Age") assert.Equal(t, binding.SourceQuery, bindErr.Source)
func (*BindError) HTTPStatus ¶
HTTPStatus implements rivaas.dev/errors.ErrorType.
type Binder ¶ added in v0.2.0
type Binder struct {
// contains filtered or unexported fields
}
Binder provides request data binding with configurable options.
Use New or MustNew to create a configured Binder, or use package-level functions (Query, JSON, etc.) for zero-configuration binding.
Binder is safe for concurrent use by multiple goroutines.
Note: Due to Go language limitations, generic methods are not supported. Use the generic helper functions QueryWith, JSONWith, etc. for generic binding with a Binder, or use the non-generic methods directly.
Example:
binder := binding.MustNew(
binding.WithConverter[uuid.UUID](uuid.Parse),
binding.WithTimeLayouts("2006-01-02"),
)
// Generic usage with helper function
user, err := binding.JSONWith[CreateUserRequest](binder, body)
// Non-generic usage
var user CreateUserRequest
err := binder.JSONTo(body, &user)
func MustNew ¶ added in v0.2.0
MustNew creates a Binder with the given options. Panics if configuration is invalid.
Use in main() or init() where panic on startup is acceptable.
Example:
binder := binding.MustNew(
binding.WithConverter[uuid.UUID](uuid.Parse),
binding.WithTimeLayouts("2006-01-02"),
)
Example ¶
ExampleMustNew demonstrates creating a reusable Binder.
package main
import (
"fmt"
"rivaas.dev/binding"
)
func main() {
type User struct {
Name string `json:"name"`
Email string `json:"email"`
}
// Create a configured binder
binder := binding.MustNew(
binding.WithMaxDepth(16),
)
body := []byte(`{"name": "Diana", "email": "[email protected]"}`)
// Use generic helper function with binder
user, err := binding.JSONWith[User](binder, body)
if err != nil {
_, _ = fmt.Printf("Error: %v\n", err)
return
}
_, _ = fmt.Printf("Name: %s, Email: %s\n", user.Name, user.Email)
}
Output: Name: Diana, Email: [email protected]
func New ¶ added in v0.2.0
New creates a Binder with the given options. Returns an error if configuration is invalid.
Example:
binder, err := binding.New(
binding.WithMaxDepth(16),
)
if err != nil {
return fmt.Errorf("failed to create binder: %w", err)
}
func TestBinder ¶ added in v0.2.0
TestBinder creates a Binder configured for testing. It uses sensible defaults that are appropriate for test scenarios.
Example:
func TestMyFeature(t *testing.T) {
binder := binding.TestBinder(t)
// use binder in test
}
func (*Binder) BindTo ¶ added in v0.2.0
BindTo binds from one or more sources specified via From* options.
Example:
var req CreateOrderRequest
err := binder.BindTo(&req,
binding.FromPath(pathParams),
binding.FromQuery(r.URL.Query()),
binding.FromJSON(body),
)
func (*Binder) CookieTo ¶ added in v0.2.0
CookieTo binds cookies to out.
Example:
var session SessionData err := binder.CookieTo(r.Cookies(), &session)
func (*Binder) FormTo ¶ added in v0.2.0
FormTo binds form data to out.
Example:
var data FormData err := binder.FormTo(r.PostForm, &data)
func (*Binder) HeaderTo ¶ added in v0.2.0
HeaderTo binds HTTP headers to out.
Example:
var headers RequestHeaders err := binder.HeaderTo(r.Header, &headers)
func (*Binder) JSONReaderTo ¶ added in v0.2.0
JSONReaderTo binds JSON from an io.Reader to out.
Example:
var user CreateUserRequest err := binder.JSONReaderTo(r.Body, &user)
func (*Binder) JSONTo ¶ added in v0.2.0
JSONTo binds JSON bytes to out.
Example:
var user CreateUserRequest err := binder.JSONTo(body, &user)
func (*Binder) PathTo ¶ added in v0.2.0
PathTo binds URL path parameters to out.
Example:
var params GetUserParams err := binder.PathTo(pathParams, ¶ms)
func (*Binder) QueryTo ¶ added in v0.2.0
QueryTo binds URL query parameters to out.
Example:
var params ListParams err := binder.QueryTo(r.URL.Query(), ¶ms)
func (*Binder) XMLReaderTo ¶ added in v0.2.0
XMLReaderTo binds XML from an io.Reader to out.
Example:
var user CreateUserRequest err := binder.XMLReaderTo(r.Body, &user)
type CookieGetter ¶
type CookieGetter struct {
// contains filtered or unexported fields
}
CookieGetter implements ValueGetter for HTTP cookies. Cookie names are case-sensitive per HTTP standard.
func NewCookieGetter ¶
func NewCookieGetter(c []*http.Cookie) *CookieGetter
NewCookieGetter creates a CookieGetter from a slice of HTTP cookies.
Example:
getter := binding.NewCookieGetter(r.Cookies()) err := binding.Raw(getter, "cookie", &result)
func (*CookieGetter) Get ¶
func (cg *CookieGetter) Get(key string) string
Get returns the first cookie value for the key. Cookie values are automatically URL-unescaped. If unescaping fails, the raw cookie value is returned.
func (*CookieGetter) GetAll ¶
func (cg *CookieGetter) GetAll(key string) []string
GetAll returns all cookie values for the key.
func (*CookieGetter) Has ¶
func (cg *CookieGetter) Has(key string) bool
Has returns whether the key exists.
type Events ¶
type Events struct {
// FieldBound is called after successfully binding a field.
// name: struct field name, fromTag: source tag (query, json, etc.)
FieldBound func(name, fromTag string)
// UnknownField is called when an unknown field is encountered.
// Only triggered when UnknownFieldPolicy is UnknownWarn or UnknownError.
// path: dot-separated field path (e.g., "user.address.unknown")
UnknownField func(path string)
// Done is called at the end of binding with statistics.
// Always called, even on error (use defer).
Done func(stats Stats)
}
Events provides hooks for observability without coupling.
type FormGetter ¶
type FormGetter struct {
// contains filtered or unexported fields
}
FormGetter implements ValueGetter for form data.
func NewFormGetter ¶
func NewFormGetter(v url.Values) *FormGetter
NewFormGetter creates a FormGetter from url.Values.
Example:
getter := binding.NewFormGetter(r.PostForm) err := binding.Raw(getter, "form", &result)
func (*FormGetter) ApproxLen ¶
func (f *FormGetter) ApproxLen(prefix string) int
ApproxLen estimates the number of keys starting with the given prefix. It checks both dot notation (prefix.) and bracket notation (prefix[).
func (*FormGetter) Get ¶
func (f *FormGetter) Get(key string) string
Get returns the first value for the key.
func (*FormGetter) GetAll ¶
func (f *FormGetter) GetAll(key string) []string
GetAll returns all values for the key. It supports both repeated key patterns ("ids=1&ids=2") and bracket notation ("ids[]=1&ids[]=2").
func (*FormGetter) Has ¶
func (f *FormGetter) Has(key string) bool
Has returns whether the key exists.
type GetterFunc ¶
GetterFunc is a function adapter that implements ValueGetter. It allows using a function directly as a ValueGetter without creating a custom type.
Example:
getter := binding.GetterFunc(func(key string) ([]string, bool) {
if val, ok := myMap[key]; ok {
return []string{val}, true
}
return nil, false
})
err := binding.Raw(getter, "custom", &result)
func (GetterFunc) Get ¶
func (f GetterFunc) Get(key string) string
Get returns the first value for the key.
func (GetterFunc) GetAll ¶
func (f GetterFunc) GetAll(key string) []string
GetAll returns all values for the key.
func (GetterFunc) Has ¶
func (f GetterFunc) Has(key string) bool
Has returns whether the key exists.
type HeaderGetter ¶
type HeaderGetter struct {
// contains filtered or unexported fields
}
HeaderGetter implements ValueGetter for HTTP headers. Headers are case-insensitive per HTTP standard, and keys are canonicalized using http.CanonicalHeaderKey.
func NewHeaderGetter ¶
func NewHeaderGetter(h http.Header) *HeaderGetter
NewHeaderGetter creates a HeaderGetter from http.Header. Header keys are normalized to canonical MIME header format for consistent lookups.
Example:
getter := binding.NewHeaderGetter(r.Header) err := binding.Raw(getter, "header", &result)
func (*HeaderGetter) Get ¶
func (h *HeaderGetter) Get(key string) string
Get returns the first header value for the key. Lookups are case-insensitive and use canonical header key format.
func (*HeaderGetter) GetAll ¶
func (h *HeaderGetter) GetAll(key string) []string
GetAll returns all header values for the key.
func (*HeaderGetter) Has ¶
func (h *HeaderGetter) Has(key string) bool
Has returns whether the key exists.
type KeyNormalizer ¶
KeyNormalizer transforms keys before lookup. Common uses include case-folding and canonicalization.
var ( // CanonicalMIME normalizes HTTP header keys (Content-Type -> Content-Type) CanonicalMIME KeyNormalizer = http.CanonicalHeaderKey // LowerCase converts keys to lowercase (case-insensitive matching) LowerCase KeyNormalizer = strings.ToLower )
Common normalizers
type Metadata ¶
type Metadata struct {
BodyRead bool // Whether the request body has been read
RawBody []byte // Cached raw body bytes
}
Metadata tracks binding state for framework integration. It is used by router.Context to cache body reads and presence maps.
type MultiError ¶
type MultiError struct {
Errors []*BindError
}
MultiError aggregates multiple binding errors. It is returned when WithAllErrors is used and multiple fields fail binding.
Use errors.As to check for MultiError:
var multi *MultiError
if errors.As(err, &multi) {
for _, e := range multi.Errors {
// Handle each error
}
}
func (*MultiError) Add ¶ added in v0.2.0
func (m *MultiError) Add(err *BindError)
Add appends an error to the MultiError.
func (*MultiError) Code ¶
func (m *MultiError) Code() string
Code implements rivaas.dev/errors.ErrorCode.
func (*MultiError) Details ¶
func (m *MultiError) Details() any
Details implements rivaas.dev/errors.ErrorDetails.
func (*MultiError) Error ¶
func (m *MultiError) Error() string
Error returns a formatted error message.
func (*MultiError) ErrorOrNil ¶ added in v0.2.0
func (m *MultiError) ErrorOrNil() error
ErrorOrNil returns nil if there are no errors, otherwise returns the MultiError.
func (*MultiError) HTTPStatus ¶
func (m *MultiError) HTTPStatus() int
HTTPStatus implements rivaas.dev/errors.ErrorType.
func (*MultiError) HasErrors ¶ added in v0.2.0
func (m *MultiError) HasErrors() bool
HasErrors returns true if there are any errors.
func (*MultiError) Unwrap ¶
func (m *MultiError) Unwrap() []error
Unwrap returns all errors for errors.Is/As compatibility.
type Option ¶
type Option func(*config)
Option configures binding behavior.
func FromCookie ¶ added in v0.2.0
FromCookie specifies cookies as a binding source for Bind or BindTo.
Example:
req, err := binding.Bind[Request](
binding.FromCookie(r.Cookies()),
)
func FromForm ¶ added in v0.2.0
FromForm specifies form data as a binding source for Bind or BindTo.
Example:
req, err := binding.Bind[Request](
binding.FromForm(r.PostForm),
)
func FromGetter ¶ added in v0.2.0
func FromGetter(getter ValueGetter, tag string) Option
FromGetter specifies a custom ValueGetter as a binding source. Use this for custom binding sources not covered by the built-in options.
Example:
customGetter := &MyCustomGetter{...}
req, err := binding.Bind[Request](
binding.FromGetter(customGetter, "custom"),
)
func FromHeader ¶ added in v0.2.0
FromHeader specifies HTTP headers as a binding source.
Example:
req, err := binding.Bind[Request](
binding.FromHeader(r.Header),
)
func FromJSON ¶ added in v0.2.0
FromJSON specifies JSON body as a binding source for Bind or BindTo. Note: JSON binding is handled separately from other sources.
Example:
req, err := binding.Bind[Request](
binding.FromQuery(r.URL.Query()),
binding.FromJSON(body),
)
func FromJSONReader ¶ added in v0.2.0
FromJSONReader specifies JSON from io.Reader as a binding source.
Example:
req, err := binding.Bind[Request](
binding.FromJSONReader(r.Body),
)
func FromPath ¶ added in v0.2.0
FromPath specifies path parameters as a binding source.
Example:
req, err := binding.Bind[Request](
binding.FromPath(pathParams),
)
func FromQuery ¶ added in v0.2.0
FromQuery specifies query parameters as a binding source for Bind or BindTo.
Example:
req, err := binding.Bind[Request](
binding.FromQuery(r.URL.Query()),
binding.FromPath(pathParams),
)
func FromXML ¶ added in v0.2.0
FromXML specifies XML body as a binding source.
Example:
req, err := binding.Bind[Request](
binding.FromQuery(r.URL.Query()),
binding.FromXML(body),
)
func FromXMLReader ¶ added in v0.2.0
FromXMLReader specifies XML from io.Reader as a binding source.
Example:
req, err := binding.Bind[Request](
binding.FromXMLReader(r.Body),
)
func WithAllErrors ¶ added in v0.2.0
func WithAllErrors() Option
WithAllErrors collects all binding errors instead of returning on first. When enabled, returns *MultiError containing all field errors.
Example:
user, err := binding.JSON[User](body, binding.WithAllErrors())
if err != nil {
var multi *binding.MultiError
if errors.As(err, &multi) {
for _, e := range multi.Errors {
// Handle each error
}
}
}
func WithConverter ¶ added in v0.2.0
WithConverter registers a custom type converter. Type-safe registration using generics.
Example:
binding.MustNew(
binding.WithConverter[uuid.UUID](uuid.Parse),
binding.WithConverter[decimal.Decimal](decimal.NewFromString),
)
func WithEvents ¶
WithEvents sets observability hooks.
Example:
binding.MustNew(binding.WithEvents(binding.Events{
FieldBound: func(name, tag string) {
log.Printf("Bound field %s from %s", name, tag)
},
Done: func(stats binding.Stats) {
log.Printf("Binding complete: %d fields", stats.FieldsBound)
},
}))
func WithIntBaseAuto ¶
func WithIntBaseAuto() Option
WithIntBaseAuto enables auto-detection of integer bases from prefixes. When enabled, recognizes 0x (hex), 0 (octal), and 0b (binary) prefixes.
Example:
binding.Query[T](values, binding.WithIntBaseAuto())
func WithJSONUseNumber ¶
func WithJSONUseNumber() Option
WithJSONUseNumber configures the JSON decoder to use json.Number instead of float64. This preserves numeric precision for large integers that would otherwise be represented as floats.
Example:
binding.JSON[T](body, binding.WithJSONUseNumber())
func WithKeyNormalizer ¶
func WithKeyNormalizer(normalizer KeyNormalizer) Option
WithKeyNormalizer sets a custom key normalization function.
Example:
binding.Header[T](h, binding.WithKeyNormalizer(binding.CanonicalMIME))
func WithMaxDepth ¶
WithMaxDepth sets the maximum nesting depth for structs and maps. When exceeded, binding returns ErrMaxDepthExceeded. The default is DefaultMaxDepth (32).
Example:
binding.JSON[T](body, binding.WithMaxDepth(16))
func WithMaxMapSize ¶
WithMaxMapSize sets the maximum number of map entries per field. When exceeded, binding returns ErrMapExceedsMaxSize. The default is DefaultMaxMapSize (1000). Set to 0 to disable the limit.
Example:
binding.Query[T](values, binding.WithMaxMapSize(500))
func WithMaxSliceLen ¶
WithMaxSliceLen sets the maximum number of slice elements per field. When exceeded, binding returns ErrSliceExceedsMaxLength. The default is DefaultMaxSliceLen (10,000). Set to 0 to disable the limit.
Example:
binding.Query[T](values, binding.WithMaxSliceLen(1000))
func WithSliceMode ¶ added in v0.2.0
func WithSliceMode(mode SliceParseMode) Option
WithSliceMode sets how slice values are parsed from query/form data. SliceRepeat (default) expects repeated keys: ?tags=a&tags=b&tags=c SliceCSV expects comma-separated values: ?tags=a,b,c
Example:
binding.Query[T](values, binding.WithSliceMode(binding.SliceCSV))
func WithStrictJSON ¶ added in v0.5.0
func WithStrictJSON() Option
WithStrictJSON is a convenience option that fails binding when unknown JSON fields are present. Equivalent to WithUnknownFields(UnknownError).
Use this in production APIs that want to reject unexpected input fields.
Example:
user, err := binding.JSON[User](body, binding.WithStrictJSON())
func WithTimeLayouts ¶
WithTimeLayouts sets custom time parsing layouts. Default layouts are tried first, then custom layouts are attempted. Layouts use Go's time format reference time: Mon Jan 2 15:04:05 MST 2006.
Example:
binding.Query[T](values,
binding.WithTimeLayouts("2006-01-02", "01/02/2006"),
)
func WithTypeConverter ¶
func WithTypeConverter(targetType reflect.Type, converter TypeConverter) Option
WithTypeConverter registers a custom converter using reflect.Type. Use WithConverter[T] for type-safe registration when possible.
Example:
binding.MustNew(
binding.WithTypeConverter(
reflect.TypeFor[uuid.UUID](),
func(s string) (any, error) { return uuid.Parse(s) },
),
)
func WithUnknownFields ¶ added in v0.2.0
func WithUnknownFields(policy UnknownFieldPolicy) Option
WithUnknownFields sets how to handle unknown JSON fields. See UnknownFieldPolicy for available policies.
Example:
binding.JSON[T](body, binding.WithUnknownFields(binding.UnknownError))
func WithXMLStrict ¶ added in v0.2.0
func WithXMLStrict() Option
WithXMLStrict enables strict XML parsing mode. When enabled, the XML decoder will be more strict about element/attribute names.
Example:
binding.XML[T](body, binding.WithXMLStrict())
type PathGetter ¶ added in v0.2.0
type PathGetter struct {
// contains filtered or unexported fields
}
PathGetter implements ValueGetter for URL path parameters.
func NewPathGetter ¶ added in v0.2.0
func NewPathGetter(p map[string]string) *PathGetter
NewPathGetter creates a PathGetter from a map of path parameters.
Example:
getter := binding.NewPathGetter(map[string]string{"id": "123"})
func (*PathGetter) Get ¶ added in v0.2.0
func (p *PathGetter) Get(key string) string
Get returns the value for the key.
func (*PathGetter) GetAll ¶ added in v0.2.0
func (p *PathGetter) GetAll(key string) []string
GetAll returns all values for the key as a slice. Path parameters are single-valued, so this returns a slice with one element if the key exists.
func (*PathGetter) Has ¶ added in v0.2.0
func (p *PathGetter) Has(key string) bool
Has returns whether the key exists.
type QueryGetter ¶
type QueryGetter struct {
// contains filtered or unexported fields
}
QueryGetter implements ValueGetter for URL query parameters.
func NewQueryGetter ¶
func NewQueryGetter(v url.Values) *QueryGetter
NewQueryGetter creates a QueryGetter from url.Values.
Example:
getter := binding.NewQueryGetter(r.URL.Query()) err := binding.Raw(getter, "query", &result)
func (*QueryGetter) ApproxLen ¶
func (q *QueryGetter) ApproxLen(prefix string) int
ApproxLen estimates the number of keys starting with the given prefix. It checks both dot notation (prefix.) and bracket notation (prefix[).
func (*QueryGetter) Get ¶
func (q *QueryGetter) Get(key string) string
Get returns the first value for the key.
func (*QueryGetter) GetAll ¶
func (q *QueryGetter) GetAll(key string) []string
GetAll returns all values for the key. It supports both repeated key patterns ("ids=1&ids=2") and bracket notation ("ids[]=1&ids[]=2").
func (*QueryGetter) Has ¶
func (q *QueryGetter) Has(key string) bool
Has returns whether the key exists.
type SliceParseMode ¶
type SliceParseMode int
SliceParseMode defines how slice values are parsed from query/form data.
const ( SliceRepeat SliceParseMode = iota // ?tags=a&tags=b&tags=c (default) SliceCSV // ?tags=a,b,c )
type Source ¶ added in v0.2.0
type Source int
Source represents the binding source type.
const ( // SourceUnknown is an unspecified source. SourceUnknown Source = iota // SourceQuery represents URL query parameters. SourceQuery // SourcePath represents URL path parameters. SourcePath // SourceForm represents form data. SourceForm // SourceHeader represents HTTP headers. SourceHeader // SourceCookie represents HTTP cookies. SourceCookie // SourceJSON represents JSON body. SourceJSON // SourceXML represents XML body. SourceXML // SourceYAML represents YAML body. SourceYAML // SourceTOML represents TOML body. SourceTOML // SourceMsgPack represents MessagePack body. SourceMsgPack // SourceProto represents Protocol Buffers body. SourceProto )
type Stats ¶
type Stats struct {
FieldsProcessed int // Total fields attempted
FieldsBound int // Successfully bound fields
ErrorsEncountered int // Errors hit during binding
Duration time.Duration // Total binding time (if tracked externally)
}
Stats tracks binding operation metrics.
type TestValidator ¶ added in v0.2.0
TestValidator is a mock validator for testing validation integration.
func AlwaysFailValidator ¶ added in v0.2.0
func AlwaysFailValidator(msg string) *TestValidator
AlwaysFailValidator returns a validator that always returns an error. Useful for testing error handling paths.
Example:
validator := binding.AlwaysFailValidator("validation failed")
func NeverFailValidator ¶ added in v0.2.0
func NeverFailValidator() *TestValidator
NeverFailValidator returns a validator that never returns an error.
Example:
validator := binding.NeverFailValidator()
func NewTestValidator ¶ added in v0.2.0
func NewTestValidator(fn func(v any) error) *TestValidator
NewTestValidator creates a TestValidator with the given validation function.
Example:
validator := binding.NewTestValidator(func(v any) error {
user, ok := v.(*User)
if !ok {
return nil
}
if user.Age < 0 {
return errors.New("age must be non-negative")
}
return nil
})
func (*TestValidator) Validate ¶ added in v0.2.0
func (tv *TestValidator) Validate(v any) error
Validate implements the Validator interface.
type TypeConverter ¶
TypeConverter converts a string value to a custom type. Registered converters are checked before built-in type handling. If a converter returns an error, binding fails for that field.
type UnknownFieldError ¶
type UnknownFieldError struct {
Fields []string // List of unknown field names
}
UnknownFieldError is returned when strict JSON decoding encounters unknown fields. It contains the list of field names that were present in the JSON but not defined in the target struct.
func (*UnknownFieldError) Code ¶ added in v0.2.0
func (e *UnknownFieldError) Code() string
Code implements rivaas.dev/errors.ErrorCode.
func (*UnknownFieldError) Error ¶
func (e *UnknownFieldError) Error() string
Error returns a formatted error message.
func (*UnknownFieldError) HTTPStatus ¶ added in v0.2.0
func (e *UnknownFieldError) HTTPStatus() int
HTTPStatus implements rivaas.dev/errors.ErrorType.
type UnknownFieldPolicy ¶
type UnknownFieldPolicy int
UnknownFieldPolicy defines how to handle unknown fields during JSON decoding.
const ( // UnknownIgnore silently ignores unknown JSON fields. // This is the default policy. UnknownIgnore UnknownFieldPolicy = iota // UnknownWarn emits warnings via Events.UnknownField but continues binding. // It uses two-pass parsing to detect unknown fields at all nesting levels. // Recommended for development and testing environments. UnknownWarn // UnknownError returns an error on the first unknown field. // It uses json.Decoder.DisallowUnknownFields for strict validation. UnknownError )
type ValueGetter ¶
type ValueGetter interface {
// Get returns the first value for the given key, or an empty string if not present.
Get(key string) string
// GetAll returns all values for the given key, or nil if not present.
GetAll(key string) []string
// Has returns true if the key is present, even if its value is empty.
// This distinguishes "key present with empty value" from "key not present".
Has(key string) bool
}
ValueGetter abstracts different sources of input values for binding.
Implementers must distinguish between "key present with empty value" and "key not present". For example:
- Query string "?name=" → Has("name") = true, Get("name") = ""
- Query string "?foo=bar" → Has("name") = false
This distinction enables proper partial update semantics and default value application. The Has method should return true if the key exists in the source, even if its value is empty.
ValueGetter is the low-level interface for custom binding sources. For built-in sources, use the type-safe functions: Query, Path, Form, etc. Use Raw or RawInto to bind from a custom ValueGetter implementation.
func MapGetter ¶ added in v0.5.0
func MapGetter(m map[string]string) ValueGetter
MapGetter creates a ValueGetter from a simple map[string]string. This is a convenience function for custom binding sources.
Example:
data := map[string]string{"name": "Alice", "age": "30"}
getter := binding.MapGetter(data)
result, err := binding.RawInto[User](getter, "custom")
func MultiMapGetter ¶ added in v0.5.0
func MultiMapGetter(m map[string][]string) ValueGetter
MultiMapGetter creates a ValueGetter from a map[string][]string. This supports multiple values per key, useful for repeated parameters.
Example:
data := map[string][]string{"tags": {"go", "rust"}, "name": {"Alice"}}
getter := binding.MultiMapGetter(data)
result, err := binding.RawInto[User](getter, "custom")
func TestCookieGetter ¶ added in v0.2.0
func TestCookieGetter(t *testing.T, pairs ...string) ValueGetter
TestCookieGetter creates a CookieGetter from key-value pairs for testing.
Example:
getter := binding.TestCookieGetter(t, "session_id", "abc123", "theme", "dark")
func TestFormGetter ¶ added in v0.2.0
func TestFormGetter(t *testing.T, pairs ...string) ValueGetter
TestFormGetter creates a FormGetter from key-value pairs for testing.
Example:
getter := binding.TestFormGetter(t, "username", "testuser", "password", "secret")
func TestHeaderGetter ¶ added in v0.2.0
func TestHeaderGetter(t *testing.T, pairs ...string) ValueGetter
TestHeaderGetter creates a HeaderGetter from key-value pairs for testing.
Example:
getter := binding.TestHeaderGetter(t, "Authorization", "Bearer token", "X-Request-ID", "123")
func TestPathGetter ¶ added in v0.2.0
func TestPathGetter(t *testing.T, pairs ...string) ValueGetter
TestPathGetter creates a PathGetter from key-value pairs for testing.
Example:
getter := binding.TestPathGetter(t, "user_id", "123", "slug", "hello-world")
func TestQueryGetter ¶ added in v0.2.0
func TestQueryGetter(t *testing.T, pairs ...string) ValueGetter
TestQueryGetter creates a QueryGetter from key-value pairs for testing. It provides a more convenient way to create query parameters in tests.
Example:
getter := binding.TestQueryGetter(t, "name", "John", "age", "30")
func TestQueryGetterMulti ¶ added in v0.2.0
func TestQueryGetterMulti(t *testing.T, values map[string][]string) ValueGetter
TestQueryGetterMulti creates a QueryGetter that supports multiple values per key. Useful for testing slice bindings.
Example:
getter := binding.TestQueryGetterMulti(t, map[string][]string{
"tags": {"go", "rust", "python"},
"page": {"1"},
})
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package msgpack provides MessagePack binding support for the binding package.
|
Package msgpack provides MessagePack binding support for the binding package. |
|
Package proto provides Protocol Buffers binding support for the binding package.
|
Package proto provides Protocol Buffers binding support for the binding package. |
|
Package toml provides TOML binding support for the binding package.
|
Package toml provides TOML binding support for the binding package. |
|
Package yaml provides YAML binding support for the binding package.
|
Package yaml provides YAML binding support for the binding package. |