vendor/github.com/spf13/cast/caste.go
changeset 260 445e01aede7e
parent 251 1c52a0eeb952
--- a/vendor/github.com/spf13/cast/caste.go	Tue Aug 23 22:33:28 2022 +0200
+++ b/vendor/github.com/spf13/cast/caste.go	Tue Aug 23 22:39:43 2022 +0200
@@ -20,13 +20,26 @@
 
 // ToTimeE casts an interface to a time.Time type.
 func ToTimeE(i interface{}) (tim time.Time, err error) {
+	return ToTimeInDefaultLocationE(i, time.UTC)
+}
+
+// ToTimeInDefaultLocationE casts an empty interface to time.Time,
+// interpreting inputs without a timezone to be in the given location,
+// or the local timezone if nil.
+func ToTimeInDefaultLocationE(i interface{}, location *time.Location) (tim time.Time, err error) {
 	i = indirect(i)
 
 	switch v := i.(type) {
 	case time.Time:
 		return v, nil
 	case string:
-		return StringToDate(v)
+		return StringToDateInDefaultLocation(v, location)
+	case json.Number:
+		s, err1 := ToInt64E(v)
+		if err1 != nil {
+			return time.Time{}, fmt.Errorf("unable to cast %#v of type %T to Time", i, i)
+		}
+		return time.Unix(s, 0), nil
 	case int:
 		return time.Unix(int64(v), 0), nil
 	case int64:
@@ -64,6 +77,11 @@
 			d, err = time.ParseDuration(s + "ns")
 		}
 		return
+	case json.Number:
+		var v float64
+		v, err = s.Float64()
+		d = time.Duration(v)
+		return
 	default:
 		err = fmt.Errorf("unable to cast %#v of type %T to Duration", i, i)
 		return
@@ -86,6 +104,12 @@
 		return false, nil
 	case string:
 		return strconv.ParseBool(i.(string))
+	case json.Number:
+		v, err := ToInt64E(b)
+		if err == nil {
+			return v != 0, nil
+		}
+		return false, fmt.Errorf("unable to cast %#v of type %T to bool", i, i)
 	default:
 		return false, fmt.Errorf("unable to cast %#v of type %T to bool", i, i)
 	}
@@ -95,13 +119,16 @@
 func ToFloat64E(i interface{}) (float64, error) {
 	i = indirect(i)
 
+	intv, ok := toInt(i)
+	if ok {
+		return float64(intv), nil
+	}
+
 	switch s := i.(type) {
 	case float64:
 		return s, nil
 	case float32:
 		return float64(s), nil
-	case int:
-		return float64(s), nil
 	case int64:
 		return float64(s), nil
 	case int32:
@@ -126,11 +153,19 @@
 			return v, nil
 		}
 		return 0, fmt.Errorf("unable to cast %#v of type %T to float64", i, i)
+	case json.Number:
+		v, err := s.Float64()
+		if err == nil {
+			return v, nil
+		}
+		return 0, fmt.Errorf("unable to cast %#v of type %T to float64", i, i)
 	case bool:
 		if s {
 			return 1, nil
 		}
 		return 0, nil
+	case nil:
+		return 0, nil
 	default:
 		return 0, fmt.Errorf("unable to cast %#v of type %T to float64", i, i)
 	}
@@ -140,13 +175,16 @@
 func ToFloat32E(i interface{}) (float32, error) {
 	i = indirect(i)
 
+	intv, ok := toInt(i)
+	if ok {
+		return float32(intv), nil
+	}
+
 	switch s := i.(type) {
 	case float64:
 		return float32(s), nil
 	case float32:
 		return s, nil
-	case int:
-		return float32(s), nil
 	case int64:
 		return float32(s), nil
 	case int32:
@@ -171,11 +209,19 @@
 			return float32(v), nil
 		}
 		return 0, fmt.Errorf("unable to cast %#v of type %T to float32", i, i)
+	case json.Number:
+		v, err := s.Float64()
+		if err == nil {
+			return float32(v), nil
+		}
+		return 0, fmt.Errorf("unable to cast %#v of type %T to float32", i, i)
 	case bool:
 		if s {
 			return 1, nil
 		}
 		return 0, nil
+	case nil:
+		return 0, nil
 	default:
 		return 0, fmt.Errorf("unable to cast %#v of type %T to float32", i, i)
 	}
