Options

Complete reference for all configuration options

Comprehensive reference for all configuration options available in the binding package.

Option Type

type Option func(*Config)

Options configure binding behavior. They can be passed to:

  • Package-level functions (e.g., binding.JSON[T](data, opts...))
  • Binder constructor (e.g., binding.MustNew(opts...))
  • Binder methods (e.g., binder.JSON[T](data, opts...))

Security Limits

WithMaxDepth

func WithMaxDepth(depth int) Option

Sets maximum struct nesting depth to prevent stack overflow from deeply nested structures.

Default: 32

Example:

user, err := binding.JSON[User](data, binding.WithMaxDepth(16))

Use Cases:

  • Protect against malicious deeply nested JSON
  • Limit resource usage
  • Prevent stack overflow

WithMaxSliceLen

func WithMaxSliceLen(length int) Option

Sets maximum slice length to prevent memory exhaustion from large arrays.

Default: 10,000

Example:

params, err := binding.Query[Params](values, binding.WithMaxSliceLen(1000))

Use Cases:

  • Protect against memory attacks
  • Limit array sizes
  • Control memory allocation

WithMaxMapSize

func WithMaxMapSize(size int) Option

Sets maximum map size to prevent memory exhaustion from large objects.

Default: 1,000

Example:

config, err := binding.JSON[Config](data, binding.WithMaxMapSize(500))

Use Cases:

  • Protect against memory attacks
  • Limit object sizes
  • Control memory allocation

Unknown Field Handling

WithStrictJSON

func WithStrictJSON() Option

Convenience function that sets WithUnknownFields(UnknownError). Fails binding if JSON contains fields not in the struct.

Example:

user, err := binding.JSON[User](data, binding.WithStrictJSON())
if err != nil {
    var unknownErr *binding.UnknownFieldError
    if errors.As(err, &unknownErr) {
        log.Printf("Unknown fields: %v", unknownErr.Fields)
    }
}

Use Cases:

  • API versioning
  • Catch typos in field names
  • Enforce strict contracts

WithUnknownFields

func WithUnknownFields(mode UnknownMode) Option

// Modes
const (
    UnknownIgnore UnknownMode = iota // Ignore unknown fields (default)
    UnknownWarn                       // Log warnings
    UnknownError                      // Return error
)

Controls how unknown fields are handled.

Example:

user, err := binding.JSON[User](data,
    binding.WithUnknownFields(binding.UnknownWarn))

Modes:

  • UnknownIgnore: Silently ignore (default, most flexible)
  • UnknownWarn: Log warnings (for debugging)
  • UnknownError: Fail binding (strict contracts)

Slice Parsing

WithSliceMode

func WithSliceMode(mode SliceMode) Option

// Modes
const (
    SliceRepeat SliceMode = iota // ?tags=a&tags=b (default)
    SliceCSV                     // ?tags=a,b,c
)

Controls how slices are parsed from query/form values.

Example:

// URL: ?tags=go,rust,python
params, err := binding.Query[Params](values,
    binding.WithSliceMode(binding.SliceCSV))

Modes:

  • SliceRepeat: Repeated parameters (default, standard HTTP)
  • SliceCSV: Comma-separated values (more compact)

Error Handling

WithAllErrors

func WithAllErrors() Option

Collects all binding errors instead of failing on the first error.

Example:

user, err := binding.JSON[User](data, binding.WithAllErrors())
if err != nil {
    var multi *binding.MultiError
    if errors.As(err, &multi) {
        for _, e := range multi.Errors {
            log.Printf("Field %s: %v", e.Field, e.Err)
        }
    }
}

Use Cases:

  • Show all validation errors to user
  • Debugging
  • Comprehensive error reporting

Type Conversion

WithConverter

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

Registers a custom type converter for type T.

Example:

import "github.com/google/uuid"

binder := binding.MustNew(
    binding.WithConverter[uuid.UUID](uuid.Parse),
)

type User struct {
    ID uuid.UUID `query:"id"`
}

user, err := binder.Query[User](values)

Use Cases:

  • Custom types (UUID, decimal, etc.)
  • Domain-specific types
  • Third-party types

WithTimeLayouts

func WithTimeLayouts(layouts ...string) Option

Sets custom time parsing layouts. Replaces default layouts.

Default Layouts: See binding.DefaultTimeLayouts

Example:

binder := binding.MustNew(
    binding.WithTimeLayouts(
        "2006-01-02",           // Date only
        "01/02/2006",           // US format
        "2006-01-02 15:04:05",  // DateTime
    ),
)

Tip: Extend defaults instead of replacing:

binder := binding.MustNew(
    binding.WithTimeLayouts(
        append(binding.DefaultTimeLayouts, "01/02/2006", "02-Jan-2006")...,
    ),
)

Observability

WithEvents

func WithEvents(events Events) Option

type Events struct {
    FieldBound   func(name, tag string)
    UnknownField func(name string)
    Done         func(stats Stats)
}

type Stats struct {
    FieldsBound int
    ErrorCount  int
    Duration    time.Duration
}

Registers event handlers for observing binding operations.

Example:

binder := binding.MustNew(
    binding.WithEvents(binding.Events{
        FieldBound: func(name, tag string) {
            metrics.Increment("binding.field.bound",
                "field:"+name, "source:"+tag)
        },
        UnknownField: func(name string) {
            log.Warn("Unknown field", "name", name)
        },
        Done: func(stats binding.Stats) {
            metrics.Histogram("binding.duration",
                stats.Duration.Milliseconds())
            metrics.Gauge("binding.fields", stats.FieldsBound)
        },
    }),
)

Use Cases:

  • Metrics collection
  • Debugging
  • Performance monitoring
  • Audit logging

