Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 29 additions & 3 deletions binder.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,43 @@ func (b *Binder[T]) buildValues(req T, target interface{}) (map[string]any, erro
mapValues := make(map[string]any)
targetValue := reflect.ValueOf(target).Elem()
typeOfTarget := targetValue.Type()

err := b.processFields(req, targetValue, typeOfTarget, &mapValues)
if err != nil {
return nil, err
}

return mapValues, nil
}

func (b *Binder[T]) processFields(req T, targetValue reflect.Value, typeOfTarget reflect.Type, mapValues *map[string]any) error {
for i := 0; i < targetValue.NumField(); i++ {
field := typeOfTarget.Field(i)

// Process regular fields with "bind" tags
fieldTag := field.Tag
if bindDefinition, ok := fieldTag.Lookup("bind"); ok {
err := b.extractValue(bindDefinition, field.Name, field.Type, req, &mapValues)
err := b.extractValue(bindDefinition, field.Name, field.Type, req, mapValues)
if err != nil {
return nil, err
return err
}
continue
}

//If the field doesnt have a bind definition and it's a struct, we deep dive
if field.Type.Kind() == reflect.Struct {
embeddedValue := targetValue.Field(i)
embeddedType := field.Type
embeddedValues := make(map[string]any)
(*mapValues)[field.Name] = embeddedValues
err := b.processFields(req, embeddedValue, embeddedType, &embeddedValues)
if err != nil {
return err
}
continue
}
}
return mapValues, nil
return nil
}

func (b *Binder[T]) extractValue(definition, fieldName string, targetType reflect.Type, req T, m *map[string]any) error {
Expand Down
35 changes: 35 additions & 0 deletions binder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ type TestInputWithBody struct {
Body Person `bind:"body"`
}

type ToBeEmbedded struct {
SubParam1 string `bind:"query=sub_param1"`
}

type TestStructWithEmbedded struct {
Embedded ToBeEmbedded
Param1 int `bind:"query=param1"`
Header1 string `bind:"header=header1"`
}

type Person struct {
Field1 string `json:"field1"`
Field2 int `json:"field2"`
Expand Down Expand Up @@ -73,6 +83,31 @@ func TestBindAllValues(t *testing.T) {
assert.Equal(t, "override", result.Header1)
}

func TestBindAllValuesWithEmbedding(t *testing.T) {
b := createHttpRequestBinder()
mockReq, err := http.NewRequest("GET", "http://example.com?param1=23&sub_param1=subvalue1", nil)
mockReq.Header.Set("header1", "override")
if err != nil {
t.Fatal(err)
}

result := TestStructWithEmbedded{
Embedded: ToBeEmbedded{
SubParam1: "",
},
Param1: 1,
Header1: "default",
}
err = b.Bind(mockReq, &result)
if err != nil {
t.Fatal(err)
}

assert.Equal(t, "subvalue1", result.Embedded.SubParam1)
assert.Equal(t, 23, result.Param1)
assert.Equal(t, "override", result.Header1)
}

func TestBindShouldIgnoreNils(t *testing.T) {
b := createHttpRequestBinder()
mockReq, err := http.NewRequest("GET", "http://example.com?param1=23", nil)
Expand Down