binding

package module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 18, 2025 License: Apache-2.0 Imports: 20 Imported by: 0

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, enum validation, and type conversion.

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.WithRequired(),
    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"),
    binding.WithRequired(),
)

// 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" required:"true"`
    Email  string `json:"email" required:"true"`

    // Enum validation
    Status string `json:"status" enum:"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
  • enum:"a,b,c": Validate value is one of the allowed values
  • required:"true": Field must be present (when WithRequired() is used)

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),
)

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
        }
    }
}

Validation Integration

Integrate external validators:

binder := binding.MustNew(
    binding.WithValidator(myValidator),
)

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),
)

Index

Examples

Constants

View Source
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.

View Source
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

View Source
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")
	ErrValueNotInAllowedValues = errors.New("value not in allowed values")
	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")
	ErrRequiredField           = errors.New("required field is missing")
	ErrNoSourcesProvided       = errors.New("no binding sources provided")
)

Static errors for binding operations.

Functions

func AssertNoBindError added in v0.2.0

func AssertNoBindError(t *testing.T, err error)

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, &params)
binding.AssertNoBindError(t, err)

func Bind

func Bind[T any](opts ...Option) (T, error)

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),
    binding.WithRequired(),
)

Errors:

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

func BindTo(out any, opts ...Option) error

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

func BindWith[T any](b *Binder, opts ...Option) (T, error)

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 Cookie[T any](cookies []*http.Cookie, opts ...Option) (T, error)

Cookie binds cookies to type T.

Example:

session, err := binding.Cookie[SessionData](r.Cookies())

Errors:

func CookieTo added in v0.2.0

func CookieTo(cookies []*http.Cookie, out any, opts ...Option) error

CookieTo binds cookies to out.

Example:

var session SessionData
err := binding.CookieTo(r.Cookies(), &session)

func CookieWith added in v0.2.0

func CookieWith[T any](b *Binder, cookies []*http.Cookie) (T, error)

CookieWith binds cookies to type T using the Binder's config.

Example:

session, err := binding.CookieWith[SessionData](binder, r.Cookies())

func Form added in v0.2.0

func Form[T any](values url.Values, opts ...Option) (T, error)

Form binds form data to type T.

Example:

data, err := binding.Form[FormData](r.PostForm)

Errors:

func FormTo added in v0.2.0

func FormTo(values url.Values, out any, opts ...Option) error

FormTo binds form data to out.

Example:

var data FormData
err := binding.FormTo(r.PostForm, &data)

func FormWith added in v0.2.0

func FormWith[T any](b *Binder, values url.Values) (T, error)

FormWith binds form data to type T using the Binder's config.

Example:

data, err := binding.FormWith[FormData](binder, r.PostForm)

func HasStructTag

func HasStructTag(t reflect.Type, tag string) bool

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:

  • t: Struct type to check
  • tag: Tag name to search for (e.g., TagJSON, TagQuery)

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[T any](h http.Header, opts ...Option) (T, error)

Header binds HTTP headers to type T.

Example:

headers, err := binding.Header[RequestHeaders](r.Header)

func HeaderTo added in v0.2.0

func HeaderTo(h http.Header, out any, opts ...Option) error

HeaderTo binds HTTP headers to out.

Example:

var headers RequestHeaders
err := binding.HeaderTo(r.Header, &headers)

func HeaderWith added in v0.2.0

func HeaderWith[T any](b *Binder, h http.Header) (T, error)

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

func JSON[T any](body []byte, opts ...Option) (T, error)

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),
    binding.WithRequired(),
)

Errors:

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": "charlie@example.com", "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: charlie@example.com, 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

func JSONReader[T any](r io.Reader, opts ...Option) (T, error)

JSONReader binds JSON from an io.Reader to type T.

Example:

user, err := binding.JSONReader[CreateUserRequest](r.Body)

Errors:

func JSONReaderTo added in v0.2.0

func JSONReaderTo(r io.Reader, out any, opts ...Option) error

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

func JSONReaderWith[T any](b *Binder, r io.Reader) (T, error)

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

func JSONTo(body []byte, out any, opts ...Option) error

JSONTo binds JSON bytes to out.

Example:

var user CreateUserRequest
err := binding.JSONTo(body, &user)

func JSONWith added in v0.2.0

func JSONWith[T any](b *Binder, body []byte) (T, error)

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

func MustBind[T any](t *testing.T, getter ValueGetter, tag string, opts ...Option) T

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

func MustBindForm[T any](t *testing.T, values url.Values, opts ...Option) T

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

func MustBindJSON[T any](t *testing.T, jsonData string, opts ...Option) T

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

func MustBindQuery[T any](t *testing.T, values url.Values, opts ...Option) T

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

func Path[T any](params map[string]string, opts ...Option) (T, error)

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

func PathTo(params map[string]string, out any, opts ...Option) error

PathTo binds URL path parameters to out.

Example:

var params GetUserParams
err := binding.PathTo(pathParams, &params)

func PathWith added in v0.2.0

func PathWith[T any](b *Binder, params map[string]string) (T, error)

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

func Query[T any](values url.Values, opts ...Option) (T, error)

Query binds URL query parameters to type T.

Example:

params, err := binding.Query[ListParams](r.URL.Query())

// With options
params, err := binding.Query[ListParams](r.URL.Query(),
    binding.WithRequired(),
)

Errors:

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", "alice@example.com")

	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: alice@example.com
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

func QueryTo(values url.Values, out any, opts ...Option) error

QueryTo binds URL query parameters to out.

Example:

var params ListParams
err := binding.QueryTo(r.URL.Query(), &params)
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", "bob@example.com")

	var params Params
	err := binding.QueryTo(values, &params)
	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: bob@example.com

func QueryWith added in v0.2.0

func QueryWith[T any](b *Binder, values url.Values) (T, error)

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:

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:

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

func XML[T any](body []byte, opts ...Option) (T, error)

XML binds XML bytes to type T.

Example:

user, err := binding.XML[CreateUserRequest](body)

// With options
user, err := binding.XML[CreateUserRequest](body,
    binding.WithRequired(),
)

Errors:

func XMLReader added in v0.2.0

func XMLReader[T any](r io.Reader, opts ...Option) (T, error)

XMLReader binds XML from an io.Reader to type T.

Example:

user, err := binding.XMLReader[CreateUserRequest](r.Body)

Errors:

func XMLReaderTo added in v0.2.0

func XMLReaderTo(r io.Reader, out any, opts ...Option) error

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

func XMLReaderWith[T any](b *Binder, r io.Reader) (T, error)

XMLReaderWith binds XML from an io.Reader to type T using the Binder's config.

Example:

user, err := binding.XMLReaderWith[CreateUserRequest](binder, r.Body)

func XMLTo added in v0.2.0

func XMLTo(body []byte, out any, opts ...Option) error

XMLTo binds XML bytes to out.

Example:

var user CreateUserRequest
err := binding.XMLTo(body, &user)

func XMLWith added in v0.2.0

func XMLWith[T any](b *Binder, body []byte) (T, error)

XMLWith binds XML bytes to type T using the Binder's config.

Example:

user, err := binding.XMLWith[CreateUserRequest](binder, 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

func AssertBindError(t *testing.T, err error, expectedField string) *BindError

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, &params)
bindErr := binding.AssertBindError(t, err, "Age")
assert.Equal(t, binding.SourceQuery, bindErr.Source)

func (*BindError) Code

func (e *BindError) Code() string

Code implements rivaas.dev/errors.ErrorCode.

func (*BindError) Error

func (e *BindError) Error() string

Error returns a formatted error message.

func (*BindError) HTTPStatus

func (e *BindError) HTTPStatus() int

HTTPStatus implements rivaas.dev/errors.ErrorType.

func (*BindError) IsEnum added in v0.2.0

func (e *BindError) IsEnum() bool

IsEnum returns true if the error is due to an invalid enum value.

func (*BindError) IsRequired added in v0.2.0

func (e *BindError) IsRequired() bool

IsRequired returns true if the error is due to a missing required field.

func (*BindError) IsType added in v0.2.0

func (e *BindError) IsType() bool

IsType returns true if the error is due to a type conversion failure.

func (*BindError) IsValidation added in v0.2.0

func (e *BindError) IsValidation() bool

IsValidation returns true if the error is a validation error.

func (*BindError) Unwrap

func (e *BindError) Unwrap() error

Unwrap returns the underlying error for errors.Is/As compatibility.

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"),
    binding.WithRequired(),
)

// 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

func MustNew(opts ...Option) *Binder

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": "diana@example.com"}`)

	// 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: diana@example.com