Multi-Source Options

WithMergeStrategy

func WithMergeStrategy(strategy MergeStrategy) Option

// Strategies
const (
    MergeLastWins  MergeStrategy = iota // Last source wins (default)
    MergeFirstWins                       // First source wins
)

Controls precedence when binding from multiple sources.

Example:

// First source wins
req, err := binding.Bind[Request](
    binding.WithMergeStrategy(binding.MergeFirstWins),
    binding.FromHeader(r.Header),      // Highest priority
    binding.FromQuery(r.URL.Query()),  // Lower priority
)

Strategies:

  • MergeLastWins: Last source overwrites (default)
  • MergeFirstWins: First non-empty value wins

JSON-Specific Options

WithDisallowUnknownFields

func WithDisallowUnknownFields() Option

Equivalent to WithStrictJSON(). Provided for clarity when explicitly disallowing unknown fields.

Example:

user, err := binding.JSON[User](data,
    binding.WithDisallowUnknownFields())

WithMaxBytes

func WithMaxBytes(bytes int64) Option

Limits the size of JSON/XML data to prevent memory exhaustion.

Example:

user, err := binding.JSON[User](data,
    binding.WithMaxBytes(1024 * 1024)) // 1MB limit

Use Cases:

  • Protect against large payloads
  • API rate limiting
  • Resource management

Custom Options

WithTagHandler

func WithTagHandler(tagName string, handler TagHandler) Option

type TagHandler interface {
    Get(fieldName, tagValue string) (string, bool)
}

Registers a custom struct tag handler.

Example:

type EnvTagHandler struct {
    prefix string
}

func (h *EnvTagHandler) Get(fieldName, tagValue string) (string, bool) {
    envKey := h.prefix + tagValue
    val, exists := os.LookupEnv(envKey)
    return val, exists
}

binder := binding.MustNew(
    binding.WithTagHandler("env", &EnvTagHandler{prefix: "APP_"}),
)

type Config struct {
    APIKey string `env:"API_KEY"`  // Looks up APP_API_KEY
}

Option Combinations

Production Configuration

var ProductionBinder = binding.MustNew(
    // Security
    binding.WithMaxDepth(16),
    binding.WithMaxSliceLen(1000),
    binding.WithMaxMapSize(500),
    binding.WithMaxBytes(10 * 1024 * 1024), // 10MB
    
    // Strict validation
    binding.WithStrictJSON(),
    
    // Custom types
    binding.WithConverter[uuid.UUID](uuid.Parse),
    binding.WithConverter[decimal.Decimal](decimal.NewFromString),
    
    // Time formats
    binding.WithTimeLayouts(append(
        binding.DefaultTimeLayouts,
        "2006-01-02",
        "01/02/2006",
    )...),
    
    // Observability
    binding.WithEvents(binding.Events{
        FieldBound:   logFieldBound,
        UnknownField: logUnknownField,
        Done:         recordMetrics,
    }),
)

Development Configuration

var DevBinder = binding.MustNew(
    // Lenient limits
    binding.WithMaxDepth(32),
    binding.WithMaxSliceLen(10000),
    
    // Warnings instead of errors
    binding.WithUnknownFields(binding.UnknownWarn),
    
    // Collect all errors for debugging
    binding.WithAllErrors(),
    
    // Verbose logging
    binding.WithEvents(binding.Events{
        FieldBound: func(name, tag string) {
            log.Printf("[DEBUG] Bound %s from %s", name, tag)
        },
        UnknownField: func(name string) {
            log.Printf("[WARN] Unknown field: %s", name)
        },
        Done: func(stats binding.Stats) {
            log.Printf("[DEBUG] Binding: %d fields, %d errors, %v",
                stats.FieldsBound, stats.ErrorCount, stats.Duration)
        },
    }),
)

Testing Configuration

var TestBinder = binding.MustNew(
    // Strict validation
    binding.WithStrictJSON(),
    
    // Fail fast
    // (don't use WithAllErrors in tests)
    
    // Smaller limits for test data
    binding.WithMaxDepth(8),
    binding.WithMaxSliceLen(100),
)

Option Precedence

When options are provided to both MustNew() and individual functions:

  1. Function-level options override binder-level options
  2. Options are applied in order (last wins for same option)

Example:

binder := binding.MustNew(
    binding.WithMaxDepth(32),  // Binder default
)

// This call uses maxDepth=16 (overrides binder default)
user, err := binder.JSON[User](data,
    binding.WithMaxDepth(16))

Best Practices

1. Use Binders for Shared Configuration

// Good - shared configuration
var AppBinder = binding.MustNew(
    binding.WithConverter[uuid.UUID](uuid.Parse),
    binding.WithMaxDepth(16),
)

func Handler1(r *http.Request) {
    user, err := AppBinder.JSON[User](r.Body)
}

func Handler2(r *http.Request) {
    params, err := AppBinder.Query[Params](r.URL.Query())
}

2. Set Security Limits

// Good - protect against attacks
user, err := binding.JSON[User](data,
    binding.WithMaxDepth(16),
    binding.WithMaxSliceLen(1000),
    binding.WithMaxBytes(1024*1024),
)

3. Use Strict Mode for APIs

// Good - catch client errors early
user, err := binding.JSON[User](data, binding.WithStrictJSON())

4. Collect All Errors for Forms

// Good - show all validation errors to user
form, err := binding.Form[Form](r.PostForm, binding.WithAllErrors())
if err != nil {
    var multi *binding.MultiError
    if errors.As(err, &multi) {
        // Show all errors to user
        for _, e := range multi.Errors {
            addError(e.Field, e.Err.Error())
        }
    }
}

See Also

For usage examples, see the Binding Guide.