bind API

bind

package

API reference for the bind package.

F
function

TestBinder_QueryBinding

Parameters

pkg/bind/bind_test.go:8-27
func TestBinder_QueryBinding(t *testing.T)

{
	req := httptest.NewRequest("GET", "/test?name=alice&age=30", nil)

	type Target struct {
		Name string `query:"name"`
		Age  int    `query:"age"`
	}

	b := New().FromQuery(req)
	var target Target
	if err := b.Bind(&target); err != nil {
		t.Fatalf("Bind: %v", err)
	}
	if target.Name != "alice" {
		t.Errorf("Name = %q, want alice", target.Name)
	}
	if target.Age != 30 {
		t.Errorf("Age = %d, want 30", target.Age)
	}
}
F
function

TestBinder_HeaderBinding

Parameters

pkg/bind/bind_test.go:29-45
func TestBinder_HeaderBinding(t *testing.T)

{
	req := httptest.NewRequest("GET", "/test", nil)
	req.Header.Set("X-Request-ID", "abc-123")

	type Target struct {
		RequestID string `header:"X-Request-ID"`
	}

	b := New().FromHeader(req)
	var target Target
	if err := b.Bind(&target); err != nil {
		t.Fatalf("Bind: %v", err)
	}
	if target.RequestID != "abc-123" {
		t.Errorf("RequestID = %q, want abc-123", target.RequestID)
	}
}
F
function

TestBinder_PathBinding

Parameters

pkg/bind/bind_test.go:47-62
func TestBinder_PathBinding(t *testing.T)

{
	params := map[string]string{"id": "42"}

	type Target struct {
		ID int `path:"id"`
	}

	b := New().FromPath(func(key string) string { return params[key] })
	var target Target
	if err := b.Bind(&target); err != nil {
		t.Fatalf("Bind: %v", err)
	}
	if target.ID != 42 {
		t.Errorf("ID = %d, want 42", target.ID)
	}
}
F
function

TestBinder_DefaultValues

Parameters

pkg/bind/bind_test.go:64-87
func TestBinder_DefaultValues(t *testing.T)

{
	req := httptest.NewRequest("GET", "/test", nil)

	type Target struct {
		Page    int    `query:"page" default:"1"`
		Verbose bool   `query:"verbose" default:"true"`
		Format  string `default:"json"`
	}

	b := New().FromQuery(req)
	var target Target
	if err := b.Bind(&target); err != nil {
		t.Fatalf("Bind: %v", err)
	}
	if target.Page != 1 {
		t.Errorf("Page = %d, want 1", target.Page)
	}
	if !target.Verbose {
		t.Error("Verbose = false, want true")
	}
	if target.Format != "json" {
		t.Errorf("Format = %q, want json", target.Format)
	}
}
F
function

TestBinder_QueryOverridesDefault

Parameters

pkg/bind/bind_test.go:89-104
func TestBinder_QueryOverridesDefault(t *testing.T)

{
	req := httptest.NewRequest("GET", "/test?page=5", nil)

	type Target struct {
		Page int `query:"page" default:"1"`
	}

	b := New().FromQuery(req)
	var target Target
	if err := b.Bind(&target); err != nil {
		t.Fatalf("Bind: %v", err)
	}
	if target.Page != 5 {
		t.Errorf("Page = %d, want 5", target.Page)
	}
}
F
function

TestBinder_BindJSON

Parameters

pkg/bind/bind_test.go:106-122
func TestBinder_BindJSON(t *testing.T)

{
	type Inner struct {
		Name string `json:"Name"`
	}

	type Target struct {
		Body Inner `body:"json"`
	}

	var target Target
	if err := New().BindJSON(&target, []byte(`{"Name":"bob"}`)); err != nil {
		t.Fatalf("BindJSON: %v", err)
	}
	if target.Body.Name != "bob" {
		t.Errorf("Name = %q, want bob", target.Body.Name)
	}
}
F
function

TestBinder_AllSources

Parameters

pkg/bind/bind_test.go:124-157
func TestBinder_AllSources(t *testing.T)

{
	req := httptest.NewRequest("GET", "/test?search=golang", nil)
	req.Header.Set("X-Token", "secret")
	params := map[string]string{"id": "99"}

	type Target struct {
		ID     int    `path:"id"`
		Search string `query:"search"`
		Token  string `header:"X-Token"`
		Limit  int    `default:"10"`
	}

	b := New().
		FromPath(func(key string) string { return params[key] }).
		FromQuery(req).
		FromHeader(req)

	var target Target
	if err := b.Bind(&target); err != nil {
		t.Fatalf("Bind: %v", err)
	}
	if target.ID != 99 {
		t.Errorf("ID = %d, want 99", target.ID)
	}
	if target.Search != "golang" {
		t.Errorf("Search = %q, want golang", target.Search)
	}
	if target.Token != "secret" {
		t.Errorf("Token = %q, want secret", target.Token)
	}
	if target.Limit != 10 {
		t.Errorf("Limit = %d, want 10", target.Limit)
	}
}
T
type

SourceFunc

