From e001fb0f0c4342e670cf5fec3e42e4d388ee3826 Mon Sep 17 00:00:00 2001 From: Greg Date: Mon, 14 Sep 2015 05:07:53 +0900 Subject: [PATCH] Time docs, tweaks, tests --- README.md | 20 +++++++++---- time.go | 9 +++--- time_test.go | 72 ++++++++++++++++++++++++++++++++++++----------- zero/time.go | 14 ++++----- zero/time_test.go | 59 ++++++++++++++++++++++++++++++++------ 5 files changed, 130 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 397bb01..8dc522b 100644 --- a/README.md +++ b/README.md @@ -23,17 +23,21 @@ Marshals to JSON null if SQL source data is null. Zero (blank) input will not pr #### null.Int Nullable int64. -Marshals to JSON null if SQL null. Zero input will not produce a null Int. Can unmarshal from `sql.NullInt64` JSON input. +Marshals to JSON null if SQL source data is null. Zero input will not produce a null Int. Can unmarshal from `sql.NullInt64` JSON input. #### null.Float Nullable float64. -Unlike `zero.Float`, `null.Float` will marshal to null if null. Zero input will not produce a null Float. Can unmarshal from `sql.NullFloat64` JSON input. +Marshals to JSON null if SQL source data is null. Zero input will not produce a null Float. Can unmarshal from `sql.NullFloat64` JSON input. #### null.Bool Nullable bool. -Unlike `zero.Bool`, `null.Bool` will marshal to null if null. False input will not produce a null Bool. Can unmarshal from `sql.NullBool` JSON input. +Marshals to JSON null if SQL source data is null. False input will not produce a null Bool. Can unmarshal from `sql.NullBool` JSON input. + +#### null.Time + +Marshals to JSON null if SQL source data is null. Uses `time.Time`'s marshaler. Can unmarshal from `pq.NullTime` and similar JSON input. ### zero package @@ -47,17 +51,21 @@ Will marshal to a blank string if null. Blank string input produces a null Strin #### zero.Int Nullable int64. -Will marshal to 0 if null. Blank string or 0 input produces a null Int. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullInt64` JSON input. +Will marshal to 0 if null. 0 produces a null Int. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullInt64` JSON input. #### zero.Float Nullable float64. -Will marshal to 0 if null. Blank string or 0 input produces a null Float. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullFloat64` JSON input. +Will marshal to 0 if null. 0.0 produces a null Float. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullFloat64` JSON input. #### zero.Bool Nullable bool. -Will marshal to false if null. Blank string, false input produces a null Float. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullBool` JSON input. +Will marshal to false if null. `false` produces a null Float. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullBool` JSON input. + +#### null.Time + +Will marshal to the zero time if null. Uses `time.Time`'s marshaler. Can unmarshal from `pq.NullTime` and similar JSON input. ### Bugs diff --git a/time.go b/time.go index afdfe9a..de1f5b3 100644 --- a/time.go +++ b/time.go @@ -55,8 +55,7 @@ func TimeFrom(t time.Time) Time { // TimeFromPtr creates a new Time that will be null if t is nil. func TimeFromPtr(t *time.Time) Time { if t == nil { - var ti time.Time - return NewTime(ti, false) + return NewTime(time.Time{}, false) } return NewTime(*t, true) } @@ -71,7 +70,7 @@ func (t Time) MarshalJSON() ([]byte, error) { } // UnmarshalJSON implements json.Unmarshaler. -// It supports string, map[string]interface{}, +// It supports string, object (e.g. pq.NullTime and friends) // and null input. func (t *Time) UnmarshalJSON(data []byte) error { var err error @@ -86,9 +85,9 @@ func (t *Time) UnmarshalJSON(data []byte) error { ti, tiOK := x["Time"].(string) valid, validOK := x["Valid"].(bool) if !tiOK || !validOK { - return fmt.Errorf("json: unmarshalling object into Go value of type null.Time requires key \"Time\" to be of type string and key \"Valid\" to be of type bool; found %T and %T, respectively", x["Time"], x["Valid"]) + return fmt.Errorf(`json: unmarshalling object into Go value of type null.Time requires key "Time" to be of type string and key "Valid" to be of type bool; found %T and %T, respectively`, x["Time"], x["Valid"]) } - err = t.Time.UnmarshalJSON([]byte(`"` + ti + `"`)) + err = t.Time.UnmarshalText([]byte(ti)) t.Valid = valid return err case nil: diff --git a/time_test.go b/time_test.go index aebfec1..6e1b432 100644 --- a/time_test.go +++ b/time_test.go @@ -7,12 +7,13 @@ import ( ) var ( - timeString = "2012-12-21T21:21:21Z" - timeJSON = []byte(`"` + timeString + `"`) - blankTimeJSON = []byte(`null`) - timeValue, _ = time.Parse(time.RFC3339, timeString) - timeMap = []byte(`{"Time":"2012-12-21T21:21:21Z","Valid":true}`) - invalidMap = []byte(`{"Time":"0001-01-01T00:00:00Z","Valid":false}`) + timeString = "2012-12-21T21:21:21Z" + timeJSON = []byte(`"` + timeString + `"`) + nullTimeJSON = []byte(`null`) + timeValue, _ = time.Parse(time.RFC3339, timeString) + timeObject = []byte(`{"Time":"2012-12-21T21:21:21Z","Valid":true}`) + nullObject = []byte(`{"Time":"0001-01-01T00:00:00Z","Valid":false}`) + badObject = []byte(`{"hello": "world"}`) ) func TestUnmarshalTimeString(t *testing.T) { @@ -21,20 +22,41 @@ func TestUnmarshalTimeString(t *testing.T) { maybePanic(err) assertTime(t, ti, "UnmarshalJSON() json") - var blank Time - err = json.Unmarshal(blankTimeJSON, &blank) + var null Time + err = json.Unmarshal(nullTimeJSON, &null) maybePanic(err) - assertNullTime(t, blank, "blank time json") + assertNullTime(t, null, "null time json") - var fromMap Time - err = json.Unmarshal(timeMap, &fromMap) + var fromObject Time + err = json.Unmarshal(timeObject, &fromObject) maybePanic(err) - assertTime(t, fromMap, "map time json") + assertTime(t, fromObject, "time from object json") + + var nullFromObj Time + err = json.Unmarshal(nullObject, &nullFromObj) + maybePanic(err) + assertNullTime(t, nullFromObj, "null from object json") var invalid Time - err = json.Unmarshal(invalidMap, &invalid) - maybePanic(err) - assertNullTime(t, invalid, "map invalid time json") + err = invalid.UnmarshalJSON(invalidJSON) + if _, ok := err.(*json.SyntaxError); !ok { + t.Errorf("expected json.SyntaxError, not %T", err) + } + assertNullTime(t, invalid, "invalid from object json") + + var bad Time + err = json.Unmarshal(badObject, &bad) + if err == nil { + t.Errorf("expected error: bad object") + } + assertNullTime(t, bad, "bad from object json") + + var wrongType Time + err = json.Unmarshal(intJSON, &wrongType) + if err == nil { + t.Errorf("expected error: wrong type JSON") + } + assertNullTime(t, wrongType, "wrong type object json") } func TestMarshalTime(t *testing.T) { @@ -42,6 +64,11 @@ func TestMarshalTime(t *testing.T) { data, err := json.Marshal(ti) maybePanic(err) assertJSONEquals(t, data, string(timeJSON), "non-empty json marshal") + + ti.Valid = false + data, err = json.Marshal(ti) + maybePanic(err) + assertJSONEquals(t, data, string(nullJSON), "null json marshal") } func TestTimeFrom(t *testing.T) { @@ -80,16 +107,29 @@ func TestTimePointer(t *testing.T) { } } -func TestTimeScan(t *testing.T) { +func TestTimeScanValue(t *testing.T) { var ti Time err := ti.Scan(timeValue) maybePanic(err) assertTime(t, ti, "scanned time") + if v, err := ti.Value(); v != timeValue || err != nil { + t.Error("bad value or err:", v, err) + } var null Time err = null.Scan(nil) maybePanic(err) assertNullTime(t, null, "scanned null") + if v, err := null.Value(); v != nil || err != nil { + t.Error("bad value or err:", v, err) + } + + var wrong Time + err = wrong.Scan(int64(42)) + if err == nil { + t.Error("expected error") + } + assertNullTime(t, wrong, "scanned wrong") } func assertTime(t *testing.T, ti Time, from string) { diff --git a/zero/time.go b/zero/time.go index f2b3852..7af26c2 100644 --- a/zero/time.go +++ b/zero/time.go @@ -58,8 +58,7 @@ func TimeFrom(t time.Time) Time { // be null if t is nil or *t is the zero value. func TimeFromPtr(t *time.Time) Time { if t == nil { - var ti time.Time - return NewTime(ti, false) + return NewTime(time.Time{}, false) } return TimeFrom(*t) } @@ -69,14 +68,13 @@ func TimeFromPtr(t *time.Time) Time { // if this time is invalid. func (t Time) MarshalJSON() ([]byte, error) { if !t.Valid { - var ti time.Time - return json.Marshal(ti) + return (time.Time{}).MarshalJSON() } - return json.Marshal(t.Time) + return t.Time.MarshalJSON() } // UnmarshalJSON implements json.Unmarshaler. -// It supports string, map[string]interface{}, +// It supports string, object (e.g. pq.NullTime and friends) // and null input. func (t *Time) UnmarshalJSON(data []byte) error { var err error @@ -96,9 +94,9 @@ func (t *Time) UnmarshalJSON(data []byte) error { ti, tiOK := x["Time"].(string) valid, validOK := x["Valid"].(bool) if !tiOK || !validOK { - return fmt.Errorf("json: unmarshalling object into Go value of type null.Time requires key \"Time\" to be of type string and key \"Valid\" to be of type bool; found %T and %T, respectively", x["Time"], x["Valid"]) + return fmt.Errorf(`json: unmarshalling object into Go value of type null.Time requires key "Time" to be of type string and key "Valid" to be of type bool; found %T and %T, respectively`, x["Time"], x["Valid"]) } - err = t.Time.UnmarshalJSON([]byte(`"` + ti + `"`)) + err = t.Time.UnmarshalText([]byte(ti)) t.Valid = valid return err case nil: diff --git a/zero/time_test.go b/zero/time_test.go index bb6db03..b1bbe78 100644 --- a/zero/time_test.go +++ b/zero/time_test.go @@ -12,13 +12,14 @@ var ( zeroTimeJSON = []byte(`"0001-01-01T00:00:00Z"`) blankTimeJSON = []byte(`null`) timeValue, _ = time.Parse(time.RFC3339, timeString) - timeMap = []byte(`{"Time":"2012-12-21T21:21:21Z","Valid":true}`) - invalidMap = []byte(`{"Time":"0001-01-01T00:00:00Z","Valid":false}`) + timeObject = []byte(`{"Time":"2012-12-21T21:21:21Z","Valid":true}`) + nullObject = []byte(`{"Time":"0001-01-01T00:00:00Z","Valid":false}`) + badObject = []byte(`{"hello": "world"}`) ) func TestUnmarshalTimeString(t *testing.T) { var ti Time - err := json.Unmarshal(timeMap, &ti) + err := json.Unmarshal(timeObject, &ti) maybePanic(err) assertTime(t, ti, "UnmarshalJSON() json") @@ -32,15 +33,48 @@ func TestUnmarshalTimeString(t *testing.T) { maybePanic(err) assertNullTime(t, zero, "zero time json") - var fromMap Time - err = json.Unmarshal(timeMap, &fromMap) + var fromObject Time + err = json.Unmarshal(timeObject, &fromObject) maybePanic(err) - assertTime(t, fromMap, "map time json") + assertTime(t, fromObject, "map time json") + + var null Time + err = json.Unmarshal(nullObject, &null) + maybePanic(err) + assertNullTime(t, null, "map null time json") + + var nullFromObj Time + err = json.Unmarshal(nullObject, &nullFromObj) + maybePanic(err) + assertNullTime(t, nullFromObj, "null from object json") var invalid Time - err = json.Unmarshal(invalidMap, &invalid) - maybePanic(err) - assertNullTime(t, invalid, "map invalid time json") + err = invalid.UnmarshalJSON(invalidJSON) + if _, ok := err.(*json.SyntaxError); !ok { + t.Errorf("expected json.SyntaxError, not %T", err) + } + assertNullTime(t, invalid, "invalid from object json") + + var bad Time + err = json.Unmarshal(badObject, &bad) + if err == nil { + t.Errorf("expected error: bad object") + } + assertNullTime(t, bad, "bad from object json") + + var wrongType Time + err = json.Unmarshal(intJSON, &wrongType) + if err == nil { + t.Errorf("expected error: wrong type JSON") + } + assertNullTime(t, wrongType, "wrong type object json") + + var wrongString Time + err = json.Unmarshal(stringJSON, &wrongString) + if err == nil { + t.Errorf("expected error: wrong string JSON") + } + assertNullTime(t, wrongString, "wrong string object json") } func TestMarshalTime(t *testing.T) { @@ -105,6 +139,13 @@ func TestTimeScan(t *testing.T) { err = null.Scan(nil) maybePanic(err) assertNullTime(t, null, "scanned null") + + var wrong Time + err = wrong.Scan(int64(42)) + if err == nil { + t.Error("expected error") + } + assertNullTime(t, wrong, "scanned wrong") } func TestTimeValue(t *testing.T) {