func New added in v0.2.0

func New(opts ...Option) (*Binder, error)

New creates a Binder with the given options. Returns an error if configuration is invalid.

Example:

binder, err := binding.New(
    binding.WithMaxDepth(16),
    binding.WithRequired(),
)
if err != nil {
    return fmt.Errorf("failed to create binder: %w", err)
}

func TestBinder added in v0.2.0

func TestBinder(t *testing.T, opts ...Option) *Binder

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

func (b *Binder) BindTo(out any, opts ...Option) error

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

func (b *Binder) CookieTo(cookies []*http.Cookie, out any) error

CookieTo binds cookies to out.

Example:

var session SessionData
err := binder.CookieTo(r.Cookies(), &session)

func (*Binder) FormTo added in v0.2.0

func (b *Binder) FormTo(values url.Values, out any) error

FormTo binds form data to out.

Example:

var data FormData
err := binder.FormTo(r.PostForm, &data)

func (*Binder) HeaderTo added in v0.2.0

func (b *Binder) HeaderTo(h http.Header, out any) error

HeaderTo binds HTTP headers to out.

Example:

var headers RequestHeaders
err := binder.HeaderTo(r.Header, &headers)

func (*Binder) JSONReaderTo added in v0.2.0

func (b *Binder) JSONReaderTo(r io.Reader, out any) error

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

