From d0d489ccca2f3220f7dc3e7c7922e1aed5a297f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sat, 14 Jun 2025 15:07:46 +0200 Subject: [PATCH 1/2] add error structs. --- errors.go | 44 +++++++++ mapstructure.go | 228 ++++++++++++++++++++++++++++++++----------- mapstructure_test.go | 14 ++- 3 files changed, 224 insertions(+), 62 deletions(-) create mode 100644 errors.go diff --git a/errors.go b/errors.go new file mode 100644 index 00000000..46e39c06 --- /dev/null +++ b/errors.go @@ -0,0 +1,44 @@ +package mapstructure + +import ( + "fmt" + "reflect" +) + +// ErrCannotDecode is a generic error type that holds information about +// a decoding error together with the name of the field that caused the error. +type ErrCannotDecode struct { + Name string + Err error +} + +func (e *ErrCannotDecode) Error() string { + return fmt.Sprintf("'%s': %s", e.Name, e.Err) +} + +// ErrCannotParse extends ErrCannotDecode to include additional information +// about the expected type and the actual value that could not be parsed. +type ErrCannotParse struct { + ErrCannotDecode + Expected reflect.Value + Value interface{} +} + +func (e *ErrCannotParse) Error() string { + return fmt.Sprintf("'%s' cannot parse '%s' as '%s': %s", + e.Name, e.Value, e.Expected.Type(), e.Err) +} + +// ErrUnconvertibleType is an error type that indicates a value could not be +// converted to the expected type. It includes the name of the field, the +// expected type, and the actual value that was attempted to be converted. +type ErrUnconvertibleType struct { + Name string + Expected reflect.Value + Value interface{} +} + +func (e *ErrUnconvertibleType) Error() string { + return fmt.Sprintf("'%s' expected type '%s', got unconvertible type '%s', value: '%v'", + e.Name, e.Expected.Type(), reflect.TypeOf(e.Value), e.Value) +} diff --git a/mapstructure.go b/mapstructure.go index 60311d6b..29e8b2d2 100644 --- a/mapstructure.go +++ b/mapstructure.go @@ -525,7 +525,10 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e var err error input, err = d.cachedDecodeHook(inputVal, outVal) if err != nil { - return fmt.Errorf("error decoding '%s': %w", name, err) + return &ErrCannotDecode{ + Name: name, + Err: err, + } } } if isNil(input) { @@ -563,7 +566,10 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e err = d.decodeFunc(name, input, outVal) default: // If we reached this point then we weren't able to decode it - return fmt.Errorf("%s: unsupported type: %s", name, outputKind) + return &ErrCannotDecode{ + Name: name, + Err: fmt.Errorf("unsupported type: %s", outputKind), + } } // If we reached here, then we successfully decoded SOMETHING, so @@ -624,9 +630,11 @@ func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) dataValType := dataVal.Type() if !dataValType.AssignableTo(val.Type()) { - return fmt.Errorf( - "'%s' expected type '%s', got '%s'", - name, val.Type(), dataValType) + return &ErrUnconvertibleType{ + Name: name, + Expected: val, + Value: data, + } } val.Set(dataVal) @@ -677,9 +685,11 @@ func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) } if !converted { - return fmt.Errorf( - "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", - name, val.Type(), dataVal.Type(), data) + return &ErrUnconvertibleType{ + Name: name, + Expected: val, + Value: data, + } } return nil @@ -713,20 +723,35 @@ func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) er if err == nil { val.SetInt(i) } else { - return fmt.Errorf("cannot parse '%s' as int: %s", name, err) + return &ErrCannotParse{ + ErrCannotDecode: ErrCannotDecode{ + Name: name, + Err: err, + }, + Expected: val, + Value: data, + } } case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": jn := data.(json.Number) i, err := jn.Int64() if err != nil { - return fmt.Errorf( - "error decoding json.Number into %s: %s", name, err) + return &ErrCannotParse{ + ErrCannotDecode: ErrCannotDecode{ + Name: name, + Err: err, + }, + Expected: val, + Value: data, + } } val.SetInt(i) default: - return fmt.Errorf( - "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", - name, val.Type(), dataVal.Type(), data) + return &ErrUnconvertibleType{ + Name: name, + Expected: val, + Value: data, + } } return nil @@ -741,8 +766,14 @@ func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) e case dataKind == reflect.Int: i := dataVal.Int() if i < 0 && !d.config.WeaklyTypedInput { - return fmt.Errorf("cannot parse '%s', %d overflows uint", - name, i) + return &ErrCannotParse{ + ErrCannotDecode: ErrCannotDecode{ + Name: name, + Err: fmt.Errorf("%d overflows uint", i), + }, + Expected: val, + Value: data, + } } val.SetUint(uint64(i)) case dataKind == reflect.Uint: @@ -750,8 +781,14 @@ func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) e case dataKind == reflect.Float32: f := dataVal.Float() if f < 0 && !d.config.WeaklyTypedInput { - return fmt.Errorf("cannot parse '%s', %f overflows uint", - name, f) + return &ErrCannotParse{ + ErrCannotDecode: ErrCannotDecode{ + Name: name, + Err: fmt.Errorf("%f overflows uint", f), + }, + Expected: val, + Value: data, + } } val.SetUint(uint64(f)) case dataKind == reflect.Bool && d.config.WeaklyTypedInput: @@ -770,20 +807,35 @@ func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) e if err == nil { val.SetUint(i) } else { - return fmt.Errorf("cannot parse '%s' as uint: %s", name, err) + return &ErrCannotParse{ + ErrCannotDecode: ErrCannotDecode{ + Name: name, + Err: err, + }, + Expected: val, + Value: data, + } } case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": jn := data.(json.Number) i, err := strconv.ParseUint(string(jn), 0, 64) if err != nil { - return fmt.Errorf( - "error decoding json.Number into %s: %s", name, err) + return &ErrCannotParse{ + ErrCannotDecode: ErrCannotDecode{ + Name: name, + Err: err, + }, + Expected: val, + Value: data, + } } val.SetUint(i) default: - return fmt.Errorf( - "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", - name, val.Type(), dataVal.Type(), data) + return &ErrUnconvertibleType{ + Name: name, + Expected: val, + Value: data, + } } return nil @@ -809,12 +861,21 @@ func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) e } else if dataVal.String() == "" { val.SetBool(false) } else { - return fmt.Errorf("cannot parse '%s' as bool: %s", name, err) + return &ErrCannotParse{ + ErrCannotDecode: ErrCannotDecode{ + Name: name, + Err: err, + }, + Expected: val, + Value: data, + } } default: - return fmt.Errorf( - "'%s' expected type '%s', got unconvertible type '%#v', value: '%#v'", - name, val, dataVal, data) + return &ErrUnconvertibleType{ + Name: name, + Expected: val, + Value: data, + } } return nil @@ -848,20 +909,35 @@ func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value) if err == nil { val.SetFloat(f) } else { - return fmt.Errorf("cannot parse '%s' as float: %s", name, err) + return &ErrCannotParse{ + ErrCannotDecode: ErrCannotDecode{ + Name: name, + Err: err, + }, + Expected: val, + Value: data, + } } case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": jn := data.(json.Number) i, err := jn.Float64() if err != nil { - return fmt.Errorf( - "error decoding json.Number into %s: %s", name, err) + return &ErrCannotParse{ + ErrCannotDecode: ErrCannotDecode{ + Name: name, + Err: err, + }, + Expected: val, + Value: data, + } } val.SetFloat(i) default: - return fmt.Errorf( - "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", - name, val.Type(), dataVal.Type(), data) + return &ErrUnconvertibleType{ + Name: name, + Expected: val, + Value: data, + } } return nil @@ -875,9 +951,11 @@ func (d *Decoder) decodeComplex(name string, data interface{}, val reflect.Value case dataKind == reflect.Complex64: val.SetComplex(dataVal.Complex()) default: - return fmt.Errorf( - "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", - name, val.Type(), dataVal.Type(), data) + return &ErrUnconvertibleType{ + Name: name, + Expected: val, + Value: data, + } } return nil @@ -921,7 +999,11 @@ func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) er fallthrough default: - return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind()) + return &ErrUnconvertibleType{ + Name: name, + Expected: val, + Value: data, + } } } @@ -1007,7 +1089,10 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re // to the map value. v := dataVal.Field(i) if !v.Type().AssignableTo(valMap.Type().Elem()) { - return fmt.Errorf("cannot assign type '%s' to map value field of type '%s'", v.Type(), valMap.Type().Elem()) + return &ErrCannotDecode{ + Name: name + "." + f.Name, + Err: fmt.Errorf("cannot assign type %q to map value field of type %q", v.Type(), valMap.Type().Elem()), + } } tagValue := f.Tag.Get(d.config.TagName) @@ -1047,12 +1132,18 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re // The final type must be a struct if v.Kind() != reflect.Struct { - return fmt.Errorf("cannot squash non-struct type '%s'", v.Type()) + return &ErrCannotDecode{ + Name: name + "." + f.Name, + Err: fmt.Errorf("cannot squash non-struct type %q", v.Type()), + } } } else { if strings.Index(tagValue[index+1:], "remain") != -1 { if v.Kind() != reflect.Map { - return fmt.Errorf("error remain-tag field with invalid type: '%s'", v.Type()) + return &ErrCannotDecode{ + Name: name + "." + f.Name, + Err: fmt.Errorf("error remain-tag field with invalid type: %q", v.Type()), + } } ptr := v.MapRange() @@ -1172,9 +1263,11 @@ func (d *Decoder) decodeFunc(name string, data interface{}, val reflect.Value) e // into that. Then set the value of the pointer to this type. dataVal := reflect.Indirect(reflect.ValueOf(data)) if val.Type() != dataVal.Type() { - return fmt.Errorf( - "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", - name, val.Type(), dataVal.Type(), data) + return &ErrUnconvertibleType{ + Name: name, + Expected: val, + Value: data, + } } val.Set(dataVal) return nil @@ -1215,8 +1308,10 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) } } - return fmt.Errorf( - "'%s': source data must be an array or slice, got %s", name, dataValKind) + return &ErrCannotDecode{ + Name: name, + Err: fmt.Errorf("source data must be an array or slice, got %s", dataValKind), + } } // If the input value is nil, then don't allocate since empty != nil @@ -1283,13 +1378,17 @@ func (d *Decoder) decodeArray(name string, data interface{}, val reflect.Value) } } - return fmt.Errorf( - "'%s': source data must be an array or slice, got %s", name, dataValKind) + return &ErrCannotDecode{ + Name: name, + Err: fmt.Errorf("source data must be an array or slice, got %s", dataValKind), + } } if dataVal.Len() > arrayType.Len() { - return fmt.Errorf( - "'%s': expected source data to have length less or equal to %d, got %d", name, arrayType.Len(), dataVal.Len()) + return &ErrCannotDecode{ + Name: name, + Err: fmt.Errorf("expected source data to have length less or equal to %d, got %d", arrayType.Len(), dataVal.Len()), + } } // Make a new array to hold our result, same size as the original data. @@ -1354,16 +1453,20 @@ func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) return result default: - return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind()) + return &ErrCannotDecode{ + Name: name, + Err: fmt.Errorf("expected a map or struct, got %q", dataValKind), + } } } func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) error { dataValType := dataVal.Type() if kind := dataValType.Key().Kind(); kind != reflect.String && kind != reflect.Interface { - return fmt.Errorf( - "'%s' needs a map with string keys, has '%s' keys", - name, dataValType.Key().Kind()) + return &ErrCannotDecode{ + Name: name, + Err: fmt.Errorf("needs a map with string keys, has %q keys", kind), + } } dataValKeys := make(map[reflect.Value]struct{}) @@ -1436,7 +1539,10 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e structs = append(structs, fieldVal.Elem().Elem()) } default: - errs = append(errs, fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldVal.Kind())) + errs = append(errs, &ErrCannotDecode{ + Name: name + "." + fieldType.Name, + Err: fmt.Errorf("unsupported type for squash: %s", fieldVal.Kind()), + }) } continue } @@ -1543,8 +1649,10 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e } sort.Strings(keys) - err := fmt.Errorf("'%s' has invalid keys: %s", name, strings.Join(keys, ", ")) - errs = append(errs, err) + errs = append(errs, &ErrCannotDecode{ + Name: name, + Err: fmt.Errorf("has invalid keys: %s", strings.Join(keys, ", ")), + }) } if d.config.ErrorUnset && len(targetValKeysUnused) > 0 { @@ -1554,8 +1662,10 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e } sort.Strings(keys) - err := fmt.Errorf("'%s' has unset fields: %s", name, strings.Join(keys, ", ")) - errs = append(errs, err) + errs = append(errs, &ErrCannotDecode{ + Name: name, + Err: fmt.Errorf("has unset fields: %s", strings.Join(keys, ", ")), + }) } if err := errors.Join(errs...); err != nil { diff --git a/mapstructure_test.go b/mapstructure_test.go index 248afaa8..ae0a67e6 100644 --- a/mapstructure_test.go +++ b/mapstructure_test.go @@ -2604,8 +2604,11 @@ func TestInvalidType(t *testing.T) { errs := derr.Unwrap() - if errs[0].Error() != "'Vstring' expected type 'string', got unconvertible type 'int', value: '42'" { + var unconvertibleErr *ErrUnconvertibleType + if !errors.As(errs[0], &unconvertibleErr) { t.Errorf("got unexpected error: %s", err) + } else if unconvertibleErr.Expected.Type() != reflect.TypeOf("") { + t.Errorf("expected type should be string, got: %s", unconvertibleErr.Expected) } inputNegIntUint := map[string]interface{}{ @@ -2623,8 +2626,11 @@ func TestInvalidType(t *testing.T) { errs = derr.Unwrap() - if errs[0].Error() != "cannot parse 'Vuint', -42 overflows uint" { + var parseErr *ErrCannotParse + if !errors.As(errs[0], &parseErr) { t.Errorf("got unexpected error: %s", err) + } else if parseErr.Expected.Type() != reflect.TypeOf(uint(0)) { + t.Errorf("expected type should be uint, got: %s", parseErr.Expected) } inputNegFloatUint := map[string]interface{}{ @@ -2642,8 +2648,10 @@ func TestInvalidType(t *testing.T) { errs = derr.Unwrap() - if errs[0].Error() != "cannot parse 'Vuint', -42.000000 overflows uint" { + if !errors.As(errs[0], &parseErr) { t.Errorf("got unexpected error: %s", err) + } else if parseErr.Expected.Type() != reflect.TypeOf(uint(0)) { + t.Errorf("expected type should be uint, got: %s", parseErr.Expected) } } From b82cfe5c8e588a6eb9e52694753bc1f043a86487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sat, 14 Jun 2025 15:43:00 +0200 Subject: [PATCH 2/2] use DecodeError as internal interface for all errors. --- errors.go | 55 ++++++----- mapstructure.go | 214 ++++++++++++++++--------------------------- mapstructure_test.go | 19 ++-- 3 files changed, 123 insertions(+), 165 deletions(-) diff --git a/errors.go b/errors.go index 46e39c06..40b78d22 100644 --- a/errors.go +++ b/errors.go @@ -5,40 +5,53 @@ import ( "reflect" ) -// ErrCannotDecode is a generic error type that holds information about +// DecodeError is a generic error type that holds information about // a decoding error together with the name of the field that caused the error. -type ErrCannotDecode struct { - Name string - Err error +type DecodeError struct { + name string + err error } -func (e *ErrCannotDecode) Error() string { - return fmt.Sprintf("'%s': %s", e.Name, e.Err) +func newDecodeError(name string, err error) *DecodeError { + return &DecodeError{ + name: name, + err: err, + } } -// ErrCannotParse extends ErrCannotDecode to include additional information -// about the expected type and the actual value that could not be parsed. -type ErrCannotParse struct { - ErrCannotDecode +func (e *DecodeError) Name() string { + return e.name +} + +func (e *DecodeError) Unwrap() error { + return e.err +} + +func (e *DecodeError) Error() string { + return fmt.Sprintf("'%s' %s", e.name, e.err) +} + +// ParseError is an error type that indicates a value could not be parsed +// into the expected type. +type ParseError struct { Expected reflect.Value Value interface{} + Err error } -func (e *ErrCannotParse) Error() string { - return fmt.Sprintf("'%s' cannot parse '%s' as '%s': %s", - e.Name, e.Value, e.Expected.Type(), e.Err) +func (e *ParseError) Error() string { + return fmt.Sprintf("cannot parse '%s' as '%s': %s", + e.Value, e.Expected.Type(), e.Err) } -// ErrUnconvertibleType is an error type that indicates a value could not be -// converted to the expected type. It includes the name of the field, the -// expected type, and the actual value that was attempted to be converted. -type ErrUnconvertibleType struct { - Name string +// UnconvertibleTypeError is an error type that indicates a value could not be +// converted to the expected type. +type UnconvertibleTypeError struct { Expected reflect.Value Value interface{} } -func (e *ErrUnconvertibleType) Error() string { - return fmt.Sprintf("'%s' expected type '%s', got unconvertible type '%s', value: '%v'", - e.Name, e.Expected.Type(), reflect.TypeOf(e.Value), e.Value) +func (e *UnconvertibleTypeError) Error() string { + return fmt.Sprintf("expected type '%s', got unconvertible type '%s', value: '%v'", + e.Expected.Type(), reflect.TypeOf(e.Value), e.Value) } diff --git a/mapstructure.go b/mapstructure.go index 29e8b2d2..57e14bf1 100644 --- a/mapstructure.go +++ b/mapstructure.go @@ -525,10 +525,7 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e var err error input, err = d.cachedDecodeHook(inputVal, outVal) if err != nil { - return &ErrCannotDecode{ - Name: name, - Err: err, - } + return newDecodeError(name, err) } } if isNil(input) { @@ -566,10 +563,7 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e err = d.decodeFunc(name, input, outVal) default: // If we reached this point then we weren't able to decode it - return &ErrCannotDecode{ - Name: name, - Err: fmt.Errorf("unsupported type: %s", outputKind), - } + return newDecodeError(name, fmt.Errorf("unsupported type: %s", outputKind)) } // If we reached here, then we successfully decoded SOMETHING, so @@ -630,11 +624,10 @@ func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) dataValType := dataVal.Type() if !dataValType.AssignableTo(val.Type()) { - return &ErrUnconvertibleType{ - Name: name, + return newDecodeError(name, &UnconvertibleTypeError{ Expected: val, Value: data, - } + }) } val.Set(dataVal) @@ -685,11 +678,10 @@ func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) } if !converted { - return &ErrUnconvertibleType{ - Name: name, + return newDecodeError(name, &UnconvertibleTypeError{ Expected: val, Value: data, - } + }) } return nil @@ -723,35 +715,28 @@ func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) er if err == nil { val.SetInt(i) } else { - return &ErrCannotParse{ - ErrCannotDecode: ErrCannotDecode{ - Name: name, - Err: err, - }, + return newDecodeError(name, &ParseError{ Expected: val, Value: data, - } + Err: err, + }) } case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": jn := data.(json.Number) i, err := jn.Int64() if err != nil { - return &ErrCannotParse{ - ErrCannotDecode: ErrCannotDecode{ - Name: name, - Err: err, - }, + return newDecodeError(name, &ParseError{ Expected: val, Value: data, - } + Err: err, + }) } val.SetInt(i) default: - return &ErrUnconvertibleType{ - Name: name, + return newDecodeError(name, &UnconvertibleTypeError{ Expected: val, Value: data, - } + }) } return nil @@ -766,14 +751,11 @@ func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) e case dataKind == reflect.Int: i := dataVal.Int() if i < 0 && !d.config.WeaklyTypedInput { - return &ErrCannotParse{ - ErrCannotDecode: ErrCannotDecode{ - Name: name, - Err: fmt.Errorf("%d overflows uint", i), - }, + return newDecodeError(name, &ParseError{ Expected: val, Value: data, - } + Err: fmt.Errorf("%d overflows uint", i), + }) } val.SetUint(uint64(i)) case dataKind == reflect.Uint: @@ -781,14 +763,11 @@ func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) e case dataKind == reflect.Float32: f := dataVal.Float() if f < 0 && !d.config.WeaklyTypedInput { - return &ErrCannotParse{ - ErrCannotDecode: ErrCannotDecode{ - Name: name, - Err: fmt.Errorf("%f overflows uint", f), - }, + return newDecodeError(name, &ParseError{ Expected: val, Value: data, - } + Err: fmt.Errorf("%f overflows uint", f), + }) } val.SetUint(uint64(f)) case dataKind == reflect.Bool && d.config.WeaklyTypedInput: @@ -807,35 +786,28 @@ func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) e if err == nil { val.SetUint(i) } else { - return &ErrCannotParse{ - ErrCannotDecode: ErrCannotDecode{ - Name: name, - Err: err, - }, + return newDecodeError(name, &ParseError{ Expected: val, Value: data, - } + Err: err, + }) } case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": jn := data.(json.Number) i, err := strconv.ParseUint(string(jn), 0, 64) if err != nil { - return &ErrCannotParse{ - ErrCannotDecode: ErrCannotDecode{ - Name: name, - Err: err, - }, + return newDecodeError(name, &ParseError{ Expected: val, Value: data, - } + Err: err, + }) } val.SetUint(i) default: - return &ErrUnconvertibleType{ - Name: name, + return newDecodeError(name, &UnconvertibleTypeError{ Expected: val, Value: data, - } + }) } return nil @@ -861,21 +833,17 @@ func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) e } else if dataVal.String() == "" { val.SetBool(false) } else { - return &ErrCannotParse{ - ErrCannotDecode: ErrCannotDecode{ - Name: name, - Err: err, - }, + return newDecodeError(name, &ParseError{ Expected: val, Value: data, - } + Err: err, + }) } default: - return &ErrUnconvertibleType{ - Name: name, + return newDecodeError(name, &UnconvertibleTypeError{ Expected: val, Value: data, - } + }) } return nil @@ -909,35 +877,28 @@ func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value) if err == nil { val.SetFloat(f) } else { - return &ErrCannotParse{ - ErrCannotDecode: ErrCannotDecode{ - Name: name, - Err: err, - }, + return newDecodeError(name, &ParseError{ Expected: val, Value: data, - } + Err: err, + }) } case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": jn := data.(json.Number) i, err := jn.Float64() if err != nil { - return &ErrCannotParse{ - ErrCannotDecode: ErrCannotDecode{ - Name: name, - Err: err, - }, + return newDecodeError(name, &ParseError{ Expected: val, Value: data, - } + Err: err, + }) } val.SetFloat(i) default: - return &ErrUnconvertibleType{ - Name: name, + return newDecodeError(name, &UnconvertibleTypeError{ Expected: val, Value: data, - } + }) } return nil @@ -951,11 +912,10 @@ func (d *Decoder) decodeComplex(name string, data interface{}, val reflect.Value case dataKind == reflect.Complex64: val.SetComplex(dataVal.Complex()) default: - return &ErrUnconvertibleType{ - Name: name, + return newDecodeError(name, &UnconvertibleTypeError{ Expected: val, Value: data, - } + }) } return nil @@ -999,11 +959,10 @@ func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) er fallthrough default: - return &ErrUnconvertibleType{ - Name: name, + return newDecodeError(name, &UnconvertibleTypeError{ Expected: val, Value: data, - } + }) } } @@ -1089,10 +1048,10 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re // to the map value. v := dataVal.Field(i) if !v.Type().AssignableTo(valMap.Type().Elem()) { - return &ErrCannotDecode{ - Name: name + "." + f.Name, - Err: fmt.Errorf("cannot assign type %q to map value field of type %q", v.Type(), valMap.Type().Elem()), - } + return newDecodeError( + name+"."+f.Name, + fmt.Errorf("cannot assign type %q to map value field of type %q", v.Type(), valMap.Type().Elem()), + ) } tagValue := f.Tag.Get(d.config.TagName) @@ -1132,18 +1091,18 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re // The final type must be a struct if v.Kind() != reflect.Struct { - return &ErrCannotDecode{ - Name: name + "." + f.Name, - Err: fmt.Errorf("cannot squash non-struct type %q", v.Type()), - } + return newDecodeError( + name+"."+f.Name, + fmt.Errorf("cannot squash non-struct type %q", v.Type()), + ) } } else { if strings.Index(tagValue[index+1:], "remain") != -1 { if v.Kind() != reflect.Map { - return &ErrCannotDecode{ - Name: name + "." + f.Name, - Err: fmt.Errorf("error remain-tag field with invalid type: %q", v.Type()), - } + return newDecodeError( + name+"."+f.Name, + fmt.Errorf("error remain-tag field with invalid type: %q", v.Type()), + ) } ptr := v.MapRange() @@ -1263,11 +1222,10 @@ func (d *Decoder) decodeFunc(name string, data interface{}, val reflect.Value) e // into that. Then set the value of the pointer to this type. dataVal := reflect.Indirect(reflect.ValueOf(data)) if val.Type() != dataVal.Type() { - return &ErrUnconvertibleType{ - Name: name, + return newDecodeError(name, &UnconvertibleTypeError{ Expected: val, Value: data, - } + }) } val.Set(dataVal) return nil @@ -1308,10 +1266,8 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) } } - return &ErrCannotDecode{ - Name: name, - Err: fmt.Errorf("source data must be an array or slice, got %s", dataValKind), - } + return newDecodeError(name, + fmt.Errorf("source data must be an array or slice, got %s", dataValKind)) } // If the input value is nil, then don't allocate since empty != nil @@ -1378,17 +1334,13 @@ func (d *Decoder) decodeArray(name string, data interface{}, val reflect.Value) } } - return &ErrCannotDecode{ - Name: name, - Err: fmt.Errorf("source data must be an array or slice, got %s", dataValKind), - } + return newDecodeError(name, + fmt.Errorf("source data must be an array or slice, got %s", dataValKind)) } if dataVal.Len() > arrayType.Len() { - return &ErrCannotDecode{ - Name: name, - Err: fmt.Errorf("expected source data to have length less or equal to %d, got %d", arrayType.Len(), dataVal.Len()), - } + return newDecodeError(name, + fmt.Errorf("expected source data to have length less or equal to %d, got %d", arrayType.Len(), dataVal.Len())) } // Make a new array to hold our result, same size as the original data. @@ -1453,20 +1405,16 @@ func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) return result default: - return &ErrCannotDecode{ - Name: name, - Err: fmt.Errorf("expected a map or struct, got %q", dataValKind), - } + return newDecodeError(name, + fmt.Errorf("expected a map or struct, got %q", dataValKind)) } } func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) error { dataValType := dataVal.Type() if kind := dataValType.Key().Kind(); kind != reflect.String && kind != reflect.Interface { - return &ErrCannotDecode{ - Name: name, - Err: fmt.Errorf("needs a map with string keys, has %q keys", kind), - } + return newDecodeError(name, + fmt.Errorf("needs a map with string keys, has %q keys", kind)) } dataValKeys := make(map[reflect.Value]struct{}) @@ -1539,10 +1487,10 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e structs = append(structs, fieldVal.Elem().Elem()) } default: - errs = append(errs, &ErrCannotDecode{ - Name: name + "." + fieldType.Name, - Err: fmt.Errorf("unsupported type for squash: %s", fieldVal.Kind()), - }) + errs = append(errs, newDecodeError( + name+"."+fieldType.Name, + fmt.Errorf("unsupported type for squash: %s", fieldVal.Kind()), + )) } continue } @@ -1649,10 +1597,10 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e } sort.Strings(keys) - errs = append(errs, &ErrCannotDecode{ - Name: name, - Err: fmt.Errorf("has invalid keys: %s", strings.Join(keys, ", ")), - }) + errs = append(errs, newDecodeError( + name, + fmt.Errorf("has invalid keys: %s", strings.Join(keys, ", ")), + )) } if d.config.ErrorUnset && len(targetValKeysUnused) > 0 { @@ -1662,10 +1610,10 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e } sort.Strings(keys) - errs = append(errs, &ErrCannotDecode{ - Name: name, - Err: fmt.Errorf("has unset fields: %s", strings.Join(keys, ", ")), - }) + errs = append(errs, newDecodeError( + name, + fmt.Errorf("has unset fields: %s", strings.Join(keys, ", ")), + )) } if err := errors.Join(errs...); err != nil { diff --git a/mapstructure_test.go b/mapstructure_test.go index ae0a67e6..8239202d 100644 --- a/mapstructure_test.go +++ b/mapstructure_test.go @@ -2604,11 +2604,11 @@ func TestInvalidType(t *testing.T) { errs := derr.Unwrap() - var unconvertibleErr *ErrUnconvertibleType - if !errors.As(errs[0], &unconvertibleErr) { + var decoderErr *DecodeError + if !errors.As(errs[0], &decoderErr) { t.Errorf("got unexpected error: %s", err) - } else if unconvertibleErr.Expected.Type() != reflect.TypeOf("") { - t.Errorf("expected type should be string, got: %s", unconvertibleErr.Expected) + } else if errors.Is(decoderErr.Unwrap(), &UnconvertibleTypeError{}) { + t.Errorf("error should be UnconvertibleTypeError, got: %s", decoderErr.Unwrap()) } inputNegIntUint := map[string]interface{}{ @@ -2626,11 +2626,10 @@ func TestInvalidType(t *testing.T) { errs = derr.Unwrap() - var parseErr *ErrCannotParse - if !errors.As(errs[0], &parseErr) { + if !errors.As(errs[0], &decoderErr) { t.Errorf("got unexpected error: %s", err) - } else if parseErr.Expected.Type() != reflect.TypeOf(uint(0)) { - t.Errorf("expected type should be uint, got: %s", parseErr.Expected) + } else if errors.Is(decoderErr.Unwrap(), &ParseError{}) { + t.Errorf("error should be ParseError, got: %s", decoderErr.Unwrap()) } inputNegFloatUint := map[string]interface{}{ @@ -2648,10 +2647,8 @@ func TestInvalidType(t *testing.T) { errs = derr.Unwrap() - if !errors.As(errs[0], &parseErr) { + if !errors.As(errs[0], &decoderErr) { t.Errorf("got unexpected error: %s", err) - } else if parseErr.Expected.Type() != reflect.TypeOf(uint(0)) { - t.Errorf("expected type should be uint, got: %s", parseErr.Expected) } }