SourceFunc extracts a string value by key from a request source.

pkg/bind/bind.go:13-13
type SourceFunc func(key string) string
S
struct

Binder

Binder populates struct fields from path, query, header, and default sources.

pkg/bind/bind.go:16-18
type Binder struct

Methods

FromPath
Method

FromPath adds a path parameter source.

Parameters

Returns

func (*Binder) FromPath(fn SourceFunc) *Binder
{
	b.sources = append(b.sources, source{tag: "path", fn: fn})
	return b
}
FromQuery
Method

FromQuery adds a query parameter source using the request URL query.

Parameters

Returns

func (*Binder) FromQuery(r *http.Request) *Binder
{
	b.sources = append(b.sources, source{tag: "query", fn: func(key string) string {
		return r.URL.Query().Get(key)
	}})
	return b
}
FromHeader
Method

FromHeader adds an HTTP header source.

Parameters

Returns

func (*Binder) FromHeader(r *http.Request) *Binder
{
	b.sources = append(b.sources, source{tag: "header", fn: func(key string) string {
		return r.Header.Get(key)
	}})
	return b
}
FromFunc
Method

FromFunc adds a custom source with the given tag name.

Parameters

tag string

Returns

func (*Binder) FromFunc(tag string, fn SourceFunc) *Binder
{
	b.sources = append(b.sources, source{tag: tag, fn: fn})
	return b
}
Bind
Method

Bind populates struct fields from all registered sources, then applies defaults.

Parameters

target any

Returns

error
func (*Binder) Bind(target any) error
{
	val := reflect.ValueOf(target)
	if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Struct {
		return fmt.Errorf("bind: target must be a pointer to a struct")
	}

	elem := val.Elem()
	typ := elem.Type()
	values := make(map[int]string)

	for _, src := range b.sources {
		for i := 0; i < typ.NumField(); i++ {
			if _, ok := values[i]; ok {
				continue
			}
			fieldType := typ.Field(i)
			tagVal, ok := fieldType.Tag.Lookup(src.tag)
			if !ok || tagVal == "" {
				continue
			}
			if v := src.fn(tagVal); v != "" {
				values[i] = v
			}
		}
	}

	for i := 0; i < typ.NumField(); i++ {
		if _, ok := values[i]; ok {
			continue
		}
		fieldType := typ.Field(i)
		if def, ok := fieldType.Tag.Lookup("default"); ok && def != "" {
			values[i] = def
		}
	}

	for idx, v := range values {
		field := elem.Field(idx)
		if !field.CanSet() {
			continue
		}
		if err := setField(field, v); err != nil {
			typeField := typ.Field(idx)
			return fmt.Errorf("bind: field %s: %w", typeField.Name, err)
		}
	}
	return nil
}
BindJSON
Method

BindJSON unmarshals data into the struct field tagged with body:"json".

Parameters

target any
data []byte

Returns

error
func (*Binder) BindJSON(target any, data []byte) error
{
	val := reflect.ValueOf(target)
	if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Struct {
		return fmt.Errorf("bind: target must be a pointer to a struct")
	}

	elem := val.Elem()
	typ := elem.Type()

	for i := 0; i < elem.NumField(); i++ {
		field := elem.Field(i)
		fieldType := typ.Field(i)
		if tag, ok := fieldType.Tag.Lookup("body"); ok && tag == "json" && field.CanSet() {
			return json.Unmarshal(data, field.Addr().Interface())
		}
	}
	return nil
}

Fields

Name Type Description
sources []source
S
struct

source

pkg/bind/bind.go:20-23
type source struct

Fields

Name Type Description
tag string
fn SourceFunc
F
function

New

New creates an empty Binder.

Returns

pkg/bind/bind.go:26-28
func New() *Binder

{
	return &Binder{}
}
F
function

setField

Parameters

valStr
string

Returns

error
pkg/bind/bind.go:128-160
func setField(field reflect.Value, valStr string) error

{
	switch field.Kind() {
	case reflect.String:
		field.SetString(valStr)
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		val, err := strconv.ParseInt(valStr, 10, 64)
		if err != nil {
			return err
		}
		field.SetInt(val)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		val, err := strconv.ParseUint(valStr, 10, 64)
		if err != nil {
			return err
		}
		field.SetUint(val)
	case reflect.Bool:
		val, err := strconv.ParseBool(valStr)
		if err != nil {
			return err
		}
		field.SetBool(val)
	case reflect.Float32, reflect.Float64:
		val, err := strconv.ParseFloat(valStr, 64)
		if err != nil {
			return err
		}
		field.SetFloat(val)
	default:
		return fmt.Errorf("unsupported type %s", field.Kind())
	}
	return nil
}
F
function

ioReadAll

ioReadAll reads the request body up to 1MB.

Parameters

Returns

[]byte
error
pkg/bind/bind.go:163-169
func ioReadAll(r *http.Request) ([]byte, error)

{
	if r.Body == nil {
		return nil, nil
	}
	defer r.Body.Close()
	return io.ReadAll(io.LimitReader(r.Body, 1<<20))
}