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
9 changes: 5 additions & 4 deletions bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"encoding"
"encoding/xml"
"errors"
"fmt"
"mime/multipart"
"net/http"
"reflect"
Expand Down Expand Up @@ -249,15 +250,15 @@ func bindData(destination any, data map[string][]string, tag string, dataFiles m
// try unmarshalling first, in case we're dealing with an alias to an array type
if ok, err := unmarshalInputsToField(typeField.Type.Kind(), inputValue, structField); ok {
if err != nil {
return err
return fmt.Errorf("%s: %w", inputFieldName, err)
}
continue
}

formatTag := typeField.Tag.Get("format")
if ok, err := unmarshalInputToField(typeField.Type.Kind(), inputValue[0], structField, formatTag); ok {
if err != nil {
return err
return fmt.Errorf("%s: %w", inputFieldName, err)
}
continue
}
Expand All @@ -275,15 +276,15 @@ func bindData(destination any, data map[string][]string, tag string, dataFiles m
slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
for j := range numElems {
if err := setWithProperType(sliceOf, inputValue[j], slice.Index(j)); err != nil {
return err
return fmt.Errorf("%s: %w", inputFieldName, err)
}
}
structField.Set(slice)
continue
}

if err := setWithProperType(structFieldKind, inputValue[0], structField); err != nil {
return err
return fmt.Errorf("%s: %w", inputFieldName, err)
}
}
return nil
Expand Down
32 changes: 32 additions & 0 deletions bind_field_error_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors

package echo

import (
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/stretchr/testify/assert"
)

// Regression test for #2629: when binding form data fails a type conversion, the
// returned error must identify which field failed (so applications can render a
// useful message), instead of a bare strconv error with no field context.
func TestBind_formConversionErrorIncludesFieldName(t *testing.T) {
e := New()
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("number=10a"))
req.Header.Set(HeaderContentType, MIMEApplicationForm)
c := e.NewContext(req, httptest.NewRecorder())

type DTO struct {
Number int `form:"number"`
}
var dto DTO
err := c.Bind(&dto)

assert.Error(t, err)
assert.ErrorContains(t, err, "number", "bind error must identify the failing field")
}
8 changes: 4 additions & 4 deletions bind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -842,7 +842,7 @@ func TestDefaultBinder_BindToStructFromMixedSources(t *testing.T) {
givenURL: "/api/real_node/endpoint?id=nope",
givenContent: strings.NewReader(`{"id": 1, "node": "zzz"}`),
expect: &Opts{ID: 0, Node: "node_from_path"}, // path params binding has already modified bind target
expectError: `code=400, message=Bad Request, err=strconv.ParseInt: parsing "nope": invalid syntax`,
expectError: `code=400, message=Bad Request, err=id: strconv.ParseInt: parsing "nope": invalid syntax`,
},
{
name: "nok, GET body bind failure - trying to bind json array to struct",
Expand Down Expand Up @@ -1196,7 +1196,7 @@ func TestBindUnmarshalParamExtras(t *testing.T) {
}{}
err := testBindURL("/?t=xxxx", &result)

assert.EqualError(t, err, `code=400, message=Bad Request, err='xxxx' is not an integer`)
assert.EqualError(t, err, `code=400, message=Bad Request, err=t: 'xxxx' is not an integer`)
})

t.Run("ok, target is struct", func(t *testing.T) {
Expand Down Expand Up @@ -1301,7 +1301,7 @@ func TestBindUnmarshalParams(t *testing.T) {
}{}
err := testBindURL("/?t=xxxx", &result)

assert.EqualError(t, err, "code=400, message=Bad Request, err='xxxx' is not an integer")
assert.EqualError(t, err, "code=400, message=Bad Request, err=t: 'xxxx' is not an integer")
})

t.Run("ok, target is struct", func(t *testing.T) {
Expand Down Expand Up @@ -1368,7 +1368,7 @@ func TestBindInt8(t *testing.T) {
}
p := target{}
err := testBindURL("/?v=x&v=2", &p)
assert.EqualError(t, err, `code=400, message=Bad Request, err=strconv.ParseInt: parsing "x": invalid syntax`)
assert.EqualError(t, err, `code=400, message=Bad Request, err=v: strconv.ParseInt: parsing "x": invalid syntax`)
})

t.Run("nok, int8 embedded in struct", func(t *testing.T) {
Expand Down
Loading