diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..b4e294bf --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +.tests +.vscode diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..88eadaa6 --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +ifeq ($(OS),Windows_NT) + rmdir := rmdir /s /q +else + rmdir := rm -rf +endif + +GOCMD=GOOS=$(GOOS) GOARCH=$(GOARCH) go + +all: test + +test: clean + mkdir -p .tests && \ + ${GOCMD} test -coverpkg=. -coverprofile=cover.out -outputdir=.tests ./... | tee .tests/report.test && \ + ${GOCMD} tool test2json -t < .tests/report.test > .tests/report.json + +clean: + $(rmdir) .tests \ No newline at end of file diff --git a/decode_hooks.go b/decode_hooks.go index 3a754ca7..d873880f 100644 --- a/decode_hooks.go +++ b/decode_hooks.go @@ -81,21 +81,28 @@ func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc { // If all hooks return an error, OrComposeDecodeHookFunc returns an error concatenating all error messages. func OrComposeDecodeHookFunc(ff ...DecodeHookFunc) DecodeHookFunc { return func(a, b reflect.Value) (interface{}, error) { - var allErrs string var out interface{} var err error + allErrs := NewDecodingErrors().SetFormatter(func(e *DecodingErrors) string { + errsStr := make([]string, len(e.errors)) + for i := 0; i < len(e.errors); i++ { + errsStr[i] = e.errors[i].Error() + } + return strings.Join(errsStr, "\n") + }) for _, f := range ff { out, err = DecodeHookExec(f, a, b) if err != nil { - allErrs += err.Error() + "\n" + allErrs.Append(err) continue } - return out, nil } - - return nil, errors.New(allErrs) + if allErrs.Len() > 0 { + return nil, allErrs + } + return nil, nil } } diff --git a/decode_hooks_test.go b/decode_hooks_test.go index bf029526..706024a5 100644 --- a/decode_hooks_test.go +++ b/decode_hooks_test.go @@ -167,7 +167,7 @@ func TestOrComposeDecodeHookFunc_err(t *testing.T) { if err == nil { t.Fatalf("bad: should return an error") } - if err.Error() != "f1 error\nf2 error\n" { + if err.Error() != "f1 error\nf2 error" { t.Fatalf("bad: %s", err) } } diff --git a/error.go b/error.go index 47a99e5a..751d5ad6 100644 --- a/error.go +++ b/error.go @@ -1,50 +1,504 @@ package mapstructure import ( - "errors" "fmt" + "reflect" "sort" "strings" ) +type NamespaceKey interface{} +type NamespaceIdx int + +type NamespaceFld struct { + useTag bool + name string + tag string +} + +func NewNamespaceFld(name string) *NamespaceFld { + return &NamespaceFld{ + name: name, + } +} + +func (nf *NamespaceFld) UseTag(useTag bool) *NamespaceFld { + nf.useTag = useTag + return nf +} + +func (nf *NamespaceFld) SetTag(tag string) *NamespaceFld { + nf.tag = tag + return nf +} + +func (nf *NamespaceFld) GetName() string { + return nf.name +} + +func (nf *NamespaceFld) GetTag() string { + return nf.tag +} + +func (nf *NamespaceFld) String() string { + // tag wille be used if it's defined + if nf.useTag && nf.tag != "" { + return nf.tag + } + return nf.name +} + +type NamespaceFormatter func(ns Namespace) string + +func NamespaceFormatterDefault(ns Namespace) string { + var result string + + valueToStr := func(item interface{}, sep string) { + switch value := item.(type) { + case NamespaceFld: + result += sep + value.String() + case NamespaceIdx: + result += fmt.Sprintf("[%d]", int(value)) + case NamespaceKey: + result += fmt.Sprintf("[%v]", value) + } + } + if len(ns.items) > 0 { + valueToStr(ns.items[0], "") + } + for i := 1; i < len(ns.items); i++ { + valueToStr(ns.items[i], ".") + } + return result +} + +type Namespace struct { + formatter NamespaceFormatter + items []interface{} +} + +func NewNamespace() *Namespace { + return &Namespace{ + formatter: NamespaceFormatterDefault, + } +} + +func (ns *Namespace) SetFormatter(formatter NamespaceFormatter) *Namespace { + if formatter != nil { + ns.formatter = formatter + } + return ns +} + +func (ns *Namespace) AppendNamespace(namespace Namespace) *Namespace { + ns.items = append(ns.items, namespace.items...) + return ns +} + +func (ns *Namespace) PrependNamespace(namespace Namespace) *Namespace { + ns.items = append(namespace.items, ns.items...) + return ns +} + +func (ns *Namespace) AppendKey(keys ...interface{}) *Namespace { + for _, k := range keys { + ns.items = append(ns.items, NamespaceKey(k)) + } + return ns +} + +func (ns *Namespace) PrependKey(keys ...interface{}) *Namespace { + ppns := (&Namespace{}).AppendKey(keys...) + ns.items = append(ppns.items, ns.items...) + return ns +} + +func (ns *Namespace) AppendIdx(idxs ...int) *Namespace { + for _, i := range idxs { + ns.items = append(ns.items, NamespaceIdx(i)) + } + return ns +} + +func (ns *Namespace) PrependIdx(idxs ...int) *Namespace { + ppns := (&Namespace{}).AppendIdx(idxs...) + ns.items = append(ppns.items, ns.items...) + return ns +} + +func (ns *Namespace) AppendFld(flds ...NamespaceFld) *Namespace { + for _, f := range flds { + ns.items = append(ns.items, f) + } + return ns +} + +func (ns *Namespace) PrependFld(flds ...NamespaceFld) *Namespace { + ppns := (&Namespace{}).AppendFld(flds...) + ns.items = append(ppns.items, ns.items...) + return ns +} + +// AppendFldName() just calls AppendFld(): makes the syntax a little lighter +func (ns *Namespace) AppendFldName(fldNames ...string) *Namespace { + for _, fn := range fldNames { + ns.items = append(ns.items, *NewNamespaceFld(fn)) + } + return ns +} + +// PrependFldName() just calls PrependFld(): makes the syntax a little lighter +func (ns *Namespace) PrependFldName(fldNames ...string) *Namespace { + ns.items = append(NewNamespace().AppendFldName(fldNames...).items, ns.items...) + return ns +} + +// UseFldTag() set preference on using the tag in place of the field name +// when the former it's defined. It affects the vale returned by String() +func (ns *Namespace) UseFldTag(useFldTag bool) *Namespace { + for i, item := range ns.items { + if fld, ok := item.(NamespaceFld); ok { + ns.items[i] = *fld.UseTag(useFldTag) + } + } + return ns +} + +func (ns *Namespace) Len() int { + return len(ns.items) +} + +// Get() return the i-th namespace item. If i < 0 return the last item. +func (ns *Namespace) Get(i int) interface{} { + if i < 0 { + i = len(ns.items) - 1 + } + if i < 0 || i >= len(ns.items) { + return nil + } + return ns.items[i] +} + +func (ns *Namespace) String() string { + return ns.formatter(*ns) +} + +func (ns *Namespace) Duplicate() *Namespace { + ns_ := *ns + ns_.items = ns.items[:] + return &ns_ +} + +type LocalizedError interface { + SetNamespace(ns Namespace) LocalizedError + PrependNamespace(ns Namespace) LocalizedError + AppendNamespace(ns Namespace) LocalizedError + SetNamespaceUseFldTag(useFieldTag bool) LocalizedError + Error() string +} + +func AsLocalizedError(err error) LocalizedError { + if e, ok := err.(LocalizedError); ok { + return e + } + return AsDecodingError(err) +} + +type DecodingErrorKind int + +const ( + // destination type is not supported + DecodingErrorUnsupportedType DecodingErrorKind = iota + // source type is unexpected: can't be assigned/converted to destination + DecodingErrorUnexpectedType + // source value of a different type cannot be parsed into the destination type (WeaklyTypedInput) + DecodingErrorParseFailure + // source value of a different type cannot be parsed into the destination type because of overflow (WeaklyTypedInput) + DecodingErrorParseOverflow + // source value of a different type cannot be decoded into the destination type through encoding/json pkg + DecodingErrorJSONDecodeFailure + // failed to squash a source struct field into destination (field's not a struct) + DecodingErrorSrcSquashFailure + // failed to squash a destination struct field into destination (field's not a struct) + DecodingErrorDstSquashFailure + // destination value is an array and source value is of greater size + DecodingErrorIncompatibleSize + // when destination is a struct and source is a map some keys of which do not correspond to any struct field (with ErrorUnused flag) + DecodingErrorUnusedKeys + // when destination is a struct and source is a map whose keys do not cover all the struct fields (with ErrorUnset flag) + DecodingErrorUnsetFields + // a generic error is a not better specified one + DecodingErrorGeneric + // custom user error, which also marks the border between internal mapstructure errors and new possible user defined errors + DecodingErrorCustom +) + +func (k DecodingErrorKind) String() string { + switch k { + case DecodingErrorUnsupportedType: + return "unsupported type" + case DecodingErrorUnexpectedType: + return "unexpected type" + case DecodingErrorParseFailure: + return "parse failure" + case DecodingErrorJSONDecodeFailure: + return "JSON decode failure" + case DecodingErrorSrcSquashFailure: + return "source squash failure" + case DecodingErrorDstSquashFailure: + return "destination squash failure" + case DecodingErrorIncompatibleSize: + return "incompatible size" + case DecodingErrorUnusedKeys: + return "unused keys" + case DecodingErrorUnsetFields: + return "unset fields" + case DecodingErrorGeneric: + return "generic error" + case DecodingErrorCustom: + fallthrough + default: + return fmt.Sprintf("custom(%d)", k) + } +} + +type DecodingError struct { + namespace Namespace // namespace refers to the destination + kind DecodingErrorKind + header string + srcValue interface{} + dstValue interface{} + error error +} + +func NewDecodingError(kind DecodingErrorKind) *DecodingError { + return &DecodingError{ + kind: kind, + error: fmt.Errorf("%s", kind), + } +} + +func AsDecodingError(err error) *DecodingError { + if err == nil { + return nil + } + if e, ok := err.(*DecodingError); ok { + return e + } + return NewDecodingError(DecodingErrorGeneric).Wrap(err) +} + +func (e *DecodingError) Format(format string, args ...interface{}) *DecodingError { + return &DecodingError{ + error: fmt.Errorf(format, args...), + } +} + +func (e *DecodingError) Wrap(err error) *DecodingError { + return &DecodingError{error: err} +} + +func (e *DecodingError) SetHeader(format string, args ...interface{}) *DecodingError { + e.header = fmt.Sprintf(format, args...) + return e +} + +func (e *DecodingError) SetSrcValue(value interface{}) *DecodingError { + e.srcValue = value + return e +} + +func (e *DecodingError) SetDstValue(value interface{}) *DecodingError { + e.srcValue = value + return e +} + +// Duplicate() won't duplicate any wrapped error in DecodingError for it doesn't +// know how to do it without loosing the error type (i.e. via errors.New()). Same +// applies to srcValue & dstValue. +func (e *DecodingError) Duplicate() *DecodingError { + e_ := *e + e_.namespace = *e.namespace.Duplicate() + return &e_ +} + +func (e *DecodingError) IsCustom() bool { + return e.kind >= DecodingErrorCustom +} + +func (e *DecodingError) GetKind() DecodingErrorKind { + return e.kind +} + +func (e *DecodingError) GetSrcValue() interface{} { + return e.srcValue +} + +func (e *DecodingError) GetDstValue() interface{} { + return e.dstValue +} + +func (e *DecodingError) GetNamespace() *Namespace { + return e.namespace.Duplicate() +} + +func (e *DecodingError) SetNamespace(namespace Namespace) LocalizedError { + e.namespace = *namespace.Duplicate() + return e +} + +func (e *DecodingError) PrependNamespace(ns Namespace) LocalizedError { + e.namespace.PrependNamespace(ns) + return e +} + +func (e *DecodingError) AppendNamespace(ns Namespace) LocalizedError { + e.namespace.AppendNamespace(ns) + return e +} + +func (e *DecodingError) SetNamespaceUseFldTag(useFldTag bool) LocalizedError { + e.namespace.UseFldTag(useFldTag) + return e +} + +func (e *DecodingError) Error() string { + if e.namespace.Len() > 0 { + return fmt.Sprintf("@'%s': %s%s", &e.namespace, e.header, e.error.Error()) + } + return e.error.Error() +} + +func (e *DecodingError) Unwrap() error { + return e.error +} + // Error implements the error interface and can represents multiple // errors that occur in the course of a single decode. -type Error struct { - Errors []string + +type DecodingErrorsFormatter func(e *DecodingErrors) string + +func DefaultDecodingErrorsFormatter(e *DecodingErrors) string { + nErrors := len(e.errors) + points := make([]string, nErrors) + for i := 0; i < nErrors; i++ { + points[i] = fmt.Sprintf("* %s", e.errors[i].Error()) + } + sort.Strings(points) + return fmt.Sprintf("%d error(s) decoding:\n\n%s", + nErrors, strings.Join(points, "\n")) +} + +type DecodingErrors struct { + formatter DecodingErrorsFormatter + errors []DecodingError } -func (e *Error) Error() string { - points := make([]string, len(e.Errors)) - for i, err := range e.Errors { - points[i] = fmt.Sprintf("* %s", err) +func NewDecodingErrors() *DecodingErrors { + return &DecodingErrors{} +} + +func AsDecodingErrors(err error) *DecodingErrors { + if err == nil { + return nil } + if e, ok := err.(*DecodingErrors); ok { + return e + } + return NewDecodingErrors().Append(err) +} - sort.Strings(points) - return fmt.Sprintf( - "%d error(s) decoding:\n\n%s", - len(e.Errors), strings.Join(points, "\n")) +func (e *DecodingErrors) SetFormatter(formatter DecodingErrorsFormatter) *DecodingErrors { + e.formatter = formatter + return e } -// WrappedErrors implements the errwrap.Wrapper interface to make this -// return value more useful with the errwrap and go-multierror libraries. -func (e *Error) WrappedErrors() []error { - if e == nil { +func (e *DecodingErrors) Len() int { + return len(e.errors) +} + +func (e *DecodingErrors) Get(i int) *DecodingError { + if i >= len(e.errors) { return nil } + return e.errors[i].Duplicate() +} - result := make([]error, len(e.Errors)) - for i, e := range e.Errors { - result[i] = errors.New(e) +func (e *DecodingErrors) Append(err error) *DecodingErrors { + if err == nil || + (reflect.TypeOf(err).Kind() == reflect.Ptr && + reflect.ValueOf(err).IsNil()) { + return e + } + switch err_ := err.(type) { + case *DecodingErrors: + e.errors = append(e.errors, err_.errors...) + default: + e.errors = append(e.errors, *AsDecodingError(err)) } + return e +} - return result +// Duplicate() duplicate DecodingErrors by duplicating each DecodedError stored +// in it. Please check also DecodedError.Duplicate() +func (e *DecodingErrors) Duplicate() *DecodingErrors { + e_ := &DecodingErrors{ + errors: make([]DecodingError, len(e.errors)), + } + for i, err := range e.errors { + e_.errors[i] = *err.Duplicate() + } + return e_ } -func appendErrors(errors []string, err error) []string { - switch e := err.(type) { - case *Error: - return append(errors, e.Errors...) - default: - return append(errors, e.Error()) +func (e *DecodingErrors) SetNamespace(ns Namespace) LocalizedError { + errors := e.errors + for i, err := range e.errors { + errors[i] = *err.SetNamespace(ns).(*DecodingError) } + return e +} + +func (e *DecodingErrors) PrependNamespace(ns Namespace) LocalizedError { + errors := e.errors + for i, err := range e.errors { + errors[i] = *err.PrependNamespace(ns).(*DecodingError) + } + return e +} + +func (e *DecodingErrors) AppendNamespace(ns Namespace) LocalizedError { + errors := e.errors + for i, err := range e.errors { + errors[i] = *err.AppendNamespace(ns).(*DecodingError) + } + return e +} + +func (e *DecodingErrors) SetNamespaceUseFldTag(useFldTag bool) LocalizedError { + for i, err := range e.errors { + e.errors[i] = *err.SetNamespaceUseFldTag(useFldTag).(*DecodingError) + } + return e +} + +func (e *DecodingErrors) Error() string { + formatter := e.formatter + if formatter == nil { + formatter = DefaultDecodingErrorsFormatter + } + return formatter(e) +} + +// WrappedErrors implements the errwrap.Wrapper interface to make this +// return value more useful with the errwrap and go-multierror libraries. +func (e *DecodingErrors) WrappedErrors() []error { + if e == nil { + return nil + } + result := make([]error, len(e.errors)) + for i, e := range e.errors { + result[i] = e.Duplicate() + } + return result } diff --git a/mapstructure.go b/mapstructure.go index 7581806a..70034db5 100644 --- a/mapstructure.go +++ b/mapstructure.go @@ -161,7 +161,6 @@ package mapstructure import ( "encoding/json" "errors" - "fmt" "reflect" "sort" "strconv" @@ -414,11 +413,11 @@ func NewDecoder(config *DecoderConfig) (*Decoder, error) { // Decode decodes the given raw interface to the target pointer specified // by the configuration. func (d *Decoder) Decode(input interface{}) error { - return d.decode("", input, reflect.ValueOf(d.config.Result).Elem()) + return d.decode(*NewNamespace(), input, reflect.ValueOf(d.config.Result).Elem()) } // Decodes an unknown data type into a specific reflection value. -func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) error { +func (d *Decoder) decode(ns Namespace, input interface{}, outVal reflect.Value) error { var inputVal reflect.Value if input != nil { inputVal = reflect.ValueOf(input) @@ -436,8 +435,8 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e if d.config.ZeroFields { outVal.Set(reflect.Zero(outVal.Type())) - if d.config.Metadata != nil && name != "" { - d.config.Metadata.Keys = append(d.config.Metadata.Keys, name) + if d.config.Metadata != nil && ns.Len() > 0 { + d.config.Metadata.Keys = append(d.config.Metadata.Keys, ns.String()) } } return nil @@ -447,8 +446,8 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e // If the input value is invalid, then we just set the value // to be the zero value. outVal.Set(reflect.Zero(outVal.Type())) - if d.config.Metadata != nil && name != "" { - d.config.Metadata.Keys = append(d.config.Metadata.Keys, name) + if d.config.Metadata != nil && ns.Len() > 0 { + d.config.Metadata.Keys = append(d.config.Metadata.Keys, ns.String()) } return nil } @@ -457,8 +456,12 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e // We have a DecodeHook, so let's pre-process the input. var err error input, err = DecodeHookExec(d.config.DecodeHook, inputVal, outVal) + // Hooks might return *DecodinfErrors, *DecodingError or a generic error. The latter + // needs to be wrapped into a *DecodingError. Finally, as the hooks do not receive the + // namespace as parameter (hence the namespace of the returned errors is relative), we + // must update the errors namespace before to return them to the caller. if err != nil { - return fmt.Errorf("error decoding '%s': %w", name, err) + return AsDecodingErrors(AsLocalizedError(err).PrependNamespace(ns)) } } @@ -467,46 +470,51 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e addMetaKey := true switch outputKind { case reflect.Bool: - err = d.decodeBool(name, input, outVal) + err = d.decodeBool(*ns.Duplicate(), input, outVal) case reflect.Interface: - err = d.decodeBasic(name, input, outVal) + err = d.decodeBasic(*ns.Duplicate(), input, outVal) case reflect.String: - err = d.decodeString(name, input, outVal) + err = d.decodeString(*ns.Duplicate(), input, outVal) case reflect.Int: - err = d.decodeInt(name, input, outVal) + err = d.decodeInt(*ns.Duplicate(), input, outVal) case reflect.Uint: - err = d.decodeUint(name, input, outVal) + err = d.decodeUint(*ns.Duplicate(), input, outVal) case reflect.Float32: - err = d.decodeFloat(name, input, outVal) + err = d.decodeFloat(*ns.Duplicate(), input, outVal) case reflect.Struct: - err = d.decodeStruct(name, input, outVal) + err = d.decodeStruct(*ns.Duplicate(), input, outVal) case reflect.Map: - err = d.decodeMap(name, input, outVal) + err = d.decodeMap(*ns.Duplicate(), input, outVal) case reflect.Ptr: - addMetaKey, err = d.decodePtr(name, input, outVal) + addMetaKey, err = d.decodePtr(*ns.Duplicate(), input, outVal) case reflect.Slice: - err = d.decodeSlice(name, input, outVal) + err = d.decodeSlice(*ns.Duplicate(), input, outVal) case reflect.Array: - err = d.decodeArray(name, input, outVal) + err = d.decodeArray(*ns.Duplicate(), input, outVal) case reflect.Func: - err = d.decodeFunc(name, input, outVal) + err = d.decodeFunc(*ns.Duplicate(), 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 AsDecodingErrors(NewDecodingError(DecodingErrorUnsupportedType).Format("unsupported type: '%s'", + outputKind).SetSrcValue( + input).SetDstValue( + outVal.Interface()).SetNamespace(ns)) } // If we reached here, then we successfully decoded SOMETHING, so // mark the key as used if we're tracking metainput. - if addMetaKey && d.config.Metadata != nil && name != "" { - d.config.Metadata.Keys = append(d.config.Metadata.Keys, name) + if addMetaKey && d.config.Metadata != nil && ns.Len() > 0 { + d.config.Metadata.Keys = append(d.config.Metadata.Keys, ns.String()) } - - return err + if err == nil { + return nil + } + return AsDecodingErrors(err) } // This decodes a basic type (bool, int, string, etc.) and sets the // value to "data" of that type. -func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decodeBasic(ns Namespace, data interface{}, val reflect.Value) error { if val.IsValid() && val.Elem().IsValid() { elem := val.Elem() @@ -529,7 +537,7 @@ func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) // Decode. If we have an error then return. We also return right // away if we're not a copy because that means we decoded directly. - if err := d.decode(name, data, elem); err != nil || !copied { + if err := d.decode(*ns.Duplicate(), data, elem); err != nil || !copied { return err } @@ -553,16 +561,18 @@ 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 NewDecodingError(DecodingErrorUnexpectedType).Format( + "expected type '%s', got '%s'", + val.Type(), dataValType).SetSrcValue( + data).SetDstValue( + val.Interface()).SetNamespace(ns) } val.Set(dataVal) return nil } -func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decodeString(ns Namespace, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) @@ -606,15 +616,17 @@ 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 NewDecodingError(DecodingErrorUnexpectedType).Format( + "expected type '%s', got unconvertible type '%s', value: '%v'", + val.Type(), dataVal.Type(), data).SetSrcValue( + data).SetDstValue( + val.Interface()).SetNamespace(ns) } return nil } -func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decodeInt(ns Namespace, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) dataType := dataVal.Type() @@ -642,26 +654,33 @@ 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 NewDecodingError(DecodingErrorParseFailure).Wrap(err).SetHeader( + "cannot parse as int: ").SetSrcValue( + data).SetDstValue( + val.Interface()).SetNamespace(ns) } 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 NewDecodingError(DecodingErrorJSONDecodeFailure).Wrap(err).SetHeader( + "cannot decode json.Number: ").SetSrcValue( + data).SetDstValue( + val.Interface()).SetNamespace(ns) } val.SetInt(i) default: - return fmt.Errorf( - "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", - name, val.Type(), dataVal.Type(), data) + return NewDecodingError(DecodingErrorUnexpectedType).Format( + "expected type '%s', got unconvertible type '%s', value: '%v'", + val.Type(), dataVal.Type(), data).SetSrcValue( + data).SetDstValue( + val.Interface()).SetNamespace(ns) } return nil } -func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decodeUint(ns Namespace, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) dataType := dataVal.Type() @@ -670,8 +689,10 @@ 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 NewDecodingError(DecodingErrorParseOverflow).Format("cannot parse: %d overflows uint", + i).SetSrcValue( + data).SetDstValue( + val.Interface()).SetNamespace(ns) } val.SetUint(uint64(i)) case dataKind == reflect.Uint: @@ -679,8 +700,10 @@ 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 NewDecodingError(DecodingErrorParseOverflow).Format("cannot parse: %f overflows uint", + f).SetSrcValue( + data).SetDstValue( + val.Interface()).SetNamespace(ns) } val.SetUint(uint64(f)) case dataKind == reflect.Bool && d.config.WeaklyTypedInput: @@ -699,26 +722,33 @@ 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 NewDecodingError(DecodingErrorParseFailure).Wrap(err).SetHeader( + "cannot parse as uint: ").SetSrcValue( + data).SetDstValue( + val.Interface()).SetNamespace(ns) } 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 NewDecodingError(DecodingErrorJSONDecodeFailure).Wrap(err).SetHeader( + "cannot parse as json.Number: ").SetSrcValue( + data).SetDstValue( + val.Interface()).SetNamespace(ns) } val.SetUint(i) default: - return fmt.Errorf( - "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", - name, val.Type(), dataVal.Type(), data) + return NewDecodingError(DecodingErrorUnexpectedType).Format( + "expected type '%s', got unconvertible type '%s', value: '%v'", + val.Type(), dataVal.Type(), data).SetSrcValue( + data).SetDstValue( + val.Interface()).SetNamespace(ns) } return nil } -func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decodeBool(ns Namespace, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) @@ -738,18 +768,23 @@ 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 NewDecodingError(DecodingErrorParseFailure).Wrap(err).SetHeader( + "cannot parse as bool: ").SetSrcValue( + data).SetDstValue( + val.Interface()).SetNamespace(ns) } default: - return fmt.Errorf( - "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", - name, val.Type(), dataVal.Type(), data) + return NewDecodingError(DecodingErrorUnexpectedType).Format( + "expected type '%s', got unconvertible type '%s', value: '%v'", + val.Type(), dataVal.Type(), data).SetSrcValue( + data).SetDstValue( + val.Interface()).SetNamespace(ns) } return nil } -func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decodeFloat(ns Namespace, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) dataType := dataVal.Type() @@ -777,26 +812,33 @@ 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 NewDecodingError(DecodingErrorParseFailure).Wrap(err).SetHeader( + "cannot parse as float: ").SetSrcValue( + data).SetDstValue( + val.Interface()).SetNamespace(ns) } 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 NewDecodingError(DecodingErrorJSONDecodeFailure).Wrap(err).SetHeader( + "cannot parse as json.Number: ").SetSrcValue( + data).SetDstValue( + val.Interface()).SetNamespace(ns) } val.SetFloat(i) default: - return fmt.Errorf( - "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", - name, val.Type(), dataVal.Type(), data) + return NewDecodingError(DecodingErrorUnexpectedType).Format( + "expected type '%s', got unconvertible type '%s', value: '%v'", + val.Type(), dataVal.Type(), data).SetSrcValue( + data).SetDstValue( + val.Interface()).SetNamespace(ns) } return nil } -func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decodeMap(ns Namespace, data interface{}, val reflect.Value) error { valType := val.Type() valKeyType := valType.Key() valElemType := valType.Elem() @@ -815,24 +857,28 @@ func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) er dataVal := reflect.Indirect(reflect.ValueOf(data)) switch dataVal.Kind() { case reflect.Map: - return d.decodeMapFromMap(name, dataVal, val, valMap) + return d.decodeMapFromMap(*ns.Duplicate(), dataVal, val, valMap) case reflect.Struct: - return d.decodeMapFromStruct(name, dataVal, val, valMap) + return d.decodeMapFromStruct(*ns.Duplicate(), dataVal, val, valMap) case reflect.Array, reflect.Slice: if d.config.WeaklyTypedInput { - return d.decodeMapFromSlice(name, dataVal, val, valMap) + return d.decodeMapFromSlice(*ns.Duplicate(), dataVal, val, valMap) } fallthrough default: - return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind()) + return NewDecodingError(DecodingErrorUnexpectedType).Format( + "expected a map, got '%s'", + dataVal.Kind()).SetSrcValue( + data).SetDstValue( + val.Interface()).SetNamespace(ns) } } -func (d *Decoder) decodeMapFromSlice(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error { +func (d *Decoder) decodeMapFromSlice(ns Namespace, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error { // Special case for BC reasons (covered by tests) if dataVal.Len() == 0 { val.Set(valMap) @@ -840,9 +886,8 @@ func (d *Decoder) decodeMapFromSlice(name string, dataVal reflect.Value, val ref } for i := 0; i < dataVal.Len(); i++ { - err := d.decode( - name+"["+strconv.Itoa(i)+"]", - dataVal.Index(i).Interface(), val) + // namespace refers to the "to" value not the "from" + err := d.decode(*ns.Duplicate(), dataVal.Index(i).Interface(), val) if err != nil { return err } @@ -851,13 +896,13 @@ func (d *Decoder) decodeMapFromSlice(name string, dataVal reflect.Value, val ref return nil } -func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error { +func (d *Decoder) decodeMapFromMap(ns Namespace, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error { valType := val.Type() valKeyType := valType.Key() valElemType := valType.Elem() // Accumulate errors - errors := make([]string, 0) + errors := NewDecodingErrors() // If the input data is empty, then we just match what the input data is. if dataVal.Len() == 0 { @@ -874,20 +919,19 @@ func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val refle } for _, k := range dataVal.MapKeys() { - fieldName := name + "[" + k.String() + "]" - // First decode the key into the proper type currentKey := reflect.Indirect(reflect.New(valKeyType)) - if err := d.decode(fieldName, k.Interface(), currentKey); err != nil { - errors = appendErrors(errors, err) + + if err := d.decode(*ns.Duplicate().AppendKey(k.Interface()), k.Interface(), currentKey); err != nil { + errors.Append(err) continue } // Next decode the data into the proper type v := dataVal.MapIndex(k).Interface() currentVal := reflect.Indirect(reflect.New(valElemType)) - if err := d.decode(fieldName, v, currentVal); err != nil { - errors = appendErrors(errors, err) + if err := d.decode(*ns.Duplicate().AppendKey(k.Interface()), v, currentVal); err != nil { + errors.Append(err) continue } @@ -897,15 +941,13 @@ func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val refle // Set the built up map to the value val.Set(valMap) - // If we had errors, return those - if len(errors) > 0 { - return &Error{errors} + if errors.Len() > 0 { + return errors } - return nil } -func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error { +func (d *Decoder) decodeMapFromStruct(ns Namespace, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error { typ := dataVal.Type() for i := 0; i < typ.NumField(); i++ { // Get the StructField first since this is a cheap operation. If the @@ -919,7 +961,14 @@ 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()) + // We localize the error at map level. The source value will be the whole structure and + // the destination the map, because if just one struct field is not assignable then the + // whole struct cannot be mapped. + return NewDecodingError(DecodingErrorUnexpectedType).Format( + "cannot assign field '%s' of type '%s' to map value of type '%s'", + f.Name, v.Type(), valMap.Type().Elem()).SetSrcValue( + dataVal.Interface()).SetDstValue( + val.Interface()).SetNamespace(ns) } tagValue := f.Tag.Get(d.config.TagName) @@ -932,7 +981,7 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re // If Squash is set in the config, we squash the field down. squash := d.config.Squash && v.Kind() == reflect.Struct && f.Anonymous - v = dereferencePtrToStructIfNeeded(v, d.config.TagName) + dv := dereferencePtrToStructIfNeeded(v, d.config.TagName) // Determine the name of the key in the map if index := strings.Index(tagValue, ","); index != -1 { @@ -940,7 +989,7 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re continue } // If "omitempty" is specified in the tag, it ignores empty values. - if strings.Index(tagValue[index+1:], "omitempty") != -1 && isEmptyValue(v) { + if strings.Index(tagValue[index+1:], "omitempty") != -1 && isEmptyValue(dv) { continue } @@ -948,13 +997,18 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re squash = squash || strings.Index(tagValue[index+1:], "squash") != -1 if squash { // When squashing, the embedded type can be a pointer to a struct. - if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct { - v = v.Elem() + if dv.Kind() == reflect.Ptr && dv.Elem().Kind() == reflect.Struct { + dv = dv.Elem() } // The final type must be a struct - if v.Kind() != reflect.Struct { - return fmt.Errorf("cannot squash non-struct type '%s'", v.Type()) + if dv.Kind() != reflect.Struct { + // As before, we localize the error at the map level + return NewDecodingError(DecodingErrorSrcSquashFailure).Format( + "cannot squash non-struct type '%s'", + dv.Type()).SetSrcValue( + dataVal.Interface()).SetDstValue( + val.Interface()).SetNamespace(ns) } } if keyNameTagValue := tagValue[:index]; keyNameTagValue != "" { @@ -967,11 +1021,11 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re keyName = tagValue } - switch v.Kind() { + switch dv.Kind() { // this is an embedded struct, so handle it differently case reflect.Struct: - x := reflect.New(v.Type()) - x.Elem().Set(v) + x := reflect.New(dv.Type()) + x.Elem().Set(dv) vType := valMap.Type() vKeyType := vType.Key() @@ -986,7 +1040,7 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re addrVal := reflect.New(vMap.Type()) reflect.Indirect(addrVal).Set(vMap) - err := d.decode(keyName, x.Interface(), reflect.Indirect(addrVal)) + err := d.decode(*ns.Duplicate().AppendKey(keyName), x.Interface(), reflect.Indirect(addrVal)) if err != nil { return err } @@ -1004,7 +1058,7 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re } default: - valMap.SetMapIndex(reflect.ValueOf(keyName), v) + valMap.SetMapIndex(reflect.ValueOf(keyName), dv) } } @@ -1015,7 +1069,7 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re return nil } -func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) (bool, error) { +func (d *Decoder) decodePtr(ns Namespace, data interface{}, val reflect.Value) (bool, error) { // If the input data is nil, then we want to just set the output // pointer to be nil as well. isNil := data == nil @@ -1049,33 +1103,35 @@ func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) (b realVal = reflect.New(valElemType) } - if err := d.decode(name, data, reflect.Indirect(realVal)); err != nil { + if err := d.decode(*ns.Duplicate(), data, reflect.Indirect(realVal)); err != nil { return false, err } val.Set(realVal) } else { - if err := d.decode(name, data, reflect.Indirect(val)); err != nil { + if err := d.decode(*ns.Duplicate(), data, reflect.Indirect(val)); err != nil { return false, err } } return false, nil } -func (d *Decoder) decodeFunc(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decodeFunc(ns Namespace, data interface{}, val reflect.Value) error { // Create an element of the concrete (non pointer) type and decode // 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 NewDecodingError(DecodingErrorUnexpectedType).Format( + "expected type '%s', got unconvertible type '%s', value: '%v'", + val.Type(), dataVal.Type(), data).SetSrcValue( + data).SetDstValue( + val.Interface()).SetNamespace(ns) } val.Set(dataVal) return nil } -func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decodeSlice(ns Namespace, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataValKind := dataVal.Kind() valType := val.Type() @@ -1097,21 +1153,24 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) return nil } // Create slice of maps of other sizes - return d.decodeSlice(name, []interface{}{data}, val) + return d.decodeSlice(*ns.Duplicate(), []interface{}{data}, val) case dataValKind == reflect.String && valElemType.Kind() == reflect.Uint8: - return d.decodeSlice(name, []byte(dataVal.String()), val) + return d.decodeSlice(*ns.Duplicate(), []byte(dataVal.String()), val) // All other types we try to convert to the slice type // and "lift" it into it. i.e. a string becomes a string slice. default: // Just re-try this function with data as a slice. - return d.decodeSlice(name, []interface{}{data}, val) + return d.decodeSlice(*ns.Duplicate(), []interface{}{data}, val) } } - return fmt.Errorf( - "'%s': source data must be an array or slice, got %s", name, dataValKind) + return NewDecodingError(DecodingErrorUnexpectedType).Format( + "source data must be an array or slice, got %s", + dataValKind).SetSrcValue( + data).SetDstValue( + val.Interface()).SetNamespace(ns) } // If the input value is nil, then don't allocate since empty != nil @@ -1128,7 +1187,7 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) } // Accumulate any errors - errors := make([]string, 0) + errors := NewDecodingErrors() for i := 0; i < dataVal.Len(); i++ { currentData := dataVal.Index(i).Interface() @@ -1137,24 +1196,21 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) } currentField := valSlice.Index(i) - fieldName := name + "[" + strconv.Itoa(i) + "]" - if err := d.decode(fieldName, currentData, currentField); err != nil { - errors = appendErrors(errors, err) + if err := d.decode(*ns.Duplicate().AppendIdx(i), currentData, currentField); err != nil { + errors.Append(err) } } // Finally, set the value to the slice we built up val.Set(valSlice) - // If there were errors, we return those - if len(errors) > 0 { - return &Error{errors} + if errors.Len() > 0 { + return errors } - return nil } -func (d *Decoder) decodeArray(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decodeArray(ns Namespace, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataValKind := dataVal.Kind() valType := val.Type() @@ -1179,17 +1235,23 @@ func (d *Decoder) decodeArray(name string, data interface{}, val reflect.Value) // and "lift" it into it. i.e. a string becomes a string array. default: // Just re-try this function with data as a slice. - return d.decodeArray(name, []interface{}{data}, val) + return d.decodeArray(*ns.Duplicate(), []interface{}{data}, val) } } - return fmt.Errorf( - "'%s': source data must be an array or slice, got %s", name, dataValKind) + return NewDecodingError(DecodingErrorUnexpectedType).Format( + "source data must be an array or slice, got %s", + dataValKind).SetSrcValue( + data).SetDstValue( + val.Interface()).SetNamespace(ns) } 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 NewDecodingError(DecodingErrorIncompatibleSize).Format( + "expected source data to have length less or equal to %d, got %d", + arrayType.Len(), dataVal.Len()).SetSrcValue( + data).SetDstValue( + val.Interface()).SetNamespace(ns) } @@ -1198,30 +1260,27 @@ func (d *Decoder) decodeArray(name string, data interface{}, val reflect.Value) } // Accumulate any errors - errors := make([]string, 0) + errors := NewDecodingErrors() for i := 0; i < dataVal.Len(); i++ { currentData := dataVal.Index(i).Interface() currentField := valArray.Index(i) - fieldName := name + "[" + strconv.Itoa(i) + "]" - if err := d.decode(fieldName, currentData, currentField); err != nil { - errors = appendErrors(errors, err) + if err := d.decode(*ns.Duplicate().AppendIdx(i), currentData, currentField); err != nil { + errors.Append(err) } } // Finally, set the value to the array we built up val.Set(valArray) - // If there were errors, we return those - if len(errors) > 0 { - return &Error{errors} + if errors.Len() > 0 { + return errors } - return nil } -func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decodeStruct(ns Namespace, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) // If the type of the value to write to and the data match directly, @@ -1234,7 +1293,7 @@ func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) dataValKind := dataVal.Kind() switch dataValKind { case reflect.Map: - return d.decodeStructFromMap(name, dataVal, val) + return d.decodeStructFromMap(*ns.Duplicate(), dataVal, val) case reflect.Struct: // Not the most efficient way to do this but we can optimize later if @@ -1252,24 +1311,30 @@ func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) addrVal := reflect.New(mval.Type()) reflect.Indirect(addrVal).Set(mval) - if err := d.decodeMapFromStruct(name, dataVal, reflect.Indirect(addrVal), mval); err != nil { + if err := d.decodeMapFromStruct(*ns.Duplicate(), dataVal, reflect.Indirect(addrVal), mval); err != nil { return err } - result := d.decodeStructFromMap(name, reflect.Indirect(addrVal), val) - return result + err := d.decodeStructFromMap(*ns.Duplicate(), reflect.Indirect(addrVal), val) + return err default: - return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind()) + return NewDecodingError(DecodingErrorUnexpectedType).Format( + "expected a map, got '%s'", + dataVal.Kind()).SetSrcValue( + data).SetDstValue( + val.Interface()).SetNamespace(ns) } } -func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) error { +func (d *Decoder) decodeStructFromMap(ns Namespace, 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 NewDecodingError(DecodingErrorUnexpectedType).Format( + "needs a map with string keys, has '%s' keys", + dataValType.Key().Kind()).SetSrcValue( + dataVal.Interface()).SetDstValue( + val.Interface()).SetNamespace(ns) } dataValKeys := make(map[reflect.Value]struct{}) @@ -1280,7 +1345,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e } targetValKeysUnused := make(map[interface{}]struct{}) - errors := make([]string, 0) + errors := NewDecodingErrors() // This slice will keep track of all the structs we'll be decoding. // There can be more than one struct if there are embedded structs @@ -1334,8 +1399,14 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e if squash { if fieldVal.Kind() != reflect.Struct { - errors = appendErrors(errors, - fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldVal.Kind())) + // This is a special case in which the error is not related to a source value but + // to the impossibility of squashing a struct field of the destination (which is not + // a struct) into the destination itself. + errors.Append(NewDecodingError(DecodingErrorDstSquashFailure).Format( + "unsupported type for squash: %s", + fieldVal.Kind()).SetSrcValue( + fieldVal.Interface()).SetDstValue( + val.Interface()).SetNamespace(*ns.Duplicate().AppendFldName(fieldType.Name))) } else { structs = append(structs, fieldVal) } @@ -1404,14 +1475,9 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e // Delete the key we're using from the unused map so we stop tracking delete(dataValKeysUnused, rawMapKey.Interface()) - // If the name is empty string, then we're at the root, and we - // don't dot-join the fields. - if name != "" { - fieldName = name + "." + fieldName - } - - if err := d.decode(fieldName, rawMapVal.Interface(), fieldValue); err != nil { - errors = appendErrors(errors, err) + if err := d.decode(*ns.Duplicate().AppendFld(*NewNamespaceFld(field.Name).SetTag(fieldName).UseTag(true)), + rawMapVal.Interface(), fieldValue); err != nil { + errors.Append(AsLocalizedError(err).SetNamespaceUseFldTag(false)) } } @@ -1425,8 +1491,8 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e } // Decode it as-if we were just decoding this map onto our map. - if err := d.decodeMap(name, remain, remainField.val); err != nil { - errors = appendErrors(errors, err) + if err := d.decodeMap(*ns.Duplicate(), remain, remainField.val); err != nil { + errors.Append(err) } // Set the map to nil so we have none so that the next check will @@ -1441,8 +1507,11 @@ 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, ", ")) - errors = appendErrors(errors, err) + err := NewDecodingError(DecodingErrorUnusedKeys).Format("has invalid keys: %s", + strings.Join(keys, ", ")).SetSrcValue( + dataVal.Interface()).SetDstValue( + val.Interface()).SetNamespace(ns) + errors.Append(err) } if d.config.ErrorUnset && len(targetValKeysUnused) > 0 { @@ -1452,28 +1521,30 @@ 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, ", ")) - errors = appendErrors(errors, err) + err := NewDecodingError(DecodingErrorUnsetFields).Format("has unset fields: %s", + strings.Join(keys, ", ")).SetDstValue( + val.Interface()).SetNamespace(ns) + errors.Append(err) } - if len(errors) > 0 { - return &Error{errors} + if errors.Len() > 0 { + return errors } // Add the unused keys to the list of unused keys if we're tracking metadata if d.config.Metadata != nil { for rawKey := range dataValKeysUnused { key := rawKey.(string) - if name != "" { - key = name + "." + key + if ns.Len() > 0 { + key = ns.String() + "." + key } d.config.Metadata.Unused = append(d.config.Metadata.Unused, key) } for rawKey := range targetValKeysUnused { key := rawKey.(string) - if name != "" { - key = name + "." + key + if ns.Len() > 0 { + key = ns.String() + "." + key } d.config.Metadata.Unset = append(d.config.Metadata.Unset, key) diff --git a/mapstructure_examples_test.go b/mapstructure_examples_test.go index 2413b694..55a9238a 100644 --- a/mapstructure_examples_test.go +++ b/mapstructure_examples_test.go @@ -62,11 +62,11 @@ func ExampleDecode_errors() { // Output: // 5 error(s) decoding: // - // * 'Age' expected type 'int', got unconvertible type 'string', value: 'bad value' - // * 'Emails[0]' expected type 'string', got unconvertible type 'int', value: '1' - // * 'Emails[1]' expected type 'string', got unconvertible type 'int', value: '2' - // * 'Emails[2]' expected type 'string', got unconvertible type 'int', value: '3' - // * 'Name' expected type 'string', got unconvertible type 'int', value: '123' + // * @'Age': expected type 'int', got unconvertible type 'string', value: 'bad value' + // * @'Emails[0]': expected type 'string', got unconvertible type 'int', value: '1' + // * @'Emails[1]': expected type 'string', got unconvertible type 'int', value: '2' + // * @'Emails[2]': expected type 'string', got unconvertible type 'int', value: '3' + // * @'Name': expected type 'string', got unconvertible type 'int', value: '123' } func ExampleDecode_metadata() { diff --git a/mapstructure_test.go b/mapstructure_test.go index d31129d7..a83d8d2c 100644 --- a/mapstructure_test.go +++ b/mapstructure_test.go @@ -2273,13 +2273,13 @@ func TestInvalidType(t *testing.T) { t.Fatal("error should exist") } - derr, ok := err.(*Error) + derr, ok := err.(*DecodingErrors) if !ok { - t.Fatalf("error should be kind of Error, instead: %#v", err) + t.Fatalf("error should be kind of DecodingErrors, instead: %#v", err) } - if derr.Errors[0] != - "'Vstring' expected type 'string', got unconvertible type 'int', value: '42'" { + if derr.Get(0).Error() != + "@'Vstring': expected type 'string', got unconvertible type 'int', value: '42'" { t.Errorf("got unexpected error: %s", err) } @@ -2292,12 +2292,12 @@ func TestInvalidType(t *testing.T) { t.Fatal("error should exist") } - derr, ok = err.(*Error) + derr, ok = err.(*DecodingErrors) if !ok { - t.Fatalf("error should be kind of Error, instead: %#v", err) + t.Fatalf("error should be kind of DecodingErrors, instead: %#v", err) } - if derr.Errors[0] != "cannot parse 'Vuint', -42 overflows uint" { + if derr.Get(0).Error() != "@'Vuint': cannot parse: -42 overflows uint" { t.Errorf("got unexpected error: %s", err) } @@ -2310,12 +2310,12 @@ func TestInvalidType(t *testing.T) { t.Fatal("error should exist") } - derr, ok = err.(*Error) + derr, ok = err.(*DecodingErrors) if !ok { - t.Fatalf("error should be kind of Error, instead: %#v", err) + t.Fatalf("error should be kind of DecodingErrors, instead: %#v", err) } - if derr.Errors[0] != "cannot parse 'Vuint', -42.000000 overflows uint" { + if derr.Get(0).Error() != "@'Vuint': cannot parse: -42.000000 overflows uint" { t.Errorf("got unexpected error: %s", err) } }