binding

package
v0.0.5 Latest Latest
Warning

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

Go to latest
Published: Nov 13, 2025 License: MIT Imports: 6 Imported by: 0

README

Package binding

The binding package provides a type-safe, reflect-free, and expression-oriented way to bind data from HTTP requests to Go structs. It is designed to produce structured, user-friendly error responses out of the box when integrated with the rakuda Lift and Responder components.

Features

  • Type-Safe: Uses generics to ensure type safety at compile time.
  • Reflect-Free: Avoids reflection for better performance and clearer code.
  • Structured Errors: Automatically generates detailed JSON error responses for validation failures.
  • Extensible: Supports custom parsers for any data type.

Usage

Here is a simple example of how to use the binding package within a rakuda application. The binding.Join function aggregates any validation errors, and rakuda.Lift handles the error by sending a structured JSON response.

package main

import (
	"fmt"
	"log"
	"net/http"
	"strconv"

	"github.com/podhmo/rakuda"
	"github.com/podhmo/rakuda/binding"
)

var responder = rakuda.NewResponder()

// Define a parser for converting a string to an int.
var parseInt = func(s string) (int, error) {
	return strconv.Atoi(s)
}

// Define a simple string parser.
var parseString = func(s string) (string, error) {
	return s, nil
}

// GistParams represents the parameters for the gist action.
type GistParams struct {
	ID    int     `json:"id"`
	Token string  `json:"-"` // Not included in JSON response
	Sort  *string `json:"sort,omitempty"`
}

// actionGist is a handler that demonstrates data binding.
func actionGist(r *http.Request) (GistParams, error) {
	var params GistParams
	b := binding.New(r, r.PathValue)

	// Use binding.Join to aggregate all validation errors.
	if err := binding.Join(
		binding.One(b, &params.ID, binding.Path, "id", parseInt, binding.Required),
		binding.One(b, &params.Token, binding.Header, "X-Auth-Token", parseString, binding.Required),
		binding.OnePtr(b, &params.Sort, binding.Query, "sort", parseString, binding.Optional),
	); err != nil {
		// Lift will automatically handle the *binding.ValidationErrors
		// and the responder will format it into a detailed JSON response.
		return params, err
	}

	return params, nil
}

func main() {
	builder := rakuda.NewBuilder()
	builder.Get("/gists/{id}", rakuda.Lift(responder, actionGist))

	handler, err := builder.Build()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("Server starting on port 8080...")
	if err := http.ListenAndServe(":8080", handler); err != nil {
		log.Fatal(err)
	}
}
How to Run the Example
  1. Save the code above as main.go.

  2. Run go mod init example and go mod tidy.

  3. Run go run main.go.

  4. Send requests to the server:

    # 1. Request with all parameters (Success)
    curl -i -H "X-Auth-Token: mysecret" "http://localhost:8080/gists/123?sort=name"
    # HTTP/1.1 200 OK
    # {"id":123,"sort":"name"}
    
    # 2. Request without the optional sort parameter (Success)
    curl -i -H "X-Auth-Token: mysecret" "http://localhost:8080/gists/456"
    # HTTP/1.1 200 OK
    # {"id":456}
    
    # 3. Request with multiple validation errors (Failure)
    # - Path parameter 'id' is not a valid integer.
    # - Required 'X-Auth-Token' header is missing.
    curl -i "http://localhost:8080/gists/invalid-id?sort=name"
    # HTTP/1.1 400 Bad Request
    # {
    #   "errors": [
    #     {
    #       "source": "path",
    #       "key": "id",
    #       "value": "invalid-id",
    #       "message": "strconv.Atoi: parsing \"invalid-id\": invalid syntax"
    #     },
    #     {
    #       "source": "header",
    #       "key": "X-Auth-Token",
    #       "value": null,
    #       "message": "required parameter is missing"
    #     }
    #   ]
    # }
    

Documentation

Overview

Package binding provides a type-safe, reflect-free, and expression-oriented way to bind data from HTTP requests to Go structs.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Join added in v0.0.4

func Join(errs ...error) error

Join collects binding errors into a single ValidationErrors instance. It filters out nil errors. If no errors are found, it returns nil.

func One

func One[T any](b *Binding, dest *T, source Source, key string, parse Parser[T], req Requirement) error

One binds a single value of a non-pointer type (e.g., int, string).

func OnePtr

func OnePtr[T any](b *Binding, dest **T, source Source, key string, parse Parser[T], req Requirement) error

OnePtr binds a single value of a pointer type (e.g., *int, *string).

func Slice

func Slice[T any](b *Binding, dest *[]T, source Source, key string, parse Parser[T], req Requirement) error

Slice binds values into a slice of a non-pointer type (e.g., []int, []string).

func SlicePtr

func SlicePtr[T any](b *Binding, dest *[]*T, source Source, key string, parse Parser[T], req Requirement) error

SlicePtr binds values into a slice of a pointer type (e.g., []*int, []*string).

Types

type Binding

type Binding struct {
	// contains filtered or unexported fields
}

Binding holds the context for a binding operation, including the HTTP request and a function to retrieve path parameters.

func New

func New(req *http.Request, pathValue func(string) string) *Binding

New creates a new Binding instance from an *http.Request and a function to retrieve path parameters. The pathValue function is typically provided by a routing library.

func (*Binding) Lookup

func (b *Binding) Lookup(source Source, key string) (string, bool)

Lookup is an internal method that retrieves a value and its existence from a given source.

type Error added in v0.0.4

type Error struct {
	Source Source `json:"source"` // e.g., "query", "header"
	Key    string `json:"key"`    // The parameter name (e.g., "id", "sort")
	Value  any    `json:"value"`  // The invalid value that was provided
	Err    error  `json:"-"`      // The underlying error (not exposed in JSON)
}

Error represents a single validation error, providing structured details.

func (*Error) Error added in v0.0.4

func (e *Error) Error() string

func (*Error) MarshalJSON added in v0.0.4

func (e *Error) MarshalJSON() ([]byte, error)

MarshalJSON customizes the JSON output to include a user-friendly message.

func (*Error) Unwrap added in v0.0.4

func (e *Error) Unwrap() error

type Parser

type Parser[T any] func(string) (T, error)

Parser is a generic function that parses a string into a value of type T. It returns an error if parsing fails.

type Requirement

type Requirement bool

Requirement specifies whether a value is required or optional.

const (
	Required Requirement = true
	Optional Requirement = false
)

type Source

type Source string

Source represents the source of a value in an HTTP request.

const (
	Query  Source = "query"
	Header Source = "header"
	Cookie Source = "cookie"
	Path   Source = "path"
	Form   Source = "form"
)

type ValidationErrors added in v0.0.4

type ValidationErrors struct {
	Errors []*Error `json:"errors"`
}

ValidationErrors collects multiple binding errors.

func (*ValidationErrors) Error added in v0.0.4

func (e *ValidationErrors) Error() string

func (*ValidationErrors) StatusCode added in v0.0.4

func (e *ValidationErrors) StatusCode() int

StatusCode returns 400 Bad Request, allowing it to work with the lift handler.

Jump to

Keyboard shortcuts

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