diff --git a/binding/form_mapping.go b/binding/form_mapping.go index e76e7510..5e70d24d 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -186,7 +186,10 @@ type BindUnmarshaler interface { func trySetCustom(val string, value reflect.Value) (isSet bool, err error) { switch v := value.Addr().Interface().(type) { case BindUnmarshaler: - return true, v.UnmarshalParam(val) + if err := v.UnmarshalParam(val); err != nil { + return true, fmt.Errorf("invalid value %q: %w", val, err) + } + return true, nil } return false, nil } @@ -245,7 +248,10 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ } if ok, err = trySetCustom(vs[0], value); ok { - return ok, err + if err != nil { + return true, fmt.Errorf("field %q: %w", field.Name, err) + } + return true, nil } if vs, err = trySplit(vs, field); err != nil { @@ -268,7 +274,10 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ } if ok, err = trySetCustom(vs[0], value); ok { - return ok, err + if err != nil { + return true, fmt.Errorf("field %q: %w", field.Name, err) + } + return true, nil } if vs, err = trySplit(vs, field); err != nil { @@ -293,7 +302,10 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ } } if ok, err := trySetCustom(val, value); ok { - return ok, err + if err != nil { + return true, fmt.Errorf("field %q: %w", field.Name, err) + } + return true, nil } return true, setWithProperType(val, value, field) } @@ -461,6 +473,12 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val func setArray(vals []string, value reflect.Value, field reflect.StructField) error { for i, s := range vals { + if ok, err := trySetCustom(s, value.Index(i)); ok { + if err != nil { + return fmt.Errorf("field %q: %w", field.Name, err) + } + continue + } err := setWithProperType(s, value.Index(i), field) if err != nil { return err diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index e007573c..399874ba 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -754,3 +754,104 @@ func TestMappingEmptyValues(t *testing.T) { assert.Equal(t, []int{1, 2, 3}, s.SliceCsv) }) } + +type testCustom struct { + Value string +} + +func (t *testCustom) UnmarshalParam(param string) error { + t.Value = "prefix_" + param + return nil +} + +func TestTrySetCustomIntegration(t *testing.T) { + var s struct { + F testCustom `form:"f"` + } + + err := mappingByPtr(&s, formSource{"f": {"hello"}}, "form") + require.NoError(t, err) + assert.Equal(t, "prefix_hello", s.F.Value) +} + +type badCustom struct{} + +func (b *badCustom) UnmarshalParam(s string) error { + return errors.New("boom") +} + +func TestTrySetCustom_SingleValues(t *testing.T) { + t.Run("Error case", func(t *testing.T) { + var s struct { + F badCustom `form:"f"` + } + + err := mappingByPtr(&s, formSource{"f": {"hello"}}, "form") + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid value") + }) + + t.Run("Integration success", func(t *testing.T) { + var s struct { + F testCustom `form:"f"` + } + + err := mappingByPtr(&s, formSource{"f": {"hello"}}, "form") + require.NoError(t, err) + assert.Equal(t, "prefix_hello", s.F.Value) + }) + + t.Run("Not applicable type", func(t *testing.T) { + var s struct { + N int `form:"n"` + } + + err := mappingByPtr(&s, formSource{"n": {"42"}}, "form") + require.NoError(t, err) + assert.Equal(t, 42, s.N) + }) +} + +func TestTrySetCustom_Collections(t *testing.T) { + t.Run("Slice success", func(t *testing.T) { + var s struct { + F []testCustom `form:"f"` + } + + err := mappingByPtr(&s, formSource{"f": {"one", "two"}}, "form") + require.NoError(t, err) + assert.Equal(t, "prefix_one", s.F[0].Value) + assert.Equal(t, "prefix_two", s.F[1].Value) + }) + + t.Run("Array success", func(t *testing.T) { + var s struct { + F [2]testCustom `form:"f"` + } + + err := mappingByPtr(&s, formSource{"f": {"hello", "world"}}, "form") + require.NoError(t, err) + assert.Equal(t, "prefix_hello", s.F[0].Value) + assert.Equal(t, "prefix_world", s.F[1].Value) + }) + + t.Run("Slice error", func(t *testing.T) { + var s struct { + F []badCustom `form:"f"` + } + + err := mappingByPtr(&s, formSource{"f": {"oops1", "oops2"}}, "form") + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid value") + }) + + t.Run("Array error", func(t *testing.T) { + var s struct { + F [2]badCustom `form:"f"` + } + + err := mappingByPtr(&s, formSource{"f": {"fail1", "fail2"}}, "form") + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid value") + }) +}