diff --git a/error.go b/error.go index 47a99e5a..0ec4e8b3 100644 --- a/error.go +++ b/error.go @@ -1,28 +1,116 @@ package mapstructure import ( - "errors" "fmt" + "reflect" "sort" "strings" ) +// FieldError implements the error interface and provide access to +// field path where that error occurred. +type FieldError interface { + error + Path() FieldPath +} + +// baseError default implementation of FieldError. +type baseError struct { + error + path FieldPath +} + +func (e baseError) Path() FieldPath { + return e.path +} + +func formatError(path FieldPath, format string, a ...interface{}) error { + return &baseError{ + path: path, + error: fmt.Errorf(format, a...), + } +} + +// SliceExpectedError implements the error interface. It provides access to +// field path where that error occurred and specific for this type of errors parameters. +type SliceExpectedError struct { + path FieldPath + Got reflect.Kind +} + +func (e SliceExpectedError) Path() FieldPath { + return e.path +} + +func (e *SliceExpectedError) Error() string { + return fmt.Sprintf( + "'%s': source data must be an array or slice, got %s", e.path, e.Got) +} + +// UnexpectedUnconvertibleTypeError implements the error interface. It provides access to +// field path where that error occurred and specific for this type of errors parameters. +type UnexpectedUnconvertibleTypeError struct { + path FieldPath + Expected reflect.Type + Got reflect.Type + Data interface{} +} + +func (e UnexpectedUnconvertibleTypeError) Path() FieldPath { + return e.path +} + +func (e *UnexpectedUnconvertibleTypeError) Error() string { + return fmt.Sprintf( + "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", + e.path, e.Expected, e.Got, e.Data) +} + +// CanNotParseError implements the error interface. It provides access to +// field path where that error occurred and specific for this type of errors parameters. +type CanNotParseError struct { + path FieldPath + Reason error + Type string +} + +func (e CanNotParseError) Path() FieldPath { + return e.path +} + +func (e *CanNotParseError) Error() string { + return fmt.Sprintf("cannot parse '%s' as %s: %s", e.path, e.Type, e.Reason) +} + // Error implements the error interface and can represents multiple // errors that occur in the course of a single decode. type Error struct { - Errors []string + // Deprecated: left for backward compatibility. + Errors []string + realErrors []error +} + +func newMultiError(errors []error) *Error { + stringErrors := make([]string, len(errors)) + for i, err := range errors { + stringErrors[i] = err.Error() + } + return &Error{ + Errors: stringErrors, + realErrors: errors, + } } func (e *Error) Error() string { - points := make([]string, len(e.Errors)) - for i, err := range e.Errors { - points[i] = fmt.Sprintf("* %s", err) + points := make([]string, len(e.realErrors)) + for i, err := range e.realErrors { + points[i] = fmt.Sprintf("* %s", err.Error()) } sort.Strings(points) return fmt.Sprintf( "%d error(s) decoding:\n\n%s", - len(e.Errors), strings.Join(points, "\n")) + len(e.realErrors), strings.Join(points, "\n")) } // WrappedErrors implements the errwrap.Wrapper interface to make this @@ -32,19 +120,14 @@ func (e *Error) WrappedErrors() []error { return nil } - result := make([]error, len(e.Errors)) - for i, e := range e.Errors { - result[i] = errors.New(e) - } - - return result + return e.realErrors } -func appendErrors(errors []string, err error) []string { +func appendErrors(errors []error, err error) []error { switch e := err.(type) { case *Error: - return append(errors, e.Errors...) + return append(errors, e.WrappedErrors()...) default: - return append(errors, e.Error()) + return append(errors, e) } } diff --git a/field_path.go b/field_path.go new file mode 100644 index 00000000..38d8591e --- /dev/null +++ b/field_path.go @@ -0,0 +1,122 @@ +package mapstructure + +import ( + "strconv" +) + +//PathPart is interface for different kinds of FieldPath elements. +type PathPart interface { + getDelimiter() string + String() string +} + +//InStructPathPart is FieldPath element that represents field name in structure. +type InStructPathPart struct { + val string +} + +func (p InStructPathPart) getDelimiter() string { + return "." +} + +func (p InStructPathPart) String() string { + return p.val +} + +func (p InStructPathPart) Value() string { + return p.val +} + +//InMapPathPart is FieldPath element that represents key in map. +type InMapPathPart struct { + val string +} + +func (p InMapPathPart) getDelimiter() string { + return "" +} + +func (p InMapPathPart) String() string { + return "[" + p.val + "]" +} + +func (p InMapPathPart) Value() string { + return p.val +} + +//InSlicePathPart is FieldPath element that represents index in slice or array. +type InSlicePathPart struct { + val int +} + +func (p InSlicePathPart) getDelimiter() string { + return "" +} + +func (p InSlicePathPart) String() string { + return "[" + strconv.Itoa(p.val) + "]" +} + +func (p InSlicePathPart) Value() int { + return p.val +} + +//FieldPath represents path to a field in nested structure. +type FieldPath struct { + parts []PathPart +} + +func (f FieldPath) addStruct(part string) FieldPath { + return FieldPath{ + parts: appendPart(f.parts, InStructPathPart{val: part}), + } +} + +func (f FieldPath) addMap(part string) FieldPath { + return FieldPath{ + parts: appendPart(f.parts, InMapPathPart{val: part}), + } +} + +func (f FieldPath) addSlice(part int) FieldPath { + return FieldPath{ + parts: appendPart(f.parts, InSlicePathPart{val: part}), + } +} + +func (f FieldPath) notEmpty() bool { + return len(f.parts) > 0 +} + +func newFieldPath() FieldPath { + return FieldPath{ + parts: make([]PathPart, 0), + } +} + +func (f FieldPath) Parts() []PathPart { + return f.parts +} + +func (f FieldPath) String() string { + result := "" + + for i, part := range f.parts { + delimiter := "" + + if i > 0 { //there is no delimiter before first element + delimiter = part.getDelimiter() + } + + result += delimiter + part.String() + } + + return result +} + +//appendPart appends PathPart to a PathPart slice with guarantee of slice immutability. +func appendPart(parts []PathPart, part PathPart) []PathPart { + p := make([]PathPart, len(parts)) + copy(p, parts) + return append(p, part) +} diff --git a/mapstructure.go b/mapstructure.go index 6b81b006..efeb9573 100644 --- a/mapstructure.go +++ b/mapstructure.go @@ -161,7 +161,6 @@ package mapstructure import ( "encoding/json" "errors" - "fmt" "reflect" "sort" "strconv" @@ -279,11 +278,20 @@ type Decoder struct { // is tedious or difficult to get otherwise. type Metadata struct { // Keys are the keys of the structure which were successfully decoded + // Deprecated: left for backward compatibility. Keys []string + // DecodedPaths are the field paths of the structure which were successfully decoded + DecodedPaths []FieldPath + // Unused is a slice of keys that were found in the raw value but // weren't decoded since there was no matching field in the result interface + // Deprecated: left for backward compatibility. Unused []string + + // Unused is a slice of field paths that were found in the raw value but + // weren't decoded since there was no matching field in the result interface + UnusedPaths []FieldPath } // Decode takes an input structure and uses reflection to translate it to @@ -372,9 +380,17 @@ func NewDecoder(config *DecoderConfig) (*Decoder, error) { config.Metadata.Keys = make([]string, 0) } + if config.Metadata.DecodedPaths == nil { + config.Metadata.DecodedPaths = make([]FieldPath, 0) + } + if config.Metadata.Unused == nil { config.Metadata.Unused = make([]string, 0) } + + if config.Metadata.UnusedPaths == nil { + config.Metadata.UnusedPaths = make([]FieldPath, 0) + } } if config.TagName == "" { @@ -395,11 +411,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(newFieldPath(), 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(path FieldPath, input interface{}, outVal reflect.Value) error { var inputVal reflect.Value if input != nil { inputVal = reflect.ValueOf(input) @@ -417,8 +433,9 @@ 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 && path.notEmpty() { + d.config.Metadata.Keys = append(d.config.Metadata.Keys, path.String()) + d.config.Metadata.DecodedPaths = append(d.config.Metadata.DecodedPaths, path) } } return nil @@ -428,8 +445,9 @@ 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 && path.notEmpty() { + d.config.Metadata.Keys = append(d.config.Metadata.Keys, path.String()) + d.config.Metadata.DecodedPaths = append(d.config.Metadata.DecodedPaths, path) } return nil } @@ -439,7 +457,7 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e var err error input, err = DecodeHookExec(d.config.DecodeHook, inputVal, outVal) if err != nil { - return fmt.Errorf("error decoding '%s': %s", name, err) + return formatError(path, "error decoding '%s': %s", path, err) } } @@ -448,38 +466,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(path, input, outVal) case reflect.Interface: - err = d.decodeBasic(name, input, outVal) + err = d.decodeBasic(path, input, outVal) case reflect.String: - err = d.decodeString(name, input, outVal) + err = d.decodeString(path, input, outVal) case reflect.Int: - err = d.decodeInt(name, input, outVal) + err = d.decodeInt(path, input, outVal) case reflect.Uint: - err = d.decodeUint(name, input, outVal) + err = d.decodeUint(path, input, outVal) case reflect.Float32: - err = d.decodeFloat(name, input, outVal) + err = d.decodeFloat(path, input, outVal) case reflect.Struct: - err = d.decodeStruct(name, input, outVal) + err = d.decodeStruct(path, input, outVal) case reflect.Map: - err = d.decodeMap(name, input, outVal) + err = d.decodeMap(path, input, outVal) case reflect.Ptr: - addMetaKey, err = d.decodePtr(name, input, outVal) + addMetaKey, err = d.decodePtr(path, input, outVal) case reflect.Slice: - err = d.decodeSlice(name, input, outVal) + err = d.decodeSlice(path, input, outVal) case reflect.Array: - err = d.decodeArray(name, input, outVal) + err = d.decodeArray(path, input, outVal) case reflect.Func: - err = d.decodeFunc(name, input, outVal) + err = d.decodeFunc(path, 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 formatError(path, "%s: unsupported type: %s", path, outputKind) } // 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 && path.notEmpty() { + d.config.Metadata.Keys = append(d.config.Metadata.Keys, path.String()) + d.config.Metadata.DecodedPaths = append(d.config.Metadata.DecodedPaths, path) } return err @@ -487,7 +506,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(path FieldPath, data interface{}, val reflect.Value) error { if val.IsValid() && val.Elem().IsValid() { elem := val.Elem() @@ -510,7 +529,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(path, data, elem); err != nil || !copied { return err } @@ -534,16 +553,14 @@ 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 formatError(path, "'%s' expected type '%s', got '%s'", path, val.Type(), dataValType) } val.Set(dataVal) return nil } -func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decodeString(path FieldPath, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) @@ -587,15 +604,18 @@ 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 &UnexpectedUnconvertibleTypeError{ + path: path, + Expected: val.Type(), + Got: dataVal.Type(), + Data: data, + } } return nil } -func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decodeInt(path FieldPath, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) dataType := dataVal.Type() @@ -623,26 +643,37 @@ 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 &CanNotParseError{ + path: path, + Reason: err, + Type: "int", + } } 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 formatError( + path, + "error decoding json.Number into %s: %s", + path, + err, + ) } val.SetInt(i) default: - return fmt.Errorf( - "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", - name, val.Type(), dataVal.Type(), data) + return &UnexpectedUnconvertibleTypeError{ + path: path, + Expected: val.Type(), + Got: dataVal.Type(), + Data: data, + } } return nil } -func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decodeUint(name FieldPath, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) dataType := dataVal.Type() @@ -651,8 +682,12 @@ 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 formatError( + name, + "cannot parse '%s', %d overflows uint", + name, + i, + ) } val.SetUint(uint64(i)) case dataKind == reflect.Uint: @@ -660,8 +695,12 @@ 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 formatError( + name, + "cannot parse '%s', %f overflows uint", + name, + f, + ) } val.SetUint(uint64(f)) case dataKind == reflect.Bool && d.config.WeaklyTypedInput: @@ -680,26 +719,37 @@ 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 &CanNotParseError{ + path: name, + Reason: err, + Type: "uint", + } } 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 formatError( + name, + "error decoding json.Number into %s: %s", + name, + err, + ) } val.SetUint(i) default: - return fmt.Errorf( - "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", - name, val.Type(), dataVal.Type(), data) + return &UnexpectedUnconvertibleTypeError{ + path: name, + Expected: val.Type(), + Got: dataVal.Type(), + Data: data, + } } return nil } -func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decodeBool(path FieldPath, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) @@ -719,18 +769,25 @@ 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 &CanNotParseError{ + path: path, + Reason: err, + Type: "bool", + } } default: - return fmt.Errorf( - "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", - name, val.Type(), dataVal.Type(), data) + return &UnexpectedUnconvertibleTypeError{ + path: path, + Expected: val.Type(), + Got: dataVal.Type(), + Data: data, + } } return nil } -func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decodeFloat(path FieldPath, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) dataType := dataVal.Type() @@ -758,26 +815,37 @@ 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 &CanNotParseError{ + path: path, + Reason: err, + Type: "float", + } } 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 formatError( + path, + "error decoding json.Number into %s: %s", + path, + err, + ) } val.SetFloat(i) default: - return fmt.Errorf( - "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", - name, val.Type(), dataVal.Type(), data) + return &UnexpectedUnconvertibleTypeError{ + path: path, + Expected: val.Type(), + Got: dataVal.Type(), + Data: data, + } } return nil } -func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decodeMap(path FieldPath, data interface{}, val reflect.Value) error { valType := val.Type() valKeyType := valType.Key() valElemType := valType.Elem() @@ -796,24 +864,24 @@ 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(path, dataVal, val, valMap) case reflect.Struct: - return d.decodeMapFromStruct(name, dataVal, val, valMap) + return d.decodeMapFromStruct(path, dataVal, val, valMap) case reflect.Array, reflect.Slice: if d.config.WeaklyTypedInput { - return d.decodeMapFromSlice(name, dataVal, val, valMap) + return d.decodeMapFromSlice(path, dataVal, val, valMap) } fallthrough default: - return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind()) + return formatError(path, "'%s' expected a map, got '%s'", path, dataVal.Kind()) } } -func (d *Decoder) decodeMapFromSlice(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error { +func (d *Decoder) decodeMapFromSlice(path FieldPath, 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) @@ -822,7 +890,7 @@ 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)+"]", + path.addSlice(i), dataVal.Index(i).Interface(), val) if err != nil { return err @@ -832,13 +900,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(path FieldPath, 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 := make([]error, 0) // If the input data is empty, then we just match what the input data is. if dataVal.Len() == 0 { @@ -855,7 +923,7 @@ func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val refle } for _, k := range dataVal.MapKeys() { - fieldName := name + "[" + k.String() + "]" + fieldName := path.addMap(k.String()) // First decode the key into the proper type currentKey := reflect.Indirect(reflect.New(valKeyType)) @@ -880,13 +948,13 @@ func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val refle // If we had errors, return those if len(errors) > 0 { - return &Error{errors} + return newMultiError(errors) } return nil } -func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error { +func (d *Decoder) decodeMapFromStruct(path FieldPath, 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 @@ -900,7 +968,12 @@ 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 formatError( + path, + "cannot assign type '%s' to map value field of type '%s'", + v.Type(), + valMap.Type().Elem(), + ) } tagValue := f.Tag.Get(d.config.TagName) @@ -909,7 +982,7 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re // If Squash is set in the config, we squash the field down. squash := d.config.Squash && v.Kind() == reflect.Struct && f.Anonymous - // Determine the name of the key in the map + // Determine the path of the key in the map if index := strings.Index(tagValue, ","); index != -1 { if tagValue[:index] == "-" { continue @@ -929,7 +1002,11 @@ 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 formatError( + path, + "cannot squash non-struct type '%s'", + v.Type(), + ) } } keyName = tagValue[:index] @@ -959,7 +1036,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(newFieldPath().addStruct(keyName), x.Interface(), reflect.Indirect(addrVal)) if err != nil { return err } @@ -988,7 +1065,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(path FieldPath, 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 @@ -1022,33 +1099,36 @@ 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(path, 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(path, 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(path FieldPath, 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 &UnexpectedUnconvertibleTypeError{ + path: path, + Expected: val.Type(), + Got: dataVal.Type(), + Data: data, + } } val.Set(dataVal) return nil } -func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decodeSlice(path FieldPath, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataValKind := dataVal.Kind() valType := val.Type() @@ -1070,21 +1150,23 @@ 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(path, []interface{}{data}, val) case dataValKind == reflect.String && valElemType.Kind() == reflect.Uint8: - return d.decodeSlice(name, []byte(dataVal.String()), val) + return d.decodeSlice(path, []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(path, []interface{}{data}, val) } } - return fmt.Errorf( - "'%s': source data must be an array or slice, got %s", name, dataValKind) + return &SliceExpectedError{ + path: path, + Got: dataValKind, + } } // If the input value is nil, then don't allocate since empty != nil @@ -1099,7 +1181,7 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) } // Accumulate any errors - errors := make([]string, 0) + errors := make([]error, 0) for i := 0; i < dataVal.Len(); i++ { currentData := dataVal.Index(i).Interface() @@ -1108,8 +1190,7 @@ 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 { + if err := d.decode(path.addSlice(i), currentData, currentField); err != nil { errors = appendErrors(errors, err) } } @@ -1119,13 +1200,13 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) // If there were errors, we return those if len(errors) > 0 { - return &Error{errors} + return newMultiError(errors) } return nil } -func (d *Decoder) decodeArray(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decodeArray(path FieldPath, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataValKind := dataVal.Kind() valType := val.Type() @@ -1150,18 +1231,18 @@ 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(path, []interface{}{data}, val) } } - return fmt.Errorf( - "'%s': source data must be an array or slice, got %s", name, dataValKind) - + return &SliceExpectedError{ + path: path, + Got: dataValKind, + } } if dataVal.Len() > arrayType.Len() { - return fmt.Errorf( - "'%s': expected source data to have length less or equal to %d, got %d", name, arrayType.Len(), dataVal.Len()) - + return formatError(path, + "'%s': expected source data to have length less or equal to %d, got %d", path, arrayType.Len(), dataVal.Len()) } // Make a new array to hold our result, same size as the original data. @@ -1169,14 +1250,13 @@ func (d *Decoder) decodeArray(name string, data interface{}, val reflect.Value) } // Accumulate any errors - errors := make([]string, 0) + errors := make([]error, 0) 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 { + if err := d.decode(path.addSlice(i), currentData, currentField); err != nil { errors = appendErrors(errors, err) } } @@ -1186,13 +1266,13 @@ func (d *Decoder) decodeArray(name string, data interface{}, val reflect.Value) // If there were errors, we return those if len(errors) > 0 { - return &Error{errors} + return newMultiError(errors) } return nil } -func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) error { +func (d *Decoder) decodeStruct(path FieldPath, 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, @@ -1205,7 +1285,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(path, dataVal, val) case reflect.Struct: // Not the most efficient way to do this but we can optimize later if @@ -1223,24 +1303,27 @@ 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(path, dataVal, reflect.Indirect(addrVal), mval); err != nil { return err } - result := d.decodeStructFromMap(name, reflect.Indirect(addrVal), val) + result := d.decodeStructFromMap(path, reflect.Indirect(addrVal), val) return result default: - return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind()) + return formatError(path, "'%s' expected a map, got '%s'", path, dataVal.Kind()) } } -func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) error { +func (d *Decoder) decodeStructFromMap(path FieldPath, dataVal, val reflect.Value) error { dataValType := dataVal.Type() if kind := dataValType.Key().Kind(); kind != reflect.String && kind != reflect.Interface { - return fmt.Errorf( + return formatError( + path, "'%s' needs a map with string keys, has '%s' keys", - name, dataValType.Key().Kind()) + path, + dataValType.Key().Kind(), + ) } dataValKeys := make(map[reflect.Value]struct{}) @@ -1250,7 +1333,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e dataValKeysUnused[dataValKey.Interface()] = struct{}{} } - errors := make([]string, 0) + errors := make([]error, 0) // 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 @@ -1305,7 +1388,7 @@ 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())) + formatError(path, "%s: unsupported type for squash: %s", fieldType.Name, fieldVal.Kind())) } else { structs = append(structs, fieldVal) } @@ -1373,13 +1456,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e // Delete the key we're using from the unused map so we stop tracking delete(dataValKeysUnused, rawMapKey.Interface()) - // If the name is empty string, then we're at the root, and we - // don't dot-join the fields. - if name != "" { - fieldName = name + "." + fieldName - } - - if err := d.decode(fieldName, rawMapVal.Interface(), fieldValue); err != nil { + if err := d.decode(path.addStruct(fieldName), rawMapVal.Interface(), fieldValue); err != nil { errors = appendErrors(errors, err) } } @@ -1394,7 +1471,7 @@ 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 { + if err := d.decodeMap(path, remain, remainField.val); err != nil { errors = appendErrors(errors, err) } @@ -1410,23 +1487,21 @@ 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, ", ")) + err := formatError(path, "'%s' has invalid keys: %s", path, strings.Join(keys, ", ")) errors = appendErrors(errors, err) } if len(errors) > 0 { - return &Error{errors} + return newMultiError(errors) } - // Add the unused keys to the list of unused keys if we're tracking metadata + // addStruct 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 - } - d.config.Metadata.Unused = append(d.config.Metadata.Unused, key) + d.config.Metadata.Unused = append(d.config.Metadata.Unused, path.addStruct(key).String()) + d.config.Metadata.UnusedPaths = append(d.config.Metadata.UnusedPaths, path.addStruct(key)) } }