@@ -185,9 +231,12 @@
 func ToInt64E(i interface{}) (int64, error) {
 	i = indirect(i)
 
+	intv, ok := toInt(i)
+	if ok {
+		return int64(intv), nil
+	}
+
 	switch s := i.(type) {
-	case int:
-		return int64(s), nil
 	case int64:
 		return s, nil
 	case int32:
@@ -211,11 +260,13 @@
 	case float32:
 		return int64(s), nil
 	case string:
-		v, err := strconv.ParseInt(s, 0, 0)
+		v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0)
 		if err == nil {
 			return v, nil
 		}
 		return 0, fmt.Errorf("unable to cast %#v of type %T to int64", i, i)
+	case json.Number:
+		return ToInt64E(string(s))
 	case bool:
 		if s {
 			return 1, nil
@@ -232,9 +283,12 @@
 func ToInt32E(i interface{}) (int32, error) {
 	i = indirect(i)
 
+	intv, ok := toInt(i)
+	if ok {
+		return int32(intv), nil
+	}
+
 	switch s := i.(type) {
-	case int:
-		return int32(s), nil
 	case int64:
 		return int32(s), nil
 	case int32:
@@ -258,11 +312,13 @@
 	case float32:
 		return int32(s), nil
 	case string:
-		v, err := strconv.ParseInt(s, 0, 0)
+		v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0)
 		if err == nil {
 			return int32(v), nil
 		}
 		return 0, fmt.Errorf("unable to cast %#v of type %T to int32", i, i)
+	case json.Number:
+		return ToInt32E(string(s))
 	case bool:
 		if s {
 			return 1, nil
@@ -279,9 +335,12 @@
 func ToInt16E(i interface{}) (int16, error) {
 	i = indirect(i)
 
+	intv, ok := toInt(i)
+	if ok {
+		return int16(intv), nil
+	}
+
 	switch s := i.(type) {
-	case int:
-		return int16(s), nil
 	case int64:
 		return int16(s), nil
 	case int32:
@@ -305,11 +364,13 @@
 	case float32:
 		return int16(s), nil
 	case string:
-		v, err := strconv.ParseInt(s, 0, 0)
+		v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0)
 		if err == nil {
 			return int16(v), nil
 		}
 		return 0, fmt.Errorf("unable to cast %#v of type %T to int16", i, i)
+	case json.Number:
+		return ToInt16E(string(s))
 	case bool:
 		if s {
 			return 1, nil
@@ -326,9 +387,12 @@
 func ToInt8E(i interface{}) (int8, error) {
 	i = indirect(i)
 
+	intv, ok := toInt(i)
+	if ok {
+		return int8(intv), nil
+	}
+
 	switch s := i.(type) {
-	case int:
-		return int8(s), nil
 	case int64:
 		return int8(s), nil
 	case int32:
@@ -352,11 +416,13 @@
 	case float32:
 		return int8(s), nil
 	case string:
-		v, err := strconv.ParseInt(s, 0, 0)
+		v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0)
 		if err == nil {
 			return int8(v), nil
 		}
 		return 0, fmt.Errorf("unable to cast %#v of type %T to int8", i, i)
+	case json.Number:
+		return ToInt8E(string(s))
 	case bool:
 		if s {
 			return 1, nil
@@ -373,9 +439,12 @@
 func ToIntE(i interface{}) (int, error) {
 	i = indirect(i)
 
+	intv, ok := toInt(i)
+	if ok {
+		return intv, nil
+	}
+
 	switch s := i.(type) {
-	case int:
-		return s, nil
 	case int64:
 		return int(s), nil
 	case int32:
@@ -399,11 +468,13 @@
 	case float32:
 		return int(s), nil
 	case string:
-		v, err := strconv.ParseInt(s, 0, 0)
+		v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0)
 		if err == nil {
 			return int(v), nil
 		}
-		return 0, fmt.Errorf("unable to cast %#v of type %T to int", i, i)
+		return 0, fmt.Errorf("unable to cast %#v of type %T to int64", i, i)
+	case json.Number:
+		return ToIntE(string(s))
 	case bool:
 		if s {
 			return 1, nil
@@ -420,18 +491,26 @@
 func ToUintE(i interface{}) (uint, error) {
 	i = indirect(i)
 
+	intv, ok := toInt(i)
+	if ok {
+		if intv < 0 {
+			return 0, errNegativeNotAllowed
+		}
+		return uint(intv), nil
+	}
+
 	switch s := i.(type) {
 	case string:
-		v, err := strconv.ParseUint(s, 0, 0)
+		v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0)
 		if err == nil {
+			if v < 0 {
+				return 0, errNegativeNotAllowed
+			}
 			return uint(v), nil
 		}
-		return 0, fmt.Errorf("unable to cast %#v to uint: %s", i, err)
-	case int:
-		if s < 0 {
-			return 0, errNegativeNotAllowed
-		}
-		return uint(s), nil
+		return 0, fmt.Errorf("unable to cast %#v of type %T to uint", i, i)
+	case json.Number:
+		return ToUintE(string(s))
 	case int64:
 		if s < 0 {
 			return 0, errNegativeNotAllowed
@@ -488,18 +567,26 @@
 func ToUint64E(i interface{}) (uint64, error) {
 	i = indirect(i)
 
+	intv, ok := toInt(i)
+	if ok {
+		if intv < 0 {
+			return 0, errNegativeNotAllowed
+		}
+		return uint64(intv), nil
+	}
+
 	switch s := i.(type) {
 	case string:
-		v, err := strconv.ParseUint(s, 0, 64)
+		v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0)
 		if err == nil {
-			return v, nil
+			if v < 0 {
+				return 0, errNegativeNotAllowed
+			}
+			return uint64(v), nil
 		}
-		return 0, fmt.Errorf("unable to cast %#v to uint64: %s", i, err)
-	case int:
-		if s < 0 {
-			return 0, errNegativeNotAllowed
-		}
-		return uint64(s), nil
+		return 0, fmt.Errorf("unable to cast %#v of type %T to uint64", i, i)
+	case json.Number:
+		return ToUint64E(string(s))
 	case int64:
 		if s < 0 {
 			return 0, errNegativeNotAllowed
@@ -556,18 +643,26 @@
 func ToUint32E(i interface{}) (uint32, error) {
 	i = indirect(i)
 
+	intv, ok := toInt(i)
+	if ok {
+		if intv < 0 {
+			return 0, errNegativeNotAllowed
+		}
+		return uint32(intv), nil
+	}
+
 	switch s := i.(type) {
 	case string:
-		v, err := strconv.ParseUint(s, 0, 32)
+		v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0)
 		if err == nil {
+			if v < 0 {
+				return 0, errNegativeNotAllowed
+			}
 			return uint32(v), nil
 		}
-		return 0, fmt.Errorf("unable to cast %#v to uint32: %s", i, err)
-	case int:
-		if s < 0 {
-			return 0, errNegativeNotAllowed
-		}
-		return uint32(s), nil
+		return 0, fmt.Errorf("unable to cast %#v of type %T to uint32", i, i)
+	case json.Number:
+		return ToUint32E(string(s))
 	case int64:
 		if s < 0 {
 			return 0, errNegativeNotAllowed
@@ -624,18 +719,26 @@
 func ToUint16E(i interface{}) (uint16, error) {
 	i = indirect(i)
 
+	intv, ok := toInt(i)
+	if ok {
+		if intv < 0 {
+			return 0, errNegativeNotAllowed
+		}
+		return uint16(intv), nil
+	}
+
 	switch s := i.(type) {
 	case string:
-		v, err := strconv.ParseUint(s, 0, 16)
+		v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0)
 		if err == nil {
+			if v < 0 {
+				return 0, errNegativeNotAllowed
+			}
 			return uint16(v), nil
 		}
-		return 0, fmt.Errorf("unable to cast %#v to uint16: %s", i, err)
-	case int:
-		if s < 0 {
-			return 0, errNegativeNotAllowed
-		}
-		return uint16(s), nil
+		return 0, fmt.Errorf("unable to cast %#v of type %T to uint16", i, i)
+	case json.Number:
+		return ToUint16E(string(s))
 	case int64:
 		if s < 0 {
 			return 0, errNegativeNotAllowed
@@ -692,18 +795,26 @@
 func ToUint8E(i interface{}) (uint8, error) {
 	i = indirect(i)
 
+	intv, ok := toInt(i)
+	if ok {
+		if intv < 0 {
+			return 0, errNegativeNotAllowed
+		}
+		return uint8(intv), nil
+	}
+
 	switch s := i.(type) {
 	case string:
-		v, err := strconv.ParseUint(s, 0, 8)
+		v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0)
 		if err == nil {
+			if v < 0 {
+				return 0, errNegativeNotAllowed
+			}
 			return uint8(v), nil
 		}
-		return 0, fmt.Errorf("unable to cast %#v to uint8: %s", i, err)
-	case int:
-		if s < 0 {
-			return 0, errNegativeNotAllowed
-		}
-		return uint8(s), nil
+		return 0, fmt.Errorf("unable to cast %#v of type %T to uint8", i, i)
+	case json.Number:
+		return ToUint8E(string(s))
 	case int64:
 		if s < 0 {
 			return 0, errNegativeNotAllowed
@@ -828,6 +939,8 @@
 		return strconv.FormatUint(uint64(s), 10), nil
 	case uint8:
 		return strconv.FormatUint(uint64(s), 10), nil
+	case json.Number:
+		return s.String(), nil
 	case []byte:
 		return string(s), nil
 	case template.HTML:
@@ -1129,8 +1242,43 @@
 		return a, nil
 	case []string:
 		return v, nil
+	case []int8:
+		for _, u := range v {
+			a = append(a, ToString(u))
+		}
+		return a, nil
+	case []int:
+		for _, u := range v {
+			a = append(a, ToString(u))
+		}
+		return a, nil
+	case []int32:
+		for _, u := range v {
+			a = append(a, ToString(u))
+		}
+		return a, nil
+	case []int64:
+		for _, u := range v {
+			a = append(a, ToString(u))
+		}
+		return a, nil
+	case []float32:
+		for _, u := range v {
+			a = append(a, ToString(u))
+		}
+		return a, nil
+	case []float64:
+		for _, u := range v {
+			a = append(a, ToString(u))
+		}
+		return a, nil
 	case string:
 		return strings.Fields(v), nil
+	case []error:
+		for _, err := range i.([]error) {
+			a = append(a, err.Error())
+		}
+		return a, nil
 	case interface{}:
 		str, err := ToStringE(v)
 		if err != nil {
@@ -1204,37 +1352,83 @@
 // predefined list of formats.  If no suitable format is found, an error is
 // returned.
 func StringToDate(s string) (time.Time, error) {
-	return parseDateWith(s, []string{
-		time.RFC3339,
-		"2006-01-02T15:04:05", // iso8601 without timezone
-		time.RFC1123Z,
-		time.RFC1123,
-		time.RFC822Z,
-		time.RFC822,
-		time.RFC850,
-		time.ANSIC,
-		time.UnixDate,
-		time.RubyDate,
-		"2006-01-02 15:04:05.999999999 -0700 MST", // Time.String()
-		"2006-01-02",
-		"02 Jan 2006",
-		"2006-01-02T15:04:05-0700", // RFC3339 without timezone hh:mm colon
-		"2006-01-02 15:04:05 -07:00",
-		"2006-01-02 15:04:05 -0700",
-		"2006-01-02 15:04:05Z07:00", // RFC3339 without T
-		"2006-01-02 15:04:05Z0700",  // RFC3339 without T or timezone hh:mm colon
-		"2006-01-02 15:04:05",
-		time.Kitchen,
-		time.Stamp,
-		time.StampMilli,
-		time.StampMicro,
-		time.StampNano,
-	})
+	return parseDateWith(s, time.UTC, timeFormats)
+}
+
+// StringToDateInDefaultLocation casts an empty interface to a time.Time,
+// interpreting inputs without a timezone to be in the given location,
+// or the local timezone if nil.
+func StringToDateInDefaultLocation(s string, location *time.Location) (time.Time, error) {
+	return parseDateWith(s, location, timeFormats)
+}
+
+type timeFormatType int
+
+const (
+	timeFormatNoTimezone timeFormatType = iota
+	timeFormatNamedTimezone
+	timeFormatNumericTimezone
+	timeFormatNumericAndNamedTimezone
+	timeFormatTimeOnly
+)
+
+type timeFormat struct {
+	format string
+	typ    timeFormatType
+}
+
+func (f timeFormat) hasTimezone() bool {
+	// We don't include the formats with only named timezones, see
+	// https://github.com/golang/go/issues/19694#issuecomment-289103522
+	return f.typ >= timeFormatNumericTimezone && f.typ <= timeFormatNumericAndNamedTimezone
 }
 
-func parseDateWith(s string, dates []string) (d time.Time, e error) {
-	for _, dateType := range dates {
-		if d, e = time.Parse(dateType, s); e == nil {
+var (
+	timeFormats = []timeFormat{
+		{time.RFC3339, timeFormatNumericTimezone},
+		{"2006-01-02T15:04:05", timeFormatNoTimezone}, // iso8601 without timezone
+		{time.RFC1123Z, timeFormatNumericTimezone},
+		{time.RFC1123, timeFormatNamedTimezone},
+		{time.RFC822Z, timeFormatNumericTimezone},
+		{time.RFC822, timeFormatNamedTimezone},
+		{time.RFC850, timeFormatNamedTimezone},
+		{"2006-01-02 15:04:05.999999999 -0700 MST", timeFormatNumericAndNamedTimezone}, // Time.String()
+		{"2006-01-02T15:04:05-0700", timeFormatNumericTimezone},                        // RFC3339 without timezone hh:mm colon
+		{"2006-01-02 15:04:05Z0700", timeFormatNumericTimezone},                        // RFC3339 without T or timezone hh:mm colon
+		{"2006-01-02 15:04:05", timeFormatNoTimezone},
+		{time.ANSIC, timeFormatNoTimezone},
+		{time.UnixDate, timeFormatNamedTimezone},
+		{time.RubyDate, timeFormatNumericTimezone},
+		{"2006-01-02 15:04:05Z07:00", timeFormatNumericTimezone},
+		{"2006-01-02", timeFormatNoTimezone},
+		{"02 Jan 2006", timeFormatNoTimezone},
+		{"2006-01-02 15:04:05 -07:00", timeFormatNumericTimezone},
+		{"2006-01-02 15:04:05 -0700", timeFormatNumericTimezone},
+		{time.Kitchen, timeFormatTimeOnly},
+		{time.Stamp, timeFormatTimeOnly},
+		{time.StampMilli, timeFormatTimeOnly},
+		{time.StampMicro, timeFormatTimeOnly},
+		{time.StampNano, timeFormatTimeOnly},
+	}
+)
+
+func parseDateWith(s string, location *time.Location, formats []timeFormat) (d time.Time, e error) {
+
+	for _, format := range formats {
+		if d, e = time.Parse(format.format, s); e == nil {
+
+			// Some time formats have a zone name, but no offset, so it gets
+			// put in that zone name (not the default one passed in to us), but
+			// without that zone's offset. So set the location manually.
+			if format.typ <= timeFormatNamedTimezone {
+				if location == nil {
+					location = time.Local
+				}
+				year, month, day := d.Date()
+				hour, min, sec := d.Clock()
+				d = time.Date(year, month, day, hour, min, sec, d.Nanosecond(), location)
+			}
+
 			return
 		}
 	}
@@ -1247,3 +1441,36 @@
 	data := []byte(s)
 	return json.Unmarshal(data, v)
 }
+
+// toInt returns the int value of v if v or v's underlying type
+// is an int.
+// Note that this will return false for int64 etc. types.
+func toInt(v interface{}) (int, bool) {
+	switch v := v.(type) {
+	case int:
+		return v, true
+	case time.Weekday:
+		return int(v), true
+	case time.Month:
+		return int(v), true
+	default:
+		return 0, false
+	}
+}
+
+func trimZeroDecimal(s string) string {
+	var foundZero bool
+	for i := len(s); i > 0; i-- {
+		switch s[i-1] {
+		case '.':
+			if foundZero {
+				return s[:i-1]
+			}
+		case '0':
+			foundZero = true
+		default:
+			return s
+		}
+	}
+	return s
+}