From 2b0eb0f724e320b655240e331aef36d1175986c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Wed, 11 May 2022 10:35:17 +0200 Subject: [PATCH] Misc number fixes * Allow strings/json.Number with just zeroes after the decmial point to be converted to integer` * Allow nil in ToFloat* * Remove some test duplication * replace testify with quicktest * Enable integer and float conversion of time.Weekday values Fixes #139 Fixes #141 --- .github/workflows/go.yml | 2 +- cast_test.go | 1002 ++++++++++++-------------------------- caste.go | 305 ++++++------ go.mod | 12 +- go.sum | 24 +- 5 files changed, 507 insertions(+), 838 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 81907bc..c4f0f68 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -10,7 +10,7 @@ jobs: build: strategy: matrix: - go-version: [1.17.x, 1.18.x] + go-version: [1.16.x, 1.17.x, 1.18.x] os: [ubuntu-latest, windows-latest] runs-on: ${{ matrix.os }} steps: diff --git a/cast_test.go b/cast_test.go index 69b5bdc..dc8ba14 100644 --- a/cast_test.go +++ b/cast_test.go @@ -11,662 +11,226 @@ import ( "fmt" "html/template" "path" + "reflect" "testing" "time" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + qt "github.com/frankban/quicktest" ) -func TestToUintE(t *testing.T) { - var jn, nj, jne json.Number - _ = json.Unmarshal([]byte("8"), &jn) - _ = json.Unmarshal([]byte("-8"), &nj) - _ = json.Unmarshal([]byte("8.0"), &jne) - tests := []struct { - input interface{} - expect uint - 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}, - {"8", 8, false}, - {jn, 8, false}, - {nil, 0, false}, - // errors - {int(-8), 0, true}, - {int8(-8), 0, true}, - {int16(-8), 0, true}, - {int32(-8), 0, true}, - {int64(-8), 0, true}, - {float32(-8.31), 0, true}, - {float64(-8.31), 0, true}, - {"-8", 0, true}, - {nj, 0, true}, - {jne, 0, true}, - {"test", 0, true}, - {testing.T{}, 0, true}, +type testStep struct { + input interface{} + expect interface{} + iserr bool +} + +func createNumberTestSteps(zero, one, eight, eightnegative, eightpoint31, eightpoint31negative interface{}) []testStep { + var jeight, jminuseight, jfloateight json.Number + _ = json.Unmarshal([]byte("8"), &jeight) + _ = json.Unmarshal([]byte("-8"), &jminuseight) + _ = json.Unmarshal([]byte("8.0"), &jfloateight) + + kind := reflect.TypeOf(zero).Kind() + isUint := kind == reflect.Uint || kind == reflect.Uint8 || kind == reflect.Uint16 || kind == reflect.Uint32 || kind == reflect.Uint64 + + // Some precision is lost when converting from float64 to float32. + eightpoint31_32 := eightpoint31 + eightpoint31negative_32 := eightpoint31negative + if kind == reflect.Float64 { + eightpoint31_32 = float64(float32(eightpoint31.(float64))) + eightpoint31negative_32 = float64(float32(eightpoint31negative.(float64))) } + return []testStep{ + {int(8), eight, false}, + {int8(8), eight, false}, + {int16(8), eight, false}, + {int32(8), eight, false}, + {int64(8), eight, false}, + {time.Weekday(8), eight, false}, + {time.Month(8), eight, false}, + {uint(8), eight, false}, + {uint8(8), eight, false}, + {uint16(8), eight, false}, + {uint32(8), eight, false}, + {uint64(8), eight, false}, + {float32(8.31), eightpoint31_32, false}, + {float64(8.31), eightpoint31, false}, + {true, one, false}, + {false, zero, false}, + {"8", eight, false}, + {nil, zero, false}, + {int(-8), eightnegative, isUint}, + {int8(-8), eightnegative, isUint}, + {int16(-8), eightnegative, isUint}, + {int32(-8), eightnegative, isUint}, + {int64(-8), eightnegative, isUint}, + {float32(-8.31), eightpoint31negative_32, isUint}, + {float64(-8.31), eightpoint31negative, isUint}, + {"-8", eightnegative, isUint}, + {jeight, eight, false}, + {jminuseight, eightnegative, isUint}, + {jfloateight, eight, false}, + {"test", zero, true}, + {testing.T{}, zero, true}, + } +} + +// Maybe Go 1.18 generics will make this less ugly? +func runNumberTest(c *qt.C, tests []testStep, tove func(interface{}) (interface{}, error), tov func(interface{}) interface{}) { + c.Helper() + for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message + errmsg := qt.Commentf("i = %d", i) - v, err := ToUintE(test.input) + v, err := tove(test.input) if test.iserr { - assert.Error(t, err, errmsg) + c.Assert(err, qt.IsNotNil, errmsg) continue } - - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(err, qt.IsNil, errmsg) + c.Assert(v, qt.Equals, test.expect, errmsg) // Non-E test: - v = ToUint(test.input) - assert.Equal(t, test.expect, v, errmsg) + v = tov(test.input) + c.Assert(v, qt.Equals, test.expect, errmsg) } } +func TestToUintE(t *testing.T) { + tests := createNumberTestSteps(uint(0), uint(1), uint(8), uint(0), uint(8), uint(8)) + + runNumberTest( + qt.New(t), + tests, + func(v interface{}) (interface{}, error) { return ToUintE(v) }, + func(v interface{}) interface{} { return ToUint(v) }, + ) +} + func TestToUint64E(t *testing.T) { - var jn, nj, jne json.Number - _ = json.Unmarshal([]byte("8"), &jn) - _ = json.Unmarshal([]byte("-8"), &nj) - _ = json.Unmarshal([]byte("8.0"), &jne) - tests := []struct { - input interface{} - expect uint64 - 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}, - {"8", 8, false}, - {jn, 8, false}, - {nil, 0, false}, - // errors - {int(-8), 0, true}, - {int8(-8), 0, true}, - {int16(-8), 0, true}, - {int32(-8), 0, true}, - {int64(-8), 0, true}, - {float32(-8.31), 0, true}, - {float64(-8.31), 0, true}, - {"-8", 0, true}, - {nj, 0, true}, - {jne, 0, true}, - {"test", 0, true}, - {testing.T{}, 0, true}, - } + tests := createNumberTestSteps(uint64(0), uint64(1), uint64(8), uint64(0), uint64(8), uint64(8)) - for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message - - v, err := ToUint64E(test.input) - if test.iserr { - assert.Error(t, err, errmsg) - continue - } - - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) - - // Non-E test: - v = ToUint64(test.input) - assert.Equal(t, test.expect, v, errmsg) - } + runNumberTest( + qt.New(t), + tests, + func(v interface{}) (interface{}, error) { return ToUint64E(v) }, + func(v interface{}) interface{} { return ToUint64(v) }, + ) } func TestToUint32E(t *testing.T) { - var jn, nj, jne json.Number - _ = json.Unmarshal([]byte("8"), &jn) - _ = json.Unmarshal([]byte("-8"), &nj) - _ = json.Unmarshal([]byte("8.0"), &jne) - tests := []struct { - input interface{} - expect uint32 - 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}, - {"8", 8, false}, - {jn, 8, false}, - {nil, 0, false}, - {int(-8), 0, true}, - {int8(-8), 0, true}, - {int16(-8), 0, true}, - {int32(-8), 0, true}, - {int64(-8), 0, true}, - {float32(-8.31), 0, true}, - {float64(-8.31), 0, true}, - {"-8", 0, true}, - {nj, 0, true}, - // errors - {jne, 0, true}, - {"test", 0, true}, - {testing.T{}, 0, true}, - } + tests := createNumberTestSteps(uint32(0), uint32(1), uint32(8), uint32(0), uint32(8), uint32(8)) - for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message - - v, err := ToUint32E(test.input) - if test.iserr { - assert.Error(t, err, errmsg) - continue - } - - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) - - // Non-E test: - v = ToUint32(test.input) - assert.Equal(t, test.expect, v, errmsg) - } + runNumberTest( + qt.New(t), + tests, + func(v interface{}) (interface{}, error) { return ToUint32E(v) }, + func(v interface{}) interface{} { return ToUint32(v) }, + ) } func TestToUint16E(t *testing.T) { - var jn, nj, jne json.Number - _ = json.Unmarshal([]byte("8"), &jn) - _ = json.Unmarshal([]byte("-8"), &nj) - _ = json.Unmarshal([]byte("8.0"), &jne) - tests := []struct { - input interface{} - expect uint16 - 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}, - {"8", 8, false}, - {jn, 8, false}, - {nil, 0, false}, - // errors - {int(-8), 0, true}, - {int8(-8), 0, true}, - {int16(-8), 0, true}, - {int32(-8), 0, true}, - {int64(-8), 0, true}, - {float32(-8.31), 0, true}, - {float64(-8.31), 0, true}, - {"-8", 0, true}, - {nj, 0, true}, - {jne, 0, true}, - {"test", 0, true}, - {testing.T{}, 0, true}, - } + tests := createNumberTestSteps(uint16(0), uint16(1), uint16(8), uint16(0), uint16(8), uint16(8)) - for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message - - v, err := ToUint16E(test.input) - if test.iserr { - assert.Error(t, err, errmsg) - continue - } - - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) - - // Non-E test - v = ToUint16(test.input) - assert.Equal(t, test.expect, v, errmsg) - } + runNumberTest( + qt.New(t), + tests, + func(v interface{}) (interface{}, error) { return ToUint16E(v) }, + func(v interface{}) interface{} { return ToUint16(v) }, + ) } func TestToUint8E(t *testing.T) { - var jn, nj, jne json.Number - _ = json.Unmarshal([]byte("8"), &jn) - _ = json.Unmarshal([]byte("-8"), &nj) - _ = json.Unmarshal([]byte("8.0"), &jne) - tests := []struct { - input interface{} - expect uint8 - 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}, - {"8", 8, false}, - {jn, 8, false}, - {nil, 0, false}, - // errors - {int(-8), 0, true}, - {int8(-8), 0, true}, - {int16(-8), 0, true}, - {int32(-8), 0, true}, - {int64(-8), 0, true}, - {float32(-8.31), 0, true}, - {float64(-8.31), 0, true}, - {"-8", 0, true}, - {nj, 0, true}, - {jne, 0, true}, - {"test", 0, true}, - {testing.T{}, 0, true}, - } + tests := createNumberTestSteps(uint8(0), uint8(1), uint8(8), uint8(0), uint8(8), uint8(8)) - for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message - - v, err := ToUint8E(test.input) - if test.iserr { - assert.Error(t, err, errmsg) - continue - } - - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) - - // Non-E test - v = ToUint8(test.input) - assert.Equal(t, test.expect, v, errmsg) - } + runNumberTest( + qt.New(t), + tests, + func(v interface{}) (interface{}, error) { return ToUint8E(v) }, + func(v interface{}) interface{} { return ToUint8(v) }, + ) } - func TestToIntE(t *testing.T) { - var jn, nj json.Number - _ = json.Unmarshal([]byte("8"), &jn) - _ = json.Unmarshal([]byte("8.0"), &nj) - tests := []struct { - input interface{} - expect int - 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}, - {"8", 8, false}, - {jn, 8, false}, - {nil, 0, false}, - // errors - {"test", 0, true}, - {nj, 0, true}, - {testing.T{}, 0, true}, - } + tests := createNumberTestSteps(int(0), int(1), int(8), int(-8), int(8), int(-8)) - for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message - - v, err := ToIntE(test.input) - if test.iserr { - assert.Error(t, err, errmsg) - continue - } - - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) - - // Non-E test - v = ToInt(test.input) - assert.Equal(t, test.expect, v, errmsg) - } + runNumberTest( + qt.New(t), + tests, + func(v interface{}) (interface{}, error) { return ToIntE(v) }, + func(v interface{}) interface{} { return ToInt(v) }, + ) } func TestToInt64E(t *testing.T) { - var jn, nj json.Number - _ = json.Unmarshal([]byte("8"), &jn) - _ = json.Unmarshal([]byte(".8"), &nj) - 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}, - {"8", 8, false}, - {jn, 8, false}, - {nil, 0, false}, - // errors - {"test", 0, true}, - {nj, 0, true}, - {testing.T{}, 0, true}, - } + tests := createNumberTestSteps(int64(0), int64(1), int64(8), int64(-8), int64(8), int64(-8)) - for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message - - v, err := ToInt64E(test.input) - if test.iserr { - assert.Error(t, err, errmsg) - continue - } - - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) - - // Non-E test - v = ToInt64(test.input) - assert.Equal(t, test.expect, v, errmsg) - } + runNumberTest( + qt.New(t), + tests, + func(v interface{}) (interface{}, error) { return ToInt64E(v) }, + func(v interface{}) interface{} { return ToInt64(v) }, + ) } func TestToInt32E(t *testing.T) { - var jn, nj json.Number - _ = json.Unmarshal([]byte("8"), &jn) - _ = json.Unmarshal([]byte("8.0"), &nj) - tests := []struct { - input interface{} - expect int32 - 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}, - {"8", 8, false}, - {jn, 8, false}, - {nil, 0, false}, - // errors - {"test", 0, true}, - {nj, 0, true}, - {testing.T{}, 0, true}, - } + tests := createNumberTestSteps(int32(0), int32(1), int32(8), int32(-8), int32(8), int32(-8)) - for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message - - v, err := ToInt32E(test.input) - if test.iserr { - assert.Error(t, err, errmsg) - continue - } - - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) - - // Non-E test - v = ToInt32(test.input) - assert.Equal(t, test.expect, v, errmsg) - } + runNumberTest( + qt.New(t), + tests, + func(v interface{}) (interface{}, error) { return ToInt32E(v) }, + func(v interface{}) interface{} { return ToInt32(v) }, + ) } func TestToInt16E(t *testing.T) { - var jn, nj json.Number - _ = json.Unmarshal([]byte("8"), &jn) - _ = json.Unmarshal([]byte("8.0"), &nj) - tests := []struct { - input interface{} - expect int16 - 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}, - {"8", 8, false}, - {jn, 8, false}, - {nil, 0, false}, - // errors - {"test", 0, true}, - {nj, 0, true}, - {testing.T{}, 0, true}, - } + tests := createNumberTestSteps(int16(0), int16(1), int16(8), int16(-8), int16(8), int16(-8)) - for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message - - v, err := ToInt16E(test.input) - if test.iserr { - assert.Error(t, err, errmsg) - continue - } - - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) - - // Non-E test - v = ToInt16(test.input) - assert.Equal(t, test.expect, v, errmsg) - } + runNumberTest( + qt.New(t), + tests, + func(v interface{}) (interface{}, error) { return ToInt16E(v) }, + func(v interface{}) interface{} { return ToInt16(v) }, + ) } func TestToInt8E(t *testing.T) { - var jn, nj json.Number - _ = json.Unmarshal([]byte("8"), &jn) - _ = json.Unmarshal([]byte("8.0"), &nj) - tests := []struct { - input interface{} - expect int8 - 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}, - {"8", 8, false}, - {jn, 8, false}, - {nil, 0, false}, - // errors - {"test", 0, true}, - {nj, 0, true}, - {testing.T{}, 0, true}, - } + tests := createNumberTestSteps(int8(0), int8(1), int8(8), int8(-8), int8(8), int8(-8)) - for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message - - v, err := ToInt8E(test.input) - if test.iserr { - assert.Error(t, err, errmsg) - continue - } - - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) - - // Non-E test - v = ToInt8(test.input) - assert.Equal(t, test.expect, v, errmsg) - } + runNumberTest( + qt.New(t), + tests, + func(v interface{}) (interface{}, error) { return ToInt8E(v) }, + func(v interface{}) interface{} { return ToInt8(v) }, + ) } func TestToFloat64E(t *testing.T) { - var jn, nj json.Number - _ = json.Unmarshal([]byte("8"), &jn) - _ = json.Unmarshal([]byte(".8"), &nj) - tests := []struct { - input interface{} - expect float64 - 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), 8, false}, - {float64(8.31), 8.31, false}, - {"8", 8, false}, - {jn, 8, false}, - {true, 1, false}, - {false, 0, false}, - // errors - {"test", 0, true}, - {nj, 0, true}, - {testing.T{}, 0, true}, - } + tests := createNumberTestSteps(float64(0), float64(1), float64(8), float64(-8), float64(8.31), float64(-8.31)) - for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message - - v, err := ToFloat64E(test.input) - if test.iserr { - assert.Error(t, err, errmsg) - continue - } - - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) - - // Non-E test - v = ToFloat64(test.input) - assert.Equal(t, test.expect, v, errmsg) - } + runNumberTest( + qt.New(t), + tests, + func(v interface{}) (interface{}, error) { return ToFloat64E(v) }, + func(v interface{}) interface{} { return ToFloat64(v) }, + ) } func TestToFloat32E(t *testing.T) { - var jn, nj json.Number - _ = json.Unmarshal([]byte("8"), &jn) - _ = json.Unmarshal([]byte(".8"), &nj) - tests := []struct { - input interface{} - expect float32 - 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.31, false}, - {float64(8.31), 8.31, false}, - {"8", 8, false}, - {jn, 8, false}, - {true, 1, false}, - {false, 0, false}, - // errors - {"test", 0, true}, - {nj, 0, true}, - {testing.T{}, 0, true}, - } + tests := createNumberTestSteps(float32(0), float32(1), float32(8), float32(-8), float32(8.31), float32(-8.31)) - for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message - - v, err := ToFloat32E(test.input) - if test.iserr { - assert.Error(t, err, errmsg) - continue - } - - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) - - // Non-E test - v = ToFloat32(test.input) - assert.Equal(t, test.expect, v, errmsg) - } + runNumberTest( + qt.New(t), + tests, + func(v interface{}) (interface{}, error) { return ToFloat32E(v) }, + func(v interface{}) interface{} { return ToFloat32(v) }, + ) } func TestToStringE(t *testing.T) { + c := qt.New(t) + var jn json.Number _ = json.Unmarshal([]byte("8"), &jn) type Key struct { @@ -708,20 +272,20 @@ func TestToStringE(t *testing.T) { } for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message + errmsg := qt.Commentf("i = %d", i) // assert helper message v, err := ToStringE(test.input) if test.iserr { - assert.Error(t, err, errmsg) + c.Assert(err, qt.IsNotNil, errmsg) continue } - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(err, qt.IsNil, errmsg) + c.Assert(v, qt.Equals, test.expect, errmsg) // Non-E test v = ToString(test.input) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(v, qt.Equals, test.expect, errmsg) } } @@ -734,9 +298,11 @@ func (x foo) String() string { } func TestStringerToString(t *testing.T) { + c := qt.New(t) + var x foo x.val = "bar" - assert.Equal(t, "bar", ToString(x)) + c.Assert(ToString(x), qt.Equals, "bar") } type fu struct { @@ -748,12 +314,16 @@ func (x fu) Error() string { } func TestErrorToString(t *testing.T) { + c := qt.New(t) + var x fu x.val = "bar" - assert.Equal(t, "bar", ToString(x)) + c.Assert(ToString(x), qt.Equals, "bar") } func TestStringMapStringSliceE(t *testing.T) { + c := qt.New(t) + // ToStringMapString inputs/outputs var stringMapString = map[string]string{"key 1": "value 1", "key 2": "value 2", "key 3": "value 3"} var stringMapInterface = map[string]interface{}{"key 1": "value 1", "key 2": "value 2", "key 3": "value 3"} @@ -811,24 +381,26 @@ func TestStringMapStringSliceE(t *testing.T) { } for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message + errmsg := qt.Commentf("i = %d", i) // assert helper message v, err := ToStringMapStringSliceE(test.input) if test.iserr { - assert.Error(t, err, errmsg) + c.Assert(err, qt.IsNotNil, errmsg) continue } - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(err, qt.IsNil, errmsg) + c.Assert(v, qt.DeepEquals, test.expect, errmsg) // Non-E test v = ToStringMapStringSlice(test.input) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(v, qt.DeepEquals, test.expect, errmsg) } } func TestToStringMapE(t *testing.T) { + c := qt.New(t) + tests := []struct { input interface{} expect map[string]interface{} @@ -846,24 +418,26 @@ func TestToStringMapE(t *testing.T) { } for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message + errmsg := qt.Commentf("i = %d", i) // assert helper message v, err := ToStringMapE(test.input) if test.iserr { - assert.Error(t, err, errmsg) + c.Assert(err, qt.IsNotNil, errmsg) continue } - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(err, qt.IsNil, errmsg) + c.Assert(v, qt.DeepEquals, test.expect, errmsg) // Non-E test v = ToStringMap(test.input) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(v, qt.DeepEquals, test.expect, errmsg) } } func TestToStringMapBoolE(t *testing.T) { + c := qt.New(t) + tests := []struct { input interface{} expect map[string]bool @@ -881,24 +455,26 @@ func TestToStringMapBoolE(t *testing.T) { } for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message + errmsg := qt.Commentf("i = %d", i) // assert helper message v, err := ToStringMapBoolE(test.input) if test.iserr { - assert.Error(t, err, errmsg) + c.Assert(err, qt.IsNotNil, errmsg) continue } - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(err, qt.IsNil, errmsg) + c.Assert(v, qt.DeepEquals, test.expect, errmsg) // Non-E test v = ToStringMapBool(test.input) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(v, qt.DeepEquals, test.expect, errmsg) } } func TestToStringMapIntE(t *testing.T) { + c := qt.New(t) + tests := []struct { input interface{} expect map[string]int @@ -919,24 +495,26 @@ func TestToStringMapIntE(t *testing.T) { } for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message + errmsg := qt.Commentf("i = %d", i) // assert helper message v, err := ToStringMapIntE(test.input) if test.iserr { - assert.Error(t, err, errmsg) + c.Assert(err, qt.IsNotNil, errmsg) continue } - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(err, qt.IsNil, errmsg) + c.Assert(v, qt.DeepEquals, test.expect, errmsg) // Non-E test v = ToStringMapInt(test.input) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(v, qt.DeepEquals, test.expect, errmsg) } } func TestToStringMapInt64E(t *testing.T) { + c := qt.New(t) + tests := []struct { input interface{} expect map[string]int64 @@ -958,24 +536,26 @@ func TestToStringMapInt64E(t *testing.T) { } for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message + errmsg := qt.Commentf("i = %d", i) // assert helper message v, err := ToStringMapInt64E(test.input) if test.iserr { - assert.Error(t, err, errmsg) + c.Assert(err, qt.IsNotNil) continue } - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(err, qt.IsNil) + c.Assert(v, qt.DeepEquals, test.expect, errmsg) // Non-E test v = ToStringMapInt64(test.input) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(v, qt.DeepEquals, test.expect, errmsg) } } func TestToStringMapStringE(t *testing.T) { + c := qt.New(t) + var stringMapString = map[string]string{"key 1": "value 1", "key 2": "value 2", "key 3": "value 3"} var stringMapInterface = map[string]interface{}{"key 1": "value 1", "key 2": "value 2", "key 3": "value 3"} var interfaceMapString = map[interface{}]string{"key 1": "value 1", "key 2": "value 2", "key 3": "value 3"} @@ -1003,24 +583,26 @@ func TestToStringMapStringE(t *testing.T) { } for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message + errmsg := qt.Commentf("i = %d", i) // assert helper message v, err := ToStringMapStringE(test.input) if test.iserr { - assert.Error(t, err, errmsg) + c.Assert(err, qt.IsNotNil) continue } - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(err, qt.IsNil) + c.Assert(v, qt.DeepEquals, test.expect, errmsg) // Non-E test v = ToStringMapString(test.input) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(v, qt.DeepEquals, test.expect, errmsg) } } func TestToBoolSliceE(t *testing.T) { + c := qt.New(t) + tests := []struct { input interface{} expect []bool @@ -1037,24 +619,26 @@ func TestToBoolSliceE(t *testing.T) { } for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message + errmsg := qt.Commentf("i = %d", i) // assert helper message v, err := ToBoolSliceE(test.input) if test.iserr { - assert.Error(t, err, errmsg) + c.Assert(err, qt.IsNotNil) continue } - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(err, qt.IsNil) + c.Assert(v, qt.DeepEquals, test.expect, errmsg) // Non-E test v = ToBoolSlice(test.input) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(v, qt.DeepEquals, test.expect, errmsg) } } func TestToIntSliceE(t *testing.T) { + c := qt.New(t) + tests := []struct { input interface{} expect []int @@ -1071,24 +655,26 @@ func TestToIntSliceE(t *testing.T) { } for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message + errmsg := qt.Commentf("i = %d", i) // assert helper message v, err := ToIntSliceE(test.input) if test.iserr { - assert.Error(t, err, errmsg) + c.Assert(err, qt.IsNotNil) continue } - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(err, qt.IsNil) + c.Assert(v, qt.DeepEquals, test.expect, errmsg) // Non-E test v = ToIntSlice(test.input) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(v, qt.DeepEquals, test.expect, errmsg) } } func TestToSliceE(t *testing.T) { + c := qt.New(t) + tests := []struct { input interface{} expect []interface{} @@ -1102,24 +688,26 @@ func TestToSliceE(t *testing.T) { } for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message + errmsg := qt.Commentf("i = %d", i) // assert helper message v, err := ToSliceE(test.input) if test.iserr { - assert.Error(t, err, errmsg) + c.Assert(err, qt.IsNotNil) continue } - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(err, qt.IsNil) + c.Assert(v, qt.DeepEquals, test.expect, errmsg) // Non-E test v = ToSlice(test.input) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(v, qt.DeepEquals, test.expect, errmsg) } } func TestToStringSliceE(t *testing.T) { + c := qt.New(t) + tests := []struct { input interface{} expect []string @@ -1141,24 +729,26 @@ func TestToStringSliceE(t *testing.T) { } for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message + errmsg := qt.Commentf("i = %d", i) // assert helper message v, err := ToStringSliceE(test.input) if test.iserr { - assert.Error(t, err, errmsg) + c.Assert(err, qt.IsNotNil) continue } - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(err, qt.IsNil) + c.Assert(v, qt.DeepEquals, test.expect, errmsg) // Non-E test v = ToStringSlice(test.input) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(v, qt.DeepEquals, test.expect, errmsg) } } func TestToDurationSliceE(t *testing.T) { + c := qt.New(t) + tests := []struct { input interface{} expect []time.Duration @@ -1176,24 +766,26 @@ func TestToDurationSliceE(t *testing.T) { } for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message + errmsg := qt.Commentf("i = %d", i) // assert helper message v, err := ToDurationSliceE(test.input) if test.iserr { - assert.Error(t, err, errmsg) + c.Assert(err, qt.IsNotNil) continue } - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(err, qt.IsNil) + c.Assert(v, qt.DeepEquals, test.expect, errmsg) // Non-E test v = ToDurationSlice(test.input) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(v, qt.DeepEquals, test.expect, errmsg) } } func TestToBoolE(t *testing.T) { + c := qt.New(t) + var jf, jt, je json.Number _ = json.Unmarshal([]byte("0"), &jf) _ = json.Unmarshal([]byte("1"), &jt) @@ -1220,30 +812,30 @@ func TestToBoolE(t *testing.T) { {"T", true, false}, {1, true, false}, {jt, true, false}, + {je, true, false}, {true, true, false}, {-1, true, false}, // errors {"test", false, true}, - {je, false, true}, {testing.T{}, false, true}, } for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message + errmsg := qt.Commentf("i = %d", i) // assert helper message v, err := ToBoolE(test.input) if test.iserr { - assert.Error(t, err, errmsg) + c.Assert(err, qt.IsNotNil) continue } - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(err, qt.IsNil) + c.Assert(v, qt.Equals, test.expect, errmsg) // Non-E test v = ToBool(test.input) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(v, qt.Equals, test.expect, errmsg) } } @@ -1268,16 +860,30 @@ func BenchmarkTooInt(b *testing.B) { } } +func BenchmarkTrimZeroDecimal(b *testing.B) { + for i := 0; i < b.N; i++ { + trimZeroDecimal("") + trimZeroDecimal("123") + trimZeroDecimal("120") + trimZeroDecimal("120.00") + } +} + func TestIndirectPointers(t *testing.T) { + c := qt.New(t) + x := 13 y := &x z := &y - assert.Equal(t, ToInt(y), 13) - assert.Equal(t, ToInt(z), 13) + c.Assert(ToInt(y), qt.Equals, 13) + c.Assert(ToInt(z), qt.Equals, 13) + } func TestToTime(t *testing.T) { + c := qt.New(t) + var jntime, jnetime json.Number _ = json.Unmarshal([]byte("1234567890"), &jntime) _ = json.Unmarshal([]byte("123.4567890"), &jnetime) @@ -1328,24 +934,26 @@ func TestToTime(t *testing.T) { } for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message + errmsg := qt.Commentf("i = %d", i) // assert helper message v, err := ToTimeE(test.input) if test.iserr { - assert.Error(t, err, errmsg) + c.Assert(err, qt.IsNotNil) continue } - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v.UTC(), errmsg) + c.Assert(err, qt.IsNil) + c.Assert(v.UTC(), qt.Equals, test.expect, errmsg) // Non-E test v = ToTime(test.input) - assert.Equal(t, test.expect, v.UTC(), errmsg) + c.Assert(v.UTC(), qt.Equals, test.expect, errmsg) } } func TestToDurationE(t *testing.T) { + c := qt.New(t) + var td time.Duration = 5 var jn json.Number _ = json.Unmarshal([]byte("5"), &jn) @@ -1383,33 +991,34 @@ func TestToDurationE(t *testing.T) { } for i, test := range tests { - errmsg := fmt.Sprintf("i = %d", i) // assert helper message + errmsg := qt.Commentf("i = %d", i) // assert helper message v, err := ToDurationE(test.input) if test.iserr { - assert.Error(t, err, errmsg) + c.Assert(err, qt.IsNotNil) continue } - assert.NoError(t, err, errmsg) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(err, qt.IsNil) + c.Assert(v, qt.Equals, test.expect, errmsg) // Non-E test v = ToDuration(test.input) - assert.Equal(t, test.expect, v, errmsg) + c.Assert(v, qt.Equals, test.expect, errmsg) } } func TestToTimeWithTimezones(t *testing.T) { + c := qt.New(t) est, err := time.LoadLocation("EST") - require.NoError(t, err) + c.Assert(err, qt.IsNil) irn, err := time.LoadLocation("Iran") - require.NoError(t, err) + c.Assert(err, qt.IsNil) swd, err := time.LoadLocation("Europe/Stockholm") - require.NoError(t, err) + c.Assert(err, qt.IsNil) // Test same local time in different timezones utc2016 := time.Date(2016, time.January, 1, 0, 0, 0, 0, time.UTC) @@ -1431,9 +1040,9 @@ func TestToTimeWithTimezones(t *testing.T) { swd2016str := swd2016.Format(format.format) t.Run("without default location", func(t *testing.T) { - assert := require.New(t) + c := qt.New(t) converted, err := ToTimeE(est2016str) - assert.NoError(err) + c.Assert(err, qt.IsNil) if format.hasTimezone() { // Converting inputs with a timezone should preserve it assertTimeEqual(t, est2016, converted) @@ -1447,9 +1056,9 @@ func TestToTimeWithTimezones(t *testing.T) { }) t.Run("local timezone without a default location", func(t *testing.T) { - assert := require.New(t) + c := qt.New(t) converted, err := ToTimeE(swd2016str) - assert.NoError(err) + c.Assert(err, qt.IsNil) if format.hasTimezone() { // Converting inputs with a timezone should preserve it assertTimeEqual(t, swd2016, converted) @@ -1463,10 +1072,10 @@ func TestToTimeWithTimezones(t *testing.T) { }) t.Run("nil default location", func(t *testing.T) { - assert := require.New(t) + c := qt.New(t) converted, err := ToTimeInDefaultLocationE(est2016str, nil) - assert.NoError(err) + c.Assert(err, qt.IsNil) if format.hasTimezone() { // Converting inputs with a timezone should preserve it assertTimeEqual(t, est2016, converted) @@ -1481,10 +1090,10 @@ func TestToTimeWithTimezones(t *testing.T) { }) t.Run("default location not UTC", func(t *testing.T) { - assert := require.New(t) + c := qt.New(t) converted, err := ToTimeInDefaultLocationE(est2016str, irn) - assert.NoError(err) + c.Assert(err, qt.IsNil) if format.hasTimezone() { // Converting inputs with a timezone should preserve it assertTimeEqual(t, est2016, converted) @@ -1499,10 +1108,10 @@ func TestToTimeWithTimezones(t *testing.T) { }) t.Run("time in the local timezone default location not UTC", func(t *testing.T) { - assert := require.New(t) + c := qt.New(t) converted, err := ToTimeInDefaultLocationE(swd2016str, irn) - assert.NoError(err) + c.Assert(err, qt.IsNil) if format.hasTimezone() { // Converting inputs with a timezone should preserve it assertTimeEqual(t, swd2016, converted) @@ -1521,18 +1130,27 @@ func TestToTimeWithTimezones(t *testing.T) { } } +func TestTrimZeroDecimal(t *testing.T) { + c := qt.New(t) + + c.Assert(trimZeroDecimal("10.0"), qt.Equals, "10") + c.Assert(trimZeroDecimal("10.00"), qt.Equals, "10") + c.Assert(trimZeroDecimal("10.010"), qt.Equals, "10.010") + c.Assert(trimZeroDecimal("0.0000000000"), qt.Equals, "0") + c.Assert(trimZeroDecimal("0.00000000001"), qt.Equals, "0.00000000001") + +} + func assertTimeEqual(t *testing.T, expected, actual time.Time) { t.Helper() // Compare the dates using a numeric zone as there are cases where // time.Parse will assign a dummy location. - // TODO(bep) - //require.Equal(t, expected, actual) - require.Equal(t, expected.Format(time.RFC1123Z), actual.Format(time.RFC1123Z)) + qt.Assert(t, actual.Format(time.RFC1123Z), qt.Equals, expected.Format(time.RFC1123Z)) } func assertLocationEqual(t *testing.T, expected, actual *time.Location) { t.Helper() - require.True(t, locationEqual(expected, actual), fmt.Sprintf("Expected location '%s', got '%s'", expected, actual)) + qt.Assert(t, locationEqual(expected, actual), qt.IsTrue) } func locationEqual(a, b *time.Location) bool { diff --git a/caste.go b/caste.go index e0b8a43..514d759 100644 --- a/caste.go +++ b/caste.go @@ -35,7 +35,7 @@ func ToTimeInDefaultLocationE(i interface{}, location *time.Location) (tim time. case string: return StringToDateInDefaultLocation(v, location) case json.Number: - s, err1 := v.Int64() + s, err1 := ToInt64E(v) if err1 != nil { return time.Time{}, fmt.Errorf("unable to cast %#v of type %T to Time", i, i) } @@ -105,7 +105,7 @@ func ToBoolE(i interface{}) (bool, error) { case string: return strconv.ParseBool(i.(string)) case json.Number: - v, err := b.Int64() + v, err := ToInt64E(b) if err == nil { return v != 0, nil } @@ -119,13 +119,16 @@ func ToBoolE(i interface{}) (bool, error) { 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: @@ -161,6 +164,8 @@ func ToFloat64E(i interface{}) (float64, error) { 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) } @@ -170,13 +175,16 @@ func ToFloat64E(i interface{}) (float64, error) { 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: @@ -212,6 +220,8 @@ func ToFloat32E(i interface{}) (float32, error) { 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) } @@ -221,9 +231,12 @@ func ToFloat32E(i interface{}) (float32, error) { 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: @@ -247,17 +260,13 @@ func ToInt64E(i interface{}) (int64, error) { 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: - v, err := s.Int64() - if err == nil { - return v, nil - } - return 0, fmt.Errorf("unable to cast %#v of type %T to int64", i, i) + return ToInt64E(string(s)) case bool: if s { return 1, nil @@ -274,9 +283,12 @@ func ToInt64E(i interface{}) (int64, error) { 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: @@ -300,17 +312,13 @@ func ToInt32E(i interface{}) (int32, error) { 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: - v, err := s.Int64() - if err == nil { - return int32(v), nil - } - return 0, fmt.Errorf("unable to cast %#v of type %T to int32", i, i) + return ToInt32E(string(s)) case bool: if s { return 1, nil @@ -327,9 +335,12 @@ func ToInt32E(i interface{}) (int32, error) { 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: @@ -353,17 +364,13 @@ func ToInt16E(i interface{}) (int16, error) { 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: - v, err := s.Int64() - if err == nil { - return int16(v), nil - } - return 0, fmt.Errorf("unable to cast %#v of type %T to int16", i, i) + return ToInt16E(string(s)) case bool: if s { return 1, nil @@ -380,9 +387,12 @@ func ToInt16E(i interface{}) (int16, error) { 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: @@ -406,17 +416,13 @@ func ToInt8E(i interface{}) (int8, error) { 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: - v, err := s.Int64() - if err == nil { - return int8(v), nil - } - return 0, fmt.Errorf("unable to cast %#v of type %T to int8", i, i) + return ToInt8E(string(s)) case bool: if s { return 1, nil @@ -433,9 +439,12 @@ func ToInt8E(i interface{}) (int8, error) { 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: @@ -459,17 +468,13 @@ func ToIntE(i interface{}) (int, error) { 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: - v, err := s.Int64() - if err == nil { - return int(v), nil - } - return 0, fmt.Errorf("unable to cast %#v of type %T to int", i, i) + return ToIntE(string(s)) case bool: if s { return 1, nil @@ -486,27 +491,26 @@ func ToIntE(i interface{}) (int, error) { 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) - if err == nil { - return uint(v), nil - } - return 0, fmt.Errorf("unable to cast %#v to uint: %s", i, err) - case json.Number: - v, err := s.Int64() + 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 @@ -563,27 +567,26 @@ func ToUintE(i interface{}) (uint, error) { 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) - if err == nil { - return v, nil - } - return 0, fmt.Errorf("unable to cast %#v to uint64: %s", i, err) - case json.Number: - v, err := s.Int64() + v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0) if err == 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 @@ -640,27 +643,26 @@ func ToUint64E(i interface{}) (uint64, error) { 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) - if err == nil { - return uint32(v), nil - } - return 0, fmt.Errorf("unable to cast %#v to uint32: %s", i, err) - case json.Number: - v, err := s.Int64() + 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 @@ -717,27 +719,26 @@ func ToUint32E(i interface{}) (uint32, error) { 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) - if err == nil { - return uint16(v), nil - } - return 0, fmt.Errorf("unable to cast %#v to uint16: %s", i, err) - case json.Number: - v, err := s.Int64() + 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 @@ -794,27 +795,26 @@ func ToUint16E(i interface{}) (uint16, error) { 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) - if err == nil { - return uint8(v), nil - } - return 0, fmt.Errorf("unable to cast %#v to uint8: %s", i, err) - case json.Number: - v, err := s.Int64() + 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 @@ -1385,30 +1385,30 @@ func (f timeFormat) hasTimezone() bool { var ( timeFormats = []timeFormat{ - timeFormat{time.RFC3339, timeFormatNumericTimezone}, - timeFormat{"2006-01-02T15:04:05", timeFormatNoTimezone}, // iso8601 without timezone - timeFormat{time.RFC1123Z, timeFormatNumericTimezone}, - timeFormat{time.RFC1123, timeFormatNamedTimezone}, - timeFormat{time.RFC822Z, timeFormatNumericTimezone}, - timeFormat{time.RFC822, timeFormatNamedTimezone}, - timeFormat{time.RFC850, timeFormatNamedTimezone}, - timeFormat{"2006-01-02 15:04:05.999999999 -0700 MST", timeFormatNumericAndNamedTimezone}, // Time.String() - timeFormat{"2006-01-02T15:04:05-0700", timeFormatNumericTimezone}, // RFC3339 without timezone hh:mm colon - timeFormat{"2006-01-02 15:04:05Z0700", timeFormatNumericTimezone}, // RFC3339 without T or timezone hh:mm colon - timeFormat{"2006-01-02 15:04:05", timeFormatNoTimezone}, - timeFormat{time.ANSIC, timeFormatNoTimezone}, - timeFormat{time.UnixDate, timeFormatNamedTimezone}, - timeFormat{time.RubyDate, timeFormatNumericTimezone}, - timeFormat{"2006-01-02 15:04:05Z07:00", timeFormatNumericTimezone}, - timeFormat{"2006-01-02", timeFormatNoTimezone}, - timeFormat{"02 Jan 2006", timeFormatNoTimezone}, - timeFormat{"2006-01-02 15:04:05 -07:00", timeFormatNumericTimezone}, - timeFormat{"2006-01-02 15:04:05 -0700", timeFormatNumericTimezone}, - timeFormat{time.Kitchen, timeFormatTimeOnly}, - timeFormat{time.Stamp, timeFormatTimeOnly}, - timeFormat{time.StampMilli, timeFormatTimeOnly}, - timeFormat{time.StampMicro, timeFormatTimeOnly}, - timeFormat{time.StampNano, timeFormatTimeOnly}, + {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}, } ) @@ -1441,3 +1441,36 @@ func jsonStringToObject(s string, v interface{}) error { 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 +} diff --git a/go.mod b/go.mod index c1c0232..255e99f 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,13 @@ module github.com/spf13/cast +go 1.18 + +require github.com/frankban/quicktest v1.14.3 + require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/testify v1.2.2 + github.com/google/go-cmp v0.5.7 // indirect + github.com/kr/pretty v0.3.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/rogpeppe/go-internal v1.6.1 // indirect + golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect ) diff --git a/go.sum b/go.sum index e03ee77..1dbb1c6 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,18 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=