diff --git a/cast_test.go b/cast_test.go index d9b0b01..f610592 100644 --- a/cast_test.go +++ b/cast_test.go @@ -827,7 +827,7 @@ func TestToStringMapStringE(t *testing.T) { var interfaceMapString = map[interface{}]string{"key 1": "value 1", "key 2": "value 2", "key 3": "value 3"} var interfaceMapInterface = map[interface{}]interface{}{"key 1": "value 1", "key 2": "value 2", "key 3": "value 3"} var jsonString = `{"key 1": "value 1", "key 2": "value 2", "key 3": "value 3"}` - var invalidJsonString = `{"key 1": "value 1", "key 2": "value 2", "key 3": "value 3"` + var invalidJSONString = `{"key 1": "value 1", "key 2": "value 2", "key 3": "value 3"` var emptyString = "" tests := []struct { @@ -844,7 +844,7 @@ func TestToStringMapStringE(t *testing.T) { // errors {nil, nil, true}, {testing.T{}, nil, true}, - {invalidJsonString, nil, true}, + {invalidJSONString, nil, true}, {emptyString, nil, true}, } @@ -1080,11 +1080,41 @@ func TestToBoolE(t *testing.T) { } func BenchmarkTooBool(b *testing.B) { - for i := 0; i < b.N; i++ { - if !ToBool(true) { - b.Fatal("ToBool returned false") + b.Run("valid bool(true)", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if !ToBool(true) { + b.Fatal("ToBool returned false") + } } - } + }) + + b.Run("valid bool(1)", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if !ToBool(1) { + b.Fatal("ToBool returned false") + } + } + }) + + b.Run(`invalid bool("xxx")`, func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if ToBool("xxx") { + b.Fatal("ToBool returned true") + } + } + }) + + b.Run(`invalid bool(1.0)`, func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if ToBool(1.0) { + b.Fatal("ToBool returned true") + } + } + }) } func TestIndirectPointers(t *testing.T) { diff --git a/caste.go b/caste.go index 4fe1928..04896c9 100644 --- a/caste.go +++ b/caste.go @@ -7,7 +7,6 @@ package cast import ( "encoding/json" - "errors" "fmt" "html/template" "reflect" @@ -16,8 +15,6 @@ import ( "time" ) -var errNegativeNotAllowed = errors.New("unable to cast negative value") - // ToTimeE casts an interface to a time.Time type. func ToTimeE(i interface{}) (tim time.Time, err error) { i = indirect(i) @@ -40,7 +37,7 @@ func ToTimeE(i interface{}) (tim time.Time, err error) { case uint32: return time.Unix(int64(v), 0), nil default: - return time.Time{}, fmt.Errorf("unable to cast %#v of type %T to Time", i, i) + return time.Time{}, newError("unable to cast %#v of type %T to Time", i, i) } } @@ -65,7 +62,7 @@ func ToDurationE(i interface{}) (d time.Duration, err error) { } return default: - err = fmt.Errorf("unable to cast %#v of type %T to Duration", i, i) + err = newError("unable to cast %#v of type %T to Duration", i, i) return } } @@ -87,7 +84,7 @@ func ToBoolE(i interface{}) (bool, error) { case string: return strconv.ParseBool(i.(string)) default: - return false, fmt.Errorf("unable to cast %#v of type %T to bool", i, i) + return false, newError("unable to cast %#v of type %T to bool", i, i) } } @@ -125,14 +122,14 @@ func ToFloat64E(i interface{}) (float64, error) { if err == nil { return v, nil } - return 0, fmt.Errorf("unable to cast %#v of type %T to float64", i, i) + return 0, newError("unable to cast %#v of type %T to float64", i, i) case bool: if s { return 1, nil } return 0, nil default: - return 0, fmt.Errorf("unable to cast %#v of type %T to float64", i, i) + return 0, newError("unable to cast %#v of type %T to float64", i, i) } } @@ -170,14 +167,14 @@ func ToFloat32E(i interface{}) (float32, error) { if err == nil { return float32(v), nil } - return 0, fmt.Errorf("unable to cast %#v of type %T to float32", i, i) + return 0, newError("unable to cast %#v of type %T to float32", i, i) case bool: if s { return 1, nil } return 0, nil default: - return 0, fmt.Errorf("unable to cast %#v of type %T to float32", i, i) + return 0, newError("unable to cast %#v of type %T to float32", i, i) } } @@ -215,7 +212,7 @@ func ToInt64E(i interface{}) (int64, error) { if err == nil { return v, nil } - return 0, fmt.Errorf("unable to cast %#v of type %T to int64", i, i) + return 0, newError("unable to cast %#v of type %T to int64", i, i) case bool: if s { return 1, nil @@ -224,7 +221,7 @@ func ToInt64E(i interface{}) (int64, error) { case nil: return 0, nil default: - return 0, fmt.Errorf("unable to cast %#v of type %T to int64", i, i) + return 0, newError("unable to cast %#v of type %T to int64", i, i) } } @@ -262,7 +259,7 @@ func ToInt32E(i interface{}) (int32, error) { if err == nil { return int32(v), nil } - return 0, fmt.Errorf("unable to cast %#v of type %T to int32", i, i) + return 0, newError("unable to cast %#v of type %T to int32", i, i) case bool: if s { return 1, nil @@ -271,7 +268,7 @@ func ToInt32E(i interface{}) (int32, error) { case nil: return 0, nil default: - return 0, fmt.Errorf("unable to cast %#v of type %T to int32", i, i) + return 0, newError("unable to cast %#v of type %T to int32", i, i) } } @@ -309,7 +306,7 @@ func ToInt16E(i interface{}) (int16, error) { if err == nil { return int16(v), nil } - return 0, fmt.Errorf("unable to cast %#v of type %T to int16", i, i) + return 0, newError("unable to cast %#v of type %T to int16", i, i) case bool: if s { return 1, nil @@ -318,7 +315,7 @@ func ToInt16E(i interface{}) (int16, error) { case nil: return 0, nil default: - return 0, fmt.Errorf("unable to cast %#v of type %T to int16", i, i) + return 0, newError("unable to cast %#v of type %T to int16", i, i) } } @@ -356,7 +353,7 @@ func ToInt8E(i interface{}) (int8, error) { if err == nil { return int8(v), nil } - return 0, fmt.Errorf("unable to cast %#v of type %T to int8", i, i) + return 0, newError("unable to cast %#v of type %T to int8", i, i) case bool: if s { return 1, nil @@ -365,7 +362,7 @@ func ToInt8E(i interface{}) (int8, error) { case nil: return 0, nil default: - return 0, fmt.Errorf("unable to cast %#v of type %T to int8", i, i) + return 0, newError("unable to cast %#v of type %T to int8", i, i) } } @@ -403,7 +400,7 @@ func ToIntE(i interface{}) (int, error) { if err == nil { return int(v), nil } - return 0, fmt.Errorf("unable to cast %#v of type %T to int", i, i) + return 0, newError("unable to cast %#v of type %T to int", i, i) case bool: if s { return 1, nil @@ -412,7 +409,7 @@ func ToIntE(i interface{}) (int, error) { case nil: return 0, nil default: - return 0, fmt.Errorf("unable to cast %#v of type %T to int", i, i) + return 0, newError("unable to cast %#v of type %T to int", i, i) } } @@ -426,7 +423,7 @@ func ToUintE(i interface{}) (uint, error) { if err == nil { return uint(v), nil } - return 0, fmt.Errorf("unable to cast %#v to uint: %s", i, err) + return 0, newError("unable to cast %#v to uint: %s", i, err) case int: if s < 0 { return 0, errNegativeNotAllowed @@ -480,7 +477,7 @@ func ToUintE(i interface{}) (uint, error) { case nil: return 0, nil default: - return 0, fmt.Errorf("unable to cast %#v of type %T to uint", i, i) + return 0, newError("unable to cast %#v of type %T to uint", i, i) } } @@ -494,7 +491,7 @@ func ToUint64E(i interface{}) (uint64, error) { if err == nil { return v, nil } - return 0, fmt.Errorf("unable to cast %#v to uint64: %s", i, err) + return 0, newError("unable to cast %#v to uint64: %s", i, err) case int: if s < 0 { return 0, errNegativeNotAllowed @@ -548,7 +545,7 @@ func ToUint64E(i interface{}) (uint64, error) { case nil: return 0, nil default: - return 0, fmt.Errorf("unable to cast %#v of type %T to uint64", i, i) + return 0, newError("unable to cast %#v of type %T to uint64", i, i) } } @@ -562,7 +559,7 @@ func ToUint32E(i interface{}) (uint32, error) { if err == nil { return uint32(v), nil } - return 0, fmt.Errorf("unable to cast %#v to uint32: %s", i, err) + return 0, newError("unable to cast %#v to uint32: %s", i, err) case int: if s < 0 { return 0, errNegativeNotAllowed @@ -616,7 +613,7 @@ func ToUint32E(i interface{}) (uint32, error) { case nil: return 0, nil default: - return 0, fmt.Errorf("unable to cast %#v of type %T to uint32", i, i) + return 0, newError("unable to cast %#v of type %T to uint32", i, i) } } @@ -630,7 +627,7 @@ func ToUint16E(i interface{}) (uint16, error) { if err == nil { return uint16(v), nil } - return 0, fmt.Errorf("unable to cast %#v to uint16: %s", i, err) + return 0, newError("unable to cast %#v to uint16: %s", i, err) case int: if s < 0 { return 0, errNegativeNotAllowed @@ -684,7 +681,7 @@ func ToUint16E(i interface{}) (uint16, error) { case nil: return 0, nil default: - return 0, fmt.Errorf("unable to cast %#v of type %T to uint16", i, i) + return 0, newError("unable to cast %#v of type %T to uint16", i, i) } } @@ -698,7 +695,7 @@ func ToUint8E(i interface{}) (uint8, error) { if err == nil { return uint8(v), nil } - return 0, fmt.Errorf("unable to cast %#v to uint8: %s", i, err) + return 0, newError("unable to cast %#v to uint8: %s", i, err) case int: if s < 0 { return 0, errNegativeNotAllowed @@ -752,7 +749,7 @@ func ToUint8E(i interface{}) (uint8, error) { case nil: return 0, nil default: - return 0, fmt.Errorf("unable to cast %#v of type %T to uint8", i, i) + return 0, newError("unable to cast %#v of type %T to uint8", i, i) } } @@ -847,7 +844,7 @@ func ToStringE(i interface{}) (string, error) { case error: return s.Error(), nil default: - return "", fmt.Errorf("unable to cast %#v of type %T to string", i, i) + return "", newError("unable to cast %#v of type %T to string", i, i) } } @@ -877,7 +874,7 @@ func ToStringMapStringE(i interface{}) (map[string]string, error) { err := jsonStringToObject(v, &m) return m, err default: - return m, fmt.Errorf("unable to cast %#v of type %T to map[string]string", i, i) + return m, newError("unable to cast %#v of type %T to map[string]string", i, i) } } @@ -928,11 +925,11 @@ func ToStringMapStringSliceE(i interface{}) (map[string][]string, error) { for k, val := range v { key, err := ToStringE(k) if err != nil { - return m, fmt.Errorf("unable to cast %#v of type %T to map[string][]string", i, i) + return m, newError("unable to cast %#v of type %T to map[string][]string", i, i) } value, err := ToStringSliceE(val) if err != nil { - return m, fmt.Errorf("unable to cast %#v of type %T to map[string][]string", i, i) + return m, newError("unable to cast %#v of type %T to map[string][]string", i, i) } m[key] = value } @@ -940,7 +937,7 @@ func ToStringMapStringSliceE(i interface{}) (map[string][]string, error) { err := jsonStringToObject(v, &m) return m, err default: - return m, fmt.Errorf("unable to cast %#v of type %T to map[string][]string", i, i) + return m, newError("unable to cast %#v of type %T to map[string][]string", i, i) } return m, nil } @@ -966,7 +963,7 @@ func ToStringMapBoolE(i interface{}) (map[string]bool, error) { err := jsonStringToObject(v, &m) return m, err default: - return m, fmt.Errorf("unable to cast %#v of type %T to map[string]bool", i, i) + return m, newError("unable to cast %#v of type %T to map[string]bool", i, i) } } @@ -986,7 +983,7 @@ func ToStringMapE(i interface{}) (map[string]interface{}, error) { err := jsonStringToObject(v, &m) return m, err default: - return m, fmt.Errorf("unable to cast %#v of type %T to map[string]interface{}", i, i) + return m, newError("unable to cast %#v of type %T to map[string]interface{}", i, i) } } @@ -1003,14 +1000,14 @@ func ToSliceE(i interface{}) ([]interface{}, error) { } return s, nil default: - return s, fmt.Errorf("unable to cast %#v of type %T to []interface{}", i, i) + return s, newError("unable to cast %#v of type %T to []interface{}", i, i) } } // ToBoolSliceE casts an interface to a []bool type. func ToBoolSliceE(i interface{}) ([]bool, error) { if i == nil { - return []bool{}, fmt.Errorf("unable to cast %#v of type %T to []bool", i, i) + return []bool{}, newError("unable to cast %#v of type %T to []bool", i, i) } switch v := i.(type) { @@ -1026,13 +1023,13 @@ func ToBoolSliceE(i interface{}) ([]bool, error) { for j := 0; j < s.Len(); j++ { val, err := ToBoolE(s.Index(j).Interface()) if err != nil { - return []bool{}, fmt.Errorf("unable to cast %#v of type %T to []bool", i, i) + return []bool{}, newError("unable to cast %#v of type %T to []bool", i, i) } a[j] = val } return a, nil default: - return []bool{}, fmt.Errorf("unable to cast %#v of type %T to []bool", i, i) + return []bool{}, newError("unable to cast %#v of type %T to []bool", i, i) } } @@ -1053,18 +1050,18 @@ func ToStringSliceE(i interface{}) ([]string, error) { case interface{}: str, err := ToStringE(v) if err != nil { - return a, fmt.Errorf("unable to cast %#v of type %T to []string", i, i) + return a, newError("unable to cast %#v of type %T to []string", i, i) } return []string{str}, nil default: - return a, fmt.Errorf("unable to cast %#v of type %T to []string", i, i) + return a, newError("unable to cast %#v of type %T to []string", i, i) } } // ToIntSliceE casts an interface to a []int type. func ToIntSliceE(i interface{}) ([]int, error) { if i == nil { - return []int{}, fmt.Errorf("unable to cast %#v of type %T to []int", i, i) + return []int{}, newError("unable to cast %#v of type %T to []int", i, i) } switch v := i.(type) { @@ -1080,20 +1077,20 @@ func ToIntSliceE(i interface{}) ([]int, error) { for j := 0; j < s.Len(); j++ { val, err := ToIntE(s.Index(j).Interface()) if err != nil { - return []int{}, fmt.Errorf("unable to cast %#v of type %T to []int", i, i) + return []int{}, newError("unable to cast %#v of type %T to []int", i, i) } a[j] = val } return a, nil default: - return []int{}, fmt.Errorf("unable to cast %#v of type %T to []int", i, i) + return []int{}, newError("unable to cast %#v of type %T to []int", i, i) } } // ToDurationSliceE casts an interface to a []time.Duration type. func ToDurationSliceE(i interface{}) ([]time.Duration, error) { if i == nil { - return []time.Duration{}, fmt.Errorf("unable to cast %#v of type %T to []time.Duration", i, i) + return []time.Duration{}, newError("unable to cast %#v of type %T to []time.Duration", i, i) } switch v := i.(type) { @@ -1109,13 +1106,13 @@ func ToDurationSliceE(i interface{}) ([]time.Duration, error) { for j := 0; j < s.Len(); j++ { val, err := ToDurationE(s.Index(j).Interface()) if err != nil { - return []time.Duration{}, fmt.Errorf("unable to cast %#v of type %T to []time.Duration", i, i) + return []time.Duration{}, newError("unable to cast %#v of type %T to []time.Duration", i, i) } a[j] = val } return a, nil default: - return []time.Duration{}, fmt.Errorf("unable to cast %#v of type %T to []time.Duration", i, i) + return []time.Duration{}, newError("unable to cast %#v of type %T to []time.Duration", i, i) } } @@ -1155,7 +1152,7 @@ func parseDateWith(s string, dates []string) (d time.Time, e error) { return } } - return d, fmt.Errorf("unable to parse date: %s", s) + return d, newError("unable to parse date: %s", s) } // jsonStringToObject attempts to unmarshall a string as JSON into diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..f327887 --- /dev/null +++ b/errors.go @@ -0,0 +1,30 @@ +package cast + +import ( + "errors" + "fmt" + "sync" +) + +var errNegativeNotAllowed = errors.New("unable to cast negative value") + +type lazyError struct { + format string + args []interface{} + once sync.Once + result string +} + +func (e *lazyError) Error() string { + e.once.Do(func() { + e.result = fmt.Sprintf(e.format, e.args...) + }) + return e.result +} + +func newError(format string, args ...interface{}) error { + return &lazyError{ + format: format, + args: args, + } +} diff --git a/errors_test.go b/errors_test.go new file mode 100644 index 0000000..707bc37 --- /dev/null +++ b/errors_test.go @@ -0,0 +1,13 @@ +package cast + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestLazyError_Error(t *testing.T) { + err := newError("foo") + + assert.NotNil(t, err) + assert.Equal(t, err.Error(), "foo") +}