aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/mitchellh
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/mitchellh')
-rw-r--r--vendor/github.com/mitchellh/mapstructure/.travis.yml9
-rw-r--r--vendor/github.com/mitchellh/mapstructure/CHANGELOG.md35
-rw-r--r--vendor/github.com/mitchellh/mapstructure/decode_hooks.go92
-rw-r--r--vendor/github.com/mitchellh/mapstructure/mapstructure.go227
4 files changed, 287 insertions, 76 deletions
diff --git a/vendor/github.com/mitchellh/mapstructure/.travis.yml b/vendor/github.com/mitchellh/mapstructure/.travis.yml
deleted file mode 100644
index 5e31a95a8..000000000
--- a/vendor/github.com/mitchellh/mapstructure/.travis.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-language: go
-
-go:
- - "1.14.x"
- - tip
-
-script:
- - go test
- - go test -bench . -benchmem
diff --git a/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md b/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md
index 20eea2b7a..c75823490 100644
--- a/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md
+++ b/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md
@@ -1,3 +1,38 @@
+## 1.5.0
+
+* New option `IgnoreUntaggedFields` to ignore decoding to any fields
+ without `mapstructure` (or the configured tag name) set [GH-277]
+* New option `ErrorUnset` which makes it an error if any fields
+ in a target struct are not set by the decoding process. [GH-225]
+* New function `OrComposeDecodeHookFunc` to help compose decode hooks. [GH-240]
+* Decoding to slice from array no longer crashes [GH-265]
+* Decode nested struct pointers to map [GH-271]
+* Fix issue where `,squash` was ignored if `Squash` option was set. [GH-280]
+* Fix issue where fields with `,omitempty` would sometimes decode
+ into a map with an empty string key [GH-281]
+
+## 1.4.3
+
+* Fix cases where `json.Number` didn't decode properly [GH-261]
+
+## 1.4.2
+
+* Custom name matchers to support any sort of casing, formatting, etc. for
+ field names. [GH-250]
+* Fix possible panic in ComposeDecodeHookFunc [GH-251]
+
+## 1.4.1
+
+* Fix regression where `*time.Time` value would be set to empty and not be sent
+ to decode hooks properly [GH-232]
+
+## 1.4.0
+
+* A new decode hook type `DecodeHookFuncValue` has been added that has
+ access to the full values. [GH-183]
+* Squash is now supported with embedded fields that are struct pointers [GH-205]
+* Empty strings will convert to 0 for all numeric types when weakly decoding [GH-206]
+
## 1.3.3
* Decoding maps from maps creates a settable value for decode hooks [GH-203]
diff --git a/vendor/github.com/mitchellh/mapstructure/decode_hooks.go b/vendor/github.com/mitchellh/mapstructure/decode_hooks.go
index 1f0abc65a..3a754ca72 100644
--- a/vendor/github.com/mitchellh/mapstructure/decode_hooks.go
+++ b/vendor/github.com/mitchellh/mapstructure/decode_hooks.go
@@ -1,6 +1,7 @@
package mapstructure
import (
+ "encoding"
"errors"
"fmt"
"net"
@@ -16,10 +17,11 @@ func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc {
// Create variables here so we can reference them with the reflect pkg
var f1 DecodeHookFuncType
var f2 DecodeHookFuncKind
+ var f3 DecodeHookFuncValue
// Fill in the variables into this interface and the rest is done
// automatically using the reflect package.
- potential := []interface{}{f1, f2}
+ potential := []interface{}{f1, f2, f3}
v := reflect.ValueOf(h)
vt := v.Type()
@@ -38,13 +40,15 @@ func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc {
// that took reflect.Kind instead of reflect.Type.
func DecodeHookExec(
raw DecodeHookFunc,
- from reflect.Type, to reflect.Type,
- data interface{}) (interface{}, error) {
+ from reflect.Value, to reflect.Value) (interface{}, error) {
+
switch f := typedDecodeHook(raw).(type) {
case DecodeHookFuncType:
- return f(from, to, data)
+ return f(from.Type(), to.Type(), from.Interface())
case DecodeHookFuncKind:
- return f(from.Kind(), to.Kind(), data)
+ return f(from.Kind(), to.Kind(), from.Interface())
+ case DecodeHookFuncValue:
+ return f(from, to)
default:
return nil, errors.New("invalid decode hook signature")
}
@@ -56,25 +60,42 @@ func DecodeHookExec(
// The composed funcs are called in order, with the result of the
// previous transformation.
func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc {
- return func(
- f reflect.Type,
- t reflect.Type,
- data interface{}) (interface{}, error) {
+ return func(f reflect.Value, t reflect.Value) (interface{}, error) {
var err error
+ data := f.Interface()
+
+ newFrom := f
for _, f1 := range fs {
- data, err = DecodeHookExec(f1, f, t, data)
+ data, err = DecodeHookExec(f1, newFrom, t)
if err != nil {
return nil, err
}
+ newFrom = reflect.ValueOf(data)
+ }
- // Modify the from kind to be correct with the new data
- f = nil
- if val := reflect.ValueOf(data); val.IsValid() {
- f = val.Type()
+ return data, nil
+ }
+}
+
+// OrComposeDecodeHookFunc executes all input hook functions until one of them returns no error. In that case its value is returned.
+// 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
+
+ for _, f := range ff {
+ out, err = DecodeHookExec(f, a, b)
+ if err != nil {
+ allErrs += err.Error() + "\n"
+ continue
}
+
+ return out, nil
}
- return data, nil
+ return nil, errors.New(allErrs)
}
}
@@ -215,3 +236,44 @@ func WeaklyTypedHook(
return data, nil
}
+
+func RecursiveStructToMapHookFunc() DecodeHookFunc {
+ return func(f reflect.Value, t reflect.Value) (interface{}, error) {
+ if f.Kind() != reflect.Struct {
+ return f.Interface(), nil
+ }
+
+ var i interface{} = struct{}{}
+ if t.Type() != reflect.TypeOf(&i).Elem() {
+ return f.Interface(), nil
+ }
+
+ m := make(map[string]interface{})
+ t.Set(reflect.ValueOf(m))
+
+ return f.Interface(), nil
+ }
+}
+
+// TextUnmarshallerHookFunc returns a DecodeHookFunc that applies
+// strings to the UnmarshalText function, when the target type
+// implements the encoding.TextUnmarshaler interface
+func TextUnmarshallerHookFunc() DecodeHookFuncType {
+ return func(
+ f reflect.Type,
+ t reflect.Type,
+ data interface{}) (interface{}, error) {
+ if f.Kind() != reflect.String {
+ return data, nil
+ }
+ result := reflect.New(t).Interface()
+ unmarshaller, ok := result.(encoding.TextUnmarshaler)
+ if !ok {
+ return data, nil
+ }
+ if err := unmarshaller.UnmarshalText([]byte(data.(string))); err != nil {
+ return nil, err
+ }
+ return result, nil
+ }
+}
diff --git a/vendor/github.com/mitchellh/mapstructure/mapstructure.go b/vendor/github.com/mitchellh/mapstructure/mapstructure.go
index f41bcc58f..1efb22ac3 100644
--- a/vendor/github.com/mitchellh/mapstructure/mapstructure.go
+++ b/vendor/github.com/mitchellh/mapstructure/mapstructure.go
@@ -72,6 +72,17 @@
// "name": "alice",
// }
//
+// When decoding from a struct to a map, the squash tag squashes the struct
+// fields into a single map. Using the example structs from above:
+//
+// Friend{Person: Person{Name: "alice"}}
+//
+// Will be decoded into a map:
+//
+// map[string]interface{}{
+// "name": "alice",
+// }
+//
// DecoderConfig has a field that changes the behavior of mapstructure
// to always squash embedded structs.
//
@@ -111,7 +122,7 @@
// field value is zero and a numeric type, the field is empty, and it won't
// be encoded into the destination type.
//
-// type Source {
+// type Source struct {
// Age int `mapstructure:",omitempty"`
// }
//
@@ -161,10 +172,11 @@ import (
// data transformations. See "DecodeHook" in the DecoderConfig
// struct.
//
-// The type should be DecodeHookFuncType or DecodeHookFuncKind.
-// Either is accepted. Types are a superset of Kinds (Types can return
-// Kinds) and are generally a richer thing to use, but Kinds are simpler
-// if you only need those.
+// The type must be one of DecodeHookFuncType, DecodeHookFuncKind, or
+// DecodeHookFuncValue.
+// Values are a superset of Types (Values can return types), and Types are a
+// superset of Kinds (Types can return Kinds) and are generally a richer thing
+// to use, but Kinds are simpler if you only need those.
//
// The reason DecodeHookFunc is multi-typed is for backwards compatibility:
// we started with Kinds and then realized Types were the better solution,
@@ -180,15 +192,22 @@ type DecodeHookFuncType func(reflect.Type, reflect.Type, interface{}) (interface
// source and target types.
type DecodeHookFuncKind func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error)
+// DecodeHookFuncValue is a DecodeHookFunc which has complete access to both the source and target
+// values.
+type DecodeHookFuncValue func(from reflect.Value, to reflect.Value) (interface{}, error)
+
// DecoderConfig is the configuration that is used to create a new decoder
// and allows customization of various aspects of decoding.
type DecoderConfig struct {
// DecodeHook, if set, will be called before any decoding and any
// type conversion (if WeaklyTypedInput is on). This lets you modify
- // the values before they're set down onto the resulting struct.
+ // the values before they're set down onto the resulting struct. The
+ // DecodeHook is called for every map and value in the input. This means
+ // that if a struct has embedded fields with squash tags the decode hook
+ // is called only once with all of the input data, not once for each
+ // embedded struct.
//
- // If an error is returned, the entire decode will fail with that
- // error.
+ // If an error is returned, the entire decode will fail with that error.
DecodeHook DecodeHookFunc
// If ErrorUnused is true, then it is an error for there to exist
@@ -196,6 +215,12 @@ type DecoderConfig struct {
// (extra keys).
ErrorUnused bool
+ // If ErrorUnset is true, then it is an error for there to exist
+ // fields in the result that were not set in the decoding process
+ // (extra fields). This only applies to decoding to a struct. This
+ // will affect all nested structs as well.
+ ErrorUnset bool
+
// ZeroFields, if set to true, will zero fields before writing them.
// For example, a map will be emptied before decoded values are put in
// it. If this is false, a map will be merged.
@@ -239,6 +264,15 @@ type DecoderConfig struct {
// The tag name that mapstructure reads for field names. This
// defaults to "mapstructure"
TagName string
+
+ // IgnoreUntaggedFields ignores all struct fields without explicit
+ // TagName, comparable to `mapstructure:"-"` as default behaviour.
+ IgnoreUntaggedFields bool
+
+ // MatchName is the function used to match the map key to the struct
+ // field name or tag. Defaults to `strings.EqualFold`. This can be used
+ // to implement case-sensitive tag values, support snake casing, etc.
+ MatchName func(mapKey, fieldName string) bool
}
// A Decoder takes a raw interface value and turns it into structured
@@ -260,6 +294,11 @@ type Metadata struct {
// 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
Unused []string
+
+ // Unset is a slice of field names that were found in the result interface
+ // but weren't set in the decoding process since there was no matching value
+ // in the input
+ Unset []string
}
// Decode takes an input structure and uses reflection to translate it to
@@ -351,12 +390,20 @@ func NewDecoder(config *DecoderConfig) (*Decoder, error) {
if config.Metadata.Unused == nil {
config.Metadata.Unused = make([]string, 0)
}
+
+ if config.Metadata.Unset == nil {
+ config.Metadata.Unset = make([]string, 0)
+ }
}
if config.TagName == "" {
config.TagName = "mapstructure"
}
+ if config.MatchName == nil {
+ config.MatchName = strings.EqualFold
+ }
+
result := &Decoder{
config: config,
}
@@ -409,9 +456,7 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e
if d.config.DecodeHook != nil {
// We have a DecodeHook, so let's pre-process the input.
var err error
- input, err = DecodeHookExec(
- d.config.DecodeHook,
- inputVal.Type(), outVal.Type(), input)
+ input, err = DecodeHookExec(d.config.DecodeHook, inputVal, outVal)
if err != nil {
return fmt.Errorf("error decoding '%s': %s", name, err)
}
@@ -562,8 +607,8 @@ func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value)
if !converted {
return fmt.Errorf(
- "'%s' expected type '%s', got unconvertible type '%s'",
- name, val.Type(), dataVal.Type())
+ "'%s' expected type '%s', got unconvertible type '%s', value: '%v'",
+ name, val.Type(), dataVal.Type(), data)
}
return nil
@@ -588,7 +633,12 @@ func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) er
val.SetInt(0)
}
case dataKind == reflect.String && d.config.WeaklyTypedInput:
- i, err := strconv.ParseInt(dataVal.String(), 0, val.Type().Bits())
+ str := dataVal.String()
+ if str == "" {
+ str = "0"
+ }
+
+ i, err := strconv.ParseInt(str, 0, val.Type().Bits())
if err == nil {
val.SetInt(i)
} else {
@@ -604,8 +654,8 @@ func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) er
val.SetInt(i)
default:
return fmt.Errorf(
- "'%s' expected type '%s', got unconvertible type '%s'",
- name, val.Type(), dataVal.Type())
+ "'%s' expected type '%s', got unconvertible type '%s', value: '%v'",
+ name, val.Type(), dataVal.Type(), data)
}
return nil
@@ -640,7 +690,12 @@ func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) e
val.SetUint(0)
}
case dataKind == reflect.String && d.config.WeaklyTypedInput:
- i, err := strconv.ParseUint(dataVal.String(), 0, val.Type().Bits())
+ str := dataVal.String()
+ if str == "" {
+ str = "0"
+ }
+
+ i, err := strconv.ParseUint(str, 0, val.Type().Bits())
if err == nil {
val.SetUint(i)
} else {
@@ -648,20 +703,16 @@ func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) e
}
case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number":
jn := data.(json.Number)
- i, err := jn.Int64()
+ i, err := strconv.ParseUint(string(jn), 0, 64)
if err != nil {
return fmt.Errorf(
"error decoding json.Number into %s: %s", name, err)
}
- if i < 0 && !d.config.WeaklyTypedInput {
- return fmt.Errorf("cannot parse '%s', %d overflows uint",
- name, i)
- }
- val.SetUint(uint64(i))
+ val.SetUint(i)
default:
return fmt.Errorf(
- "'%s' expected type '%s', got unconvertible type '%s'",
- name, val.Type(), dataVal.Type())
+ "'%s' expected type '%s', got unconvertible type '%s', value: '%v'",
+ name, val.Type(), dataVal.Type(), data)
}
return nil
@@ -691,8 +742,8 @@ func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) e
}
default:
return fmt.Errorf(
- "'%s' expected type '%s', got unconvertible type '%s'",
- name, val.Type(), dataVal.Type())
+ "'%s' expected type '%s', got unconvertible type '%s', value: '%v'",
+ name, val.Type(), dataVal.Type(), data)
}
return nil
@@ -717,7 +768,12 @@ func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value)
val.SetFloat(0)
}
case dataKind == reflect.String && d.config.WeaklyTypedInput:
- f, err := strconv.ParseFloat(dataVal.String(), val.Type().Bits())
+ str := dataVal.String()
+ if str == "" {
+ str = "0"
+ }
+
+ f, err := strconv.ParseFloat(str, val.Type().Bits())
if err == nil {
val.SetFloat(f)
} else {
@@ -733,8 +789,8 @@ func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value)
val.SetFloat(i)
default:
return fmt.Errorf(
- "'%s' expected type '%s', got unconvertible type '%s'",
- name, val.Type(), dataVal.Type())
+ "'%s' expected type '%s', got unconvertible type '%s', value: '%v'",
+ name, val.Type(), dataVal.Type(), data)
}
return nil
@@ -785,7 +841,7 @@ func (d *Decoder) decodeMapFromSlice(name string, dataVal reflect.Value, val ref
for i := 0; i < dataVal.Len(); i++ {
err := d.decode(
- fmt.Sprintf("%s[%d]", name, i),
+ name+"["+strconv.Itoa(i)+"]",
dataVal.Index(i).Interface(), val)
if err != nil {
return err
@@ -818,7 +874,7 @@ func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val refle
}
for _, k := range dataVal.MapKeys() {
- fieldName := fmt.Sprintf("%s[%s]", name, k)
+ fieldName := name + "[" + k.String() + "]"
// First decode the key into the proper type
currentKey := reflect.Indirect(reflect.New(valKeyType))
@@ -869,8 +925,15 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
tagValue := f.Tag.Get(d.config.TagName)
keyName := f.Name
+ if tagValue == "" && d.config.IgnoreUntaggedFields {
+ continue
+ }
+
// 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)
+
// Determine the name of the key in the map
if index := strings.Index(tagValue, ","); index != -1 {
if tagValue[:index] == "-" {
@@ -882,11 +945,21 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
}
// If "squash" is specified in the tag, we squash the field down.
- squash = !squash && strings.Index(tagValue[index+1:], "squash") != -1
- if squash && v.Kind() != reflect.Struct {
- return fmt.Errorf("cannot squash non-struct type '%s'", v.Type())
+ 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()
+ }
+
+ // The final type must be a struct
+ if v.Kind() != reflect.Struct {
+ return fmt.Errorf("cannot squash non-struct type '%s'", v.Type())
+ }
+ }
+ if keyNameTagValue := tagValue[:index]; keyNameTagValue != "" {
+ keyName = keyNameTagValue
}
- keyName = tagValue[:index]
} else if len(tagValue) > 0 {
if tagValue == "-" {
continue
@@ -995,8 +1068,8 @@ func (d *Decoder) decodeFunc(name string, data interface{}, val reflect.Value) e
dataVal := reflect.Indirect(reflect.ValueOf(data))
if val.Type() != dataVal.Type() {
return fmt.Errorf(
- "'%s' expected type '%s', got unconvertible type '%s'",
- name, val.Type(), dataVal.Type())
+ "'%s' expected type '%s', got unconvertible type '%s', value: '%v'",
+ name, val.Type(), dataVal.Type(), data)
}
val.Set(dataVal)
return nil
@@ -1042,7 +1115,7 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value)
}
// If the input value is nil, then don't allocate since empty != nil
- if dataVal.IsNil() {
+ if dataValKind != reflect.Array && dataVal.IsNil() {
return nil
}
@@ -1062,7 +1135,7 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value)
}
currentField := valSlice.Index(i)
- fieldName := fmt.Sprintf("%s[%d]", name, i)
+ fieldName := name + "[" + strconv.Itoa(i) + "]"
if err := d.decode(fieldName, currentData, currentField); err != nil {
errors = appendErrors(errors, err)
}
@@ -1129,7 +1202,7 @@ func (d *Decoder) decodeArray(name string, data interface{}, val reflect.Value)
currentData := dataVal.Index(i).Interface()
currentField := valArray.Index(i)
- fieldName := fmt.Sprintf("%s[%d]", name, i)
+ fieldName := name + "[" + strconv.Itoa(i) + "]"
if err := d.decode(fieldName, currentData, currentField); err != nil {
errors = appendErrors(errors, err)
}
@@ -1204,6 +1277,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
dataValKeysUnused[dataValKey.Interface()] = struct{}{}
}
+ targetValKeysUnused := make(map[interface{}]struct{})
errors := make([]string, 0)
// This slice will keep track of all the structs we'll be decoding.
@@ -1232,10 +1306,14 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
for i := 0; i < structType.NumField(); i++ {
fieldType := structType.Field(i)
- fieldKind := fieldType.Type.Kind()
+ fieldVal := structVal.Field(i)
+ if fieldVal.Kind() == reflect.Ptr && fieldVal.Elem().Kind() == reflect.Struct {
+ // Handle embedded struct pointers as embedded structs.
+ fieldVal = fieldVal.Elem()
+ }
// If "squash" is specified in the tag, we squash the field down.
- squash := d.config.Squash && fieldKind == reflect.Struct && fieldType.Anonymous
+ squash := d.config.Squash && fieldVal.Kind() == reflect.Struct && fieldType.Anonymous
remain := false
// We always parse the tags cause we're looking for other tags too
@@ -1253,21 +1331,21 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
}
if squash {
- if fieldKind != reflect.Struct {
+ if fieldVal.Kind() != reflect.Struct {
errors = appendErrors(errors,
- fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldKind))
+ fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldVal.Kind()))
} else {
- structs = append(structs, structVal.FieldByName(fieldType.Name))
+ structs = append(structs, fieldVal)
}
continue
}
// Build our field
if remain {
- remainField = &field{fieldType, structVal.Field(i)}
+ remainField = &field{fieldType, fieldVal}
} else {
// Normal struct field, store it away
- fields = append(fields, field{fieldType, structVal.Field(i)})
+ fields = append(fields, field{fieldType, fieldVal})
}
}
}
@@ -1295,7 +1373,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
continue
}
- if strings.EqualFold(mK, fieldName) {
+ if d.config.MatchName(mK, fieldName) {
rawMapKey = dataValKey
rawMapVal = dataVal.MapIndex(dataValKey)
break
@@ -1304,7 +1382,8 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
if !rawMapVal.IsValid() {
// There was no matching key in the map for the value in
- // the struct. Just ignore.
+ // the struct. Remember it for potential errors and metadata.
+ targetValKeysUnused[fieldName] = struct{}{}
continue
}
}
@@ -1326,7 +1405,7 @@ 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 = fmt.Sprintf("%s.%s", name, fieldName)
+ fieldName = name + "." + fieldName
}
if err := d.decode(fieldName, rawMapVal.Interface(), fieldValue); err != nil {
@@ -1364,6 +1443,17 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
errors = appendErrors(errors, err)
}
+ if d.config.ErrorUnset && len(targetValKeysUnused) > 0 {
+ keys := make([]string, 0, len(targetValKeysUnused))
+ for rawKey := range targetValKeysUnused {
+ keys = append(keys, rawKey.(string))
+ }
+ sort.Strings(keys)
+
+ err := fmt.Errorf("'%s' has unset fields: %s", name, strings.Join(keys, ", "))
+ errors = appendErrors(errors, err)
+ }
+
if len(errors) > 0 {
return &Error{errors}
}
@@ -1373,11 +1463,19 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
for rawKey := range dataValKeysUnused {
key := rawKey.(string)
if name != "" {
- key = fmt.Sprintf("%s.%s", name, key)
+ key = name + "." + key
}
d.config.Metadata.Unused = append(d.config.Metadata.Unused, key)
}
+ for rawKey := range targetValKeysUnused {
+ key := rawKey.(string)
+ if name != "" {
+ key = name + "." + key
+ }
+
+ d.config.Metadata.Unset = append(d.config.Metadata.Unset, key)
+ }
}
return nil
@@ -1415,3 +1513,28 @@ func getKind(val reflect.Value) reflect.Kind {
return kind
}
}
+
+func isStructTypeConvertibleToMap(typ reflect.Type, checkMapstructureTags bool, tagName string) bool {
+ for i := 0; i < typ.NumField(); i++ {
+ f := typ.Field(i)
+ if f.PkgPath == "" && !checkMapstructureTags { // check for unexported fields
+ return true
+ }
+ if checkMapstructureTags && f.Tag.Get(tagName) != "" { // check for mapstructure tags inside
+ return true
+ }
+ }
+ return false
+}
+
+func dereferencePtrToStructIfNeeded(v reflect.Value, tagName string) reflect.Value {
+ if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
+ return v
+ }
+ deref := v.Elem()
+ derefT := deref.Type()
+ if isStructTypeConvertibleToMap(derefT, true, tagName) {
+ return deref
+ }
+ return v
+}