diff --git a/README.md b/README.md index 9140125..bd3751e 100644 --- a/README.md +++ b/README.md @@ -63,42 +63,116 @@ func main() { var tb generic.Bool tb.Set(v) - vb := tb.Value() + vb := tb.Weak() fmt.Printf("%v, (%T)\n", vb, vb) // true, (bool) var tf generic.Float tf.Set(v) - vf := tf.Value() + vf := tf.Weak() fmt.Printf("%v, (%T)\n", vf, vf) // 1, (float64) var ti generic.Int ti.Set(v) - vi := ti.Value() + vi := ti.Weak() fmt.Printf("%v, (%T)\n", vi, vi) // 1, (int64) var ts generic.String ts.Set(v) - vs := ts.Value() + vs := ts.Weak() fmt.Printf("%v, (%T)\n", vs, vs) // 1, (string) var tt generic.Time tt.Set(v) - vt := tt.Value() + vt := tt.Weak() fmt.Printf("%v, (%T)\n", vt.UTC(), vt) // 1970-01-01 09:00:01 +0900 JST, (time.Time) var tu generic.Uint tu.Set(v) - vu := tu.Value() + vu := tu.Weak() fmt.Printf("%v, (%T)\n", vu, vu) // 1, (uint64) } ``` +## Benchmarks + +### Marshal + +#### Bool + +| version | requests | /op | B/op | allocs/op | +|---|---|---|---|---| +| 1.0.0 | 5000000 | 240 ns | 185 | 3 | +| 2.0.0 | 200000000 | 6.69 ns | 0 | 0 | + +#### Float + +| version | requests | /op | B/op | allocs/op | +|---|---|---|---|---| +| 1.0.0 | 3000000 | 425 ns | 192 | 3 | +| 2.0.0 | 5000000 | 260 ns | 64 | 3 | + +#### Int + +| version | requests | /op | B/op | allocs/op | +|---|---|---|---|---| +| 1.0.0 | 5000000 | 265 ns | 192 | 3 | +| 2.0.0 | 20000000 | 70.5 ns | 16 | 2 | + +#### String (small) + +| version | requests | /op | B/op | allocs/op | +|---|---|---|---|---| +| 1.0.0 | 3000000 | 382 ns | 200 | 3 | +| 2.0.0 | 20000000 | 89.0 ns | 128 | 2 | + +#### String (Large) + +| version | requests | /op | B/op | allocs/op | +|---|---|---|---|---| +| 1.0.0 | 1000000 | 1056 ns | 776 | 4 | +| 2.0.0 | 5000000 | 237 ns | 896 | 2 | + +#### Time + +| version | requests | /op | B/op | allocs/op | +|---|---|---|---|---| +| 1.0.0 | 1000000 | 1122 ns | 360 | 5 | +| 2.0.0 | 3000000 | 401 ns | 48 | 1 | + +#### TimestampMS + +| version | requests | /op | B/op | allocs/op | +|---|---|---|---|---| +| 1.0.0 | 20000000 | 97.9 ns | 32 | 2 | +| 2.0.0 | 20000000 | 91.2 ns | 32 | 2 | + +#### TimestampNano + +| version | requests | /op | B/op | allocs/op | +|---|---|---|---|---| +| 1.0.0 | 10000000 | 114 ns | 64 | 2 | +| 2.0.0 | 10000000 | 112 ns | 64 | 2 | + +#### Timestamp + +| version | requests | /op | B/op | allocs/op | +|---|---|---|---|---| +| 1.0.0 | 20000000 | 88.4 ns | 32 | 2 | +| 2.0.0 | 20000000 | 86.7 ns | 32 | 2 | + +#### Uint + +| version | requests | /op | B/op | allocs/op | +|---|---|---|---|---| +| 1.0.0 | 5000000 | 277 ns | 192 | 3 | +| 2.0.0 | 20000000 | 64.2 ns | 16 | 2 | + ## Licence [MIT](https://github.com/usk81/generic/blob/master/LICENSE) diff --git a/convert.go b/convert.go index 34479b7..fa07850 100644 --- a/convert.go +++ b/convert.go @@ -128,7 +128,7 @@ func asInt(x interface{}) (result int64, isValid ValidFlag, err error) { result = int64(x.(float64)) case bool: b := x.(bool) - if b == true { + if b { result = 1 } else { result = 0 @@ -193,7 +193,7 @@ func asTime(x interface{}) (result time.Time, isValid ValidFlag, err error) { case time.Time: result = x.(time.Time) if result.IsZero() { - return result, false, nil + return result, true, nil } default: return result, false, ErrInvalidGenericValue{Value: x} @@ -203,137 +203,23 @@ func asTime(x interface{}) (result time.Time, isValid ValidFlag, err error) { // asTimestamp converts a specified value to time.Time value. func asTimestamp(x interface{}) (result time.Time, isValid ValidFlag, err error) { - var i int64 - switch x.(type) { - case nil: - return result, false, nil - case time.Time: - result = x.(time.Time) - if result.IsZero() { - return result, false, nil - } - return result, true, nil - case int: - i = int64(x.(int)) - case int8: - i = int64(x.(int8)) - case int16: - i = int64(x.(int16)) - case int32: - i = int64(x.(int32)) - case int64: - i = x.(int64) - case uint: - i = int64(x.(uint)) - case uint8: - i = int64(x.(uint8)) - case uint16: - i = int64(x.(uint16)) - case uint32: - i = int64(x.(uint32)) - case uint64: - i = int64(x.(uint64)) - case float32: - i = int64(x.(float32)) - case float64: - i = int64(x.(float64)) - default: - return result, false, ErrInvalidGenericValue{Value: x} - } - if i < 0 { - return result, false, ErrInvalidGenericValue{Value: x} - } - return time.Unix(i, 0), true, nil + return asTimestampWithFunc(x, func(i int64) time.Time { + return time.Unix(i, 0) + }) } // asTimestampNanoseconds converts a specified value to time.Time value. func asTimestampNanoseconds(x interface{}) (result time.Time, isValid ValidFlag, err error) { - var i int64 - switch x.(type) { - case nil: - return result, false, nil - case time.Time: - result = x.(time.Time) - if result.IsZero() { - return result, false, nil - } - return result, true, nil - case int: - i = int64(x.(int)) - case int8: - i = int64(x.(int8)) - case int16: - i = int64(x.(int16)) - case int32: - i = int64(x.(int32)) - case int64: - i = x.(int64) - case uint: - i = int64(x.(uint)) - case uint8: - i = int64(x.(uint8)) - case uint16: - i = int64(x.(uint16)) - case uint32: - i = int64(x.(uint32)) - case uint64: - i = int64(x.(uint64)) - case float32: - i = int64(x.(float32)) - case float64: - i = int64(x.(float64)) - default: - return result, false, ErrInvalidGenericValue{Value: x} - } - if i < 0 { - return result, false, ErrInvalidGenericValue{Value: x} - } - return time.Unix(0, i), true, nil + return asTimestampWithFunc(x, func(i int64) time.Time { + return time.Unix(0, i) + }) } // asTimestampMilliseconds converts a specified value to time.Time value. func asTimestampMilliseconds(x interface{}) (result time.Time, isValid ValidFlag, err error) { - var i int64 - switch x.(type) { - case nil: - return result, false, nil - case time.Time: - result = x.(time.Time) - if result.IsZero() { - return result, false, nil - } - return result, true, nil - case int: - i = int64(x.(int)) - case int8: - i = int64(x.(int8)) - case int16: - i = int64(x.(int16)) - case int32: - i = int64(x.(int32)) - case int64: - i = x.(int64) - case uint: - i = int64(x.(uint)) - case uint8: - i = int64(x.(uint8)) - case uint16: - i = int64(x.(uint16)) - case uint32: - i = int64(x.(uint32)) - case uint64: - i = int64(x.(uint64)) - case float32: - i = int64(x.(float32)) - case float64: - i = int64(x.(float64)) - default: - return result, false, ErrInvalidGenericValue{Value: x} - } - if i < 0 { - return result, false, ErrInvalidGenericValue{Value: x} - } - return time.Unix(0, i*1000000), true, nil + return asTimestampWithFunc(x, func(i int64) time.Time { + return time.Unix(0, i*1000000) + }) } // asBool converts a specified value to uint64 value. @@ -410,3 +296,50 @@ func asUint(x interface{}) (result uint64, isValid ValidFlag, err error) { } return result, true, nil } + +func asTimestampWithFunc(x interface{}, f func(i int64) time.Time) (result time.Time, isValid ValidFlag, err error) { + var i int64 + switch x.(type) { + case nil: + return result, false, nil + case time.Time: + result = x.(time.Time) + if result.IsZero() { + return result, true, nil + } + return result, true, nil + case string: + result, err = time.Parse(time.RFC3339Nano, x.(string)) + return result, err == nil, err + case int: + i = int64(x.(int)) + case int8: + i = int64(x.(int8)) + case int16: + i = int64(x.(int16)) + case int32: + i = int64(x.(int32)) + case int64: + i = x.(int64) + case uint: + i = int64(x.(uint)) + case uint8: + i = int64(x.(uint8)) + case uint16: + i = int64(x.(uint16)) + case uint32: + i = int64(x.(uint32)) + case uint64: + i = int64(x.(uint64)) + case float32: + i = int64(x.(float32)) + case float64: + i = int64(x.(float64)) + default: + return result, false, ErrInvalidGenericValue{Value: x} + } + if i < 0 { + return result, false, ErrInvalidGenericValue{Value: x} + } + return f(i), true, nil +} diff --git a/convert_asBool_test.go b/convert_asBool_test.go new file mode 100644 index 0000000..ba1bcb8 --- /dev/null +++ b/convert_asBool_test.go @@ -0,0 +1,116 @@ +package generic + +import "testing" + +func TestAsBoolInt(t *testing.T) { + i := 100 + asBoolTest(i, t) +} + +func TestAsBoolInt8(t *testing.T) { + var i int8 = 100 + asBoolTest(i, t) +} + +func TestAsBoolInt16(t *testing.T) { + var i int16 = 100 + asBoolTest(i, t) +} + +func TestAsBoolInt32(t *testing.T) { + var i int32 = 100 + asBoolTest(i, t) +} + +func TestAsBoolInt64(t *testing.T) { + var i int64 = 100 + asBoolTest(i, t) +} + +func TestAsBoolUint(t *testing.T) { + var u uint = 100 + asBoolTest(u, t) +} + +func TestAsBoolUint8(t *testing.T) { + var u uint8 = 100 + asBoolTest(u, t) +} + +func TestAsBoolUint16(t *testing.T) { + var u uint16 = 100 + asBoolTest(u, t) +} + +func TestAsBoolUint32(t *testing.T) { + var u uint32 = 100 + asBoolTest(u, t) +} + +func TestAsBoolUint64(t *testing.T) { + var u uint64 = 100 + asBoolTest(u, t) +} + +func TestAsBoolFloat32(t *testing.T) { + var f float32 = 1.0001 + asBoolTest(f, t) +} + +func TestAsBoolFloat64(t *testing.T) { + f := 1.0001 + asBoolTest(f, t) +} + +func TestAsBoolTrue(t *testing.T) { + b := true + asBoolTest(b, t) +} + +func TestAsBoolFalse(t *testing.T) { + b := false + r, v, err := asBool(b) + if err != nil { + t.Errorf("Not Expected error. error:%s", err.Error()) + } + if !v { + t.Error("expected: true, actual: false") + } + if r { + t.Error("expected: false, actual: true") + } +} + +func TestAsBoolNumericString(t *testing.T) { + s := "1" + asBoolTest(s, t) +} + +func TestAsBoolUnumericString(t *testing.T) { + s := "abd" + _, _, err := asBool(s) + if err == nil { + t.Error("Expected error") + } +} + +func TestAsBoolInvalidType(t *testing.T) { + bs := []byte("true") + _, _, err := asBool(bs) + if err == nil { + t.Error("Expected error") + } +} + +func asBoolTest(x interface{}, t *testing.T) { + r, v, err := asBool(x) + if err != nil { + t.Errorf("Not Expected error. error:%s", err.Error()) + } + if !v { + t.Error("expected: true, actual: false") + } + if !r { + t.Error("expected: true, actual: false") + } +} diff --git a/convert_asFloat_test.go b/convert_asFloat_test.go new file mode 100644 index 0000000..9ff8a8b --- /dev/null +++ b/convert_asFloat_test.go @@ -0,0 +1,143 @@ +package generic + +import "testing" + +func TestAsFloatInt(t *testing.T) { + i := int(100) + asFloatTest(i, t) +} + +func TestAsFloatInt8(t *testing.T) { + i := int8(100) + asFloatTest(i, t) +} + +func TestAsFloatInt16(t *testing.T) { + i := int16(100) + asFloatTest(i, t) +} + +func TestAsFloatInt32(t *testing.T) { + i := int32(100) + asFloatTest(i, t) +} + +func TestAsFloatInt64(t *testing.T) { + i := int64(100) + asFloatTest(i, t) +} + +func TestAsFloatUint(t *testing.T) { + u := uint(100) + asFloatTest(u, t) +} + +func TestAsFloatUint8(t *testing.T) { + u := uint8(100) + asFloatTest(u, t) +} + +func TestAsFloatUint16(t *testing.T) { + u := uint16(100) + asFloatTest(u, t) +} + +func TestAsFloatUint32(t *testing.T) { + u := uint32(100) + asFloatTest(u, t) +} + +func TestAsFloatUint64(t *testing.T) { + u := uint64(100) + asFloatTest(u, t) +} + +func TestAsFloatFloat32(t *testing.T) { + f := float32(100.0001) + r, v, err := asFloat(f) + if err != nil { + t.Errorf("Not Expected error. error:%s", err.Error()) + } + if !v { + t.Error("expected: true, actual: false") + } + if float32(r) != 100.0001 { + t.Errorf("expected: 100.0001, actual: %v", r) + } +} + +func TestAsFloatFloat64(t *testing.T) { + f := float64(100.0001) + r, v, err := asFloat(f) + if err != nil { + t.Errorf("Not Expected error. error:%s", err.Error()) + } + if !v { + t.Error("expected: true, actual: false") + } + if r != 100.0001 { + t.Errorf("expected: 100, actual: %v", r) + } +} + +func TestAsFloatTrue(t *testing.T) { + b := true + r, v, err := asFloat(b) + if err != nil { + t.Errorf("Not Expected error. error:%s", err.Error()) + } + if !v { + t.Error("expected: true, actual: false") + } + if r != 1 { + t.Errorf("expected: 1, actual: %v", r) + } +} + +func TestAsFloatFalse(t *testing.T) { + b := false + r, v, err := asFloat(b) + if err != nil { + t.Errorf("Not Expected error. error:%s", err.Error()) + } + if !v { + t.Error("expected: true, actual: false") + } + if r != 0 { + t.Errorf("expected: 0, actual: %v", r) + } +} + +func TestAsFloatNumericString(t *testing.T) { + s := "100" + asFloatTest(s, t) +} + +func TestAsFloatUnumericString(t *testing.T) { + s := "abd" + _, _, err := asFloat(s) + if err == nil { + t.Error("Expected error") + } +} + +func TestAsFloatInvalidType(t *testing.T) { + bs := []byte("1") + _, _, err := asFloat(bs) + if err == nil { + t.Error("Expected error") + } +} + +func asFloatTest(x interface{}, t *testing.T) { + r, v, err := asFloat(x) + if err != nil { + t.Errorf("Not Expected error. error:%s", err.Error()) + } + if !v { + t.Error("expected: true, actual: false") + } + if r != 100 { + t.Errorf("expected: 100, actual: %v", r) + } +} diff --git a/convert_asInt_test.go b/convert_asInt_test.go new file mode 100644 index 0000000..661529c --- /dev/null +++ b/convert_asInt_test.go @@ -0,0 +1,125 @@ +package generic + +import "testing" + +func TestAsIntInt(t *testing.T) { + i := int(100) + asIntTest(i, t) +} + +func TestAsIntInt8(t *testing.T) { + i := int8(100) + asIntTest(i, t) +} + +func TestAsIntInt16(t *testing.T) { + i := int16(100) + asIntTest(i, t) +} + +func TestAsIntInt32(t *testing.T) { + i := int32(100) + asIntTest(i, t) +} + +func TestAsIntInt64(t *testing.T) { + i := int64(100) + asIntTest(i, t) +} + +func TestAsIntUint(t *testing.T) { + u := uint(100) + asIntTest(u, t) +} + +func TestAsIntUint8(t *testing.T) { + u := uint8(100) + asIntTest(u, t) +} + +func TestAsIntUint16(t *testing.T) { + u := uint16(100) + asIntTest(u, t) +} + +func TestAsIntUint32(t *testing.T) { + u := uint32(100) + asIntTest(u, t) +} + +func TestAsIntUint64(t *testing.T) { + u := uint64(100) + asIntTest(u, t) +} + +func TestAsIntFloat32(t *testing.T) { + f := float32(100.0001) + asIntTest(f, t) +} + +func TestAsIntFloat64(t *testing.T) { + f := float64(100.0001) + asIntTest(f, t) +} + +func TestAsIntTrue(t *testing.T) { + b := true + r, v, err := asInt(b) + if err != nil { + t.Errorf("Not Expected error. error:%s", err.Error()) + } + if !v { + t.Error("expected: true, actual: false") + } + if r != 1 { + t.Errorf("expected: 1, actual: %d", r) + } +} + +func TestAsIntFalse(t *testing.T) { + b := false + r, v, err := asInt(b) + if err != nil { + t.Errorf("Not Expected error. error:%s", err.Error()) + } + if !v { + t.Error("expected: true, actual: false") + } + if r != 0 { + t.Errorf("expected: 0, actual: %d", r) + } +} + +func TestAsIntNumericString(t *testing.T) { + s := "100" + asIntTest(s, t) +} + +func TestAsIntUnumericString(t *testing.T) { + s := "abd" + _, _, err := asInt(s) + if err == nil { + t.Error("Expected error") + } +} + +func TestAsIntInvalidType(t *testing.T) { + bs := []byte("1") + _, _, err := asInt(bs) + if err == nil { + t.Error("Expected error") + } +} + +func asIntTest(x interface{}, t *testing.T) { + r, v, err := asInt(x) + if err != nil { + t.Errorf("Not Expected error. error:%s", err.Error()) + } + if !v { + t.Error("expected: true, actual: false") + } + if r != 100 { + t.Errorf("expected: 100, actual: %d", r) + } +} diff --git a/convert_asTime_test.go b/convert_asTime_test.go new file mode 100644 index 0000000..0f4c336 --- /dev/null +++ b/convert_asTime_test.go @@ -0,0 +1,34 @@ +package generic + +import ( + "testing" + "time" +) + +func TestAsTimeTime(t *testing.T) { + x := time.Date(2020, time.Month(7), 24, 20, 0, 0, 0, time.FixedZone("Asia/Tokyo", 9*60*60)) + r, v, err := asTime(x) + if err != nil { + t.Errorf("Not Expected error. error:%s", err.Error()) + } + if !v { + t.Error("expected: true, actual: false") + } + if s := r.String(); s != "2020-07-24 20:00:00 +0900 Asia/Tokyo" { + t.Errorf("expected: 2020-07-24 20:00:00 +0900 Asia/Tokyo, actual: %s", s) + } +} + +func TestAsTimeZero(t *testing.T) { + x := time.Time{} + r, v, err := asTime(x) + if err != nil { + t.Errorf("Not Expected error. error:%s", err.Error()) + } + if !v { + t.Error("expected: true, actual: false") + } + if !r.IsZero() { + t.Errorf("expected: time.IsZero is true, actual: %s", r.String()) + } +} diff --git a/convert_asUint_test.go b/convert_asUint_test.go new file mode 100644 index 0000000..99e17ec --- /dev/null +++ b/convert_asUint_test.go @@ -0,0 +1,181 @@ +package generic + +import "testing" + +func TestAsUintInt(t *testing.T) { + i := int(100) + asUintTest(i, t) +} + +func TestAsUintIntMinus(t *testing.T) { + x := int(-100) + _, _, err := asUint(x) + if err == nil { + t.Error("Expected error") + } +} + +func TestAsUintInt8(t *testing.T) { + i := int8(100) + asUintTest(i, t) +} + +func TestAsUintInt8Minus(t *testing.T) { + x := int8(-100) + _, _, err := asUint(x) + if err == nil { + t.Error("Expected error") + } +} + +func TestAsUintInt16(t *testing.T) { + i := int16(100) + asUintTest(i, t) +} + +func TestAsUintInt16Minus(t *testing.T) { + x := int16(-100) + _, _, err := asUint(x) + if err == nil { + t.Error("Expected error") + } +} + +func TestAsUintInt32(t *testing.T) { + i := int32(100) + asUintTest(i, t) +} + +func TestAsUintInt32Minus(t *testing.T) { + x := int32(-100) + _, _, err := asUint(x) + if err == nil { + t.Error("Expected error") + } +} + +func TestAsUintInt64(t *testing.T) { + i := int64(100) + asUintTest(i, t) +} + +func TestAsUintInt64Minus(t *testing.T) { + x := int64(-100) + _, _, err := asUint(x) + if err == nil { + t.Error("Expected error") + } +} + +func TestAsUintUint(t *testing.T) { + u := uint(100) + asUintTest(u, t) +} + +func TestAsUintUint8(t *testing.T) { + u := uint8(100) + asUintTest(u, t) +} + +func TestAsUintUint16(t *testing.T) { + u := uint16(100) + asUintTest(u, t) +} + +func TestAsUintUint32(t *testing.T) { + u := uint32(100) + asUintTest(u, t) +} + +func TestAsUintUint64(t *testing.T) { + u := uint64(100) + asUintTest(u, t) +} + +func TestAsUintFloat32(t *testing.T) { + f := float32(100.001) + asUintTest(f, t) +} + +func TestAsUintFloat32Minus(t *testing.T) { + x := float32(-100.001) + _, _, err := asUint(x) + if err == nil { + t.Error("Expected error") + } +} + +func TestAsUintFloat64(t *testing.T) { + f := float64(100.001) + asUintTest(f, t) +} + +func TestAsUintFloat64Minus(t *testing.T) { + x := float64(-100.001) + _, _, err := asUint(x) + if err == nil { + t.Error("Expected error") + } +} + +func TestAsUintTrue(t *testing.T) { + b := true + r, v, err := asUint(b) + if err != nil { + t.Errorf("Not Expected error. error:%s", err.Error()) + } + if !v { + t.Error("expected: true, actual: false") + } + if r != 1 { + t.Errorf("expected: 1, actual: %d", r) + } +} + +func TestAsUintFalse(t *testing.T) { + b := false + r, v, err := asUint(b) + if err != nil { + t.Errorf("Not Expected error. error:%s", err.Error()) + } + if !v { + t.Error("expected: true, actual: false") + } + if r != 0 { + t.Errorf("expected: 0, actual: %d", r) + } +} + +func TestAsUintNumericString(t *testing.T) { + s := "100" + asUintTest(s, t) +} + +func TestAsUintUnumericString(t *testing.T) { + s := "abd.01" + _, _, err := asUint(s) + if err == nil { + t.Error("Expected error") + } +} + +func TestAsUintInvalidType(t *testing.T) { + bs := []byte("100") + _, _, err := asUint(bs) + if err == nil { + t.Error("Expected error") + } +} + +func asUintTest(x interface{}, t *testing.T) { + r, v, err := asUint(x) + if err != nil { + t.Errorf("Not Expected error. error:%s", err.Error()) + } + if !v { + t.Error("expected: true, actual: false") + } + if r != 100 { + t.Errorf("expected: 100, actual: %v", r) + } +} diff --git a/generic.go b/generic.go index 13e8e36..e1a0d98 100644 --- a/generic.go +++ b/generic.go @@ -2,13 +2,14 @@ package generic import ( "bytes" + "database/sql/driver" "reflect" ) -// GenericType is the interface used as the basis for generic types -type GenericType interface { +// Type is the interface used as the basis for generic types +type Type interface { Valid() bool - Value() interface{} + Value() (driver.Value, error) Scan(interface{}) error Set(interface{}) error Reset() @@ -19,20 +20,22 @@ type ErrInvalidGenericValue struct { Value interface{} } -// ValidFlag +// ValidFlag is the flag to check that value is valid type ValidFlag bool +var nullBytes = []byte("null") + // Reset resets ValidFlag func (v *ValidFlag) Reset() { *v = false } -// Valid validates the specified value is nil or not. +// Valid validates the specified value is nil or not func (v ValidFlag) Valid() bool { return bool(v) } -// Error +// Error returns error message func (e ErrInvalidGenericValue) Error() string { buf := bytes.Buffer{} buf.WriteString("invalid value: ") diff --git a/generic_test.go b/generic_test.go index 9b2ffcf..d00c9f4 100644 --- a/generic_test.go +++ b/generic_test.go @@ -18,3 +18,11 @@ func TestErrInvalidGenericValueString(t *testing.T) { t.Errorf("actual:%s, expected:%s", err.Error(), expected) } } + +func TestValidFlagReset(t *testing.T) { + var v ValidFlag = true + v.Reset() + if v { + t.Error("actual:true, expected:false") + } +} diff --git a/marshal_benchmark_test.go b/json_marshal_benchmark_test.go similarity index 85% rename from marshal_benchmark_test.go rename to json_marshal_benchmark_test.go index 73dae68..1be9529 100644 --- a/marshal_benchmark_test.go +++ b/json_marshal_benchmark_test.go @@ -8,7 +8,7 @@ import ( func BenchmarkMarshalJSONBool(b *testing.B) { x := Bool{ ValidFlag: true, - Bool: true, + bool: true, } for i := 0; i < b.N; i++ { x.MarshalJSON() @@ -18,7 +18,7 @@ func BenchmarkMarshalJSONBool(b *testing.B) { func BenchmarkMarshalJSONFloat(b *testing.B) { x := Float{ ValidFlag: true, - Float: 1000.000001, + float: 1000.000001, } for i := 0; i < b.N; i++ { x.MarshalJSON() @@ -28,7 +28,7 @@ func BenchmarkMarshalJSONFloat(b *testing.B) { func BenchmarkMarshalJSONInt(b *testing.B) { x := Int{ ValidFlag: true, - Int: 10000, + int: 10000, } for i := 0; i < b.N; i++ { x.MarshalJSON() @@ -38,7 +38,7 @@ func BenchmarkMarshalJSONInt(b *testing.B) { func BenchmarkMarshalJSONString(b *testing.B) { x := String{ ValidFlag: true, - String: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", + string: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", } for i := 0; i < b.N; i++ { x.MarshalJSON() @@ -48,7 +48,7 @@ func BenchmarkMarshalJSONString(b *testing.B) { func BenchmarkMarshalJSONStringLarge(b *testing.B) { x := String{ ValidFlag: true, - String: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", + string: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", } for i := 0; i < b.N; i++ { x.MarshalJSON() @@ -58,7 +58,7 @@ func BenchmarkMarshalJSONStringLarge(b *testing.B) { func BenchmarkMarshalJSONTime(b *testing.B) { x := Time{ ValidFlag: true, - Time: time.Now(), + time: time.Now(), } for i := 0; i < b.N; i++ { x.MarshalJSON() @@ -68,7 +68,7 @@ func BenchmarkMarshalJSONTime(b *testing.B) { func BenchmarkMarshalJSONTimestampMS(b *testing.B) { x := TimestampMS{ ValidFlag: true, - Time: time.Now(), + time: time.Now(), } for i := 0; i < b.N; i++ { x.MarshalJSON() @@ -78,7 +78,7 @@ func BenchmarkMarshalJSONTimestampMS(b *testing.B) { func BenchmarkMarshalJSONTimestampNano(b *testing.B) { x := TimestampNano{ ValidFlag: true, - Time: time.Now(), + time: time.Now(), } for i := 0; i < b.N; i++ { x.MarshalJSON() @@ -88,7 +88,7 @@ func BenchmarkMarshalJSONTimestampNano(b *testing.B) { func BenchmarkMarshalJSONTimestamp(b *testing.B) { x := Timestamp{ ValidFlag: true, - Time: time.Now(), + time: time.Now(), } for i := 0; i < b.N; i++ { x.MarshalJSON() @@ -98,7 +98,7 @@ func BenchmarkMarshalJSONTimestamp(b *testing.B) { func BenchmarkMarshalJSONUint(b *testing.B) { x := Uint{ ValidFlag: true, - Uint: 10000, + uint: 10000, } for i := 0; i < b.N; i++ { x.MarshalJSON() diff --git a/json_unmarshal_benchmark_test.go b/json_unmarshal_benchmark_test.go new file mode 100644 index 0000000..82e137a --- /dev/null +++ b/json_unmarshal_benchmark_test.go @@ -0,0 +1,106 @@ +package generic + +import "testing" + +func BenchmarkUnmarshalJSONBoolFromBool(b *testing.B) { + unmarshalJSONBoolBenchmark(b, []byte(`true`)) +} + +func BenchmarkUnmarshalJSONBoolFromFloat(b *testing.B) { + unmarshalJSONBoolBenchmark(b, []byte(`1.0`)) +} + +func BenchmarkUnmarshalJSONBoolFromInt(b *testing.B) { + unmarshalJSONBoolBenchmark(b, []byte(`1`)) +} + +func BenchmarkUnmarshalJSONBoolFromString(b *testing.B) { + unmarshalJSONBoolBenchmark(b, []byte(`"true"`)) +} + +func BenchmarkUnmarshalJSONIntFromBool(b *testing.B) { + unmarshalJSONIntBenchmark(b, []byte(`true`)) +} + +func BenchmarkUnmarshalJSONIntFromFloat(b *testing.B) { + unmarshalJSONIntBenchmark(b, []byte(`1.0`)) +} + +func BenchmarkUnmarshalJSONIntFromInt(b *testing.B) { + unmarshalJSONIntBenchmark(b, []byte(`1`)) +} + +func BenchmarkUnmarshalJSONIntFromString(b *testing.B) { + unmarshalJSONIntBenchmark(b, []byte(`"1.0"`)) +} + +func BenchmarkUnmarshalJSONFloatFromBool(b *testing.B) { + unmarshalJSONFloatBenchmark(b, []byte(`true`)) +} + +func BenchmarkUnmarshalJSONFloatFromFloat(b *testing.B) { + unmarshalJSONFloatBenchmark(b, []byte(`1.0`)) +} + +func BenchmarkUnmarshalJSONFloatFromInt(b *testing.B) { + unmarshalJSONFloatBenchmark(b, []byte(`1`)) +} + +func BenchmarkUnmarshalJSONFloatFromString(b *testing.B) { + unmarshalJSONFloatBenchmark(b, []byte(`"1.0"`)) +} + +func BenchmarkUnmarshalJSONFloatFromUint(b *testing.B) { + unmarshalJSONFloatBenchmark(b, []byte(`1`)) +} + +func BenchmarkUnmarshalJSONStringFromBool(b *testing.B) { + unmarshalJSONStringBenchmark(b, []byte(`true`)) +} + +func BenchmarkUnmarshalJSONStringFromFloat(b *testing.B) { + unmarshalJSONStringBenchmark(b, []byte(`1.0`)) +} + +func BenchmarkUnmarshalJSONStringFromInt(b *testing.B) { + unmarshalJSONStringBenchmark(b, []byte(`1`)) +} + +func BenchmarkUnmarshalJSONStringFromString(b *testing.B) { + unmarshalJSONStringBenchmark(b, []byte(`"true"`)) +} + +func unmarshalJSONBoolBenchmark(b *testing.B, bs []byte) { + x := Bool{} + for i := 0; i < b.N; i++ { + x.UnmarshalJSON(bs) + } +} + +func unmarshalJSONFloatBenchmark(b *testing.B, bs []byte) { + x := Float{} + for i := 0; i < b.N; i++ { + x.UnmarshalJSON(bs) + } +} + +func unmarshalJSONIntBenchmark(b *testing.B, bs []byte) { + x := Int{} + for i := 0; i < b.N; i++ { + x.UnmarshalJSON(bs) + } +} + +func unmarshalJSONStringBenchmark(b *testing.B, bs []byte) { + x := String{} + for i := 0; i < b.N; i++ { + x.UnmarshalJSON(bs) + } +} + +func unmarshalJSONUintBenchmark(b *testing.B, bs []byte) { + x := Uint{} + for i := 0; i < b.N; i++ { + x.UnmarshalJSON(bs) + } +} diff --git a/type_bool.go b/type_bool.go index bac4616..61bf2cb 100644 --- a/type_bool.go +++ b/type_bool.go @@ -1,24 +1,27 @@ package generic -import "encoding/json" +import ( + "database/sql/driver" + "encoding/json" +) -// Bool +// Bool is generic boolean type structure type Bool struct { ValidFlag - Bool bool + bool bool } -// Value returns Bool.Bool, but if Bool.ValidFlag is false, returns nil. -func (v Bool) Value() interface{} { +// Value implements the driver Valuer interface. +func (v Bool) Value() (driver.Value, error) { if !v.Valid() { - return nil + return nil, nil } - return v.Bool + return v.bool, nil } // Scan implements the sql.Scanner interface. func (v *Bool) Scan(x interface{}) (err error) { - v.Bool, v.ValidFlag, err = asBool(x) + v.bool, v.ValidFlag, err = asBool(x) if err != nil { v.ValidFlag = false return err @@ -26,21 +29,49 @@ func (v *Bool) Scan(x interface{}) (err error) { return } +// Weak returns Bool.Bool, but if Bool.ValidFlag is false, returns nil. +func (v Bool) Weak() interface{} { + i, _ := v.Value() + return i +} + // Set sets a specified value. func (v *Bool) Set(x interface{}) (err error) { return v.Scan(x) } +// Bool returns bool value +func (v Bool) Bool() bool { + if v.Valid() && v.bool { + return true + } + return false +} + +// String implements the Stringer interface. +func (v Bool) String() string { + if v.Valid() && v.bool { + return "true" + } + return "false" +} + // MarshalJSON implements the json.Marshaler interface. func (v Bool) MarshalJSON() ([]byte, error) { if !v.Valid() { - return json.Marshal(nil) + return nullBytes, nil } - return json.Marshal(v.Bool) + if v.bool { + return []byte("true"), nil + } + return []byte("false"), nil } // UnmarshalJSON implements the json.Unmarshaler interface. func (v *Bool) UnmarshalJSON(data []byte) error { + if len(data) == 0 { + return nil + } var in interface{} if err := json.Unmarshal(data, &in); err != nil { return err diff --git a/type_bool_test.go b/type_bool_test.go index b4ae12f..4478845 100644 --- a/type_bool_test.go +++ b/type_bool_test.go @@ -49,14 +49,26 @@ func TestBoolJsonError(t *testing.T) { } } +func TestBoolUnmarshalNil(t *testing.T) { + var actual Bool + expected := Bool{} + err := actual.UnmarshalJSON(nil) + if err != nil { + t.Errorf("Not Expected error when json.Unmarshal. error:%s", err.Error()) + } + if actual != expected { + t.Errorf("actual:%#v, expected:%#v", actual, expected) + } +} + func TestBoolSetNil(t *testing.T) { ts := Bool{} err := ts.Set(nil) if err != nil { t.Errorf("Not Expected error. error:%v", err.Error()) } - if ts.Value() != nil { - t.Errorf("This value should return nil. error:%#v", ts.Value()) + if ts.Weak() != nil { + t.Errorf("This value should return nil. error:%#v", ts.Weak()) } } @@ -68,8 +80,8 @@ func TestBoolSetInt64(t *testing.T) { if err != nil { t.Errorf("Not Expected error. error:%v", err.Error()) } - if ts.Value() != expected { - t.Errorf("actual:%v, expected:%v", ts.Value(), expected) + if ts.Weak() != expected { + t.Errorf("actual:%v, expected:%v", ts.Weak(), expected) } } @@ -81,7 +93,67 @@ func TestBoolSetString(t *testing.T) { if err != nil { t.Errorf("Not Expected error. error:%v", err.Error()) } - if ts.Value() != expected { - t.Errorf("actual:%v, expected:%v", ts.Value(), expected) + if ts.Weak() != expected { + t.Errorf("actual:%v, expected:%v", ts.Weak(), expected) + } +} + +func TestBoolTrue(t *testing.T) { + ts := Bool{ + ValidFlag: true, + bool: true, + } + if !ts.Bool() { + t.Errorf("actual:%v, expected:true", ts.Bool()) + } +} + +func TestBoolFalse(t *testing.T) { + ts := Bool{ + ValidFlag: true, + bool: false, + } + if ts.Bool() { + t.Errorf("actual:%v, expected:false", ts.Bool()) + } +} + +func TestBoolInvalid(t *testing.T) { + ts := Bool{ + ValidFlag: false, + bool: true, + } + if ts.Bool() { + t.Errorf("actual:%v, expected:false", ts.Bool()) + } +} + +func TestBoolStringTrue(t *testing.T) { + ts := Bool{ + ValidFlag: true, + bool: true, + } + if ts.String() != "true" { + t.Errorf("actual:%s, expected:true", ts.String()) + } +} + +func TestBoolStringFalse(t *testing.T) { + ts := Bool{ + ValidFlag: true, + bool: false, + } + if ts.String() != "false" { + t.Errorf("actual:%s, expected:false", ts.String()) + } +} + +func TestBoolStringInvalid(t *testing.T) { + ts := Bool{ + ValidFlag: false, + bool: true, + } + if ts.String() != "false" { + t.Errorf("actual:%s, expected:false", ts.String()) } } diff --git a/type_float.go b/type_float.go index 1e4cff7..d2add9a 100644 --- a/type_float.go +++ b/type_float.go @@ -1,24 +1,28 @@ package generic -import "encoding/json" +import ( + "database/sql/driver" + "encoding/json" + "strconv" +) -// Float +// Float is generic float type structure type Float struct { ValidFlag - Float float64 + float float64 } -// Value returns Float.Float, but if Float.ValidFlag is false, returns nil. -func (v Float) Value() interface{} { +// Value implements the driver Valuer interface. +func (v Float) Value() (driver.Value, error) { if !v.Valid() { - return nil + return nil, nil } - return v.Float + return v.float, nil } // Scan implements the sql.Scanner interface. func (v *Float) Scan(x interface{}) (err error) { - v.Float, v.ValidFlag, err = asFloat(x) + v.float, v.ValidFlag, err = asFloat(x) if err != nil { v.ValidFlag = false return err @@ -26,21 +30,51 @@ func (v *Float) Scan(x interface{}) (err error) { return } +// Weak returns Bool.Bool, but if Bool.ValidFlag is false, returns nil. +func (v Float) Weak() interface{} { + i, _ := v.Value() + return i +} + // Set sets a specified value. func (v *Float) Set(x interface{}) (err error) { return v.Scan(x) } +// Float32 returns float32 value +func (v Float) Float32() float32 { + return float32(v.Float64()) +} + +// Float64 returns float64 value +func (v Float) Float64() float64 { + if !v.Valid() { + return 0 + } + return v.float +} + +// String implements the Stringer interface. +func (v Float) String() string { + if !v.Valid() { + return "" + } + return strconv.FormatFloat(v.float, 'f', -1, 64) +} + // MarshalJSON implements the json.Marshaler interface. func (v Float) MarshalJSON() ([]byte, error) { if !v.Valid() { - return json.Marshal(nil) + return nullBytes, nil } - return json.Marshal(v.Float) + return []byte(strconv.FormatFloat(v.float, 'f', -1, 64)), nil } // UnmarshalJSON implements the json.Unmarshaler interface. func (v *Float) UnmarshalJSON(data []byte) error { + if len(data) == 0 { + return nil + } var in interface{} if err := json.Unmarshal(data, &in); err != nil { return err diff --git a/type_float_test.go b/type_float_test.go index e2d9ed9..f932f77 100644 --- a/type_float_test.go +++ b/type_float_test.go @@ -31,6 +31,14 @@ func TestFloatJsonUnmarshalAndMarshal(t *testing.T) { } } +func TestFloatUnmarshalEmpty(t *testing.T) { + tf := Float{} + err := tf.UnmarshalJSON(nil) + if err != nil { + t.Errorf("Not Expected error when json.Marshal. error:%s", err.Error()) + } +} + func TestFloatJsonError(t *testing.T) { var ts TestFloatStruct jstr := `{"int":10,"float":1.0,"bool":true,"string":"あ","null_value":null}` @@ -41,7 +49,7 @@ func TestFloatJsonError(t *testing.T) { } b, err := json.Marshal(ts) if err != nil { - t.Errorf("Not Expected error when json.Marshal. error:%v", err.Error()) + t.Errorf("Not Expected error when json.Marshal. error:%s", err.Error()) } actual := string(b) if actual != expected { @@ -50,51 +58,113 @@ func TestFloatJsonError(t *testing.T) { } func TestFloatSetNil(t *testing.T) { - ti := Uint{} - err := ti.Set(nil) + tf := Float{} + err := tf.Set(nil) if err != nil { - t.Errorf("Not Expected error. error:%v", err.Error()) + t.Errorf("Not Expected error. error:%s", err.Error()) } - if ti.Value() != nil { - t.Errorf("This value should return nil. error:%#v", ti.Value()) + if tf.Weak() != nil { + t.Errorf("This value should return nil. error:%#v", tf.Weak()) } } func TestFloatSetInt64(t *testing.T) { var v int64 = 100 var expected float64 = 100 - ti := Float{} - err := ti.Set(v) + tf := Float{} + err := tf.Set(v) if err != nil { t.Errorf("Not Expected error. error:%v", err.Error()) } - if ti.Value() != expected { - t.Errorf("actual:%#v, expected:%#v", ti.Value(), expected) + if tf.Weak() != expected { + t.Errorf("actual:%#v, expected:%#v", tf.Weak(), expected) } } func TestFloatSetNumericString(t *testing.T) { v := "56.0001" expected := 56.0001 - ti := Float{} - err := ti.Set(v) + tf := Float{} + err := tf.Set(v) if err != nil { t.Errorf("Not Expected error. error:%v", err.Error()) } - if ti.Value() != expected { - t.Errorf("actual:%v, expected:%v", ti.Value(), expected) + if tf.Weak() != expected { + t.Errorf("actual:%v, expected:%v", tf.Weak(), expected) } } func TestFloatSetNonNumericString(t *testing.T) { v := "a" var expected float64 - ti := Float{} - err := ti.Set(v) + tf := Float{} + err := tf.Set(v) if err == nil { t.Error("Expected error.") } - if ti.Value() == expected { - t.Errorf("This value should return 0. value:%#v", ti.Value()) + if tf.Weak() == expected { + t.Errorf("This value should return 0. value:%#v", tf.Weak()) + } +} + +func TestFloatFloat32(t *testing.T) { + var expected float32 = 56.0001 + tf := Float{ + ValidFlag: true, + float: float64(expected), + } + if tf.Float32() != expected { + t.Errorf("actual:%v, expected:%v", tf.Float32(), expected) + } +} + +func TestFloatFloat32Invalid(t *testing.T) { + var expected float32 = 56.0001 + tf := Float{ + ValidFlag: false, + float: float64(expected), + } + if tf.Float32() != 0 { + t.Errorf("actual:%v, expected:0", tf.Float32()) + } +} + +func TestFloatFloat64(t *testing.T) { + var expected = 56.0001 + tf := Float{ + ValidFlag: true, + float: expected, + } + if tf.Float64() != expected { + t.Errorf("actual:%v, expected:%v", tf.Float64(), expected) + } +} + +func TestFloatFloat64Invalid(t *testing.T) { + tf := Float{ + ValidFlag: false, + float: 56.0001, + } + if tf.Float64() != 0 { + t.Errorf("actual:%v, expected:0", tf.Float64()) + } +} + +func TestFloatString(t *testing.T) { + var expected = "56.0001" + tf := Float{} + tf.Set(expected) + if tf.String() != expected { + t.Errorf("actual:%s, expected:%s", tf.String(), expected) + } +} + +func TestFloatStringInvalid(t *testing.T) { + tf := Float{ + ValidFlag: false, + float: 56.0001, + } + if tf.String() != "" { + t.Errorf("expected empty string, actual:%s", tf.String()) } } diff --git a/type_int.go b/type_int.go index ae34dcd..6b75770 100644 --- a/type_int.go +++ b/type_int.go @@ -1,24 +1,28 @@ package generic -import "encoding/json" +import ( + "database/sql/driver" + "encoding/json" + "strconv" +) -// Int +// Int is generic integer type structure type Int struct { ValidFlag - Int int64 + int int64 } -// Value returns Int.Int, but if Int.ValidFlag is false, returns nil. -func (v Int) Value() interface{} { +// Value implements the driver Valuer interface. +func (v Int) Value() (driver.Value, error) { if !v.Valid() { - return nil + return nil, nil } - return v.Int + return v.int, nil } // Scan implements the sql.Scanner interface. func (v *Int) Scan(x interface{}) (err error) { - v.Int, v.ValidFlag, err = asInt(x) + v.int, v.ValidFlag, err = asInt(x) if err != nil { v.ValidFlag = false return err @@ -26,21 +30,62 @@ func (v *Int) Scan(x interface{}) (err error) { return } +// Weak returns Int.Int, but if Int.ValidFlag is false, returns nil. +func (v Int) Weak() interface{} { + i, _ := v.Value() + return i +} + // Set sets a specified value. func (v *Int) Set(x interface{}) (err error) { return v.Scan(x) } +// Int return int value +func (v Int) Int() int { + if !v.Valid() { + return 0 + } + return int(v.int) +} + +// Int32 return int32 value +func (v Int) Int32() int32 { + if !v.Valid() { + return 0 + } + return int32(v.int) +} + +// Int64 return int64 value +func (v Int) Int64() int64 { + if !v.Valid() { + return 0 + } + return v.int +} + +// String implements the Stringer interface. +func (v Int) String() string { + if !v.Valid() { + return "" + } + return strconv.FormatInt(v.int, 10) +} + // MarshalJSON implements the json.Marshaler interface. func (v Int) MarshalJSON() ([]byte, error) { if !v.Valid() { - return json.Marshal(nil) + return nullBytes, nil } - return json.Marshal(v.Int) + return []byte(strconv.FormatInt(v.int, 10)), nil } // UnmarshalJSON implements the json.Unmarshaler interface. func (v *Int) UnmarshalJSON(data []byte) error { + if len(data) == 0 || string(data) == "null" { + return nil + } var in interface{} if err := json.Unmarshal(data, &in); err != nil { return err diff --git a/type_int_test.go b/type_int_test.go index cd8a7d7..cd9773a 100644 --- a/type_int_test.go +++ b/type_int_test.go @@ -11,12 +11,13 @@ type TestIntStruct struct { Bool Int `json:"bool"` String Int `json:"string"` NullValue Int `json:"null_value"` + Empty Int `json:"empty"` } func TestIntJsonUnmarshalAndMarshal(t *testing.T) { var ts TestIntStruct - jstr := `{"int":10,"float":1.0,"bool":true,"string":"50","null_value":null}` - expected := `{"int":10,"float":1,"bool":1,"string":50,"null_value":null}` + jstr := `{"int":10,"float":1.0,"bool":true,"string":"-50","null_value":null}` + expected := `{"int":10,"float":1,"bool":1,"string":-50,"null_value":null,"empty":null}` err := json.Unmarshal([]byte(jstr), &ts) if err != nil { t.Errorf("Not Expected error when json.Unmarshal. error:%v", err.Error()) @@ -31,10 +32,113 @@ func TestIntJsonUnmarshalAndMarshal(t *testing.T) { } } +func TestIntJsonMarshal(t *testing.T) { + ts := Int{ + ValidFlag: true, + int: 1000, + } + expected := `1000` + actual, err := json.Marshal(ts) + if err != nil { + t.Errorf("Not Expected error when json.Marshal. error:%v", err.Error()) + } + if string(actual) != expected { + t.Errorf("actual:%s, expected:%s", string(actual), expected) + } +} + +func TestIntJsonMarshalValidFalse(t *testing.T) { + ts := Int{ + ValidFlag: false, + int: 1000, + } + expected := []byte("null") + actual, err := json.Marshal(ts) + if err != nil { + t.Errorf("Not Expected error when json.Marshal. error:%v", err.Error()) + } + if string(actual) != string(expected) { + t.Errorf("actual:%v, expected:%v", actual, expected) + } +} + +func TestIntJsonMarshalZero(t *testing.T) { + ts := Int{ + ValidFlag: true, + int: 0, + } + expected := `0` + actual, err := json.Marshal(ts) + if err != nil { + t.Errorf("Not Expected error when json.Marshal. error:%v", err.Error()) + } + if string(actual) != expected { + t.Errorf("actual:%s, expected:%s", string(actual), expected) + } +} + +func TestIntJsonUnmarshal(t *testing.T) { + jstr := `{"int":10,"float":1.0,"bool":true,"string":"-50","null_value":null}` + expected := TestIntStruct{ + Int: Int{ + ValidFlag: true, + int: 10, + }, + Float: Int{ + ValidFlag: true, + int: 1, + }, + Bool: Int{ + ValidFlag: true, + int: 1, + }, + String: Int{ + ValidFlag: true, + int: -50, + }, + NullValue: Int{ + ValidFlag: false, + int: 0, + }, + Empty: Int{ + ValidFlag: false, + int: 0, + }, + } + var actual TestIntStruct + err := json.Unmarshal([]byte(jstr), &actual) + if err != nil { + t.Errorf("Not Expected error when json.Marshal. error:%s", err.Error()) + } + if actual != expected { + t.Errorf("actual:%#v, expected:%#v", actual, expected) + } +} + +func TestUnmarshalNil(t *testing.T) { + var actual Int + expected := Int{} + err := actual.UnmarshalJSON(nil) + if err != nil { + t.Errorf("Not Expected error when json.Unmarshal. error:%s", err.Error()) + } + if actual != expected { + t.Errorf("actual:%#v, expected:%#v", actual, expected) + } +} + +func TestUnmarshalInvalidData(t *testing.T) { + var actual Int + err := actual.UnmarshalJSON([]byte(`"1`)) + if err == nil { + t.Error("Expected error when json.Unmarshal") + } +} + func TestIntJsonError(t *testing.T) { var ts TestIntStruct jstr := `{"int":10,"float":1.0,"bool":true,"string":"あ","null_value":null}` - expected := `{"int":10,"float":1,"bool":1,"string":null,"null_value":null}` + expected := `{"int":10,"float":1,"bool":1,"string":null,"null_value":null,"empty":null}` err := json.Unmarshal([]byte(jstr), &ts) if err == nil { t.Error("Expected error when json.Unmarshal.") @@ -50,13 +154,13 @@ func TestIntJsonError(t *testing.T) { } func TestIntSetNil(t *testing.T) { - ti := Uint{} + ti := Int{} err := ti.Set(nil) if err != nil { t.Errorf("Not Expected error. error:%v", err.Error()) } - if ti.Value() != nil { - t.Errorf("This value should return nil. error:%#v", ti.Value()) + if ti.Weak() != nil { + t.Errorf("This value should return nil. error:%#v", ti.Weak()) } } @@ -68,8 +172,8 @@ func TestIntSetInt64(t *testing.T) { if err != nil { t.Errorf("Not Expected error. error:%v", err.Error()) } - if ti.Value() != expected { - t.Errorf("actual:%v, expected:%v", ti.Value(), expected) + if ti.Weak() != expected { + t.Errorf("actual:%v, expected:%v", ti.Weak(), expected) } } @@ -81,8 +185,8 @@ func TestIntSetNumericString(t *testing.T) { if err != nil { t.Errorf("Not Expected error. error:%v", err.Error()) } - if ti.Value() != expected { - t.Errorf("actual:%v, expected:%v", ti.Value(), expected) + if ti.Weak() != expected { + t.Errorf("actual:%v, expected:%v", ti.Weak(), expected) } } @@ -94,7 +198,89 @@ func TestIntSetNonNumericString(t *testing.T) { if err == nil { t.Error("Expected error.") } - if ti.Value() == expected { - t.Errorf("This value should return 0. value:%#v", ti.Value()) + if ti.Weak() == expected { + t.Errorf("This value should return 0. value:%#v", ti.Weak()) + } +} + +func TestIntInt(t *testing.T) { + expected := 123456789 + ti := Int{ + ValidFlag: true, + int: int64(expected), + } + if ti.Int() != expected { + t.Errorf("actual:%d, expected:%d", ti.Int(), expected) + } +} + +func TestIntIntInvalid(t *testing.T) { + ti := Int{ + ValidFlag: false, + int: 123456789, + } + if ti.Int() != 0 { + t.Errorf("actual:%d, expected:0", ti.Int()) + } +} + +func TestIntInt32(t *testing.T) { + var expected int32 = 123456789 + ti := Int{ + ValidFlag: true, + int: int64(expected), + } + if ti.Int32() != expected { + t.Errorf("actual:%d, expected:%d", ti.Int32(), expected) + } +} + +func TestIntInt32Invalid(t *testing.T) { + ti := Int{ + ValidFlag: false, + int: 123456789, + } + if ti.Int32() != 0 { + t.Errorf("actual:%d, expected:0", ti.Int32()) + } +} + +func TestIntInt64(t *testing.T) { + var expected int64 = 123456789 + ti := Int{ + ValidFlag: true, + int: expected, + } + if ti.Int64() != expected { + t.Errorf("actual:%d, expected:%d", ti.Int64(), expected) + } +} + +func TestIntInt64Invalid(t *testing.T) { + ti := Int{ + ValidFlag: false, + int: 123456789, + } + if ti.Int64() != 0 { + t.Errorf("actual:%d, expected:0", ti.Int64()) + } +} + +func TestIntString(t *testing.T) { + var expected = "123456789" + ti := Int{} + ti.Set(expected) + if ti.String() != expected { + t.Errorf("actual:%s, expected:%s", ti.String(), expected) + } +} + +func TestIntStringInvalid(t *testing.T) { + ti := Int{ + ValidFlag: false, + int: 123456789, + } + if ti.String() != "" { + t.Errorf("expected empty string, actual:%s", ti.String()) } } diff --git a/type_string.go b/type_string.go index 65d77fe..e2f069b 100644 --- a/type_string.go +++ b/type_string.go @@ -1,24 +1,27 @@ package generic -import "encoding/json" +import ( + "database/sql/driver" + "encoding/json" +) -// String +// String is generic string type structure type String struct { ValidFlag - String string + string string } -// Value returns String.String, but if String.ValidFlag is false, returns nil. -func (v String) Value() interface{} { +// Value implements the driver Valuer interface. +func (v String) Value() (driver.Value, error) { if !v.Valid() { - return nil + return nil, nil } - return v.String + return v.string, nil } // Scan implements the sql.Scanner interface. func (v *String) Scan(x interface{}) (err error) { - v.String, v.ValidFlag, err = asString(x) + v.string, v.ValidFlag, err = asString(x) if err != nil { v.ValidFlag = false return err @@ -26,21 +29,40 @@ func (v *String) Scan(x interface{}) (err error) { return } +// Weak returns string, but if String.ValidFlag is false, returns nil. +func (v String) Weak() interface{} { + i, _ := v.Value() + return i +} + // Set sets a specified value. func (v *String) Set(x interface{}) (err error) { return v.Scan(x) } +// String implements the Stringer interface. +func (v String) String() string { + if !v.Valid() { + return "" + } + return v.string +} + // MarshalJSON implements the json.Marshaler interface. func (v String) MarshalJSON() ([]byte, error) { if !v.Valid() { - return json.Marshal(nil) + return nullBytes, nil } - return json.Marshal(v.String) + s := `"` + v.string + `"` + bs := make([]byte, 0, len(s)) + return append(bs, s...), nil } // UnmarshalJSON implements the json.Unmarshaler interface. func (v *String) UnmarshalJSON(data []byte) error { + if len(data) == 0 { + return nil + } var in interface{} if err := json.Unmarshal(data, &in); err != nil { return err diff --git a/type_string_test.go b/type_string_test.go index 78019ee..ed4bd81 100644 --- a/type_string_test.go +++ b/type_string_test.go @@ -10,13 +10,14 @@ type TestStringStruct struct { Float String `json:"float"` Bool String `json:"bool"` String String `json:"string"` + HTML String `json:"html"` NullValue String `json:"null_value"` } func TestStringJsonUnmarshalAndMarshal(t *testing.T) { var ts TestStringStruct - jstr := `{"int":10,"float":1.1,"bool":false,"string":"qwertyuiopkjhgv876","null_value":null}` - expected := `{"int":"10","float":"1.1","bool":"false","string":"qwertyuiopkjhgv876","null_value":null}` + jstr := `{"int":10,"float":1.1,"bool":false,"string":"qwertyuiopkjhgv876","html":"https://golang.org/src/encoding/json/encode.go?h=float64Encoder&foo=bar#L409","null_value":null}` + expected := `{"int":"10","float":"1.1","bool":"false","string":"qwertyuiopkjhgv876","html":"https://golang.org/src/encoding/json/encode.go?h=float64Encoder\u0026foo=bar#L409","null_value":null}` err := json.Unmarshal([]byte(jstr), &ts) if err != nil { t.Errorf("Not Expected error when json.Unmarshal. error:%v", err.Error()) @@ -31,14 +32,38 @@ func TestStringJsonUnmarshalAndMarshal(t *testing.T) { } } +func TestStringUnmarshalNil(t *testing.T) { + var actual String + expected := String{} + err := actual.UnmarshalJSON(nil) + if err != nil { + t.Errorf("Not Expected error when json.Unmarshal. error:%s", err.Error()) + } + if actual != expected { + t.Errorf("actual:%#v, expected:%#v", actual, expected) + } +} + +func TestStringUnmarshalNull(t *testing.T) { + var actual String + expected := String{} + err := actual.UnmarshalJSON([]byte("null")) + if err != nil { + t.Errorf("Not Expected error when json.Unmarshal. error:%s", err.Error()) + } + if actual != expected { + t.Errorf("actual:%#v, expected:%#v", actual, expected) + } +} + func TestStringSetNil(t *testing.T) { ts := String{} err := ts.Set(nil) if err != nil { t.Errorf("Not Expected error. error:%v", err.Error()) } - if ts.Value() != nil { - t.Errorf("This value should return nil. error:%#v", ts.Value()) + if ts.Weak() != nil { + t.Errorf("This value should return nil. error:%#v", ts.Weak()) } } @@ -50,8 +75,8 @@ func TestStringSetInt64(t *testing.T) { if err != nil { t.Errorf("Not Expected error. error:%v", err.Error()) } - if ts.Value() != expected { - t.Errorf("actual:%v, expected:%v", ts.Value(), expected) + if ts.Weak() != expected { + t.Errorf("actual:%v, expected:%v", ts.Weak(), expected) } } @@ -63,8 +88,8 @@ func TestStringSetString(t *testing.T) { if err != nil { t.Errorf("Not Expected error. error:%v", err.Error()) } - if ts.Value() != expected { - t.Errorf("actual:%v, expected:%v", ts.Value(), expected) + if ts.Weak() != expected { + t.Errorf("actual:%v, expected:%v", ts.Weak(), expected) } } @@ -76,7 +101,28 @@ func TestStringSetBool(t *testing.T) { if err != nil { t.Errorf("Not Expected error. error:%v", err.Error()) } - if ts.Value() != expected { - t.Errorf("actual:%v, expected:%v", ts.Value(), expected) + if ts.Weak() != expected { + t.Errorf("actual:%v, expected:%v", ts.Weak(), expected) + } +} + +func TestString(t *testing.T) { + expected := "vcrtyhjki876tfdews" + ts := String{ + ValidFlag: true, + string: expected, + } + if ts.String() != expected { + t.Errorf("actual:%s, expected:%s", ts.String(), expected) + } +} + +func TestStringInvalid(t *testing.T) { + ts := String{ + ValidFlag: false, + string: "vcrtyhjki876tfdews", + } + if ts.String() != "" { + t.Errorf("actual:%s, expected: (empty)", ts.String()) } } diff --git a/type_time.go b/type_time.go index f51de8f..e71b497 100644 --- a/type_time.go +++ b/type_time.go @@ -1,27 +1,27 @@ package generic import ( - "encoding/json" + "database/sql/driver" "time" ) -// Time +// Time is generic time type structure type Time struct { ValidFlag - Time time.Time + time time.Time } -// Value returns Time.Time, but if Time.ValidFlag is false, returns nil. -func (v Time) Value() interface{} { +// Value implements the driver Valuer interface. +func (v Time) Value() (driver.Value, error) { if !v.Valid() { - return nil + return nil, nil } - return v.Time + return v.time, nil } // Scan implements the sql.Scanner interface. func (v *Time) Scan(x interface{}) (err error) { - v.Time, v.ValidFlag, err = asTime(x) + v.time, v.ValidFlag, err = asTime(x) if err != nil { v.ValidFlag = false return err @@ -29,24 +29,49 @@ func (v *Time) Scan(x interface{}) (err error) { return } +// Weak returns Time.Time, but if Time.ValidFlag is false, returns nil. +func (v Time) Weak() interface{} { + i, _ := v.Value() + return i +} + // Set sets a specified value. func (v *Time) Set(x interface{}) (err error) { return v.Scan(x) } +// String implements the Stringer interface. +func (v Time) String() string { + if !v.Valid() { + return "" + } + return v.time.String() +} + +// Time returns value as time.Time +func (v Time) Time() time.Time { + if !v.Valid() { + return time.Unix(0, 0) + } + return v.time +} + // MarshalJSON implements the json.Marshaler interface. func (v Time) MarshalJSON() ([]byte, error) { if !v.Valid() { - return json.Marshal(nil) + return nullBytes, nil } - return json.Marshal(v.Time) + return v.time.MarshalJSON() } // UnmarshalJSON implements the json.Unmarshaler interface. func (v *Time) UnmarshalJSON(data []byte) error { - var in interface{} - if err := json.Unmarshal(data, &in); err != nil { + if len(data) == 0 { + return nil + } + if err := v.time.UnmarshalJSON(data); err != nil { return err } - return v.Scan(in) + v.ValidFlag = true + return nil } diff --git a/type_time_test.go b/type_time_test.go index c487e20..534e6c7 100644 --- a/type_time_test.go +++ b/type_time_test.go @@ -1,15 +1,90 @@ package generic -import "testing" +import ( + "encoding/json" + "testing" + "time" +) + +func TestTimeJsonMarshal(t *testing.T) { + v := time.Now() + tt := Time{ + ValidFlag: true, + time: v, + } + expected := `"` + v.Format(time.RFC3339Nano) + `"` + actual, err := json.Marshal(tt) + if err != nil { + t.Errorf("Not Expected error when json.Marshal. error:%v", err.Error()) + } + if string(actual) != expected { + t.Errorf("actual:%s, expected:%s", string(actual), expected) + } +} + +func TestTimeJsonMarshalValidFalse(t *testing.T) { + tt := Time{ + ValidFlag: false, + time: time.Now(), + } + expected := []byte("null") + actual, err := json.Marshal(tt) + if err != nil { + t.Errorf("Not Expected error when json.Marshal. error:%v", err.Error()) + } + if string(actual) != string(expected) { + t.Errorf("actual:%v, expected:%v", actual, expected) + } +} + +func TestTimeJsonUnmarshal(t *testing.T) { + v := time.Now() + in, _ := v.MarshalJSON() + tt := Time{} + if err := tt.UnmarshalJSON(in); err != nil { + t.Errorf("Not Expected error when json.Unmarshal. error:%v", err.Error()) + } + if !tt.Valid() { + t.Error("ValidFlag should be TRUE") + } + if tt.Time() != v { + t.Errorf("actual:%v, expected:%v", tt.Time(), v) + } +} + +func TestTimeJsonUnmarshalNil(t *testing.T) { + tt := Time{} + if err := tt.UnmarshalJSON(nil); err != nil { + t.Errorf("Not Expected error when json.Unmarshal. error:%v", err.Error()) + } + if tt.Valid() { + t.Error("ValidFlag should be FALSE") + } + if tt.Time() != time.Unix(0, 0) { + t.Errorf("actual:%v, expected:%v", tt.Time(), time.Unix(0, 0)) + } +} func TestTimeSetNil(t *testing.T) { tt := Time{} err := tt.Set(nil) if err != nil { - t.Errorf("Not Expected error. error:%v", err.Error()) + t.Errorf("Not Expected error. error:%s", err.Error()) + } + if _, err = tt.Value(); err != nil { + t.Errorf("This value should return nil. error:%s", err.Error()) + } +} + +func TestTimeSetTime(t *testing.T) { + v := time.Now() + tt := Time{} + err := tt.Set(v) + if err != nil { + t.Errorf("Not Expected error") } - if tt.Value() != nil { - t.Errorf("This value should return nil. error:%#v", tt.Value()) + if tt.Weak() == nil { + t.Errorf("This value should return nil. error:%#v", tt.Weak()) } } @@ -20,8 +95,8 @@ func TestTimeSetInt64(t *testing.T) { if err == nil { t.Errorf("Not Expected error") } - if tt.Value() != nil { - t.Errorf("This value should return nil. error:%#v", tt.Value()) + if tt.Weak() != nil { + t.Errorf("This value should return nil. error:%#v", tt.Weak()) } } @@ -32,8 +107,8 @@ func TestTimeSetNumericString(t *testing.T) { if err == nil { t.Errorf("Expected error.") } - if tt.Value() != nil { - t.Errorf("This value should return nil. error:%#v", tt.Value()) + if tt.Weak() != nil { + t.Errorf("This value should return nil. error:%#v", tt.Weak()) } } @@ -44,8 +119,8 @@ func TestTimeSetNonNumericString(t *testing.T) { if err == nil { t.Error("Expected error.") } - if tt.Value() != nil { - t.Errorf("This value should return nil. error:%#v", tt.Value()) + if tt.Weak() != nil { + t.Errorf("This value should return nil. error:%#v", tt.Weak()) } } @@ -56,7 +131,26 @@ func TestTimeSetBool(t *testing.T) { if err == nil { t.Error("Expected error.") } - if tt.Value() != nil { - t.Errorf("This value should return nil. error:%#v", tt.Value()) + if tt.Weak() != nil { + t.Errorf("This value should return nil. error:%#v", tt.Weak()) + } +} + +func TestTimeString(t *testing.T) { + var expected = time.Now() + tt := Time{} + tt.Set(expected) + if tt.String() != expected.String() { + t.Errorf("actual:%s, expected:%s", tt.String(), expected.String()) + } +} + +func TestTimeStringInvalid(t *testing.T) { + tt := Time{ + ValidFlag: false, + time: time.Now(), + } + if tt.String() != "" { + t.Errorf("expected empty string, actual:%s", tt.String()) } } diff --git a/type_timestamp.go b/type_timestamp.go index 7a13153..95e9dd7 100644 --- a/type_timestamp.go +++ b/type_timestamp.go @@ -1,28 +1,29 @@ package generic import ( + "database/sql/driver" "encoding/json" "strconv" "time" ) -// Timestamp +// Timestamp is a wrapped time type structure type Timestamp struct { ValidFlag - Time time.Time + time time.Time } // Value returns Time.Time, but if Time.ValidFlag is false, returns nil. -func (v Timestamp) Value() interface{} { +func (v Timestamp) Value() (driver.Value, error) { if !v.Valid() { - return nil + return nil, nil } - return v.Time + return v.time, nil } // Scan implements the sql.Scanner interface. func (v *Timestamp) Scan(x interface{}) (err error) { - v.Time, v.ValidFlag, err = asTimestamp(x) + v.time, v.ValidFlag, err = asTimestamp(x) if err != nil { v.ValidFlag = false return err @@ -30,21 +31,48 @@ func (v *Timestamp) Scan(x interface{}) (err error) { return } +// Weak returns timestamp, but if Timestamp.ValidFlag is false, returns nil. +func (v Timestamp) Weak() interface{} { + i, _ := v.Value() + return i +} + // Set sets a specified value. func (v *Timestamp) Set(x interface{}) (err error) { return v.Scan(x) } +// String implements the Stringer interface. +func (v Timestamp) String() string { + return strconv.FormatInt(v.Int64(), 10) +} + +// Int return int value +func (v Timestamp) Int() int { + return int(v.Int64()) +} + +// Int64 return int64 value +func (v Timestamp) Int64() int64 { + if !v.Valid() || v.time.Unix() == 0 { + return 0 + } + return v.time.Unix() +} + // MarshalJSON implements the json.Marshaler interface. func (v Timestamp) MarshalJSON() ([]byte, error) { if !v.Valid() { - return json.Marshal(nil) + return nullBytes, nil } - return []byte(strconv.FormatInt(v.Time.Unix(), 10)), nil + return []byte(strconv.FormatInt(v.time.Unix(), 10)), nil } // UnmarshalJSON implements the json.Unmarshaler interface. func (v *Timestamp) UnmarshalJSON(data []byte) error { + if len(data) == 0 { + return nil + } var in interface{} if err := json.Unmarshal(data, &in); err != nil { return err diff --git a/type_timestamp_milliseconds.go b/type_timestamp_milliseconds.go index a2272b4..07988f5 100644 --- a/type_timestamp_milliseconds.go +++ b/type_timestamp_milliseconds.go @@ -1,28 +1,29 @@ package generic import ( + "database/sql/driver" "encoding/json" "strconv" "time" ) -// TimestampMS +// TimestampMS is a wrapped time type structure type TimestampMS struct { ValidFlag - Time time.Time + time time.Time } -// Value returns Time.Time, but if Time.ValidFlag is false, returns nil. -func (v TimestampMS) Value() interface{} { +// Value returns timestamp with milliseconds, but if TimestampMS.ValidFlag is false, returns nil. +func (v TimestampMS) Value() (driver.Value, error) { if !v.Valid() { - return nil + return nil, nil } - return v.Time + return v.time.UnixNano() / 1000000, nil } // Scan implements the sql.Scanner interface. func (v *TimestampMS) Scan(x interface{}) (err error) { - v.Time, v.ValidFlag, err = asTimestampMilliseconds(x) + v.time, v.ValidFlag, err = asTimestampMilliseconds(x) if err != nil { v.ValidFlag = false return err @@ -30,21 +31,48 @@ func (v *TimestampMS) Scan(x interface{}) (err error) { return } +// Weak returns timestamp int value, but if TimestampMS.ValidFlag is false, returns nil. +func (v TimestampMS) Weak() interface{} { + i, _ := v.Value() + return i +} + // Set sets a specified value. func (v *TimestampMS) Set(x interface{}) (err error) { return v.Scan(x) } +// String implements the Stringer interface. +func (v TimestampMS) String() string { + return strconv.FormatInt(v.Int64(), 10) +} + +// Int return int value +func (v TimestampMS) Int() int { + return int(v.Int64()) +} + +// Int64 return int64 value +func (v TimestampMS) Int64() int64 { + if !v.Valid() || v.time.UnixNano() == 0 { + return 0 + } + return v.time.UnixNano() / 1000000 +} + // MarshalJSON implements the json.Marshaler interface. func (v TimestampMS) MarshalJSON() ([]byte, error) { if !v.Valid() { - return json.Marshal(nil) + return nullBytes, nil } - return []byte(strconv.FormatInt(v.Time.UnixNano()/1000000, 10)), nil + return []byte(v.String()), nil } // UnmarshalJSON implements the json.Unmarshaler interface. func (v *TimestampMS) UnmarshalJSON(data []byte) error { + if len(data) == 0 { + return nil + } var in interface{} if err := json.Unmarshal(data, &in); err != nil { return err diff --git a/type_timestamp_milliseconds_test.go b/type_timestamp_milliseconds_test.go index b94d4ca..a9b330c 100644 --- a/type_timestamp_milliseconds_test.go +++ b/type_timestamp_milliseconds_test.go @@ -1,18 +1,79 @@ package generic import ( + "encoding/json" + "strconv" "testing" "time" ) +func TestTimestampMSJsonMarshal(t *testing.T) { + v := time.Now() + tm := TimestampMS{ + ValidFlag: true, + time: v, + } + expected := strconv.FormatInt(v.UnixNano()/1000000, 10) + actual, err := json.Marshal(tm) + if err != nil { + t.Errorf("Not Expected error when json.Marshal. error:%v", err.Error()) + } + if string(actual) != expected { + t.Errorf("actual:%s, expected:%s", string(actual), expected) + } +} + +func TestTimestampMSJsonMarshalValidFalse(t *testing.T) { + tm := TimestampMS{ + ValidFlag: false, + time: time.Now(), + } + expected := []byte("null") + actual, err := json.Marshal(tm) + if err != nil { + t.Errorf("Not Expected error when json.Marshal. error:%v", err.Error()) + } + if string(actual) != string(expected) { + t.Errorf("actual:%v, expected:%v", actual, expected) + } +} + +func TestTimestampMSJsonUnmarshal(t *testing.T) { + v := time.Now() + in, _ := v.MarshalJSON() + tm := TimestampMS{} + if err := tm.UnmarshalJSON(in); err != nil { + t.Errorf("Not Expected error when json.Unmarshal. error:%v", err.Error()) + } + if !tm.Valid() { + t.Error("ValidFlag should be TRUE") + } + if tm.Int64() != v.UnixNano()/1000000 { + t.Errorf("actual:%d, expected:%d", tm.Int64(), v.UnixNano()/1000000) + } +} + +func TestTimestampMSJsonUnmarshalNil(t *testing.T) { + tm := TimestampMS{} + if err := tm.UnmarshalJSON(nil); err != nil { + t.Errorf("Not Expected error when json.Unmarshal. error:%v", err.Error()) + } + if tm.Valid() { + t.Error("ValidFlag should be FALSE") + } + if tm.Int64() != 0 { + t.Errorf("actual:%d, expected:%d", tm.Int64(), 0) + } +} + func TestTimestampMSSetNil(t *testing.T) { tm := TimestampMS{} err := tm.Set(nil) if err != nil { t.Errorf("Not Expected error. error:%s", err.Error()) } - if tm.Value() != nil { - t.Errorf("This value should return nil. error:%#v", tm.Value()) + if _, err = tm.Value(); err != nil { + t.Errorf("This value should return nil. error:%s", err.Error()) } } @@ -24,8 +85,8 @@ func TestTimestampMSSetTime(t *testing.T) { if err != nil { t.Errorf("Not Expected error. error:%s", err.Error()) } - if tm.Value().(time.Time).Unix() != expected.Unix() { - t.Errorf("actual:%v, expected:%v", tm.Value(), expected) + if tm.Weak() != expected.UnixNano()/1000000 { + t.Errorf("actual:%v, expected:%v", tm.Weak(), expected) } } @@ -37,8 +98,8 @@ func TestTimestampMSSetInt64(t *testing.T) { if err != nil { t.Errorf("Not Expected error. error:%s", err.Error()) } - if tm.Value() != expected { - t.Errorf("actual:%v, expected:%v", tm.Value(), expected) + if tm.Weak() != expected.UnixNano()/1000000 { + t.Errorf("actual:%v, expected:%v", tm.Weak(), expected) } } @@ -49,8 +110,8 @@ func TestTimestampMSSetNumericString(t *testing.T) { if err == nil { t.Errorf("Expected error.") } - if tm.Value() != nil { - t.Errorf("This value should return nil. value:%#v", tm.Value()) + if tm.Weak() != nil { + t.Errorf("This value should return nil. value:%#v", tm.Weak()) } } @@ -61,8 +122,8 @@ func TestTimestampMSSetNonNumericString(t *testing.T) { if err == nil { t.Error("Expected error.") } - if tm.Value() != nil { - t.Errorf("This value should return nil. value:%#v", tm.Value()) + if tm.Weak() != nil { + t.Errorf("This value should return nil. value:%#v", tm.Weak()) } } @@ -71,9 +132,61 @@ func TestTimestampMSSetBool(t *testing.T) { tm := TimestampMS{} err := tm.Set(v) if err == nil { - t.Error("Expected error.") + t.Errorf("Not Expected error. error:%s", err.Error()) + } + if tm.Weak() != nil { + t.Errorf("This value should return nil. value:%#v", tm.Weak()) + } +} + +func TestTimestampMSInt64(t *testing.T) { + v := time.Now() + expected := v.UnixNano() / 1000000 + tm := TimestampMS{} + err := tm.Set(v) + if err != nil { + t.Error("Not expected error.") + } + if tm.Int64() != expected { + t.Errorf("This value should return %d. value:%d", expected, tm.Int()) + } +} + +func TestTimestampMSInt64Zero(t *testing.T) { + v := time.Unix(0, 0) + var expected int64 + tm := TimestampMS{} + err := tm.Set(v) + if err != nil { + t.Error("Not expected error.") + } + if tm.Int64() != expected { + t.Errorf("This value should return %d. value:%d", expected, tm.Int()) + } +} + +func TestTimestampMSInt(t *testing.T) { + v := time.Now() + expected := int(v.UnixNano() / 1000000) + tm := TimestampMS{} + err := tm.Set(v) + if err != nil { + t.Error("Not expected error.") + } + if tm.Int() != expected { + t.Errorf("This value should return %d. value:%d", expected, tm.Int()) + } +} + +func TestTimestampMSString(t *testing.T) { + v := time.Now() + expected := strconv.FormatInt(v.UnixNano()/1000000, 10) + tm := TimestampMS{} + err := tm.Set(v) + if err != nil { + t.Error("Not expected error.") } - if tm.Value() != nil { - t.Errorf("This value should return nil. value:%#v", tm.Value()) + if tm.String() != expected { + t.Errorf("This value should return %s. value:%s", expected, tm.String()) } } diff --git a/type_timestamp_nanoseconds.go b/type_timestamp_nanoseconds.go index 32ced4f..cc36006 100644 --- a/type_timestamp_nanoseconds.go +++ b/type_timestamp_nanoseconds.go @@ -1,28 +1,29 @@ package generic import ( + "database/sql/driver" "encoding/json" "strconv" "time" ) -// Timestamp +// TimestampNano is a wrapped time type structure type TimestampNano struct { ValidFlag - Time time.Time + time time.Time } -// Value returns Time.Time, but if Time.ValidFlag is false, returns nil. -func (v TimestampNano) Value() interface{} { +// Value returns timestamp with nanoseconds, but if TimestampNano.ValidFlag is false, returns nil. +func (v TimestampNano) Value() (driver.Value, error) { if !v.Valid() { - return nil + return nil, nil } - return v.Time + return v.time.UnixNano(), nil } // Scan implements the sql.Scanner interface. func (v *TimestampNano) Scan(x interface{}) (err error) { - v.Time, v.ValidFlag, err = asTimestampNanoseconds(x) + v.time, v.ValidFlag, err = asTimestampNanoseconds(x) if err != nil { v.ValidFlag = false return err @@ -30,21 +31,48 @@ func (v *TimestampNano) Scan(x interface{}) (err error) { return } +// Weak returns timestamp with nano seconds, but if TimestampNano.ValidFlag is false, returns nil. +func (v TimestampNano) Weak() interface{} { + i, _ := v.Value() + return i +} + // Set sets a specified value. func (v *TimestampNano) Set(x interface{}) (err error) { return v.Scan(x) } +// String implements the Stringer interface. +func (v TimestampNano) String() string { + return strconv.FormatInt(v.Int64(), 10) +} + +// Int return int value +func (v TimestampNano) Int() int { + return int(v.Int64()) +} + +// Int64 return int64 value +func (v TimestampNano) Int64() int64 { + if !v.Valid() || v.time.UnixNano() == 0 { + return 0 + } + return v.time.UnixNano() +} + // MarshalJSON implements the json.Marshaler interface. func (v TimestampNano) MarshalJSON() ([]byte, error) { if !v.Valid() { - return json.Marshal(nil) + return nullBytes, nil } - return []byte(strconv.FormatInt(v.Time.UnixNano(), 10)), nil + return []byte(strconv.FormatInt(v.time.UnixNano(), 10)), nil } // UnmarshalJSON implements the json.Unmarshaler interface. func (v *TimestampNano) UnmarshalJSON(data []byte) error { + if len(data) == 0 { + return nil + } var in interface{} if err := json.Unmarshal(data, &in); err != nil { return err diff --git a/type_timestamp_nanoseconds_test.go b/type_timestamp_nanoseconds_test.go index 2529482..357897e 100644 --- a/type_timestamp_nanoseconds_test.go +++ b/type_timestamp_nanoseconds_test.go @@ -1,18 +1,83 @@ package generic import ( + "encoding/json" + "strconv" "testing" "time" ) +func TestTimestampNanoJsonMarshal(t *testing.T) { + v := time.Now() + tn := TimestampNano{ + ValidFlag: true, + time: v, + } + expected := strconv.FormatInt(v.UnixNano(), 10) + actual, err := json.Marshal(tn) + if err != nil { + t.Errorf("Not Expected error when json.Marshal. error:%v", err.Error()) + } + if string(actual) != expected { + t.Errorf("actual:%s, expected:%s", string(actual), expected) + } +} + +func TestTimestampNanoJsonMarshalValidFalse(t *testing.T) { + tn := TimestampNano{ + ValidFlag: false, + time: time.Now(), + } + expected := []byte("null") + actual, err := json.Marshal(tn) + if err != nil { + t.Errorf("Not Expected error when json.Marshal. error:%v", err.Error()) + } + if string(actual) != string(expected) { + t.Errorf("actual:%v, expected:%v", actual, expected) + } +} + +func TestTimestampNanoJsonUnmarshal(t *testing.T) { + v := time.Now() + in, _ := v.MarshalJSON() + tn := TimestampNano{} + if err := tn.UnmarshalJSON(in); err != nil { + t.Errorf("Not Expected error when json.Unmarshal. error:%v", err.Error()) + } + if !tn.Valid() { + t.Error("ValidFlag should be TRUE") + } + if tn.Int64() != v.UnixNano() { + t.Errorf("actual:%d, expected:%d", tn.Int64(), v.UnixNano()) + } +} + +func TestTimestampNanoJsonUnmarshalNil(t *testing.T) { + tn := TimestampNano{} + if err := tn.UnmarshalJSON(nil); err != nil { + t.Errorf("Not Expected error when json.Unmarshal. error:%v", err.Error()) + } + if tn.Valid() { + t.Error("ValidFlag should be FALSE") + } + if tn.Int64() != 0 { + t.Errorf("actual:%d, expected:%d", tn.Int64(), 0) + } +} + func TestTimestampNanoSetNil(t *testing.T) { tn := TimestampNano{} err := tn.Set(nil) if err != nil { t.Errorf("Not Expected error. error:%s", err.Error()) } - if tn.Value() != nil { - t.Errorf("This value should return nil. error:%#v", tn.Value()) + actual, err := tn.Value() + if err != nil { + t.Errorf("This value should return nil. error:%s", err.Error()) + } + if actual != nil { + t.Errorf("actual:%d, expected:nil", actual) } } @@ -24,8 +89,8 @@ func TestTimestampNanoSetTime(t *testing.T) { if err != nil { t.Errorf("Not Expected error. error:%s", err.Error()) } - if tn.Value().(time.Time).Unix() != expected.Unix() { - t.Errorf("actual:%v, expected:%v", tn.Value(), expected) + if tn.Weak() != expected.UnixNano() { + t.Errorf("actual:%d, expected:%d", tn.Weak(), expected) } } @@ -37,8 +102,8 @@ func TestTimestampNanoSetInt64(t *testing.T) { if err != nil { t.Errorf("Not Expected error. error:%s", err.Error()) } - if tn.Value() != expected { - t.Errorf("actual:%v, expected:%v", tn.Value(), expected) + if actual := tn.Weak(); actual != expected.UnixNano() { + t.Errorf("actual:%v, expected:%v", actual, expected.UnixNano()) } } @@ -49,8 +114,8 @@ func TestTimestampNanoSetNumericString(t *testing.T) { if err == nil { t.Errorf("Expected error.") } - if tn.Value() != nil { - t.Errorf("This value should return nil. value:%#v", tn.Value()) + if tn.Weak() != nil { + t.Errorf("This value should return nil. value:%#v", tn.Weak()) } } @@ -61,8 +126,8 @@ func TestTimestampNanoSetNonNumericString(t *testing.T) { if err == nil { t.Error("Expected error.") } - if tn.Value() != nil { - t.Errorf("This value should return nil. value:%#v", tn.Value()) + if tn.Weak() != nil { + t.Errorf("This value should return nil. value:%#v", tn.Weak()) } } @@ -73,7 +138,59 @@ func TestTimestampNanoSetBool(t *testing.T) { if err == nil { t.Error("Expected error.") } - if tn.Value() != nil { - t.Errorf("This value should return nil. value:%#v", tn.Value()) + if tn.Weak() != nil { + t.Errorf("This value should return nil. value:%#v", tn.Weak()) + } +} + +func TestTimestampNanoInt64(t *testing.T) { + v := time.Now() + expected := v.UnixNano() + tm := TimestampNano{} + err := tm.Set(v) + if err != nil { + t.Error("Not expected error.") + } + if tm.Int64() != expected { + t.Errorf("This value should return %d. value:%d", expected, tm.Int()) + } +} + +func TestTimestampNanoInt64Zero(t *testing.T) { + v := time.Unix(0, 0) + var expected int64 + tm := TimestampNano{} + err := tm.Set(v) + if err != nil { + t.Error("Not expected error.") + } + if tm.Int64() != expected { + t.Errorf("This value should return %d. value:%d", expected, tm.Int()) + } +} + +func TestTimestampNanoInt(t *testing.T) { + v := time.Now() + expected := int(v.UnixNano()) + tm := TimestampNano{} + err := tm.Set(v) + if err != nil { + t.Error("Not expected error.") + } + if tm.Int() != expected { + t.Errorf("This value should return %d. value:%d", expected, tm.Int()) + } +} + +func TestTimestampNanoString(t *testing.T) { + v := time.Now() + expected := strconv.FormatInt(v.UnixNano(), 10) + tm := TimestampNano{} + err := tm.Set(v) + if err != nil { + t.Error("Not expected error.") + } + if tm.String() != expected { + t.Errorf("This value should return %s. value:%s", expected, tm.String()) } } diff --git a/type_timestamp_test.go b/type_timestamp_test.go index 6d4f631..5b96123 100644 --- a/type_timestamp_test.go +++ b/type_timestamp_test.go @@ -1,18 +1,55 @@ package generic import ( + "encoding/json" + "strconv" "testing" "time" ) +func TestTimestampJsonMarshal(t *testing.T) { + v := time.Now() + ts := Timestamp{ + ValidFlag: true, + time: v, + } + expected := strconv.FormatInt(v.Unix(), 10) + actual, err := json.Marshal(ts) + if err != nil { + t.Errorf("Not Expected error when json.Marshal. error:%v", err.Error()) + } + if string(actual) != expected { + t.Errorf("actual:%s, expected:%s", string(actual), expected) + } +} + +func TestTimestampJsonMarshalValidFalse(t *testing.T) { + ts := Timestamp{ + ValidFlag: false, + time: time.Now(), + } + expected := []byte("null") + actual, err := json.Marshal(ts) + if err != nil { + t.Errorf("Not Expected error when json.Marshal. error:%v", err.Error()) + } + if string(actual) != string(expected) { + t.Errorf("actual:%v, expected:%v", actual, expected) + } +} + func TestTimestampSetNil(t *testing.T) { ts := Timestamp{} err := ts.Set(nil) if err != nil { t.Errorf("Not Expected error. error:%s", err.Error()) } - if ts.Value() != nil { - t.Errorf("This value should return nil. error:%#v", ts.Value()) + actual, err := ts.Value() + if err != nil { + t.Errorf("This value should return nil. error:%s", err.Error()) + } + if actual != nil { + t.Errorf("actual:%d, expected:nil", actual) } } @@ -24,8 +61,8 @@ func TestTimestampSetTime(t *testing.T) { if err != nil { t.Errorf("Not Expected error. error:%s", err.Error()) } - if ts.Value() != expected { - t.Errorf("actual:%v, expected:%v", ts.Value(), expected) + if ts.Weak() != expected { + t.Errorf("actual:%v, expected:%v", ts.Weak(), expected) } } @@ -37,8 +74,8 @@ func TestTimestampSetInt64(t *testing.T) { if err != nil { t.Errorf("Not Expected error. error:%s", err.Error()) } - if ts.Value() != expected { - t.Errorf("actual:%v, expected:%v", ts.Value(), expected) + if ts.Weak() != expected { + t.Errorf("actual:%v, expected:%v", ts.Weak(), expected) } } @@ -49,8 +86,8 @@ func TestTimestampSetNumericString(t *testing.T) { if err == nil { t.Errorf("Expected error.") } - if ts.Value() != nil { - t.Errorf("This value should return nil. error:%#v", ts.Value()) + if ts.Weak() != nil { + t.Errorf("This value should return nil. actual:%#v", ts.Weak()) } } @@ -61,8 +98,8 @@ func TestTimestampSetNonNumericString(t *testing.T) { if err == nil { t.Error("Expected error.") } - if ts.Value() != nil { - t.Errorf("This value should return nil. error:%#v", ts.Value()) + if ts.Weak() != nil { + t.Errorf("This value should return nil. actual:%#v", ts.Weak()) } } @@ -73,7 +110,59 @@ func TestTimestampSetBool(t *testing.T) { if err == nil { t.Error("Expected error.") } - if ts.Value() != nil { - t.Errorf("This value should return nil. error:%#v", ts.Value()) + if ts.Weak() != nil { + t.Errorf("This value should return nil. actual:%#v", ts.Weak()) + } +} + +func TestTimestampInt64(t *testing.T) { + v := time.Now() + expected := v.Unix() + ts := Timestamp{} + err := ts.Set(v) + if err != nil { + t.Error("Not expected error.") + } + if ts.Int64() != expected { + t.Errorf("This value should return %d. value:%d", expected, ts.Int()) + } +} + +func TestTimestampInt64Zero(t *testing.T) { + v := time.Unix(0, 0) + var expected int64 + ts := Timestamp{} + err := ts.Set(v) + if err != nil { + t.Error("Not expected error.") + } + if ts.Int64() != expected { + t.Errorf("This value should return %d. value:%d", expected, ts.Int64()) + } +} + +func TestTimestampInt(t *testing.T) { + v := time.Now() + expected := int(v.Unix()) + ts := Timestamp{} + err := ts.Set(v) + if err != nil { + t.Error("Not expected error.") + } + if ts.Int() != expected { + t.Errorf("This value should return %d. value:%d", expected, ts.Int()) + } +} + +func TestTimestampString(t *testing.T) { + v := time.Now() + expected := strconv.FormatInt(v.Unix(), 10) + ts := Timestamp{} + err := ts.Set(v) + if err != nil { + t.Error("Not expected error.") + } + if ts.String() != expected { + t.Errorf("This value should return %s. value:%s", expected, ts.String()) } } diff --git a/type_uint.go b/type_uint.go index bae2469..f313349 100644 --- a/type_uint.go +++ b/type_uint.go @@ -1,24 +1,28 @@ package generic -import "encoding/json" +import ( + "database/sql/driver" + "encoding/json" + "strconv" +) -// Uint +// Uint is generic uint type structure type Uint struct { ValidFlag - Uint uint64 + uint uint64 } -// Value returns Uint.Uint, but if Uint.ValidFlag is false, returns nil. -func (v Uint) Value() interface{} { +// Value implements the driver Valuer interface. +func (v Uint) Value() (driver.Value, error) { if !v.Valid() { - return nil + return nil, nil } - return v.Uint + return v.uint, nil } // Scan implements the sql.Scanner interface. func (v *Uint) Scan(x interface{}) (err error) { - v.Uint, v.ValidFlag, err = asUint(x) + v.uint, v.ValidFlag, err = asUint(x) if err != nil { v.ValidFlag = false return err @@ -26,17 +30,55 @@ func (v *Uint) Scan(x interface{}) (err error) { return } +// Weak returns Uint.Uint, but if Uint.ValidFlag is false, returns nil. +func (v Uint) Weak() interface{} { + i, _ := v.Value() + return i +} + // Set sets a specified value. func (v *Uint) Set(x interface{}) (err error) { return v.Scan(x) } +// Uint return uint value +func (v Uint) Uint() uint { + if !v.Valid() { + return 0 + } + return uint(v.uint) +} + +// Uint32 return uint32 value +func (v Uint) Uint32() uint32 { + if !v.Valid() { + return 0 + } + return uint32(v.uint) +} + +// Uint64 return uint64 value +func (v Uint) Uint64() uint64 { + if !v.Valid() { + return 0 + } + return v.uint +} + +// String implements the Stringer interface. +func (v Uint) String() string { + if !v.Valid() { + return "" + } + return strconv.FormatUint(v.uint, 10) +} + // MarshalJSON implements the json.Marshaler interface. func (v Uint) MarshalJSON() ([]byte, error) { if !v.Valid() { - return json.Marshal(nil) + return nullBytes, nil } - return json.Marshal(v.Uint) + return []byte(strconv.FormatUint(v.uint, 10)), nil } // UnmarshalJSON implements the json.Unmarshaler interface. diff --git a/type_uint_test.go b/type_uint_test.go index b7faff7..afbf46c 100644 --- a/type_uint_test.go +++ b/type_uint_test.go @@ -55,8 +55,8 @@ func TestUintSetNil(t *testing.T) { if err != nil { t.Errorf("Not Expected error. error:%v", err.Error()) } - if tu.Value() != nil { - t.Errorf("This value should return nil. error:%#v", tu.Value()) + if tu.Weak() != nil { + t.Errorf("This value should return nil. error:%#v", tu.Weak()) } } @@ -68,8 +68,8 @@ func TestUintSetInt64(t *testing.T) { if err != nil { t.Errorf("Not Expected error. error:%v", err.Error()) } - if tu.Value() != expected { - t.Errorf("This value should return 100 (uint64). value:%#v", tu.Value()) + if tu.Weak() != expected { + t.Errorf("This value should return 100 (uint64). value:%#v", tu.Weak()) } } @@ -81,8 +81,8 @@ func TestUintSetNumericString(t *testing.T) { if err != nil { t.Errorf("Not Expected error. error:%v", err.Error()) } - if tu.Value() != expected { - t.Errorf("This value should return nil. error:%#v", tu.Value()) + if tu.Weak() != expected { + t.Errorf("This value should return nil. error:%#v", tu.Weak()) } } @@ -94,7 +94,89 @@ func TestUintSetNonNumericString(t *testing.T) { if err == nil { t.Error("Expected error.") } - if tu.Value() == expected { - t.Errorf("This value should return 0. value:%#v", tu.Value()) + if tu.Weak() == expected { + t.Errorf("This value should return 0. value:%#v", tu.Weak()) + } +} + +func TestUintUint(t *testing.T) { + var expected uint = 123456789 + ti := Uint{ + ValidFlag: true, + uint: uint64(expected), + } + if ti.Uint() != expected { + t.Errorf("actual:%d, expected:%d", ti.Uint(), expected) + } +} + +func TestUintUintInvalid(t *testing.T) { + ti := Uint{ + ValidFlag: false, + uint: 123456789, + } + if ti.Uint() != 0 { + t.Errorf("actual:%d, expected:0", ti.Uint()) + } +} + +func TestUintUint32(t *testing.T) { + var expected uint32 = 123456789 + ti := Uint{ + ValidFlag: true, + uint: uint64(expected), + } + if ti.Uint32() != expected { + t.Errorf("actual:%d, expected:%d", ti.Uint32(), expected) + } +} + +func TestUintUint32Invalid(t *testing.T) { + ti := Uint{ + ValidFlag: false, + uint: 123456789, + } + if ti.Uint32() != 0 { + t.Errorf("actual:%d, expected:0", ti.Uint32()) + } +} + +func TestUintUint64(t *testing.T) { + var expected uint64 = 123456789 + ti := Uint{ + ValidFlag: true, + uint: expected, + } + if ti.Uint64() != expected { + t.Errorf("actual:%d, expected:%d", ti.Uint64(), expected) + } +} + +func TestUintUint64Invalid(t *testing.T) { + ti := Uint{ + ValidFlag: false, + uint: 123456789, + } + if ti.Uint64() != 0 { + t.Errorf("actual:%d, expected:0", ti.Uint64()) + } +} + +func TestUintString(t *testing.T) { + var expected = "123456789" + ti := Uint{} + ti.Set(expected) + if ti.String() != expected { + t.Errorf("actual:%s, expected:%s", ti.String(), expected) + } +} + +func TestUintStringInvalid(t *testing.T) { + ti := Uint{ + ValidFlag: false, + uint: 123456789, + } + if ti.String() != "" { + t.Errorf("expected empty string, actual:%s", ti.String()) } }