diff --git a/cvt.go b/cvt.go index aff17eb..4014bc2 100644 --- a/cvt.go +++ b/cvt.go @@ -77,3 +77,16 @@ func Uint(v interface{}, def ...uint) uint { return 0 } + +// Int64 convert an interface to a int64 type, with default value +func Int64(v interface{}, def ...int64) int64 { + if v, err := Int64E(v); err == nil { + return v + } + + if len(def) > 0 { + return def[0] + } + + return 0 +} diff --git a/cvt_test.go b/cvt_test.go index 1db5ff0..c694e5c 100644 --- a/cvt_test.go +++ b/cvt_test.go @@ -586,3 +586,96 @@ func TestUint_BaseLine(t *testing.T) { assert.Equal(t, tt.expect, v, msg) } } + +func TestInt64_HasDefault(t *testing.T) { + tests := []struct { + input interface{} + def int64 + expect int64 + }{ + // supported value, def is not used, def != expect + {int(8), 1, 8}, + {int8(8), 1, 8}, + {int16(8), 1, 8}, + {int32(8), 1, 8}, + {int64(8), 1, 8}, + {uint(8), 1, 8}, + {uint8(8), 1, 8}, + {uint16(8), 1, 8}, + {uint32(8), 1, 8}, + {uint64(8), 1, 8}, + {float32(8.31), 1, 8}, + {float64(8.31), 1, 8}, + {"8", 2, 8}, + {"8.00", 2, 8}, + {"8.01", 2, 8}, + {int(-8), 1, -8}, + {int8(-8), 1, -8}, + {int16(-8), 1, -8}, + {int32(-8), 1, -8}, + {int64(-8), 1, -8}, + {float32(-8.31), 1, -8}, + {float64(-8.31), 1, -8}, + {"-8", 1, -8}, + {"-8.01", 1, -8}, + {true, 2, 1}, + {false, 2, 0}, + {nil, 2, 0}, + {aliasTypeInt_0, 2, 0}, + {&aliasTypeInt_0, 2, 0}, + {aliasTypeInt_1, 2, 1}, + {&aliasTypeInt_1, 2, 1}, + {aliasTypeString_0, 2, 0}, + {&aliasTypeString_0, 2, 0}, + {aliasTypeString_1, 2, 1}, + {&aliasTypeString_1, 2, 1}, + {aliasTypeString_8d15, 2, 8}, + {&aliasTypeString_8d15, 2, 8}, + {aliasTypeString_8d15_minus, 1, -8}, + {&aliasTypeString_8d15_minus, 1, -8}, + + // unsupported value, def == expect + {"10a", 1, 1}, + {"a10a", 1, 1}, + {"8.01a", 1, 1}, + {"8.01 ", 1, 1}, + {"hello", 1, 1}, + {testing.T{}, 1, 1}, + {&testing.T{}, 1, 1}, + {[]int{}, 1, 1}, + {[]string{}, 1, 1}, + {[...]string{}, 1, 1}, + {map[int]string{}, 1, 1}, + } + + for i, tt := range tests { + msg := fmt.Sprintf("i = %d, input[%+v], def[%+v], expect[%+v]", i, tt.input, tt.def, tt.expect) + + v := cvt.Int64(tt.input, tt.def) + assert.Equal(t, tt.expect, v, msg) + } +} + +func TestInt64_BaseLine(t *testing.T) { + tests := []struct { + input interface{} + expect int64 + }{ + {testing.T{}, 0}, + {&testing.T{}, 0}, + {[]int{}, 0}, + {[]int{1, 2, 3}, 0}, + {[]string{}, 0}, + {[]string{"a", "b", "c"}, 0}, + {[...]string{}, 0}, + {map[int]string{}, 0}, + {"4873546382743564386435354655456575456754356765546554643456", 0}, + } + + for i, tt := range tests { + msg := fmt.Sprintf("i = %d, input[%+v], expect[%+v]", i, tt.input, tt.expect) + + v := cvt.Int64(tt.input) + assert.Equal(t, tt.expect, v, msg) + } +} diff --git a/cvte.go b/cvte.go index 8763fd3..f4a29d8 100644 --- a/cvte.go +++ b/cvte.go @@ -1,6 +1,7 @@ package cvt import ( + "errors" "fmt" "math" "reflect" @@ -8,6 +9,10 @@ import ( "strings" ) +var convErr = errors.New("convert failed") +var formatOutOfLimit = "%w, out of max limit value(%d)" +var formatExtend = "%v, %w" + // BoolE convert an interface to a bool type func BoolE(val interface{}) (bool, error) { v, rk, rv := Indirect(val) @@ -35,7 +40,7 @@ func BoolE(val interface{}) (bool, error) { return rv.Len() > 0, nil } - return false, err(val, "bool") + return false, newErr(val, "bool") } // returns the boolean value represented by the string @@ -52,51 +57,31 @@ func str2bool(str string) (bool, error) { case "off": return false, nil default: - return false, err(str, "bool") + return false, newErr(str, "bool") } } // Uint64E convert an interface to a uint64 type func Uint64E(val interface{}) (uint64, error) { - e := err(val, "uint64") - v, _, rv := Indirect(val) - - switch vv := v.(type) { - case nil: - return 0, nil - case bool: - if vv { - return 1, nil - } - return 0, nil - case string: - vvv, err := strconv.ParseFloat(vv, 64) - if err == nil && vvv >= 0 && vvv <= math.MaxUint64 { - return uint64(math.Floor(vvv)), nil - } - case uint, uint8, uint16, uint32, uint64: - return rv.Uint(), nil - case int, int8, int16, int32, int64: - if rv.Int() >= 0 { - return uint64(rv.Int()), nil - } - case float32, float64: - if rv.Float() >= 0 && rv.Float() <= math.MaxUint64 { - return uint64(math.Floor(rv.Float())), nil - } + v, e := convUint64(val) + if e := catch("uint64", val, e); e != nil { + return 0, e + } + if v > math.MaxUint64 { + return 0, fmt.Errorf(formatOutOfLimit, newErr(val, "uint64"), uint64(math.MaxUint64)) } - return 0, e + return v, nil } // Uint32E convert an interface to a uint32 type func Uint32E(val interface{}) (uint32, error) { - v, e := Uint64E(val) - if e != nil { - return 0, err(val, "uint32") + v, e := convUint64(val) + if e := catch("uint32", val, e); e != nil { + return 0, e } if v > math.MaxUint32 { - return 0, fmt.Errorf("%w, out of max limit value(%d)", err(val, "uint32"), math.MaxUint32) + return 0, fmt.Errorf(formatOutOfLimit, newErr(val, "uint32"), uint32(math.MaxUint32)) } return uint32(v), nil @@ -104,12 +89,12 @@ func Uint32E(val interface{}) (uint32, error) { // Uint16E convert an interface to a uint16 type func Uint16E(val interface{}) (uint16, error) { - v, e := Uint64E(val) - if e != nil { - return 0, err(val, "uint16") + v, e := convUint64(val) + if e := catch("uint16", val, e); e != nil { + return 0, e } if v > math.MaxUint16 { - return 0, fmt.Errorf("%w, out of max limit value(%d)", err(val, "uint16"), math.MaxUint16) + return 0, fmt.Errorf(formatOutOfLimit, newErr(val, "uint16"), uint16(math.MaxUint16)) } return uint16(v), nil @@ -117,12 +102,12 @@ func Uint16E(val interface{}) (uint16, error) { // Uint8E convert an interface to a uint8 type func Uint8E(val interface{}) (uint8, error) { - v, e := Uint64E(val) - if e != nil { - return 0, err(val, "uint8") + v, e := convUint64(val) + if e := catch("uint8", val, e); e != nil { + return 0, e } if v > math.MaxUint8 { - return 0, fmt.Errorf("%w, out of max limit value(%d)", err(val, "uint8"), math.MaxUint8) + return 0, fmt.Errorf(formatOutOfLimit, newErr(val, "uint8"), uint8(math.MaxUint8)) } return uint8(v), nil @@ -130,17 +115,104 @@ func Uint8E(val interface{}) (uint8, error) { // UintE convert an interface to a uint type func UintE(val interface{}) (uint, error) { - v, e := Uint64E(val) - if e != nil { - return 0, err(val, "uint") + v, e := convUint64(val) + if e := catch("uint", val, e); e != nil { + return 0, e } if v > uint64(^uint(0)) { - return 0, fmt.Errorf("%w, out of max limit value(%d)", err(val, "uint"), ^uint(0)) + return 0, fmt.Errorf(formatOutOfLimit, newErr(val, "uint"), ^uint(0)) } return uint(v), nil } +func convUint64(val interface{}) (uint64, error) { + v, _, rv := Indirect(val) + + switch vv := v.(type) { + case nil: + return 0, nil + case bool: + if vv { + return 1, nil + } + return 0, nil + case string: + vvv, err := strconv.ParseFloat(vv, 64) + if err == nil && vvv >= 0 && vvv <= math.MaxUint64 { + return uint64(math.Trunc(vvv)), nil + } + case []byte: + vvv, err := strconv.ParseFloat(string(vv), 64) + if err == nil && vvv >= 0 && vvv <= math.MaxUint64 { + return uint64(math.Trunc(vvv)), nil + } + case uint, uint8, uint16, uint32, uint64: + return rv.Uint(), nil + case int, int8, int16, int32, int64: + if rv.Int() >= 0 { + return uint64(rv.Int()), nil + } + case float32, float64: + if rv.Float() >= 0 && rv.Float() <= math.MaxUint64 { + return uint64(math.Trunc(rv.Float())), nil + } + } + + return 0, convErr +} + +// Int64E convert an interface to a int64 type +func Int64E(val interface{}) (int64, error) { + v, e := convInt64(val) + if e := catch("int64", val, e); e != nil { + return 0, e + } + if strconv.IntSize == 64 && v > math.MaxInt64 { + return 0, fmt.Errorf(formatOutOfLimit, newErr(val, "int64"), math.MaxInt64) + } else if strconv.IntSize == 32 && v > math.MaxInt32 { + return 0, fmt.Errorf(formatOutOfLimit, newErr(val, "int64"), math.MaxInt32) + } + + return v, nil +} + +func convInt64(val interface{}) (int64, error) { + v, _, rv := Indirect(val) + + switch vv := v.(type) { + case nil: + return 0, nil + case bool: + if vv { + return 1, nil + } + return 0, nil + case string: + vvv, err := strconv.ParseFloat(vv, 64) + if err == nil && vvv <= math.MaxInt64 { + return int64(math.Trunc(vvv)), nil + } + case []byte: + vvv, err := strconv.ParseFloat(string(vv), 64) + if err == nil && vvv <= math.MaxInt64 { + return int64(math.Trunc(vvv)), nil + } + case uint, uint8, uint16, uint32, uint64, uintptr: + if rv.Uint() <= math.MaxInt64 { + return int64(rv.Uint()), nil + } + case int, int8, int16, int32, int64: + return rv.Int(), nil + case float32, float64: + if rv.Float() <= math.MaxInt64 { + return int64(math.Trunc(rv.Float())), nil + } + } + + return 0, convErr +} + // Indirect returns the value with base type func Indirect(a interface{}) (val interface{}, k reflect.Kind, v reflect.Value) { if a == nil { @@ -198,6 +270,16 @@ func Indirect(a interface{}) (val interface{}, k reflect.Kind, v reflect.Value) return } -func err(val interface{}, t string) error { +func newErr(val interface{}, t string) error { return fmt.Errorf("unable to convert %#v of type %T to %s", val, val, t) } + +func catch(t string, val interface{}, e error) error { + if e != nil { + if errors.Is(e, convErr) { + return newErr(val, t) + } + return fmt.Errorf(formatExtend, newErr(val, t), e) + } + return nil +} diff --git a/cvte_test.go b/cvte_test.go index 0a6883d..b250e53 100644 --- a/cvte_test.go +++ b/cvte_test.go @@ -10,9 +10,17 @@ import ( "github.com/shockerli/cvt" ) +// alias type: bool +type AliasTypeBool bool + // alias type: int type AliasTypeInt int +var ( + aliasTypeBool_true AliasTypeBool = true + aliasTypeBool_false AliasTypeBool = false +) + var ( aliasTypeInt_0 AliasTypeInt = 0 aliasTypeInt_1 AliasTypeInt = 1 @@ -36,7 +44,7 @@ func TestBoolE(t *testing.T) { expect bool isErr bool }{ - // true/scale + // false/scale {0, false, false}, {float64(0.00), false, false}, {int(0.00), false, false}, @@ -45,16 +53,17 @@ func TestBoolE(t *testing.T) { {uint64(0.00), false, false}, {uint8(0.00), false, false}, {nil, false, false}, + {false, false, false}, {"false", false, false}, {"FALSE", false, false}, {"False", false, false}, {"f", false, false}, {"F", false, false}, - {false, false, false}, {"off", false, false}, {"Off", false, false}, {"0", false, false}, {"0.00", false, false}, + {[]byte("false"), false, false}, {[]byte("Off"), false, false}, {aliasTypeInt_0, false, false}, {&aliasTypeInt_0, false, false}, @@ -62,6 +71,8 @@ func TestBoolE(t *testing.T) { {&aliasTypeString_0, false, false}, {aliasTypeString_off, false, false}, {&aliasTypeString_off, false, false}, + {aliasTypeBool_false, false, false}, + {&aliasTypeBool_false, false, false}, // false/slice/array/map {[]int{}, false, false}, @@ -71,24 +82,27 @@ func TestBoolE(t *testing.T) { {map[string]string{}, false, false}, // true/scale + {true, true, false}, {"true", true, false}, {"TRUE", true, false}, {"True", true, false}, {"t", true, false}, {"T", true, false}, {1, true, false}, - {true, true, false}, {-1, true, false}, {"on", true, false}, {"On", true, false}, {0.01, true, false}, {"0.01", true, false}, + {[]byte("true"), true, false}, {aliasTypeInt_1, true, false}, {&aliasTypeInt_1, true, false}, {aliasTypeString_1, true, false}, {&aliasTypeString_1, true, false}, {aliasTypeString_on, true, false}, {&aliasTypeString_on, true, false}, + {aliasTypeBool_true, true, false}, + {&aliasTypeBool_true, true, false}, // true/slice/array/map {[]int{1, 2, 3}, true, false}, @@ -149,6 +163,9 @@ func TestUint64E(t *testing.T) { {"8", 8, false}, {"8.00", 8, false}, {"8.01", 8, false}, + {[]byte("8"), 8, false}, + {[]byte("8.00"), 8, false}, + {[]byte("8.01"), 8, false}, {nil, 0, false}, {aliasTypeInt_0, 0, false}, {&aliasTypeInt_0, 0, false}, @@ -160,6 +177,10 @@ func TestUint64E(t *testing.T) { {&aliasTypeString_1, 1, false}, {aliasTypeString_8d15, 8, false}, {&aliasTypeString_8d15, 8, false}, + {aliasTypeBool_true, 1, false}, + {&aliasTypeBool_true, 1, false}, + {aliasTypeBool_false, 0, false}, + {&aliasTypeBool_false, 0, false}, // errors {int(-8), 0, true}, @@ -542,3 +563,94 @@ func TestUintE(t *testing.T) { assert.Equal(t, tt.expect, v, msg) } } + +func TestInt64E(t *testing.T) { + tests := []struct { + input interface{} + expect int64 + isErr bool + }{ + {int(8), 8, false}, + {int8(8), 8, false}, + {int16(8), 8, false}, + {int32(8), 8, false}, + {int64(8), 8, false}, + {uint(8), 8, false}, + {uint8(8), 8, false}, + {uint16(8), 8, false}, + {uint32(8), 8, false}, + {uint64(8), 8, false}, + {float32(8.31), 8, false}, + {float64(8.31), 8, false}, + {true, 1, false}, + {false, 0, false}, + {int(-8), -8, false}, + {int8(-8), -8, false}, + {int16(-8), -8, false}, + {int32(-8), -8, false}, + {int64(-8), -8, false}, + {float32(-8.31), -8, false}, + {float64(-8.31), -8, false}, + {"-8", -8, false}, + {"-8.01", -8, false}, + {"8", 8, false}, + {"8.00", 8, false}, + {"8.01", 8, false}, + {[]byte("-8"), -8, false}, + {[]byte("-8.01"), -8, false}, + {[]byte("8"), 8, false}, + {[]byte("8.00"), 8, false}, + {[]byte("8.01"), 8, false}, + {uint32(math.MaxUint32), int64(math.MaxUint32), false}, + {nil, 0, false}, + {aliasTypeInt_0, 0, false}, + {&aliasTypeInt_0, 0, false}, + {aliasTypeInt_1, 1, false}, + {&aliasTypeInt_1, 1, false}, + {aliasTypeString_0, 0, false}, + {&aliasTypeString_0, 0, false}, + {aliasTypeString_1, 1, false}, + {&aliasTypeString_1, 1, false}, + {aliasTypeString_8d15, 8, false}, + {&aliasTypeString_8d15, 8, false}, + {aliasTypeString_8d15_minus, -8, false}, + {&aliasTypeString_8d15_minus, -8, false}, + {aliasTypeBool_true, 1, false}, + {&aliasTypeBool_true, 1, false}, + {aliasTypeBool_false, 0, false}, + {&aliasTypeBool_false, 0, false}, + + // errors + {"10a", 0, true}, + {"a10a", 0, true}, + {"8.01a", 0, true}, + {"8.01 ", 0, true}, + {"4873546382743564386435354655456575456754356765546554643456", 0, true}, + {float64(4873546382743564386435354655456575456754356765546554643456), 0, true}, + {uint64(math.MaxUint64), 0, true}, + {"hello", 0, true}, + {testing.T{}, 0, true}, + {&testing.T{}, 0, true}, + {[]int{}, 0, true}, + {[]string{}, 0, true}, + {[...]string{}, 0, true}, + {map[int]string{}, 0, true}, + } + + for i, tt := range tests { + msg := fmt.Sprintf("i = %d, input[%+v], expect[%v], isErr[%v]", i, tt.input, tt.expect, tt.isErr) + + v, err := cvt.Int64E(tt.input) + if tt.isErr { + assert.Error(t, err, msg) + continue + } + + assert.NoError(t, err, msg) + assert.Equal(t, tt.expect, v, msg) + + // Non-E test with no default value: + v = cvt.Int64(tt.input) + assert.Equal(t, tt.expect, v, msg) + } +} diff --git a/go.mod b/go.mod index e053fa2..b1e7d59 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.15 require ( github.com/davecgh/go-spew v1.1.1 - github.com/stretchr/objx v0.3.0 // indirect github.com/stretchr/testify v1.7.0 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/go.sum b/go.sum index 246b4aa..801080b 100644 --- a/go.sum +++ b/go.sum @@ -5,11 +5,9 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As= -github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=