vendor/github.com/mitchellh/mapstructure/mapstructure.go
changeset 260 445e01aede7e
parent 256 6d9efbef00a9
--- a/vendor/github.com/mitchellh/mapstructure/mapstructure.go	Tue Aug 23 22:33:28 2022 +0200
+++ b/vendor/github.com/mitchellh/mapstructure/mapstructure.go	Tue Aug 23 22:39:43 2022 +0200
@@ -122,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"`
 //     }
 //
@@ -192,7 +192,7 @@
 // source and target types.
 type DecodeHookFuncKind func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error)
 
-// DecodeHookFuncRaw is a DecodeHookFunc which has complete access to both the source and target
+// 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)
 
@@ -215,6 +215,12 @@
 	// (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.
@@ -258,6 +264,15 @@
 	// 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
@@ -279,6 +294,11 @@
 	// 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
@@ -370,12 +390,20 @@
 		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,
 	}
@@ -675,16 +703,12 @@
 		}
 	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', value: '%v'",
@@ -901,9 +925,15 @@
 		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] == "-" {
@@ -915,7 +945,7 @@
 			}
 
 			// If "squash" is specified in the tag, we squash the field down.
-			squash = !squash && strings.Index(tagValue[index+1:], "squash") != -1
+			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 {
@@ -927,7 +957,9 @@
 					return fmt.Errorf("cannot squash non-struct type '%s'", v.Type())
 				}
 			}
-			keyName = tagValue[:index]
+			if keyNameTagValue := tagValue[:index]; keyNameTagValue != "" {
+				keyName = keyNameTagValue
+			}
 		} else if len(tagValue) > 0 {
 			if tagValue == "-" {
 				continue
@@ -1083,7 +1115,7 @@
 	}
 
 	// If the input value is nil, then don't allocate since empty != nil
-	if dataVal.IsNil() {
+	if dataValKind != reflect.Array && dataVal.IsNil() {
 		return nil
 	}
 
@@ -1245,6 +1277,7 @@
 		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.
@@ -1340,7 +1373,7 @@
 					continue
 				}
 
-				if strings.EqualFold(mK, fieldName) {
+				if d.config.MatchName(mK, fieldName) {
 					rawMapKey = dataValKey
 					rawMapVal = dataVal.MapIndex(dataValKey)
 					break
@@ -1349,7 +1382,8 @@
 
 			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
 			}
 		}
@@ -1409,6 +1443,17 @@
 		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}
 	}
@@ -1423,6 +1468,14 @@
 
 			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
@@ -1460,3 +1513,28 @@
 		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
+}