func (b *Binder) JSONTo(body []byte, out any) error

JSONTo binds JSON bytes to out.

Example:

var user CreateUserRequest
err := binder.JSONTo(body, &user)

func (*Binder) PathTo added in v0.2.0

func (b *Binder) PathTo(params map[string]string, out any) error

PathTo binds URL path parameters to out.

Example:

var params GetUserParams
err := binder.PathTo(pathParams, &params)

func (*Binder) QueryTo added in v0.2.0

func (b *Binder) QueryTo(values url.Values, out any) error

QueryTo binds URL query parameters to out.

Example:

var params ListParams
err := binder.QueryTo(r.URL.Query(), &params)

func (*Binder) XMLReaderTo added in v0.2.0

func (b *Binder) XMLReaderTo(r io.Reader, out any) error

XMLReaderTo binds XML from an io.Reader to out.

Example:

var user CreateUserRequest
err := binder.XMLReaderTo(r.Body, &user)

func (*Binder) XMLTo added in v0.2.0

func (b *Binder) XMLTo(body []byte, out any) error

XMLTo binds XML bytes to out.

Example:

var user CreateUserRequest
err := binder.XMLTo(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

type GetterFunc func(key string) (values []string, has bool)

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

type KeyNormalizer func(string) string

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

func FromCookie(cookies []*http.Cookie) Option

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

func FromForm(values url.Values) Option

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

func FromHeader(h http.Header) Option

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

func FromJSON(body []byte) Option

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

func FromJSONReader(r io.Reader) Option

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

func FromPath(params map[string]string) Option

FromPath specifies path parameters as a binding source.

Example:

req, err := binding.Bind[Request](
    binding.FromPath(pathParams),
)

func FromQuery added in v0.2.0

func FromQuery(values url.Values) Option

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

func FromXML(body []byte) Option

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

func FromXMLReader(r io.Reader) Option

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

func WithConverter[T any](fn func(string) (T, error)) Option

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

func WithEvents(events Events) Option

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

func WithMaxDepth(depth int) Option

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

func WithMaxMapSize(n int) Option

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

func WithMaxSliceLen(n int) Option

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 WithRequired added in v0.2.0

func WithRequired() Option

WithRequired enables checking of `required` struct tags. When enabled, missing required fields return ErrRequiredField.

Example:

type User struct {
    Name  string `json:"name" required:"true"`
    Email string `json:"email" required:"true"`
}

user, err := binding.JSON[User](body, binding.WithRequired())

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 WithTimeLayouts

func WithTimeLayouts(layouts ...string) Option

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 WithValidator added in v0.2.0

func WithValidator(v Validator) Option

WithValidator integrates external validation via a Validator implementation. The validator is called after successful binding.

Example:

binding.MustNew(binding.WithValidator(myValidator))

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
)

func (Source) String added in v0.2.0

func (s Source) String() string

String returns the string representation of the source.

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

type TestValidator struct {
	ValidateFunc func(v any) error
}

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

type TypeConverter func(string) (any, error)

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 Validator added in v0.2.0

type Validator interface {
	Validate(v any) error
}

Validator validates a struct after binding.

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 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"},
})

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL