From edeb2db4de0688acbd6e3f32d7814ece10e27573 Mon Sep 17 00:00:00 2001 From: Luca Filippin Date: Wed, 10 May 2023 15:13:09 +0200 Subject: [PATCH 1/9] Adds namespace management --- .gitignore | 3 + Makefile | 17 ++ error.go | 284 ++++++++++++++++++++++++++++--- mapstructure.go | 308 +++++++++++++++++----------------- mapstructure_examples_test.go | 10 +- mapstructure_test.go | 20 +-- 6 files changed, 450 insertions(+), 192 deletions(-) create mode 100644 .gitignore create mode 100644 Makefile 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/error.go b/error.go index 47a99e5a..e3d8d79b 100644 --- a/error.go +++ b/error.go @@ -1,50 +1,284 @@ package mapstructure import ( - "errors" "fmt" + "reflect" "sort" "strings" ) -// 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 NamespaceKey interface{} +type NamespaceIdx int +type NamespaceFld string + +type Namespace struct { + items []interface{} +} + +func NewNamespace() *Namespace { + return &Namespace{} +} + +func (ns *Namespace) AppendNamespace(namespace Namespace) *Namespace { + ns.items = append(ns.items, namespace.items...) + return ns } -func (e *Error) Error() string { - points := make([]string, len(e.Errors)) - for i, err := range e.Errors { - points[i] = fmt.Sprintf("* %s", err) +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 +} - sort.Strings(points) - return fmt.Sprintf( - "%d error(s) decoding:\n\n%s", - len(e.Errors), strings.Join(points, "\n")) +func (ns *Namespace) PrependKey(keys ...interface{}) *Namespace { + ppns := (&Namespace{}).AppendKey(keys...) + ns.items = append(ppns.items, ns.items...) + return ns } -// 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 (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 ...string) *Namespace { + for _, f := range flds { + ns.items = append(ns.items, NamespaceFld(f)) + } + return ns +} + +func (ns *Namespace) PrependFld(flds ...string) *Namespace { + ppns := (&Namespace{}).AppendFld(flds...) + ns.items = append(ppns.items, ns.items...) + 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] +} - result := make([]error, len(e.Errors)) - for i, e := range e.Errors { - result[i] = errors.New(e) +// GetAsString() as Get() but return the item string representation +func (ns Namespace) GetAsString(i int) string { + if item := ns.Get(i); item != nil { + str := ns.string(item) + return str } + return "" +} +func (ns Namespace) string(item interface{}) string { + var result string + switch value := item.(type) { + case NamespaceFld: + result = string(value) + case NamespaceIdx: + result = fmt.Sprintf("[%d]", int(value)) + case NamespaceKey: + result = fmt.Sprintf("[%v]", value) + } return result } -func appendErrors(errors []string, err error) []string { - switch e := err.(type) { - case *Error: - return append(errors, e.Errors...) +func (ns Namespace) Format(fldSeparator string, idxSeparator string, keySeparator string) string { + var result, sep string + + if len(ns.items) > 0 { + result = ns.string(ns.items[0]) + } + for i := 1; i < len(ns.items); i++ { + item := ns.items[i] + switch item.(type) { + case NamespaceFld: + sep = fldSeparator + case NamespaceIdx: + sep = idxSeparator + case NamespaceKey: + sep = keySeparator + } + result += sep + ns.string(item) + } + return result +} + +func (ns Namespace) String() string { + return ns.Format(".", "", "") +} + +func (ns Namespace) Duplicate() *Namespace { + return &Namespace{items: ns.items[:]} +} + +type DecodingError struct { + namespace Namespace + header string + error error +} + +func NewDecodingErrorFormat(format string, args ...interface{}) *DecodingError { + return &DecodingError{ + error: fmt.Errorf(format, args...), + } +} + +func NewDecodingErrorWrap(err error) *DecodingError { + return &DecodingError{error: err} +} + +func (dErr *DecodingError) WithHeader(format string, args ...interface{}) *DecodingError { + dErr.header = fmt.Sprintf(format, args...) + return dErr +} + +// 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()). +func (dErr DecodingError) Duplicate() *DecodingError { + return &DecodingError{ + namespace: *dErr.namespace.Duplicate(), + error: dErr.error, + } +} + +func (dErr *DecodingError) GetNamespace() Namespace { + return *dErr.namespace.Duplicate() +} + +func (dErr *DecodingError) SetNamespace(namespace Namespace) *DecodingError { + dErr.namespace = *namespace.Duplicate() + return dErr +} + +func (dErr *DecodingError) PrependNamespace(ns Namespace) *DecodingError { + dErr.namespace.PrependNamespace(ns) + return dErr +} + +func (dErr *DecodingError) AppendNamespace(ns Namespace) *DecodingError { + dErr.namespace.AppendNamespace(ns) + return dErr +} + +func (dErr *DecodingError) Error() string { + if dErr.namespace.Len() > 0 { + return fmt.Sprintf("while decoding '%s': %s%s", dErr.namespace, dErr.header, dErr.error.Error()) + } + return dErr.error.Error() +} + +func (dErr *DecodingError) Unwrap() error { + return dErr.error +} + +// Error implements the error interface and can represents multiple +// errors that occur in the course of a single decode. +type DecodingErrors struct { + errors []DecodingError +} + +func NewDecodingErrors() *DecodingErrors { + return &DecodingErrors{} +} + +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] +} + +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...) + case *DecodingError: + e.errors = append(e.errors, *err_) default: - return append(errors, e.Error()) + e.errors = append(e.errors, *NewDecodingErrorWrap(e)) } + return e +} + +// 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 (dErr *DecodingErrors) PrependNamespace(ns Namespace) *DecodingErrors { + errors := dErr.errors + for i, err := range dErr.errors { + errors[i] = *err.PrependNamespace(ns) + } + return dErr +} + +func (dErr *DecodingErrors) AppendNamespace(ns Namespace) *DecodingErrors { + errors := dErr.errors + for i, err := range dErr.errors { + errors[i] = *err.AppendNamespace(ns) + } + return dErr +} + +func (e *DecodingErrors) Error() string { + points := make([]string, len(e.errors)) + for i, err := range e.errors { + points[i] = fmt.Sprintf("* %s", &err) + } + sort.Strings(points) + return fmt.Sprintf("%d error(s) decoding:\n\n%s", + len(e.errors), strings.Join(points, "\n")) +} + +// 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..e17e8881 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,20 @@ 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) + switch err_ := err.(type) { + case *DecodingError: + err_.PrependNamespace(ns) + case *DecodingErrors: + err_.PrependNamespace(ns) + default: + err = NewDecodingErrorWrap(err).SetNamespace(ns) + } + return err } } @@ -467,38 +478,39 @@ 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 NewDecodingErrorFormat("unsupported type: '%s'", + outputKind).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 @@ -506,7 +518,7 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e // 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 +541,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 +565,15 @@ 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 NewDecodingErrorFormat("expected type '%s', got '%s'", + val.Type(), dataValType).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 +617,14 @@ 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 NewDecodingErrorFormat("expected type '%s', got unconvertible type '%s', value: '%v'", + val.Type(), dataVal.Type(), data).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 +652,24 @@ 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 NewDecodingErrorWrap(err).WithHeader("cannot parse as int: ").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 NewDecodingErrorWrap(err).WithHeader("cannot decode json.Number: ").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 NewDecodingErrorFormat("expected type '%s', got unconvertible type '%s', value: '%v'", + val.Type(), dataVal.Type(), data).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 +678,8 @@ 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 NewDecodingErrorFormat("cannot parse: %d overflows uint", + i).SetNamespace(ns) } val.SetUint(uint64(i)) case dataKind == reflect.Uint: @@ -679,8 +687,8 @@ 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 NewDecodingErrorFormat("cannot parse: %f overflows uint", + f).SetNamespace(ns) } val.SetUint(uint64(f)) case dataKind == reflect.Bool && d.config.WeaklyTypedInput: @@ -699,26 +707,25 @@ 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 NewDecodingErrorWrap(err).WithHeader("cannot parse as uint: ").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 NewDecodingErrorWrap(err).WithHeader("cannot parse as json.Number: ").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 NewDecodingErrorFormat( + "expected type '%s', got unconvertible type '%s', value: '%v'", + val.Type(), dataVal.Type(), data).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 +745,18 @@ 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 NewDecodingErrorWrap(err).WithHeader("cannot parse as bool: ").SetNamespace(ns) } default: - return fmt.Errorf( - "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", - name, val.Type(), dataVal.Type(), data) + return NewDecodingErrorFormat( + "expected type '%s', got unconvertible type '%s', value: '%v'", + val.Type(), dataVal.Type(), data).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 +784,25 @@ 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 NewDecodingErrorWrap(err).WithHeader("cannot parse as float: ").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 NewDecodingErrorWrap(err).WithHeader("cannot parse as json.Number: ").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 NewDecodingErrorFormat( + "expected type '%s', got unconvertible type '%s', value: '%v'", + val.Type(), dataVal.Type(), data).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 +821,25 @@ 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 NewDecodingErrorFormat("expected a map, got '%s'", + dataVal.Kind()).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 +847,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) + // CHECK: 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 +857,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 +880,19 @@ func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val refle } for _, k := range dataVal.MapKeys() { - fieldName := name + "[" + k.String() + "]" - + // fieldName := ns.String() + "[\"" + k.String() + "\"]" // CHECK // 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 +902,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 +922,8 @@ 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 NewDecodingErrorFormat("cannot assign type '%s' to map value field of type '%s'", + v.Type(), valMap.Type().Elem()).SetNamespace(ns) // CHECK } tagValue := f.Tag.Get(d.config.TagName) @@ -954,7 +958,8 @@ 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 NewDecodingErrorFormat("cannot squash non-struct type '%s'", + v.Type()).SetNamespace(ns) // CHECK } } if keyNameTagValue := tagValue[:index]; keyNameTagValue != "" { @@ -986,7 +991,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().AppendFld(keyName), x.Interface(), reflect.Indirect(addrVal)) if err != nil { return err } @@ -1015,7 +1020,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 +1054,33 @@ 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 NewDecodingErrorFormat( + "expected type '%s', got unconvertible type '%s', value: '%v'", + val.Type(), dataVal.Type(), data).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 +1102,21 @@ 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 NewDecodingErrorFormat("source data must be an array or slice, got %s", + dataValKind).SetNamespace(ns) } // If the input value is nil, then don't allocate since empty != nil @@ -1128,7 +1133,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 +1142,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 +1181,17 @@ 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 NewDecodingErrorFormat("source data must be an array or slice, got %s", + dataValKind).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 NewDecodingErrorFormat("expected source data to have length less or equal to %d, got %d", + arrayType.Len(), dataVal.Len()).SetNamespace(ns) } @@ -1198,30 +1200,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 +1233,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 +1251,24 @@ 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 NewDecodingErrorFormat("expected a map, got '%s'", + dataVal.Kind()).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 NewDecodingErrorFormat("needs a map with string keys, has '%s' keys", + dataValType.Key().Kind()).SetNamespace(ns) } dataValKeys := make(map[reflect.Value]struct{}) @@ -1280,7 +1279,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 +1333,9 @@ 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())) + // CHECK + errors.Append(NewDecodingErrorFormat("unsupported type for squash: %s", + fieldVal.Kind()).SetNamespace(*ns.Duplicate().AppendFld(fieldType.Name))) } else { structs = append(structs, fieldVal) } @@ -1406,12 +1406,14 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e // 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 ns.Len() > 0 { // CHECK + // fieldName = ns.String() + "." + fieldName + // } + + if err := d.decode(*ns.Duplicate().AppendFld(fieldName), + rawMapVal.Interface(), fieldValue); err != nil { + errors.Append(err) } } @@ -1425,8 +1427,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 +1443,9 @@ 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 := NewDecodingErrorFormat("has invalid keys: %s", + strings.Join(keys, ", ")).SetNamespace(ns) + errors.Append(err) } if d.config.ErrorUnset && len(targetValKeysUnused) > 0 { @@ -1452,28 +1455,29 @@ 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 := NewDecodingErrorFormat("has unset fields: %s", + strings.Join(keys, ", ")).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..deef2a32 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' + // * while decoding 'Age': expected type 'int', got unconvertible type 'string', value: 'bad value' + // * while decoding 'Emails[0]': expected type 'string', got unconvertible type 'int', value: '1' + // * while decoding 'Emails[1]': expected type 'string', got unconvertible type 'int', value: '2' + // * while decoding 'Emails[2]': expected type 'string', got unconvertible type 'int', value: '3' + // * while decoding '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..eb303d1e 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() != + "while decoding '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() != "while decoding '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() != "while decoding 'Vuint': cannot parse: -42.000000 overflows uint" { t.Errorf("got unexpected error: %s", err) } } From 218e272cc69af35d6d766260113f3dc534d77a12 Mon Sep 17 00:00:00 2001 From: Luca Filippin Date: Wed, 10 May 2023 17:59:48 +0200 Subject: [PATCH 2/9] Adds srcValue & dstValue to the DecodingError + slghtly refactor the errors --- error.go | 103 +++++++++++++++++++++-------------- mapstructure.go | 141 ++++++++++++++++++++++++++++++++++-------------- 2 files changed, 164 insertions(+), 80 deletions(-) diff --git a/error.go b/error.go index e3d8d79b..4f076d92 100644 --- a/error.go +++ b/error.go @@ -68,12 +68,12 @@ func (ns *Namespace) PrependFld(flds ...string) *Namespace { return ns } -func (ns Namespace) Len() int { +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{} { +func (ns *Namespace) Get(i int) interface{} { if i < 0 { i = len(ns.items) - 1 } @@ -84,7 +84,7 @@ func (ns Namespace) Get(i int) interface{} { } // GetAsString() as Get() but return the item string representation -func (ns Namespace) GetAsString(i int) string { +func (ns *Namespace) GetAsString(i int) string { if item := ns.Get(i); item != nil { str := ns.string(item) return str @@ -92,7 +92,7 @@ func (ns Namespace) GetAsString(i int) string { return "" } -func (ns Namespace) string(item interface{}) string { +func (ns *Namespace) string(item interface{}) string { var result string switch value := item.(type) { case NamespaceFld: @@ -105,7 +105,7 @@ func (ns Namespace) string(item interface{}) string { return result } -func (ns Namespace) Format(fldSeparator string, idxSeparator string, keySeparator string) string { +func (ns *Namespace) Format(fldSeparator string, idxSeparator string, keySeparator string) string { var result, sep string if len(ns.items) > 0 { @@ -126,17 +126,19 @@ func (ns Namespace) Format(fldSeparator string, idxSeparator string, keySeparato return result } -func (ns Namespace) String() string { +func (ns *Namespace) String() string { return ns.Format(".", "", "") } -func (ns Namespace) Duplicate() *Namespace { +func (ns *Namespace) Duplicate() *Namespace { return &Namespace{items: ns.items[:]} } type DecodingError struct { namespace Namespace header string + srcValue interface{} + dstValue interface{} error error } @@ -150,48 +152,69 @@ func NewDecodingErrorWrap(err error) *DecodingError { return &DecodingError{error: err} } -func (dErr *DecodingError) WithHeader(format string, args ...interface{}) *DecodingError { - dErr.header = fmt.Sprintf(format, args...) - return dErr +func (e *DecodingError) WithHeader(format string, args ...interface{}) *DecodingError { + e.header = fmt.Sprintf(format, args...) + return e +} + +func (e *DecodingError) WithSrcValue(value interface{}) *DecodingError { + e.srcValue = value + return e +} + +func (e *DecodingError) WithDstValue(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()). -func (dErr DecodingError) Duplicate() *DecodingError { +// know how to do it without loosing the error type (i.e. via errors.New()). Same +// applies to the value. +func (e *DecodingError) Duplicate() *DecodingError { return &DecodingError{ - namespace: *dErr.namespace.Duplicate(), - error: dErr.error, + namespace: *e.namespace.Duplicate(), + error: e.error, + srcValue: e.srcValue, + dstValue: e.dstValue, } } -func (dErr *DecodingError) GetNamespace() Namespace { - return *dErr.namespace.Duplicate() +func (e *DecodingError) GetSrcValue() interface{} { + return e.srcValue +} + +func (e *DecodingError) GetDstValue() interface{} { + return e.dstValue } -func (dErr *DecodingError) SetNamespace(namespace Namespace) *DecodingError { - dErr.namespace = *namespace.Duplicate() - return dErr +func (e *DecodingError) GetNamespace() Namespace { + return *e.namespace.Duplicate() } -func (dErr *DecodingError) PrependNamespace(ns Namespace) *DecodingError { - dErr.namespace.PrependNamespace(ns) - return dErr +func (e *DecodingError) SetNamespace(namespace Namespace) *DecodingError { + e.namespace = *namespace.Duplicate() + return e } -func (dErr *DecodingError) AppendNamespace(ns Namespace) *DecodingError { - dErr.namespace.AppendNamespace(ns) - return dErr +func (e *DecodingError) PrependNamespace(ns Namespace) *DecodingError { + e.namespace.PrependNamespace(ns) + return e +} + +func (e *DecodingError) AppendNamespace(ns Namespace) *DecodingError { + e.namespace.AppendNamespace(ns) + return e } -func (dErr *DecodingError) Error() string { - if dErr.namespace.Len() > 0 { - return fmt.Sprintf("while decoding '%s': %s%s", dErr.namespace, dErr.header, dErr.error.Error()) +func (e *DecodingError) Error() string { + if e.namespace.Len() > 0 { + return fmt.Sprintf("while decoding '%s': %s%s", &e.namespace, e.header, e.error.Error()) } - return dErr.error.Error() + return e.error.Error() } -func (dErr *DecodingError) Unwrap() error { - return dErr.error +func (e *DecodingError) Unwrap() error { + return e.error } // Error implements the error interface and can represents multiple @@ -212,7 +235,7 @@ func (e *DecodingErrors) Get(i int) *DecodingError { if i >= len(e.errors) { return nil } - return &e.errors[i] + return e.errors[i].Duplicate() } func (e *DecodingErrors) Append(err error) *DecodingErrors { @@ -244,20 +267,20 @@ func (e *DecodingErrors) Duplicate() *DecodingErrors { return e_ } -func (dErr *DecodingErrors) PrependNamespace(ns Namespace) *DecodingErrors { - errors := dErr.errors - for i, err := range dErr.errors { +func (e *DecodingErrors) PrependNamespace(ns Namespace) *DecodingErrors { + errors := e.errors + for i, err := range e.errors { errors[i] = *err.PrependNamespace(ns) } - return dErr + return e } -func (dErr *DecodingErrors) AppendNamespace(ns Namespace) *DecodingErrors { - errors := dErr.errors - for i, err := range dErr.errors { +func (e *DecodingErrors) AppendNamespace(ns Namespace) *DecodingErrors { + errors := e.errors + for i, err := range e.errors { errors[i] = *err.AppendNamespace(ns) } - return dErr + return e } func (e *DecodingErrors) Error() string { diff --git a/mapstructure.go b/mapstructure.go index e17e8881..e31116dd 100644 --- a/mapstructure.go +++ b/mapstructure.go @@ -504,7 +504,9 @@ func (d *Decoder) decode(ns Namespace, input interface{}, outVal reflect.Value) default: // If we reached this point then we weren't able to decode it return NewDecodingErrorFormat("unsupported type: '%s'", - outputKind).SetNamespace(ns) + outputKind).WithSrcValue( + input).WithDstValue( + outVal.Interface()).SetNamespace(ns) } // If we reached here, then we successfully decoded SOMETHING, so @@ -566,7 +568,9 @@ func (d *Decoder) decodeBasic(ns Namespace, data interface{}, val reflect.Value) dataValType := dataVal.Type() if !dataValType.AssignableTo(val.Type()) { return NewDecodingErrorFormat("expected type '%s', got '%s'", - val.Type(), dataValType).SetNamespace(ns) + val.Type(), dataValType).WithSrcValue( + data).WithDstValue( + val.Interface()).SetNamespace(ns) } val.Set(dataVal) @@ -618,7 +622,9 @@ func (d *Decoder) decodeString(ns Namespace, data interface{}, val reflect.Value if !converted { return NewDecodingErrorFormat("expected type '%s', got unconvertible type '%s', value: '%v'", - val.Type(), dataVal.Type(), data).SetNamespace(ns) + val.Type(), dataVal.Type(), data).WithSrcValue( + data).WithDstValue( + val.Interface()).SetNamespace(ns) } return nil @@ -652,18 +658,26 @@ func (d *Decoder) decodeInt(ns Namespace, data interface{}, val reflect.Value) e if err == nil { val.SetInt(i) } else { - return NewDecodingErrorWrap(err).WithHeader("cannot parse as int: ").SetNamespace(ns) + return NewDecodingErrorWrap(err).WithHeader( + "cannot parse as int: ").WithSrcValue( + data).WithDstValue( + val.Interface()).SetNamespace(ns) } case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": jn := data.(json.Number) i, err := jn.Int64() if err != nil { - return NewDecodingErrorWrap(err).WithHeader("cannot decode json.Number: ").SetNamespace(ns) + return NewDecodingErrorWrap(err).WithHeader( + "cannot decode json.Number: ").WithSrcValue( + data).WithDstValue( + val.Interface()).SetNamespace(ns) } val.SetInt(i) default: return NewDecodingErrorFormat("expected type '%s', got unconvertible type '%s', value: '%v'", - val.Type(), dataVal.Type(), data).SetNamespace(ns) + val.Type(), dataVal.Type(), data).WithSrcValue( + data).WithDstValue( + val.Interface()).SetNamespace(ns) } return nil @@ -679,7 +693,9 @@ func (d *Decoder) decodeUint(ns Namespace, data interface{}, val reflect.Value) i := dataVal.Int() if i < 0 && !d.config.WeaklyTypedInput { return NewDecodingErrorFormat("cannot parse: %d overflows uint", - i).SetNamespace(ns) + i).WithSrcValue( + data).WithDstValue( + val.Interface()).SetNamespace(ns) } val.SetUint(uint64(i)) case dataKind == reflect.Uint: @@ -688,7 +704,9 @@ func (d *Decoder) decodeUint(ns Namespace, data interface{}, val reflect.Value) f := dataVal.Float() if f < 0 && !d.config.WeaklyTypedInput { return NewDecodingErrorFormat("cannot parse: %f overflows uint", - f).SetNamespace(ns) + f).WithSrcValue( + data).WithDstValue( + val.Interface()).SetNamespace(ns) } val.SetUint(uint64(f)) case dataKind == reflect.Bool && d.config.WeaklyTypedInput: @@ -707,19 +725,27 @@ func (d *Decoder) decodeUint(ns Namespace, data interface{}, val reflect.Value) if err == nil { val.SetUint(i) } else { - return NewDecodingErrorWrap(err).WithHeader("cannot parse as uint: ").SetNamespace(ns) + return NewDecodingErrorWrap(err).WithHeader( + "cannot parse as uint: ").WithSrcValue( + data).WithDstValue( + 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 NewDecodingErrorWrap(err).WithHeader("cannot parse as json.Number: ").SetNamespace(ns) + return NewDecodingErrorWrap(err).WithHeader( + "cannot parse as json.Number: ").WithSrcValue( + data).WithDstValue( + val.Interface()).SetNamespace(ns) } val.SetUint(i) default: return NewDecodingErrorFormat( "expected type '%s', got unconvertible type '%s', value: '%v'", - val.Type(), dataVal.Type(), data).SetNamespace(ns) + val.Type(), dataVal.Type(), data).WithSrcValue( + data).WithDstValue( + val.Interface()).SetNamespace(ns) } return nil @@ -745,12 +771,17 @@ func (d *Decoder) decodeBool(ns Namespace, data interface{}, val reflect.Value) } else if dataVal.String() == "" { val.SetBool(false) } else { - return NewDecodingErrorWrap(err).WithHeader("cannot parse as bool: ").SetNamespace(ns) + return NewDecodingErrorWrap(err).WithHeader( + "cannot parse as bool: ").WithSrcValue( + data).WithDstValue( + val.Interface()).SetNamespace(ns) } default: return NewDecodingErrorFormat( "expected type '%s', got unconvertible type '%s', value: '%v'", - val.Type(), dataVal.Type(), data).SetNamespace(ns) + val.Type(), dataVal.Type(), data).WithSrcValue( + data).WithDstValue( + val.Interface()).SetNamespace(ns) } return nil @@ -784,19 +815,27 @@ func (d *Decoder) decodeFloat(ns Namespace, data interface{}, val reflect.Value) if err == nil { val.SetFloat(f) } else { - return NewDecodingErrorWrap(err).WithHeader("cannot parse as float: ").SetNamespace(ns) + return NewDecodingErrorWrap(err).WithHeader( + "cannot parse as float: ").WithSrcValue( + data).WithDstValue( + val.Interface()).SetNamespace(ns) } case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": jn := data.(json.Number) i, err := jn.Float64() if err != nil { - return NewDecodingErrorWrap(err).WithHeader("cannot parse as json.Number: ").SetNamespace(ns) + return NewDecodingErrorWrap(err).WithHeader( + "cannot parse as json.Number: ").WithSrcValue( + data).WithDstValue( + val.Interface()).SetNamespace(ns) } val.SetFloat(i) default: return NewDecodingErrorFormat( "expected type '%s', got unconvertible type '%s', value: '%v'", - val.Type(), dataVal.Type(), data).SetNamespace(ns) + val.Type(), dataVal.Type(), data).WithSrcValue( + data).WithDstValue( + val.Interface()).SetNamespace(ns) } return nil @@ -835,7 +874,9 @@ func (d *Decoder) decodeMap(ns Namespace, data interface{}, val reflect.Value) e default: return NewDecodingErrorFormat("expected a map, got '%s'", - dataVal.Kind()).SetNamespace(ns) + dataVal.Kind()).WithSrcValue( + data).WithDstValue( + val.Interface()).SetNamespace(ns) } } @@ -880,9 +921,9 @@ func (d *Decoder) decodeMapFromMap(ns Namespace, dataVal reflect.Value, val refl } for _, k := range dataVal.MapKeys() { - // fieldName := ns.String() + "[\"" + k.String() + "\"]" // CHECK // First decode the key into the proper type currentKey := reflect.Indirect(reflect.New(valKeyType)) + // CHECK if err := d.decode(*ns.Duplicate().AppendKey(k.Interface()), k.Interface(), currentKey); err != nil { errors.Append(err) continue @@ -923,7 +964,9 @@ func (d *Decoder) decodeMapFromStruct(ns Namespace, dataVal reflect.Value, val r v := dataVal.Field(i) if !v.Type().AssignableTo(valMap.Type().Elem()) { return NewDecodingErrorFormat("cannot assign type '%s' to map value field of type '%s'", - v.Type(), valMap.Type().Elem()).SetNamespace(ns) // CHECK + v.Type(), valMap.Type().Elem()).WithSrcValue( + v.Interface()).WithDstValue( + val.Interface()).SetNamespace(ns) // CHECK } tagValue := f.Tag.Get(d.config.TagName) @@ -936,7 +979,7 @@ func (d *Decoder) decodeMapFromStruct(ns Namespace, dataVal reflect.Value, val r // 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 { @@ -944,7 +987,7 @@ func (d *Decoder) decodeMapFromStruct(ns Namespace, dataVal reflect.Value, val r 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 } @@ -952,14 +995,16 @@ func (d *Decoder) decodeMapFromStruct(ns Namespace, dataVal reflect.Value, val r 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 { + if dv.Kind() != reflect.Struct { return NewDecodingErrorFormat("cannot squash non-struct type '%s'", - v.Type()).SetNamespace(ns) // CHECK + dv.Type()).WithSrcValue( + v.Interface()).WithDstValue( + val.Interface()).SetNamespace(ns) // CHECK } } if keyNameTagValue := tagValue[:index]; keyNameTagValue != "" { @@ -972,11 +1017,11 @@ func (d *Decoder) decodeMapFromStruct(ns Namespace, dataVal reflect.Value, val r 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() @@ -1009,7 +1054,7 @@ func (d *Decoder) decodeMapFromStruct(ns Namespace, dataVal reflect.Value, val r } default: - valMap.SetMapIndex(reflect.ValueOf(keyName), v) + valMap.SetMapIndex(reflect.ValueOf(keyName), dv) } } @@ -1072,9 +1117,10 @@ func (d *Decoder) decodeFunc(ns Namespace, data interface{}, val reflect.Value) // into that. Then set the value of the pointer to this type. dataVal := reflect.Indirect(reflect.ValueOf(data)) if val.Type() != dataVal.Type() { - return NewDecodingErrorFormat( - "expected type '%s', got unconvertible type '%s', value: '%v'", - val.Type(), dataVal.Type(), data).SetNamespace(ns) + return NewDecodingErrorFormat("expected type '%s', got unconvertible type '%s', value: '%v'", + val.Type(), dataVal.Type(), data).WithSrcValue( + data).WithDstValue( + val.Interface()).SetNamespace(ns) } val.Set(dataVal) return nil @@ -1116,7 +1162,9 @@ func (d *Decoder) decodeSlice(ns Namespace, data interface{}, val reflect.Value) } return NewDecodingErrorFormat("source data must be an array or slice, got %s", - dataValKind).SetNamespace(ns) + dataValKind).WithSrcValue( + data).WithDstValue( + val.Interface()).SetNamespace(ns) } // If the input value is nil, then don't allocate since empty != nil @@ -1186,12 +1234,16 @@ func (d *Decoder) decodeArray(ns Namespace, data interface{}, val reflect.Value) } return NewDecodingErrorFormat("source data must be an array or slice, got %s", - dataValKind).SetNamespace(ns) + dataValKind).WithSrcValue( + data).WithDstValue( + val.Interface()).SetNamespace(ns) } if dataVal.Len() > arrayType.Len() { return NewDecodingErrorFormat("expected source data to have length less or equal to %d, got %d", - arrayType.Len(), dataVal.Len()).SetNamespace(ns) + arrayType.Len(), dataVal.Len()).WithSrcValue( + data).WithDstValue( + val.Interface()).SetNamespace(ns) } @@ -1260,7 +1312,9 @@ func (d *Decoder) decodeStruct(ns Namespace, data interface{}, val reflect.Value default: return NewDecodingErrorFormat("expected a map, got '%s'", - dataVal.Kind()).SetNamespace(ns) + dataVal.Kind()).WithSrcValue( + data).WithDstValue( + val.Interface()).SetNamespace(ns) } } @@ -1268,7 +1322,9 @@ func (d *Decoder) decodeStructFromMap(ns Namespace, dataVal, val reflect.Value) dataValType := dataVal.Type() if kind := dataValType.Key().Kind(); kind != reflect.String && kind != reflect.Interface { return NewDecodingErrorFormat("needs a map with string keys, has '%s' keys", - dataValType.Key().Kind()).SetNamespace(ns) + dataValType.Key().Kind()).WithSrcValue( + dataVal.Interface()).WithDstValue( + val.Interface()).SetNamespace(ns) } dataValKeys := make(map[reflect.Value]struct{}) @@ -1335,7 +1391,9 @@ func (d *Decoder) decodeStructFromMap(ns Namespace, dataVal, val reflect.Value) if fieldVal.Kind() != reflect.Struct { // CHECK errors.Append(NewDecodingErrorFormat("unsupported type for squash: %s", - fieldVal.Kind()).SetNamespace(*ns.Duplicate().AppendFld(fieldType.Name))) + fieldVal.Kind()).WithSrcValue( + fieldVal.Interface()).WithDstValue( + val.Interface()).SetNamespace(*ns.Duplicate().AppendFld(fieldType.Name))) } else { structs = append(structs, fieldVal) } @@ -1444,7 +1502,9 @@ func (d *Decoder) decodeStructFromMap(ns Namespace, dataVal, val reflect.Value) sort.Strings(keys) err := NewDecodingErrorFormat("has invalid keys: %s", - strings.Join(keys, ", ")).SetNamespace(ns) + strings.Join(keys, ", ")).WithSrcValue( + dataVal.Interface()).WithDstValue( + val.Interface()).SetNamespace(ns) errors.Append(err) } @@ -1456,7 +1516,8 @@ func (d *Decoder) decodeStructFromMap(ns Namespace, dataVal, val reflect.Value) sort.Strings(keys) err := NewDecodingErrorFormat("has unset fields: %s", - strings.Join(keys, ", ")).SetNamespace(ns) + strings.Join(keys, ", ")).WithDstValue( + val.Interface()).SetNamespace(ns) errors.Append(err) } From b2ef0c22b3d52813dcbc57560a8b5510937f8025 Mon Sep 17 00:00:00 2001 From: Luca Filippin Date: Wed, 10 May 2023 18:07:24 +0200 Subject: [PATCH 3/9] Updates GetNamespace() to return *Namespace --- error.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/error.go b/error.go index 4f076d92..effec927 100644 --- a/error.go +++ b/error.go @@ -187,8 +187,8 @@ func (e *DecodingError) GetDstValue() interface{} { return e.dstValue } -func (e *DecodingError) GetNamespace() Namespace { - return *e.namespace.Duplicate() +func (e *DecodingError) GetNamespace() *Namespace { + return e.namespace.Duplicate() } func (e *DecodingError) SetNamespace(namespace Namespace) *DecodingError { From 501118b3db284617c26333bc3737fa068bf03f9c Mon Sep 17 00:00:00 2001 From: Luca Filippin Date: Thu, 11 May 2023 09:49:10 +0200 Subject: [PATCH 4/9] Fixes OrComposeDecodeHookFunc + renames some functions --- decode_hooks.go | 17 +++++-- error.go | 40 +++++++++++----- mapstructure.go | 124 ++++++++++++++++++++++++------------------------ 3 files changed, 103 insertions(+), 78 deletions(-) diff --git a/decode_hooks.go b/decode_hooks.go index 3a754ca7..32f4b652 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 { + fmtErr := "" + for i := 0; i < e.Len(); i++ { + fmtErr += e.Get(i).Error() + "\n" + } + return fmtErr + }) 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/error.go b/error.go index effec927..38e603f1 100644 --- a/error.go +++ b/error.go @@ -152,17 +152,17 @@ func NewDecodingErrorWrap(err error) *DecodingError { return &DecodingError{error: err} } -func (e *DecodingError) WithHeader(format string, args ...interface{}) *DecodingError { +func (e *DecodingError) SetHeader(format string, args ...interface{}) *DecodingError { e.header = fmt.Sprintf(format, args...) return e } -func (e *DecodingError) WithSrcValue(value interface{}) *DecodingError { +func (e *DecodingError) SetSrcValue(value interface{}) *DecodingError { e.srcValue = value return e } -func (e *DecodingError) WithDstValue(value interface{}) *DecodingError { +func (e *DecodingError) SetDstValue(value interface{}) *DecodingError { e.srcValue = value return e } @@ -219,14 +219,34 @@ func (e *DecodingError) Unwrap() error { // Error implements the error interface and can represents multiple // errors that occur in the course of a single decode. + +type DecodingErrorsFormatter func(e *DecodingErrors) string + +func DefaultDecodingErrorsFormatter(e *DecodingErrors) string { + nErrors := e.Len() + points := make([]string, nErrors) + for i := 0; i < nErrors; i++ { + points[i] = fmt.Sprintf("* %s", e.Get(i).Error()) + } + sort.Strings(points) + return fmt.Sprintf("%d error(s) decoding:\n\n%s", + nErrors, strings.Join(points, "\n")) +} + type DecodingErrors struct { - errors []DecodingError + formatter DecodingErrorsFormatter + errors []DecodingError } func NewDecodingErrors() *DecodingErrors { return &DecodingErrors{} } +func (e *DecodingErrors) SetFormatter(formatter DecodingErrorsFormatter) *DecodingErrors { + e.formatter = formatter + return e +} + func (e *DecodingErrors) Len() int { return len(e.errors) } @@ -250,7 +270,7 @@ func (e *DecodingErrors) Append(err error) *DecodingErrors { case *DecodingError: e.errors = append(e.errors, *err_) default: - e.errors = append(e.errors, *NewDecodingErrorWrap(e)) + e.errors = append(e.errors, *NewDecodingErrorWrap(err)) } return e } @@ -284,13 +304,11 @@ func (e *DecodingErrors) AppendNamespace(ns Namespace) *DecodingErrors { } func (e *DecodingErrors) Error() string { - points := make([]string, len(e.errors)) - for i, err := range e.errors { - points[i] = fmt.Sprintf("* %s", &err) + formatter := e.formatter + if formatter == nil { + formatter = DefaultDecodingErrorsFormatter } - sort.Strings(points) - return fmt.Sprintf("%d error(s) decoding:\n\n%s", - len(e.errors), strings.Join(points, "\n")) + return formatter(e) } // WrappedErrors implements the errwrap.Wrapper interface to make this diff --git a/mapstructure.go b/mapstructure.go index e31116dd..6ae8b4cc 100644 --- a/mapstructure.go +++ b/mapstructure.go @@ -504,8 +504,8 @@ func (d *Decoder) decode(ns Namespace, input interface{}, outVal reflect.Value) default: // If we reached this point then we weren't able to decode it return NewDecodingErrorFormat("unsupported type: '%s'", - outputKind).WithSrcValue( - input).WithDstValue( + outputKind).SetSrcValue( + input).SetDstValue( outVal.Interface()).SetNamespace(ns) } @@ -568,8 +568,8 @@ func (d *Decoder) decodeBasic(ns Namespace, data interface{}, val reflect.Value) dataValType := dataVal.Type() if !dataValType.AssignableTo(val.Type()) { return NewDecodingErrorFormat("expected type '%s', got '%s'", - val.Type(), dataValType).WithSrcValue( - data).WithDstValue( + val.Type(), dataValType).SetSrcValue( + data).SetDstValue( val.Interface()).SetNamespace(ns) } @@ -622,8 +622,8 @@ func (d *Decoder) decodeString(ns Namespace, data interface{}, val reflect.Value if !converted { return NewDecodingErrorFormat("expected type '%s', got unconvertible type '%s', value: '%v'", - val.Type(), dataVal.Type(), data).WithSrcValue( - data).WithDstValue( + val.Type(), dataVal.Type(), data).SetSrcValue( + data).SetDstValue( val.Interface()).SetNamespace(ns) } @@ -658,25 +658,25 @@ func (d *Decoder) decodeInt(ns Namespace, data interface{}, val reflect.Value) e if err == nil { val.SetInt(i) } else { - return NewDecodingErrorWrap(err).WithHeader( - "cannot parse as int: ").WithSrcValue( - data).WithDstValue( + return NewDecodingErrorWrap(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 NewDecodingErrorWrap(err).WithHeader( - "cannot decode json.Number: ").WithSrcValue( - data).WithDstValue( + return NewDecodingErrorWrap(err).SetHeader( + "cannot decode json.Number: ").SetSrcValue( + data).SetDstValue( val.Interface()).SetNamespace(ns) } val.SetInt(i) default: return NewDecodingErrorFormat("expected type '%s', got unconvertible type '%s', value: '%v'", - val.Type(), dataVal.Type(), data).WithSrcValue( - data).WithDstValue( + val.Type(), dataVal.Type(), data).SetSrcValue( + data).SetDstValue( val.Interface()).SetNamespace(ns) } @@ -693,8 +693,8 @@ func (d *Decoder) decodeUint(ns Namespace, data interface{}, val reflect.Value) i := dataVal.Int() if i < 0 && !d.config.WeaklyTypedInput { return NewDecodingErrorFormat("cannot parse: %d overflows uint", - i).WithSrcValue( - data).WithDstValue( + i).SetSrcValue( + data).SetDstValue( val.Interface()).SetNamespace(ns) } val.SetUint(uint64(i)) @@ -704,8 +704,8 @@ func (d *Decoder) decodeUint(ns Namespace, data interface{}, val reflect.Value) f := dataVal.Float() if f < 0 && !d.config.WeaklyTypedInput { return NewDecodingErrorFormat("cannot parse: %f overflows uint", - f).WithSrcValue( - data).WithDstValue( + f).SetSrcValue( + data).SetDstValue( val.Interface()).SetNamespace(ns) } val.SetUint(uint64(f)) @@ -725,26 +725,26 @@ func (d *Decoder) decodeUint(ns Namespace, data interface{}, val reflect.Value) if err == nil { val.SetUint(i) } else { - return NewDecodingErrorWrap(err).WithHeader( - "cannot parse as uint: ").WithSrcValue( - data).WithDstValue( + return NewDecodingErrorWrap(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 NewDecodingErrorWrap(err).WithHeader( - "cannot parse as json.Number: ").WithSrcValue( - data).WithDstValue( + return NewDecodingErrorWrap(err).SetHeader( + "cannot parse as json.Number: ").SetSrcValue( + data).SetDstValue( val.Interface()).SetNamespace(ns) } val.SetUint(i) default: return NewDecodingErrorFormat( "expected type '%s', got unconvertible type '%s', value: '%v'", - val.Type(), dataVal.Type(), data).WithSrcValue( - data).WithDstValue( + val.Type(), dataVal.Type(), data).SetSrcValue( + data).SetDstValue( val.Interface()).SetNamespace(ns) } @@ -771,16 +771,16 @@ func (d *Decoder) decodeBool(ns Namespace, data interface{}, val reflect.Value) } else if dataVal.String() == "" { val.SetBool(false) } else { - return NewDecodingErrorWrap(err).WithHeader( - "cannot parse as bool: ").WithSrcValue( - data).WithDstValue( + return NewDecodingErrorWrap(err).SetHeader( + "cannot parse as bool: ").SetSrcValue( + data).SetDstValue( val.Interface()).SetNamespace(ns) } default: return NewDecodingErrorFormat( "expected type '%s', got unconvertible type '%s', value: '%v'", - val.Type(), dataVal.Type(), data).WithSrcValue( - data).WithDstValue( + val.Type(), dataVal.Type(), data).SetSrcValue( + data).SetDstValue( val.Interface()).SetNamespace(ns) } @@ -815,26 +815,26 @@ func (d *Decoder) decodeFloat(ns Namespace, data interface{}, val reflect.Value) if err == nil { val.SetFloat(f) } else { - return NewDecodingErrorWrap(err).WithHeader( - "cannot parse as float: ").WithSrcValue( - data).WithDstValue( + return NewDecodingErrorWrap(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 NewDecodingErrorWrap(err).WithHeader( - "cannot parse as json.Number: ").WithSrcValue( - data).WithDstValue( + return NewDecodingErrorWrap(err).SetHeader( + "cannot parse as json.Number: ").SetSrcValue( + data).SetDstValue( val.Interface()).SetNamespace(ns) } val.SetFloat(i) default: return NewDecodingErrorFormat( "expected type '%s', got unconvertible type '%s', value: '%v'", - val.Type(), dataVal.Type(), data).WithSrcValue( - data).WithDstValue( + val.Type(), dataVal.Type(), data).SetSrcValue( + data).SetDstValue( val.Interface()).SetNamespace(ns) } @@ -874,8 +874,8 @@ func (d *Decoder) decodeMap(ns Namespace, data interface{}, val reflect.Value) e default: return NewDecodingErrorFormat("expected a map, got '%s'", - dataVal.Kind()).WithSrcValue( - data).WithDstValue( + dataVal.Kind()).SetSrcValue( + data).SetDstValue( val.Interface()).SetNamespace(ns) } } @@ -964,8 +964,8 @@ func (d *Decoder) decodeMapFromStruct(ns Namespace, dataVal reflect.Value, val r v := dataVal.Field(i) if !v.Type().AssignableTo(valMap.Type().Elem()) { return NewDecodingErrorFormat("cannot assign type '%s' to map value field of type '%s'", - v.Type(), valMap.Type().Elem()).WithSrcValue( - v.Interface()).WithDstValue( + v.Type(), valMap.Type().Elem()).SetSrcValue( + v.Interface()).SetDstValue( val.Interface()).SetNamespace(ns) // CHECK } @@ -1002,8 +1002,8 @@ func (d *Decoder) decodeMapFromStruct(ns Namespace, dataVal reflect.Value, val r // The final type must be a struct if dv.Kind() != reflect.Struct { return NewDecodingErrorFormat("cannot squash non-struct type '%s'", - dv.Type()).WithSrcValue( - v.Interface()).WithDstValue( + dv.Type()).SetSrcValue( + v.Interface()).SetDstValue( val.Interface()).SetNamespace(ns) // CHECK } } @@ -1118,8 +1118,8 @@ func (d *Decoder) decodeFunc(ns Namespace, data interface{}, val reflect.Value) dataVal := reflect.Indirect(reflect.ValueOf(data)) if val.Type() != dataVal.Type() { return NewDecodingErrorFormat("expected type '%s', got unconvertible type '%s', value: '%v'", - val.Type(), dataVal.Type(), data).WithSrcValue( - data).WithDstValue( + val.Type(), dataVal.Type(), data).SetSrcValue( + data).SetDstValue( val.Interface()).SetNamespace(ns) } val.Set(dataVal) @@ -1162,8 +1162,8 @@ func (d *Decoder) decodeSlice(ns Namespace, data interface{}, val reflect.Value) } return NewDecodingErrorFormat("source data must be an array or slice, got %s", - dataValKind).WithSrcValue( - data).WithDstValue( + dataValKind).SetSrcValue( + data).SetDstValue( val.Interface()).SetNamespace(ns) } @@ -1234,15 +1234,15 @@ func (d *Decoder) decodeArray(ns Namespace, data interface{}, val reflect.Value) } return NewDecodingErrorFormat("source data must be an array or slice, got %s", - dataValKind).WithSrcValue( - data).WithDstValue( + dataValKind).SetSrcValue( + data).SetDstValue( val.Interface()).SetNamespace(ns) } if dataVal.Len() > arrayType.Len() { return NewDecodingErrorFormat("expected source data to have length less or equal to %d, got %d", - arrayType.Len(), dataVal.Len()).WithSrcValue( - data).WithDstValue( + arrayType.Len(), dataVal.Len()).SetSrcValue( + data).SetDstValue( val.Interface()).SetNamespace(ns) } @@ -1312,8 +1312,8 @@ func (d *Decoder) decodeStruct(ns Namespace, data interface{}, val reflect.Value default: return NewDecodingErrorFormat("expected a map, got '%s'", - dataVal.Kind()).WithSrcValue( - data).WithDstValue( + dataVal.Kind()).SetSrcValue( + data).SetDstValue( val.Interface()).SetNamespace(ns) } } @@ -1322,8 +1322,8 @@ func (d *Decoder) decodeStructFromMap(ns Namespace, dataVal, val reflect.Value) dataValType := dataVal.Type() if kind := dataValType.Key().Kind(); kind != reflect.String && kind != reflect.Interface { return NewDecodingErrorFormat("needs a map with string keys, has '%s' keys", - dataValType.Key().Kind()).WithSrcValue( - dataVal.Interface()).WithDstValue( + dataValType.Key().Kind()).SetSrcValue( + dataVal.Interface()).SetDstValue( val.Interface()).SetNamespace(ns) } @@ -1391,8 +1391,8 @@ func (d *Decoder) decodeStructFromMap(ns Namespace, dataVal, val reflect.Value) if fieldVal.Kind() != reflect.Struct { // CHECK errors.Append(NewDecodingErrorFormat("unsupported type for squash: %s", - fieldVal.Kind()).WithSrcValue( - fieldVal.Interface()).WithDstValue( + fieldVal.Kind()).SetSrcValue( + fieldVal.Interface()).SetDstValue( val.Interface()).SetNamespace(*ns.Duplicate().AppendFld(fieldType.Name))) } else { structs = append(structs, fieldVal) @@ -1502,8 +1502,8 @@ func (d *Decoder) decodeStructFromMap(ns Namespace, dataVal, val reflect.Value) sort.Strings(keys) err := NewDecodingErrorFormat("has invalid keys: %s", - strings.Join(keys, ", ")).WithSrcValue( - dataVal.Interface()).WithDstValue( + strings.Join(keys, ", ")).SetSrcValue( + dataVal.Interface()).SetDstValue( val.Interface()).SetNamespace(ns) errors.Append(err) } @@ -1516,7 +1516,7 @@ func (d *Decoder) decodeStructFromMap(ns Namespace, dataVal, val reflect.Value) sort.Strings(keys) err := NewDecodingErrorFormat("has unset fields: %s", - strings.Join(keys, ", ")).WithDstValue( + strings.Join(keys, ", ")).SetDstValue( val.Interface()).SetNamespace(ns) errors.Append(err) } From 447b8fe88f101bca16e397eb6cec0551daa1b88a Mon Sep 17 00:00:00 2001 From: Luca Filippin Date: Thu, 11 May 2023 13:42:07 +0200 Subject: [PATCH 5/9] Adds LocalizedError interface and related functions --- error.go | 47 ++++++++++++++++++++++++++++++++++++++--------- mapstructure.go | 10 +--------- 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/error.go b/error.go index 38e603f1..960f2285 100644 --- a/error.go +++ b/error.go @@ -134,8 +134,22 @@ func (ns *Namespace) Duplicate() *Namespace { return &Namespace{items: ns.items[:]} } +type LocalizedError interface { + SetNamespace(ns Namespace) LocalizedError + PrependNamespace(ns Namespace) LocalizedError + AppendNamespace(ns Namespace) LocalizedError + Error() string +} + +func AsLocalizedError(err error) LocalizedError { + if e, ok := err.(LocalizedError); ok { + return e + } + return AsDecodingError(err) +} + type DecodingError struct { - namespace Namespace + namespace Namespace // namespace refers to the destination header string srcValue interface{} dstValue interface{} @@ -152,6 +166,13 @@ func NewDecodingErrorWrap(err error) *DecodingError { return &DecodingError{error: err} } +func AsDecodingError(err error) *DecodingError { + if e, ok := err.(*DecodingError); ok { + return e + } + return NewDecodingErrorWrap(err) +} + func (e *DecodingError) SetHeader(format string, args ...interface{}) *DecodingError { e.header = fmt.Sprintf(format, args...) return e @@ -169,7 +190,7 @@ func (e *DecodingError) SetDstValue(value interface{}) *DecodingError { // 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 the value. +// applies to srcValue & dstValue. func (e *DecodingError) Duplicate() *DecodingError { return &DecodingError{ namespace: *e.namespace.Duplicate(), @@ -191,17 +212,17 @@ func (e *DecodingError) GetNamespace() *Namespace { return e.namespace.Duplicate() } -func (e *DecodingError) SetNamespace(namespace Namespace) *DecodingError { +func (e *DecodingError) SetNamespace(namespace Namespace) LocalizedError { e.namespace = *namespace.Duplicate() return e } -func (e *DecodingError) PrependNamespace(ns Namespace) *DecodingError { +func (e *DecodingError) PrependNamespace(ns Namespace) LocalizedError { e.namespace.PrependNamespace(ns) return e } -func (e *DecodingError) AppendNamespace(ns Namespace) *DecodingError { +func (e *DecodingError) AppendNamespace(ns Namespace) LocalizedError { e.namespace.AppendNamespace(ns) return e } @@ -287,18 +308,26 @@ func (e *DecodingErrors) Duplicate() *DecodingErrors { return e_ } -func (e *DecodingErrors) PrependNamespace(ns Namespace) *DecodingErrors { +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) + errors[i] = *err.PrependNamespace(ns).(*DecodingError) } return e } -func (e *DecodingErrors) AppendNamespace(ns Namespace) *DecodingErrors { +func (e *DecodingErrors) AppendNamespace(ns Namespace) LocalizedError { errors := e.errors for i, err := range e.errors { - errors[i] = *err.AppendNamespace(ns) + errors[i] = *err.AppendNamespace(ns).(*DecodingError) } return e } diff --git a/mapstructure.go b/mapstructure.go index 6ae8b4cc..4feeb3fb 100644 --- a/mapstructure.go +++ b/mapstructure.go @@ -461,15 +461,7 @@ func (d *Decoder) decode(ns Namespace, input interface{}, outVal reflect.Value) // 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 { - switch err_ := err.(type) { - case *DecodingError: - err_.PrependNamespace(ns) - case *DecodingErrors: - err_.PrependNamespace(ns) - default: - err = NewDecodingErrorWrap(err).SetNamespace(ns) - } - return err + return AsLocalizedError(err).PrependNamespace(ns) } } From 784b430fea361fc12a7a83b2d0fbd9d41b55b826 Mon Sep 17 00:00:00 2001 From: Luca Filippin Date: Thu, 11 May 2023 16:39:50 +0200 Subject: [PATCH 6/9] Adds AsDecodingErrors() + changes decode() to return always DecodingErrors --- error.go | 19 +++++++++++++++---- mapstructure.go | 12 +++++++----- mapstructure_examples_test.go | 10 +++++----- mapstructure_test.go | 6 +++--- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/error.go b/error.go index 960f2285..8c6b72a1 100644 --- a/error.go +++ b/error.go @@ -167,6 +167,9 @@ func NewDecodingErrorWrap(err error) *DecodingError { } func AsDecodingError(err error) *DecodingError { + if err == nil { + return nil + } if e, ok := err.(*DecodingError); ok { return e } @@ -229,7 +232,7 @@ func (e *DecodingError) AppendNamespace(ns Namespace) LocalizedError { func (e *DecodingError) Error() string { if e.namespace.Len() > 0 { - return fmt.Sprintf("while decoding '%s': %s%s", &e.namespace, e.header, e.error.Error()) + return fmt.Sprintf("@'%s': %s%s", &e.namespace, e.header, e.error.Error()) } return e.error.Error() } @@ -263,6 +266,16 @@ 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) +} + func (e *DecodingErrors) SetFormatter(formatter DecodingErrorsFormatter) *DecodingErrors { e.formatter = formatter return e @@ -288,10 +301,8 @@ func (e *DecodingErrors) Append(err error) *DecodingErrors { switch err_ := err.(type) { case *DecodingErrors: e.errors = append(e.errors, err_.errors...) - case *DecodingError: - e.errors = append(e.errors, *err_) default: - e.errors = append(e.errors, *NewDecodingErrorWrap(err)) + e.errors = append(e.errors, *AsDecodingError(err)) } return e } diff --git a/mapstructure.go b/mapstructure.go index 4feeb3fb..1428c1c7 100644 --- a/mapstructure.go +++ b/mapstructure.go @@ -461,7 +461,7 @@ func (d *Decoder) decode(ns Namespace, input interface{}, outVal reflect.Value) // 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 AsLocalizedError(err).PrependNamespace(ns) + return AsDecodingErrors(AsLocalizedError(err).PrependNamespace(ns)) } } @@ -495,10 +495,10 @@ func (d *Decoder) decode(ns Namespace, input interface{}, outVal reflect.Value) err = d.decodeFunc(*ns.Duplicate(), input, outVal) default: // If we reached this point then we weren't able to decode it - return NewDecodingErrorFormat("unsupported type: '%s'", + return AsDecodingErrors(NewDecodingErrorFormat("unsupported type: '%s'", outputKind).SetSrcValue( input).SetDstValue( - outVal.Interface()).SetNamespace(ns) + outVal.Interface()).SetNamespace(ns)) } // If we reached here, then we successfully decoded SOMETHING, so @@ -506,8 +506,10 @@ func (d *Decoder) decode(ns Namespace, input interface{}, outVal reflect.Value) 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 diff --git a/mapstructure_examples_test.go b/mapstructure_examples_test.go index deef2a32..55a9238a 100644 --- a/mapstructure_examples_test.go +++ b/mapstructure_examples_test.go @@ -62,11 +62,11 @@ func ExampleDecode_errors() { // Output: // 5 error(s) decoding: // - // * while decoding 'Age': expected type 'int', got unconvertible type 'string', value: 'bad value' - // * while decoding 'Emails[0]': expected type 'string', got unconvertible type 'int', value: '1' - // * while decoding 'Emails[1]': expected type 'string', got unconvertible type 'int', value: '2' - // * while decoding 'Emails[2]': expected type 'string', got unconvertible type 'int', value: '3' - // * while decoding '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 eb303d1e..a83d8d2c 100644 --- a/mapstructure_test.go +++ b/mapstructure_test.go @@ -2279,7 +2279,7 @@ func TestInvalidType(t *testing.T) { } if derr.Get(0).Error() != - "while decoding 'Vstring': expected type 'string', got unconvertible type 'int', value: '42'" { + "@'Vstring': expected type 'string', got unconvertible type 'int', value: '42'" { t.Errorf("got unexpected error: %s", err) } @@ -2297,7 +2297,7 @@ func TestInvalidType(t *testing.T) { t.Fatalf("error should be kind of DecodingErrors, instead: %#v", err) } - if derr.Get(0).Error() != "while decoding 'Vuint': cannot parse: -42 overflows uint" { + if derr.Get(0).Error() != "@'Vuint': cannot parse: -42 overflows uint" { t.Errorf("got unexpected error: %s", err) } @@ -2315,7 +2315,7 @@ func TestInvalidType(t *testing.T) { t.Fatalf("error should be kind of DecodingErrors, instead: %#v", err) } - if derr.Get(0).Error() != "while decoding 'Vuint': cannot parse: -42.000000 overflows uint" { + if derr.Get(0).Error() != "@'Vuint': cannot parse: -42.000000 overflows uint" { t.Errorf("got unexpected error: %s", err) } } From 1ab0f0bbd1cde65fc234df117b5512f5eb207baf Mon Sep 17 00:00:00 2001 From: Luca Filippin Date: Thu, 11 May 2023 18:54:11 +0200 Subject: [PATCH 7/9] Adds NamespaceFld as structure to support field name & tag --- error.go | 71 +++++++++++++++++++++++++++++++++++++++++++++---- mapstructure.go | 8 +++--- 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/error.go b/error.go index 8c6b72a1..5f9d685a 100644 --- a/error.go +++ b/error.go @@ -9,7 +9,46 @@ import ( type NamespaceKey interface{} type NamespaceIdx int -type NamespaceFld string + +type NamespaceFld struct { + useName bool + name string + tag string +} + +func NewNamespaceFld() *NamespaceFld { + return &NamespaceFld{useName: true} +} + +func (nf *NamespaceFld) UseName(useFieldName bool) *NamespaceFld { + nf.useName = useFieldName + return nf +} + +func (nf *NamespaceFld) SetName(name string) *NamespaceFld { + nf.name = name + 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 { + if nf.useName { + return nf.name + } + return nf.tag +} type Namespace struct { items []interface{} @@ -55,19 +94,28 @@ func (ns *Namespace) PrependIdx(idxs ...int) *Namespace { return ns } -func (ns *Namespace) AppendFld(flds ...string) *Namespace { +func (ns *Namespace) AppendFld(flds ...NamespaceFld) *Namespace { for _, f := range flds { - ns.items = append(ns.items, NamespaceFld(f)) + ns.items = append(ns.items, f) } return ns } -func (ns *Namespace) PrependFld(flds ...string) *Namespace { +func (ns *Namespace) PrependFld(flds ...NamespaceFld) *Namespace { ppns := (&Namespace{}).AppendFld(flds...) ns.items = append(ppns.items, ns.items...) return ns } +func (ns *Namespace) UseFldName(useFldName bool) *Namespace { + for i, item := range ns.items { + if fld, ok := item.(NamespaceFld); ok { + ns.items[i] = *fld.UseName(useFldName) + } + } + return ns +} + func (ns *Namespace) Len() int { return len(ns.items) } @@ -96,7 +144,7 @@ func (ns *Namespace) string(item interface{}) string { var result string switch value := item.(type) { case NamespaceFld: - result = string(value) + result = value.String() case NamespaceIdx: result = fmt.Sprintf("[%d]", int(value)) case NamespaceKey: @@ -138,6 +186,7 @@ type LocalizedError interface { SetNamespace(ns Namespace) LocalizedError PrependNamespace(ns Namespace) LocalizedError AppendNamespace(ns Namespace) LocalizedError + SetNamespaceUseFieldName(useFieldName bool) LocalizedError Error() string } @@ -230,6 +279,11 @@ func (e *DecodingError) AppendNamespace(ns Namespace) LocalizedError { return e } +func (e *DecodingError) SetNamespaceUseFieldName(useFieldName bool) LocalizedError { + e.namespace.UseFldName(useFieldName) + 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()) @@ -343,6 +397,13 @@ func (e *DecodingErrors) AppendNamespace(ns Namespace) LocalizedError { return e } +func (e *DecodingErrors) SetNamespaceUseFieldName(useFieldName bool) LocalizedError { + for i, err := range e.errors { + e.errors[i] = *err.SetNamespaceUseFieldName(useFieldName).(*DecodingError) + } + return e +} + func (e *DecodingErrors) Error() string { formatter := e.formatter if formatter == nil { diff --git a/mapstructure.go b/mapstructure.go index 1428c1c7..686b8899 100644 --- a/mapstructure.go +++ b/mapstructure.go @@ -1030,7 +1030,7 @@ func (d *Decoder) decodeMapFromStruct(ns Namespace, dataVal reflect.Value, val r addrVal := reflect.New(vMap.Type()) reflect.Indirect(addrVal).Set(vMap) - err := d.decode(*ns.Duplicate().AppendFld(keyName), x.Interface(), reflect.Indirect(addrVal)) + err := d.decode(*ns.Duplicate().AppendKey(keyName), x.Interface(), reflect.Indirect(addrVal)) if err != nil { return err } @@ -1387,7 +1387,7 @@ func (d *Decoder) decodeStructFromMap(ns Namespace, dataVal, val reflect.Value) errors.Append(NewDecodingErrorFormat("unsupported type for squash: %s", fieldVal.Kind()).SetSrcValue( fieldVal.Interface()).SetDstValue( - val.Interface()).SetNamespace(*ns.Duplicate().AppendFld(fieldType.Name))) + val.Interface()).SetNamespace(*ns.Duplicate().AppendFld(*NewNamespaceFld().SetName(fieldType.Name)))) } else { structs = append(structs, fieldVal) } @@ -1463,9 +1463,9 @@ func (d *Decoder) decodeStructFromMap(ns Namespace, dataVal, val reflect.Value) // fieldName = ns.String() + "." + fieldName // } - if err := d.decode(*ns.Duplicate().AppendFld(fieldName), + if err := d.decode(*ns.Duplicate().AppendFld(*NewNamespaceFld().SetName(field.Name).SetTag(fieldName).UseName(false)), rawMapVal.Interface(), fieldValue); err != nil { - errors.Append(err) + errors.Append(AsLocalizedError(err).SetNamespaceUseFieldName(true)) } } From 71db875a4384244b0d1415b3d02bc588174a8adf Mon Sep 17 00:00:00 2001 From: Luca Filippin Date: Thu, 11 May 2023 19:15:58 +0200 Subject: [PATCH 8/9] Adds AppendFldName(), AppendFldTag() to Namespace --- error.go | 24 ++++++++++++++++++++++++ mapstructure.go | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/error.go b/error.go index 5f9d685a..47f486da 100644 --- a/error.go +++ b/error.go @@ -107,6 +107,30 @@ func (ns *Namespace) PrependFld(flds ...NamespaceFld) *Namespace { return ns } +func (ns *Namespace) PrependFldName(fldNames ...string) *Namespace { + ns.items = append(NewNamespace().AppendFldName(fldNames...).items, ns.items...) + return ns +} + +func (ns *Namespace) AppendFldName(fldNames ...string) *Namespace { + for _, fn := range fldNames { + ns.items = append(ns.items, *NewNamespaceFld().SetName(fn)) + } + return ns +} + +func (ns *Namespace) PrependFldTag(fldTags ...string) *Namespace { + ns.items = append(NewNamespace().AppendFldName(fldTags...).items, ns.items...) + return ns +} + +func (ns *Namespace) AppendFldTag(fldTags ...string) *Namespace { + for _, tn := range fldTags { + ns.items = append(ns.items, *NewNamespaceFld().SetName(tn)) + } + return ns +} + func (ns *Namespace) UseFldName(useFldName bool) *Namespace { for i, item := range ns.items { if fld, ok := item.(NamespaceFld); ok { diff --git a/mapstructure.go b/mapstructure.go index 686b8899..21d91772 100644 --- a/mapstructure.go +++ b/mapstructure.go @@ -1387,7 +1387,7 @@ func (d *Decoder) decodeStructFromMap(ns Namespace, dataVal, val reflect.Value) errors.Append(NewDecodingErrorFormat("unsupported type for squash: %s", fieldVal.Kind()).SetSrcValue( fieldVal.Interface()).SetDstValue( - val.Interface()).SetNamespace(*ns.Duplicate().AppendFld(*NewNamespaceFld().SetName(fieldType.Name)))) + val.Interface()).SetNamespace(*ns.Duplicate().AppendFldName(fieldType.Name))) } else { structs = append(structs, fieldVal) } From 27661085e23b99c12c217550c800c867cbf9927a Mon Sep 17 00:00:00 2001 From: Luca Filippin Date: Fri, 12 May 2023 18:19:28 +0200 Subject: [PATCH 9/9] Introduces error's kind and improve code in general --- decode_hooks.go | 8 +- decode_hooks_test.go | 2 +- error.go | 252 ++++++++++++++++++++++++++----------------- mapstructure.go | 102 ++++++++++-------- 4 files changed, 215 insertions(+), 149 deletions(-) diff --git a/decode_hooks.go b/decode_hooks.go index 32f4b652..d873880f 100644 --- a/decode_hooks.go +++ b/decode_hooks.go @@ -85,11 +85,11 @@ func OrComposeDecodeHookFunc(ff ...DecodeHookFunc) DecodeHookFunc { var err error allErrs := NewDecodingErrors().SetFormatter(func(e *DecodingErrors) string { - fmtErr := "" - for i := 0; i < e.Len(); i++ { - fmtErr += e.Get(i).Error() + "\n" + errsStr := make([]string, len(e.errors)) + for i := 0; i < len(e.errors); i++ { + errsStr[i] = e.errors[i].Error() } - return fmtErr + return strings.Join(errsStr, "\n") }) for _, f := range ff { out, err = DecodeHookExec(f, a, b) 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 47f486da..751d5ad6 100644 --- a/error.go +++ b/error.go @@ -11,22 +11,19 @@ type NamespaceKey interface{} type NamespaceIdx int type NamespaceFld struct { - useName bool - name string - tag string + useTag bool + name string + tag string } -func NewNamespaceFld() *NamespaceFld { - return &NamespaceFld{useName: true} -} - -func (nf *NamespaceFld) UseName(useFieldName bool) *NamespaceFld { - nf.useName = useFieldName - return nf +func NewNamespaceFld(name string) *NamespaceFld { + return &NamespaceFld{ + name: name, + } } -func (nf *NamespaceFld) SetName(name string) *NamespaceFld { - nf.name = name +func (nf *NamespaceFld) UseTag(useTag bool) *NamespaceFld { + nf.useTag = useTag return nf } @@ -44,18 +41,53 @@ func (nf *NamespaceFld) GetTag() string { } func (nf *NamespaceFld) String() string { - if nf.useName { - return nf.name + // tag wille be used if it's defined + if nf.useTag && nf.tag != "" { + return 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 { - items []interface{} + formatter NamespaceFormatter + items []interface{} } func NewNamespace() *Namespace { - return &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 { @@ -107,34 +139,26 @@ func (ns *Namespace) PrependFld(flds ...NamespaceFld) *Namespace { return ns } -func (ns *Namespace) PrependFldName(fldNames ...string) *Namespace { - ns.items = append(NewNamespace().AppendFldName(fldNames...).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().SetName(fn)) + ns.items = append(ns.items, *NewNamespaceFld(fn)) } return ns } -func (ns *Namespace) PrependFldTag(fldTags ...string) *Namespace { - ns.items = append(NewNamespace().AppendFldName(fldTags...).items, ns.items...) - return ns -} - -func (ns *Namespace) AppendFldTag(fldTags ...string) *Namespace { - for _, tn := range fldTags { - ns.items = append(ns.items, *NewNamespaceFld().SetName(tn)) - } +// 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 } -func (ns *Namespace) UseFldName(useFldName bool) *Namespace { +// 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.UseName(useFldName) + ns.items[i] = *fld.UseTag(useFldTag) } } return ns @@ -155,62 +179,21 @@ func (ns *Namespace) Get(i int) interface{} { return ns.items[i] } -// GetAsString() as Get() but return the item string representation -func (ns *Namespace) GetAsString(i int) string { - if item := ns.Get(i); item != nil { - str := ns.string(item) - return str - } - return "" -} - -func (ns *Namespace) string(item interface{}) string { - var result string - switch value := item.(type) { - case NamespaceFld: - result = value.String() - case NamespaceIdx: - result = fmt.Sprintf("[%d]", int(value)) - case NamespaceKey: - result = fmt.Sprintf("[%v]", value) - } - return result -} - -func (ns *Namespace) Format(fldSeparator string, idxSeparator string, keySeparator string) string { - var result, sep string - - if len(ns.items) > 0 { - result = ns.string(ns.items[0]) - } - for i := 1; i < len(ns.items); i++ { - item := ns.items[i] - switch item.(type) { - case NamespaceFld: - sep = fldSeparator - case NamespaceIdx: - sep = idxSeparator - case NamespaceKey: - sep = keySeparator - } - result += sep + ns.string(item) - } - return result -} - func (ns *Namespace) String() string { - return ns.Format(".", "", "") + return ns.formatter(*ns) } func (ns *Namespace) Duplicate() *Namespace { - return &Namespace{items: ns.items[:]} + ns_ := *ns + ns_.items = ns.items[:] + return &ns_ } type LocalizedError interface { SetNamespace(ns Namespace) LocalizedError PrependNamespace(ns Namespace) LocalizedError AppendNamespace(ns Namespace) LocalizedError - SetNamespaceUseFieldName(useFieldName bool) LocalizedError + SetNamespaceUseFldTag(useFieldTag bool) LocalizedError Error() string } @@ -221,24 +204,80 @@ func AsLocalizedError(err error) LocalizedError { 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 NewDecodingErrorFormat(format string, args ...interface{}) *DecodingError { +func NewDecodingError(kind DecodingErrorKind) *DecodingError { return &DecodingError{ - error: fmt.Errorf(format, args...), + kind: kind, + error: fmt.Errorf("%s", kind), } } -func NewDecodingErrorWrap(err error) *DecodingError { - return &DecodingError{error: err} -} - func AsDecodingError(err error) *DecodingError { if err == nil { return nil @@ -246,7 +285,17 @@ func AsDecodingError(err error) *DecodingError { if e, ok := err.(*DecodingError); ok { return e } - return NewDecodingErrorWrap(err) + 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 { @@ -268,12 +317,17 @@ func (e *DecodingError) SetDstValue(value interface{}) *DecodingError { // 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 { - return &DecodingError{ - namespace: *e.namespace.Duplicate(), - error: e.error, - srcValue: e.srcValue, - dstValue: e.dstValue, - } + 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{} { @@ -303,8 +357,8 @@ func (e *DecodingError) AppendNamespace(ns Namespace) LocalizedError { return e } -func (e *DecodingError) SetNamespaceUseFieldName(useFieldName bool) LocalizedError { - e.namespace.UseFldName(useFieldName) +func (e *DecodingError) SetNamespaceUseFldTag(useFldTag bool) LocalizedError { + e.namespace.UseFldTag(useFldTag) return e } @@ -325,10 +379,10 @@ func (e *DecodingError) Unwrap() error { type DecodingErrorsFormatter func(e *DecodingErrors) string func DefaultDecodingErrorsFormatter(e *DecodingErrors) string { - nErrors := e.Len() + nErrors := len(e.errors) points := make([]string, nErrors) for i := 0; i < nErrors; i++ { - points[i] = fmt.Sprintf("* %s", e.Get(i).Error()) + points[i] = fmt.Sprintf("* %s", e.errors[i].Error()) } sort.Strings(points) return fmt.Sprintf("%d error(s) decoding:\n\n%s", @@ -421,9 +475,9 @@ func (e *DecodingErrors) AppendNamespace(ns Namespace) LocalizedError { return e } -func (e *DecodingErrors) SetNamespaceUseFieldName(useFieldName bool) LocalizedError { +func (e *DecodingErrors) SetNamespaceUseFldTag(useFldTag bool) LocalizedError { for i, err := range e.errors { - e.errors[i] = *err.SetNamespaceUseFieldName(useFieldName).(*DecodingError) + e.errors[i] = *err.SetNamespaceUseFldTag(useFldTag).(*DecodingError) } return e } diff --git a/mapstructure.go b/mapstructure.go index 21d91772..70034db5 100644 --- a/mapstructure.go +++ b/mapstructure.go @@ -495,7 +495,7 @@ func (d *Decoder) decode(ns Namespace, input interface{}, outVal reflect.Value) err = d.decodeFunc(*ns.Duplicate(), input, outVal) default: // If we reached this point then we weren't able to decode it - return AsDecodingErrors(NewDecodingErrorFormat("unsupported type: '%s'", + return AsDecodingErrors(NewDecodingError(DecodingErrorUnsupportedType).Format("unsupported type: '%s'", outputKind).SetSrcValue( input).SetDstValue( outVal.Interface()).SetNamespace(ns)) @@ -561,7 +561,8 @@ func (d *Decoder) decodeBasic(ns Namespace, data interface{}, val reflect.Value) dataValType := dataVal.Type() if !dataValType.AssignableTo(val.Type()) { - return NewDecodingErrorFormat("expected type '%s', got '%s'", + return NewDecodingError(DecodingErrorUnexpectedType).Format( + "expected type '%s', got '%s'", val.Type(), dataValType).SetSrcValue( data).SetDstValue( val.Interface()).SetNamespace(ns) @@ -615,7 +616,8 @@ func (d *Decoder) decodeString(ns Namespace, data interface{}, val reflect.Value } if !converted { - return NewDecodingErrorFormat("expected type '%s', got unconvertible type '%s', value: '%v'", + 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) @@ -652,7 +654,7 @@ func (d *Decoder) decodeInt(ns Namespace, data interface{}, val reflect.Value) e if err == nil { val.SetInt(i) } else { - return NewDecodingErrorWrap(err).SetHeader( + return NewDecodingError(DecodingErrorParseFailure).Wrap(err).SetHeader( "cannot parse as int: ").SetSrcValue( data).SetDstValue( val.Interface()).SetNamespace(ns) @@ -661,14 +663,15 @@ func (d *Decoder) decodeInt(ns Namespace, data interface{}, val reflect.Value) e jn := data.(json.Number) i, err := jn.Int64() if err != nil { - return NewDecodingErrorWrap(err).SetHeader( + return NewDecodingError(DecodingErrorJSONDecodeFailure).Wrap(err).SetHeader( "cannot decode json.Number: ").SetSrcValue( data).SetDstValue( val.Interface()).SetNamespace(ns) } val.SetInt(i) default: - return NewDecodingErrorFormat("expected type '%s', got unconvertible type '%s', value: '%v'", + 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) @@ -686,7 +689,7 @@ func (d *Decoder) decodeUint(ns Namespace, data interface{}, val reflect.Value) case dataKind == reflect.Int: i := dataVal.Int() if i < 0 && !d.config.WeaklyTypedInput { - return NewDecodingErrorFormat("cannot parse: %d overflows uint", + return NewDecodingError(DecodingErrorParseOverflow).Format("cannot parse: %d overflows uint", i).SetSrcValue( data).SetDstValue( val.Interface()).SetNamespace(ns) @@ -697,7 +700,7 @@ func (d *Decoder) decodeUint(ns Namespace, data interface{}, val reflect.Value) case dataKind == reflect.Float32: f := dataVal.Float() if f < 0 && !d.config.WeaklyTypedInput { - return NewDecodingErrorFormat("cannot parse: %f overflows uint", + return NewDecodingError(DecodingErrorParseOverflow).Format("cannot parse: %f overflows uint", f).SetSrcValue( data).SetDstValue( val.Interface()).SetNamespace(ns) @@ -719,7 +722,7 @@ func (d *Decoder) decodeUint(ns Namespace, data interface{}, val reflect.Value) if err == nil { val.SetUint(i) } else { - return NewDecodingErrorWrap(err).SetHeader( + return NewDecodingError(DecodingErrorParseFailure).Wrap(err).SetHeader( "cannot parse as uint: ").SetSrcValue( data).SetDstValue( val.Interface()).SetNamespace(ns) @@ -728,14 +731,14 @@ func (d *Decoder) decodeUint(ns Namespace, data interface{}, val reflect.Value) jn := data.(json.Number) i, err := strconv.ParseUint(string(jn), 0, 64) if err != nil { - return NewDecodingErrorWrap(err).SetHeader( + return NewDecodingError(DecodingErrorJSONDecodeFailure).Wrap(err).SetHeader( "cannot parse as json.Number: ").SetSrcValue( data).SetDstValue( val.Interface()).SetNamespace(ns) } val.SetUint(i) default: - return NewDecodingErrorFormat( + return NewDecodingError(DecodingErrorUnexpectedType).Format( "expected type '%s', got unconvertible type '%s', value: '%v'", val.Type(), dataVal.Type(), data).SetSrcValue( data).SetDstValue( @@ -765,13 +768,13 @@ func (d *Decoder) decodeBool(ns Namespace, data interface{}, val reflect.Value) } else if dataVal.String() == "" { val.SetBool(false) } else { - return NewDecodingErrorWrap(err).SetHeader( + return NewDecodingError(DecodingErrorParseFailure).Wrap(err).SetHeader( "cannot parse as bool: ").SetSrcValue( data).SetDstValue( val.Interface()).SetNamespace(ns) } default: - return NewDecodingErrorFormat( + return NewDecodingError(DecodingErrorUnexpectedType).Format( "expected type '%s', got unconvertible type '%s', value: '%v'", val.Type(), dataVal.Type(), data).SetSrcValue( data).SetDstValue( @@ -809,7 +812,7 @@ func (d *Decoder) decodeFloat(ns Namespace, data interface{}, val reflect.Value) if err == nil { val.SetFloat(f) } else { - return NewDecodingErrorWrap(err).SetHeader( + return NewDecodingError(DecodingErrorParseFailure).Wrap(err).SetHeader( "cannot parse as float: ").SetSrcValue( data).SetDstValue( val.Interface()).SetNamespace(ns) @@ -818,14 +821,14 @@ func (d *Decoder) decodeFloat(ns Namespace, data interface{}, val reflect.Value) jn := data.(json.Number) i, err := jn.Float64() if err != nil { - return NewDecodingErrorWrap(err).SetHeader( + return NewDecodingError(DecodingErrorJSONDecodeFailure).Wrap(err).SetHeader( "cannot parse as json.Number: ").SetSrcValue( data).SetDstValue( val.Interface()).SetNamespace(ns) } val.SetFloat(i) default: - return NewDecodingErrorFormat( + return NewDecodingError(DecodingErrorUnexpectedType).Format( "expected type '%s', got unconvertible type '%s', value: '%v'", val.Type(), dataVal.Type(), data).SetSrcValue( data).SetDstValue( @@ -867,7 +870,8 @@ func (d *Decoder) decodeMap(ns Namespace, data interface{}, val reflect.Value) e fallthrough default: - return NewDecodingErrorFormat("expected a map, got '%s'", + return NewDecodingError(DecodingErrorUnexpectedType).Format( + "expected a map, got '%s'", dataVal.Kind()).SetSrcValue( data).SetDstValue( val.Interface()).SetNamespace(ns) @@ -882,7 +886,7 @@ func (d *Decoder) decodeMapFromSlice(ns Namespace, dataVal reflect.Value, val re } for i := 0; i < dataVal.Len(); i++ { - // CHECK: namespace refers to the "to" value not the "from" + // namespace refers to the "to" value not the "from" err := d.decode(*ns.Duplicate(), dataVal.Index(i).Interface(), val) if err != nil { return err @@ -917,7 +921,7 @@ func (d *Decoder) decodeMapFromMap(ns Namespace, dataVal reflect.Value, val refl for _, k := range dataVal.MapKeys() { // First decode the key into the proper type currentKey := reflect.Indirect(reflect.New(valKeyType)) - // CHECK + if err := d.decode(*ns.Duplicate().AppendKey(k.Interface()), k.Interface(), currentKey); err != nil { errors.Append(err) continue @@ -957,10 +961,14 @@ func (d *Decoder) decodeMapFromStruct(ns Namespace, dataVal reflect.Value, val r // to the map value. v := dataVal.Field(i) if !v.Type().AssignableTo(valMap.Type().Elem()) { - return NewDecodingErrorFormat("cannot assign type '%s' to map value field of type '%s'", - v.Type(), valMap.Type().Elem()).SetSrcValue( - v.Interface()).SetDstValue( - val.Interface()).SetNamespace(ns) // CHECK + // 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) @@ -995,10 +1003,12 @@ func (d *Decoder) decodeMapFromStruct(ns Namespace, dataVal reflect.Value, val r // The final type must be a struct if dv.Kind() != reflect.Struct { - return NewDecodingErrorFormat("cannot squash non-struct type '%s'", + // As before, we localize the error at the map level + return NewDecodingError(DecodingErrorSrcSquashFailure).Format( + "cannot squash non-struct type '%s'", dv.Type()).SetSrcValue( - v.Interface()).SetDstValue( - val.Interface()).SetNamespace(ns) // CHECK + dataVal.Interface()).SetDstValue( + val.Interface()).SetNamespace(ns) } } if keyNameTagValue := tagValue[:index]; keyNameTagValue != "" { @@ -1111,7 +1121,8 @@ func (d *Decoder) decodeFunc(ns Namespace, data interface{}, val reflect.Value) // into that. Then set the value of the pointer to this type. dataVal := reflect.Indirect(reflect.ValueOf(data)) if val.Type() != dataVal.Type() { - return NewDecodingErrorFormat("expected type '%s', got unconvertible type '%s', value: '%v'", + 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) @@ -1155,7 +1166,8 @@ func (d *Decoder) decodeSlice(ns Namespace, data interface{}, val reflect.Value) } } - return NewDecodingErrorFormat("source data must be an array or slice, got %s", + return NewDecodingError(DecodingErrorUnexpectedType).Format( + "source data must be an array or slice, got %s", dataValKind).SetSrcValue( data).SetDstValue( val.Interface()).SetNamespace(ns) @@ -1227,14 +1239,16 @@ func (d *Decoder) decodeArray(ns Namespace, data interface{}, val reflect.Value) } } - return NewDecodingErrorFormat("source data must be an array or slice, got %s", + 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 NewDecodingErrorFormat("expected source data to have length less or equal to %d, got %d", + 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) @@ -1305,7 +1319,8 @@ func (d *Decoder) decodeStruct(ns Namespace, data interface{}, val reflect.Value return err default: - return NewDecodingErrorFormat("expected a map, got '%s'", + return NewDecodingError(DecodingErrorUnexpectedType).Format( + "expected a map, got '%s'", dataVal.Kind()).SetSrcValue( data).SetDstValue( val.Interface()).SetNamespace(ns) @@ -1315,7 +1330,8 @@ func (d *Decoder) decodeStruct(ns Namespace, data interface{}, val reflect.Value 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 NewDecodingErrorFormat("needs a map with string keys, has '%s' keys", + return NewDecodingError(DecodingErrorUnexpectedType).Format( + "needs a map with string keys, has '%s' keys", dataValType.Key().Kind()).SetSrcValue( dataVal.Interface()).SetDstValue( val.Interface()).SetNamespace(ns) @@ -1383,8 +1399,11 @@ func (d *Decoder) decodeStructFromMap(ns Namespace, dataVal, val reflect.Value) if squash { if fieldVal.Kind() != reflect.Struct { - // CHECK - errors.Append(NewDecodingErrorFormat("unsupported type for squash: %s", + // 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))) @@ -1456,16 +1475,9 @@ func (d *Decoder) decodeStructFromMap(ns Namespace, dataVal, val reflect.Value) // 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 ns.Len() > 0 { // CHECK - // fieldName = ns.String() + "." + fieldName - // } - - if err := d.decode(*ns.Duplicate().AppendFld(*NewNamespaceFld().SetName(field.Name).SetTag(fieldName).UseName(false)), + if err := d.decode(*ns.Duplicate().AppendFld(*NewNamespaceFld(field.Name).SetTag(fieldName).UseTag(true)), rawMapVal.Interface(), fieldValue); err != nil { - errors.Append(AsLocalizedError(err).SetNamespaceUseFieldName(true)) + errors.Append(AsLocalizedError(err).SetNamespaceUseFldTag(false)) } } @@ -1495,7 +1507,7 @@ func (d *Decoder) decodeStructFromMap(ns Namespace, dataVal, val reflect.Value) } sort.Strings(keys) - err := NewDecodingErrorFormat("has invalid keys: %s", + err := NewDecodingError(DecodingErrorUnusedKeys).Format("has invalid keys: %s", strings.Join(keys, ", ")).SetSrcValue( dataVal.Interface()).SetDstValue( val.Interface()).SetNamespace(ns) @@ -1509,7 +1521,7 @@ func (d *Decoder) decodeStructFromMap(ns Namespace, dataVal, val reflect.Value) } sort.Strings(keys) - err := NewDecodingErrorFormat("has unset fields: %s", + err := NewDecodingError(DecodingErrorUnsetFields).Format("has unset fields: %s", strings.Join(keys, ", ")).SetDstValue( val.Interface()).SetNamespace(ns) errors.Append(err)