New upstream version 3.1+git20160228.0.41961ce
Diego M. Rodriguez
7 years ago
0 | Copyright (c) 2014, Greg Roseberry | |
1 | All rights reserved. | |
2 | ||
3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | |
4 | ||
5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. | |
6 | ||
7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. | |
8 | ||
9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.⏎ |
0 | ## null [![GoDoc](https://godoc.org/github.com/guregu/null?status.svg)](https://godoc.org/github.com/guregu/null) [![Coverage](http://gocover.io/_badge/github.com/guregu/null)](http://gocover.io/github.com/guregu/null) | |
1 | `import "gopkg.in/guregu/null.v3"` | |
2 | ||
3 | null is a library with reasonable options for dealing with nullable SQL and JSON values | |
4 | ||
5 | There are two packages: `null` and its subpackage `zero`. | |
6 | ||
7 | Types in `null` will only be considered null on null input, and will JSON encode to `null`. If you need zero and null be considered separate values, use these. | |
8 | ||
9 | Types in `zero` are treated like zero values in Go: blank string input will produce a null `zero.String`, and null Strings will JSON encode to `""`. Zero values of these types will be considered null to SQL. If you need zero and null treated the same, use these. | |
10 | ||
11 | All types implement `sql.Scanner` and `driver.Valuer`, so you can use this library in place of `sql.NullXXX`. All types also implement: `encoding.TextMarshaler`, `encoding.TextUnmarshaler`, `json.Marshaler`, and `json.Unmarshaler`. | |
12 | ||
13 | ### null package | |
14 | ||
15 | `import "gopkg.in/guregu/null.v3"` | |
16 | ||
17 | #### null.String | |
18 | Nullable string. | |
19 | ||
20 | Marshals to JSON null if SQL source data is null. Zero (blank) input will not produce a null String. Can unmarshal from `sql.NullString` JSON input or string input. | |
21 | ||
22 | #### null.Int | |
23 | Nullable int64. | |
24 | ||
25 | 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. | |
26 | ||
27 | #### null.Float | |
28 | Nullable float64. | |
29 | ||
30 | 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. | |
31 | ||
32 | #### null.Bool | |
33 | Nullable bool. | |
34 | ||
35 | 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. | |
36 | ||
37 | #### null.Time | |
38 | ||
39 | Marshals to JSON null if SQL source data is null. Uses `time.Time`'s marshaler. Can unmarshal from `pq.NullTime` and similar JSON input. | |
40 | ||
41 | ### zero package | |
42 | ||
43 | `import "gopkg.in/guregu/null.v3/zero"` | |
44 | ||
45 | #### zero.String | |
46 | Nullable string. | |
47 | ||
48 | Will marshal to a blank string if null. Blank string input produces a null String. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullString` JSON input. | |
49 | ||
50 | #### zero.Int | |
51 | Nullable int64. | |
52 | ||
53 | 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. | |
54 | ||
55 | #### zero.Float | |
56 | Nullable float64. | |
57 | ||
58 | 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. | |
59 | ||
60 | #### zero.Bool | |
61 | Nullable bool. | |
62 | ||
63 | 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. | |
64 | ||
65 | #### zero.Time | |
66 | ||
67 | Will marshal to the zero time if null. Uses `time.Time`'s marshaler. Can unmarshal from `pq.NullTime` and similar JSON input. | |
68 | ||
69 | ||
70 | ### Bugs | |
71 | `json`'s `",omitempty"` struct tag does not work correctly right now. It will never omit a null or empty String. This might be [fixed eventually](https://github.com/golang/go/issues/4357). | |
72 | ||
73 | ### License | |
74 | BSD |
0 | package null | |
1 | ||
2 | import ( | |
3 | "database/sql" | |
4 | "encoding/json" | |
5 | "errors" | |
6 | "fmt" | |
7 | "reflect" | |
8 | ) | |
9 | ||
10 | // Bool is a nullable bool. | |
11 | // It does not consider false values to be null. | |
12 | // It will decode to null, not false, if null. | |
13 | type Bool struct { | |
14 | sql.NullBool | |
15 | } | |
16 | ||
17 | // NewBool creates a new Bool | |
18 | func NewBool(b bool, valid bool) Bool { | |
19 | return Bool{ | |
20 | NullBool: sql.NullBool{ | |
21 | Bool: b, | |
22 | Valid: valid, | |
23 | }, | |
24 | } | |
25 | } | |
26 | ||
27 | // BoolFrom creates a new Bool that will always be valid. | |
28 | func BoolFrom(b bool) Bool { | |
29 | return NewBool(b, true) | |
30 | } | |
31 | ||
32 | // BoolFromPtr creates a new Bool that will be null if f is nil. | |
33 | func BoolFromPtr(b *bool) Bool { | |
34 | if b == nil { | |
35 | return NewBool(false, false) | |
36 | } | |
37 | return NewBool(*b, true) | |
38 | } | |
39 | ||
40 | // UnmarshalJSON implements json.Unmarshaler. | |
41 | // It supports number and null input. | |
42 | // 0 will not be considered a null Bool. | |
43 | // It also supports unmarshalling a sql.NullBool. | |
44 | func (b *Bool) UnmarshalJSON(data []byte) error { | |
45 | var err error | |
46 | var v interface{} | |
47 | if err = json.Unmarshal(data, &v); err != nil { | |
48 | return err | |
49 | } | |
50 | switch x := v.(type) { | |
51 | case bool: | |
52 | b.Bool = x | |
53 | case map[string]interface{}: | |
54 | err = json.Unmarshal(data, &b.NullBool) | |
55 | case nil: | |
56 | b.Valid = false | |
57 | return nil | |
58 | default: | |
59 | err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Bool", reflect.TypeOf(v).Name()) | |
60 | } | |
61 | b.Valid = err == nil | |
62 | return err | |
63 | } | |
64 | ||
65 | // UnmarshalText implements encoding.TextUnmarshaler. | |
66 | // It will unmarshal to a null Bool if the input is a blank or not an integer. | |
67 | // It will return an error if the input is not an integer, blank, or "null". | |
68 | func (b *Bool) UnmarshalText(text []byte) error { | |
69 | str := string(text) | |
70 | switch str { | |
71 | case "", "null": | |
72 | b.Valid = false | |
73 | return nil | |
74 | case "true": | |
75 | b.Bool = true | |
76 | case "false": | |
77 | b.Bool = false | |
78 | default: | |
79 | b.Valid = false | |
80 | return errors.New("invalid input:" + str) | |
81 | } | |
82 | b.Valid = true | |
83 | return nil | |
84 | } | |
85 | ||
86 | // MarshalJSON implements json.Marshaler. | |
87 | // It will encode null if this Bool is null. | |
88 | func (b Bool) MarshalJSON() ([]byte, error) { | |
89 | if !b.Valid { | |
90 | return []byte("null"), nil | |
91 | } | |
92 | if !b.Bool { | |
93 | return []byte("false"), nil | |
94 | } | |
95 | return []byte("true"), nil | |
96 | } | |
97 | ||
98 | // MarshalText implements encoding.TextMarshaler. | |
99 | // It will encode a blank string if this Bool is null. | |
100 | func (b Bool) MarshalText() ([]byte, error) { | |
101 | if !b.Valid { | |
102 | return []byte{}, nil | |
103 | } | |
104 | if !b.Bool { | |
105 | return []byte("false"), nil | |
106 | } | |
107 | return []byte("true"), nil | |
108 | } | |
109 | ||
110 | // SetValid changes this Bool's value and also sets it to be non-null. | |
111 | func (b *Bool) SetValid(v bool) { | |
112 | b.Bool = v | |
113 | b.Valid = true | |
114 | } | |
115 | ||
116 | // Ptr returns a pointer to this Bool's value, or a nil pointer if this Bool is null. | |
117 | func (b Bool) Ptr() *bool { | |
118 | if !b.Valid { | |
119 | return nil | |
120 | } | |
121 | return &b.Bool | |
122 | } | |
123 | ||
124 | // IsZero returns true for invalid Bools, for future omitempty support (Go 1.4?) | |
125 | // A non-null Bool with a 0 value will not be considered zero. | |
126 | func (b Bool) IsZero() bool { | |
127 | return !b.Valid | |
128 | } |
0 | package null | |
1 | ||
2 | import ( | |
3 | "encoding/json" | |
4 | "testing" | |
5 | ) | |
6 | ||
7 | var ( | |
8 | boolJSON = []byte(`true`) | |
9 | falseJSON = []byte(`false`) | |
10 | nullBoolJSON = []byte(`{"Bool":true,"Valid":true}`) | |
11 | ) | |
12 | ||
13 | func TestBoolFrom(t *testing.T) { | |
14 | b := BoolFrom(true) | |
15 | assertBool(t, b, "BoolFrom()") | |
16 | ||
17 | zero := BoolFrom(false) | |
18 | if !zero.Valid { | |
19 | t.Error("BoolFrom(false)", "is invalid, but should be valid") | |
20 | } | |
21 | } | |
22 | ||
23 | func TestBoolFromPtr(t *testing.T) { | |
24 | n := true | |
25 | bptr := &n | |
26 | b := BoolFromPtr(bptr) | |
27 | assertBool(t, b, "BoolFromPtr()") | |
28 | ||
29 | null := BoolFromPtr(nil) | |
30 | assertNullBool(t, null, "BoolFromPtr(nil)") | |
31 | } | |
32 | ||
33 | func TestUnmarshalBool(t *testing.T) { | |
34 | var b Bool | |
35 | err := json.Unmarshal(boolJSON, &b) | |
36 | maybePanic(err) | |
37 | assertBool(t, b, "bool json") | |
38 | ||
39 | var nb Bool | |
40 | err = json.Unmarshal(nullBoolJSON, &nb) | |
41 | maybePanic(err) | |
42 | assertBool(t, nb, "sq.NullBool json") | |
43 | ||
44 | var null Bool | |
45 | err = json.Unmarshal(nullJSON, &null) | |
46 | maybePanic(err) | |
47 | assertNullBool(t, null, "null json") | |
48 | ||
49 | var badType Bool | |
50 | err = json.Unmarshal(intJSON, &badType) | |
51 | if err == nil { | |
52 | panic("err should not be nil") | |
53 | } | |
54 | assertNullBool(t, badType, "wrong type json") | |
55 | ||
56 | var invalid Bool | |
57 | err = invalid.UnmarshalJSON(invalidJSON) | |
58 | if _, ok := err.(*json.SyntaxError); !ok { | |
59 | t.Errorf("expected json.SyntaxError, not %T", err) | |
60 | } | |
61 | } | |
62 | ||
63 | func TestTextUnmarshalBool(t *testing.T) { | |
64 | var b Bool | |
65 | err := b.UnmarshalText([]byte("true")) | |
66 | maybePanic(err) | |
67 | assertBool(t, b, "UnmarshalText() bool") | |
68 | ||
69 | var zero Bool | |
70 | err = zero.UnmarshalText([]byte("false")) | |
71 | maybePanic(err) | |
72 | assertFalseBool(t, zero, "UnmarshalText() false") | |
73 | ||
74 | var blank Bool | |
75 | err = blank.UnmarshalText([]byte("")) | |
76 | maybePanic(err) | |
77 | assertNullBool(t, blank, "UnmarshalText() empty bool") | |
78 | ||
79 | var null Bool | |
80 | err = null.UnmarshalText([]byte("null")) | |
81 | maybePanic(err) | |
82 | assertNullBool(t, null, `UnmarshalText() "null"`) | |
83 | ||
84 | var invalid Bool | |
85 | err = invalid.UnmarshalText([]byte(":D")) | |
86 | if err == nil { | |
87 | panic("err should not be nil") | |
88 | } | |
89 | assertNullBool(t, invalid, "invalid json") | |
90 | } | |
91 | ||
92 | func TestMarshalBool(t *testing.T) { | |
93 | b := BoolFrom(true) | |
94 | data, err := json.Marshal(b) | |
95 | maybePanic(err) | |
96 | assertJSONEquals(t, data, "true", "non-empty json marshal") | |
97 | ||
98 | zero := NewBool(false, true) | |
99 | data, err = json.Marshal(zero) | |
100 | maybePanic(err) | |
101 | assertJSONEquals(t, data, "false", "zero json marshal") | |
102 | ||
103 | // invalid values should be encoded as null | |
104 | null := NewBool(false, false) | |
105 | data, err = json.Marshal(null) | |
106 | maybePanic(err) | |
107 | assertJSONEquals(t, data, "null", "null json marshal") | |
108 | } | |
109 | ||
110 | func TestMarshalBoolText(t *testing.T) { | |
111 | b := BoolFrom(true) | |
112 | data, err := b.MarshalText() | |
113 | maybePanic(err) | |
114 | assertJSONEquals(t, data, "true", "non-empty text marshal") | |
115 | ||
116 | zero := NewBool(false, true) | |
117 | data, err = zero.MarshalText() | |
118 | maybePanic(err) | |
119 | assertJSONEquals(t, data, "false", "zero text marshal") | |
120 | ||
121 | // invalid values should be encoded as null | |
122 | null := NewBool(false, false) | |
123 | data, err = null.MarshalText() | |
124 | maybePanic(err) | |
125 | assertJSONEquals(t, data, "", "null text marshal") | |
126 | } | |
127 | ||
128 | func TestBoolPointer(t *testing.T) { | |
129 | b := BoolFrom(true) | |
130 | ptr := b.Ptr() | |
131 | if *ptr != true { | |
132 | t.Errorf("bad %s bool: %#v ≠ %v\n", "pointer", ptr, true) | |
133 | } | |
134 | ||
135 | null := NewBool(false, false) | |
136 | ptr = null.Ptr() | |
137 | if ptr != nil { | |
138 | t.Errorf("bad %s bool: %#v ≠ %s\n", "nil pointer", ptr, "nil") | |
139 | } | |
140 | } | |
141 | ||
142 | func TestBoolIsZero(t *testing.T) { | |
143 | b := BoolFrom(true) | |
144 | if b.IsZero() { | |
145 | t.Errorf("IsZero() should be false") | |
146 | } | |
147 | ||
148 | null := NewBool(false, false) | |
149 | if !null.IsZero() { | |
150 | t.Errorf("IsZero() should be true") | |
151 | } | |
152 | ||
153 | zero := NewBool(false, true) | |
154 | if zero.IsZero() { | |
155 | t.Errorf("IsZero() should be false") | |
156 | } | |
157 | } | |
158 | ||
159 | func TestBoolSetValid(t *testing.T) { | |
160 | change := NewBool(false, false) | |
161 | assertNullBool(t, change, "SetValid()") | |
162 | change.SetValid(true) | |
163 | assertBool(t, change, "SetValid()") | |
164 | } | |
165 | ||
166 | func TestBoolScan(t *testing.T) { | |
167 | var b Bool | |
168 | err := b.Scan(true) | |
169 | maybePanic(err) | |
170 | assertBool(t, b, "scanned bool") | |
171 | ||
172 | var null Bool | |
173 | err = null.Scan(nil) | |
174 | maybePanic(err) | |
175 | assertNullBool(t, null, "scanned null") | |
176 | } | |
177 | ||
178 | func assertBool(t *testing.T, b Bool, from string) { | |
179 | if b.Bool != true { | |
180 | t.Errorf("bad %s bool: %v ≠ %v\n", from, b.Bool, true) | |
181 | } | |
182 | if !b.Valid { | |
183 | t.Error(from, "is invalid, but should be valid") | |
184 | } | |
185 | } | |
186 | ||
187 | func assertFalseBool(t *testing.T, b Bool, from string) { | |
188 | if b.Bool != false { | |
189 | t.Errorf("bad %s bool: %v ≠ %v\n", from, b.Bool, false) | |
190 | } | |
191 | if !b.Valid { | |
192 | t.Error(from, "is invalid, but should be valid") | |
193 | } | |
194 | } | |
195 | ||
196 | func assertNullBool(t *testing.T, b Bool, from string) { | |
197 | if b.Valid { | |
198 | t.Error(from, "is valid, but should be invalid") | |
199 | } | |
200 | } |
0 | package null | |
1 | ||
2 | import ( | |
3 | "database/sql" | |
4 | "encoding/json" | |
5 | "fmt" | |
6 | "reflect" | |
7 | "strconv" | |
8 | ) | |
9 | ||
10 | // Float is a nullable float64. | |
11 | // It does not consider zero values to be null. | |
12 | // It will decode to null, not zero, if null. | |
13 | type Float struct { | |
14 | sql.NullFloat64 | |
15 | } | |
16 | ||
17 | // NewFloat creates a new Float | |
18 | func NewFloat(f float64, valid bool) Float { | |
19 | return Float{ | |
20 | NullFloat64: sql.NullFloat64{ | |
21 | Float64: f, | |
22 | Valid: valid, | |
23 | }, | |
24 | } | |
25 | } | |
26 | ||
27 | // FloatFrom creates a new Float that will always be valid. | |
28 | func FloatFrom(f float64) Float { | |
29 | return NewFloat(f, true) | |
30 | } | |
31 | ||
32 | // FloatFromPtr creates a new Float that be null if f is nil. | |
33 | func FloatFromPtr(f *float64) Float { | |
34 | if f == nil { | |
35 | return NewFloat(0, false) | |
36 | } | |
37 | return NewFloat(*f, true) | |
38 | } | |
39 | ||
40 | // UnmarshalJSON implements json.Unmarshaler. | |
41 | // It supports number and null input. | |
42 | // 0 will not be considered a null Float. | |
43 | // It also supports unmarshalling a sql.NullFloat64. | |
44 | func (f *Float) UnmarshalJSON(data []byte) error { | |
45 | var err error | |
46 | var v interface{} | |
47 | if err = json.Unmarshal(data, &v); err != nil { | |
48 | return err | |
49 | } | |
50 | switch x := v.(type) { | |
51 | case float64: | |
52 | f.Float64 = float64(x) | |
53 | case map[string]interface{}: | |
54 | err = json.Unmarshal(data, &f.NullFloat64) | |
55 | case nil: | |
56 | f.Valid = false | |
57 | return nil | |
58 | default: | |
59 | err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Float", reflect.TypeOf(v).Name()) | |
60 | } | |
61 | f.Valid = err == nil | |
62 | return err | |
63 | } | |
64 | ||
65 | // UnmarshalText implements encoding.TextUnmarshaler. | |
66 | // It will unmarshal to a null Float if the input is a blank or not an integer. | |
67 | // It will return an error if the input is not an integer, blank, or "null". | |
68 | func (f *Float) UnmarshalText(text []byte) error { | |
69 | str := string(text) | |
70 | if str == "" || str == "null" { | |
71 | f.Valid = false | |
72 | return nil | |
73 | } | |
74 | var err error | |
75 | f.Float64, err = strconv.ParseFloat(string(text), 64) | |
76 | f.Valid = err == nil | |
77 | return err | |
78 | } | |
79 | ||
80 | // MarshalJSON implements json.Marshaler. | |
81 | // It will encode null if this Float is null. | |
82 | func (f Float) MarshalJSON() ([]byte, error) { | |
83 | if !f.Valid { | |
84 | return []byte("null"), nil | |
85 | } | |
86 | return []byte(strconv.FormatFloat(f.Float64, 'f', -1, 64)), nil | |
87 | } | |
88 | ||
89 | // MarshalText implements encoding.TextMarshaler. | |
90 | // It will encode a blank string if this Float is null. | |
91 | func (f Float) MarshalText() ([]byte, error) { | |
92 | if !f.Valid { | |
93 | return []byte{}, nil | |
94 | } | |
95 | return []byte(strconv.FormatFloat(f.Float64, 'f', -1, 64)), nil | |
96 | } | |
97 | ||
98 | // SetValid changes this Float's value and also sets it to be non-null. | |
99 | func (f *Float) SetValid(n float64) { | |
100 | f.Float64 = n | |
101 | f.Valid = true | |
102 | } | |
103 | ||
104 | // Ptr returns a pointer to this Float's value, or a nil pointer if this Float is null. | |
105 | func (f Float) Ptr() *float64 { | |
106 | if !f.Valid { | |
107 | return nil | |
108 | } | |
109 | return &f.Float64 | |
110 | } | |
111 | ||
112 | // IsZero returns true for invalid Floats, for future omitempty support (Go 1.4?) | |
113 | // A non-null Float with a 0 value will not be considered zero. | |
114 | func (f Float) IsZero() bool { | |
115 | return !f.Valid | |
116 | } |
0 | package null | |
1 | ||
2 | import ( | |
3 | "encoding/json" | |
4 | "testing" | |
5 | ) | |
6 | ||
7 | var ( | |
8 | floatJSON = []byte(`1.2345`) | |
9 | nullFloatJSON = []byte(`{"Float64":1.2345,"Valid":true}`) | |
10 | ) | |
11 | ||
12 | func TestFloatFrom(t *testing.T) { | |
13 | f := FloatFrom(1.2345) | |
14 | assertFloat(t, f, "FloatFrom()") | |
15 | ||
16 | zero := FloatFrom(0) | |
17 | if !zero.Valid { | |
18 | t.Error("FloatFrom(0)", "is invalid, but should be valid") | |
19 | } | |
20 | } | |
21 | ||
22 | func TestFloatFromPtr(t *testing.T) { | |
23 | n := float64(1.2345) | |
24 | iptr := &n | |
25 | f := FloatFromPtr(iptr) | |
26 | assertFloat(t, f, "FloatFromPtr()") | |
27 | ||
28 | null := FloatFromPtr(nil) | |
29 | assertNullFloat(t, null, "FloatFromPtr(nil)") | |
30 | } | |
31 | ||
32 | func TestUnmarshalFloat(t *testing.T) { | |
33 | var f Float | |
34 | err := json.Unmarshal(floatJSON, &f) | |
35 | maybePanic(err) | |
36 | assertFloat(t, f, "float json") | |
37 | ||
38 | var nf Float | |
39 | err = json.Unmarshal(nullFloatJSON, &nf) | |
40 | maybePanic(err) | |
41 | assertFloat(t, nf, "sq.NullFloat64 json") | |
42 | ||
43 | var null Float | |
44 | err = json.Unmarshal(nullJSON, &null) | |
45 | maybePanic(err) | |
46 | assertNullFloat(t, null, "null json") | |
47 | ||
48 | var badType Float | |
49 | err = json.Unmarshal(boolJSON, &badType) | |
50 | if err == nil { | |
51 | panic("err should not be nil") | |
52 | } | |
53 | assertNullFloat(t, badType, "wrong type json") | |
54 | ||
55 | var invalid Float | |
56 | err = invalid.UnmarshalJSON(invalidJSON) | |
57 | if _, ok := err.(*json.SyntaxError); !ok { | |
58 | t.Errorf("expected json.SyntaxError, not %T", err) | |
59 | } | |
60 | } | |
61 | ||
62 | func TestTextUnmarshalFloat(t *testing.T) { | |
63 | var f Float | |
64 | err := f.UnmarshalText([]byte("1.2345")) | |
65 | maybePanic(err) | |
66 | assertFloat(t, f, "UnmarshalText() float") | |
67 | ||
68 | var blank Float | |
69 | err = blank.UnmarshalText([]byte("")) | |
70 | maybePanic(err) | |
71 | assertNullFloat(t, blank, "UnmarshalText() empty float") | |
72 | ||
73 | var null Float | |
74 | err = null.UnmarshalText([]byte("null")) | |
75 | maybePanic(err) | |
76 | assertNullFloat(t, null, `UnmarshalText() "null"`) | |
77 | } | |
78 | ||
79 | func TestMarshalFloat(t *testing.T) { | |
80 | f := FloatFrom(1.2345) | |
81 | data, err := json.Marshal(f) | |
82 | maybePanic(err) | |
83 | assertJSONEquals(t, data, "1.2345", "non-empty json marshal") | |
84 | ||
85 | // invalid values should be encoded as null | |
86 | null := NewFloat(0, false) | |
87 | data, err = json.Marshal(null) | |
88 | maybePanic(err) | |
89 | assertJSONEquals(t, data, "null", "null json marshal") | |
90 | } | |
91 | ||
92 | func TestMarshalFloatText(t *testing.T) { | |
93 | f := FloatFrom(1.2345) | |
94 | data, err := f.MarshalText() | |
95 | maybePanic(err) | |
96 | assertJSONEquals(t, data, "1.2345", "non-empty text marshal") | |
97 | ||
98 | // invalid values should be encoded as null | |
99 | null := NewFloat(0, false) | |
100 | data, err = null.MarshalText() | |
101 | maybePanic(err) | |
102 | assertJSONEquals(t, data, "", "null text marshal") | |
103 | } | |
104 | ||
105 | func TestFloatPointer(t *testing.T) { | |
106 | f := FloatFrom(1.2345) | |
107 | ptr := f.Ptr() | |
108 | if *ptr != 1.2345 { | |
109 | t.Errorf("bad %s float: %#v ≠ %v\n", "pointer", ptr, 1.2345) | |
110 | } | |
111 | ||
112 | null := NewFloat(0, false) | |
113 | ptr = null.Ptr() | |
114 | if ptr != nil { | |
115 | t.Errorf("bad %s float: %#v ≠ %s\n", "nil pointer", ptr, "nil") | |
116 | } | |
117 | } | |
118 | ||
119 | func TestFloatIsZero(t *testing.T) { | |
120 | f := FloatFrom(1.2345) | |
121 | if f.IsZero() { | |
122 | t.Errorf("IsZero() should be false") | |
123 | } | |
124 | ||
125 | null := NewFloat(0, false) | |
126 | if !null.IsZero() { | |
127 | t.Errorf("IsZero() should be true") | |
128 | } | |
129 | ||
130 | zero := NewFloat(0, true) | |
131 | if zero.IsZero() { | |
132 | t.Errorf("IsZero() should be false") | |
133 | } | |
134 | } | |
135 | ||
136 | func TestFloatSetValid(t *testing.T) { | |
137 | change := NewFloat(0, false) | |
138 | assertNullFloat(t, change, "SetValid()") | |
139 | change.SetValid(1.2345) | |
140 | assertFloat(t, change, "SetValid()") | |
141 | } | |
142 | ||
143 | func TestFloatScan(t *testing.T) { | |
144 | var f Float | |
145 | err := f.Scan(1.2345) | |
146 | maybePanic(err) | |
147 | assertFloat(t, f, "scanned float") | |
148 | ||
149 | var null Float | |
150 | err = null.Scan(nil) | |
151 | maybePanic(err) | |
152 | assertNullFloat(t, null, "scanned null") | |
153 | } | |
154 | ||
155 | func assertFloat(t *testing.T, f Float, from string) { | |
156 | if f.Float64 != 1.2345 { | |
157 | t.Errorf("bad %s float: %f ≠ %f\n", from, f.Float64, 1.2345) | |
158 | } | |
159 | if !f.Valid { | |
160 | t.Error(from, "is invalid, but should be valid") | |
161 | } | |
162 | } | |
163 | ||
164 | func assertNullFloat(t *testing.T, f Float, from string) { | |
165 | if f.Valid { | |
166 | t.Error(from, "is valid, but should be invalid") | |
167 | } | |
168 | } |
0 | package null | |
1 | ||
2 | import ( | |
3 | "database/sql" | |
4 | "encoding/json" | |
5 | "fmt" | |
6 | "reflect" | |
7 | "strconv" | |
8 | ) | |
9 | ||
10 | // Int is an nullable int64. | |
11 | // It does not consider zero values to be null. | |
12 | // It will decode to null, not zero, if null. | |
13 | type Int struct { | |
14 | sql.NullInt64 | |
15 | } | |
16 | ||
17 | // NewInt creates a new Int | |
18 | func NewInt(i int64, valid bool) Int { | |
19 | return Int{ | |
20 | NullInt64: sql.NullInt64{ | |
21 | Int64: i, | |
22 | Valid: valid, | |
23 | }, | |
24 | } | |
25 | } | |
26 | ||
27 | // IntFrom creates a new Int that will always be valid. | |
28 | func IntFrom(i int64) Int { | |
29 | return NewInt(i, true) | |
30 | } | |
31 | ||
32 | // IntFromPtr creates a new Int that be null if i is nil. | |
33 | func IntFromPtr(i *int64) Int { | |
34 | if i == nil { | |
35 | return NewInt(0, false) | |
36 | } | |
37 | return NewInt(*i, true) | |
38 | } | |
39 | ||
40 | // UnmarshalJSON implements json.Unmarshaler. | |
41 | // It supports number and null input. | |
42 | // 0 will not be considered a null Int. | |
43 | // It also supports unmarshalling a sql.NullInt64. | |
44 | func (i *Int) UnmarshalJSON(data []byte) error { | |
45 | var err error | |
46 | var v interface{} | |
47 | if err = json.Unmarshal(data, &v); err != nil { | |
48 | return err | |
49 | } | |
50 | switch v.(type) { | |
51 | case float64: | |
52 | // Unmarshal again, directly to int64, to avoid intermediate float64 | |
53 | err = json.Unmarshal(data, &i.Int64) | |
54 | case map[string]interface{}: | |
55 | err = json.Unmarshal(data, &i.NullInt64) | |
56 | case nil: | |
57 | i.Valid = false | |
58 | return nil | |
59 | default: | |
60 | err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Int", reflect.TypeOf(v).Name()) | |
61 | } | |
62 | i.Valid = err == nil | |
63 | return err | |
64 | } | |
65 | ||
66 | // UnmarshalText implements encoding.TextUnmarshaler. | |
67 | // It will unmarshal to a null Int if the input is a blank or not an integer. | |
68 | // It will return an error if the input is not an integer, blank, or "null". | |
69 | func (i *Int) UnmarshalText(text []byte) error { | |
70 | str := string(text) | |
71 | if str == "" || str == "null" { | |
72 | i.Valid = false | |
73 | return nil | |
74 | } | |
75 | var err error | |
76 | i.Int64, err = strconv.ParseInt(string(text), 10, 64) | |
77 | i.Valid = err == nil | |
78 | return err | |
79 | } | |
80 | ||
81 | // MarshalJSON implements json.Marshaler. | |
82 | // It will encode null if this Int is null. | |
83 | func (i Int) MarshalJSON() ([]byte, error) { | |
84 | if !i.Valid { | |
85 | return []byte("null"), nil | |
86 | } | |
87 | return []byte(strconv.FormatInt(i.Int64, 10)), nil | |
88 | } | |
89 | ||
90 | // MarshalText implements encoding.TextMarshaler. | |
91 | // It will encode a blank string if this Int is null. | |
92 | func (i Int) MarshalText() ([]byte, error) { | |
93 | if !i.Valid { | |
94 | return []byte{}, nil | |
95 | } | |
96 | return []byte(strconv.FormatInt(i.Int64, 10)), nil | |
97 | } | |
98 | ||
99 | // SetValid changes this Int's value and also sets it to be non-null. | |
100 | func (i *Int) SetValid(n int64) { | |
101 | i.Int64 = n | |
102 | i.Valid = true | |
103 | } | |
104 | ||
105 | // Ptr returns a pointer to this Int's value, or a nil pointer if this Int is null. | |
106 | func (i Int) Ptr() *int64 { | |
107 | if !i.Valid { | |
108 | return nil | |
109 | } | |
110 | return &i.Int64 | |
111 | } | |
112 | ||
113 | // IsZero returns true for invalid Ints, for future omitempty support (Go 1.4?) | |
114 | // A non-null Int with a 0 value will not be considered zero. | |
115 | func (i Int) IsZero() bool { | |
116 | return !i.Valid | |
117 | } |
0 | package null | |
1 | ||
2 | import ( | |
3 | "encoding/json" | |
4 | "math" | |
5 | "strconv" | |
6 | "testing" | |
7 | ) | |
8 | ||
9 | var ( | |
10 | intJSON = []byte(`12345`) | |
11 | nullIntJSON = []byte(`{"Int64":12345,"Valid":true}`) | |
12 | ) | |
13 | ||
14 | func TestIntFrom(t *testing.T) { | |
15 | i := IntFrom(12345) | |
16 | assertInt(t, i, "IntFrom()") | |
17 | ||
18 | zero := IntFrom(0) | |
19 | if !zero.Valid { | |
20 | t.Error("IntFrom(0)", "is invalid, but should be valid") | |
21 | } | |
22 | } | |
23 | ||
24 | func TestIntFromPtr(t *testing.T) { | |
25 | n := int64(12345) | |
26 | iptr := &n | |
27 | i := IntFromPtr(iptr) | |
28 | assertInt(t, i, "IntFromPtr()") | |
29 | ||
30 | null := IntFromPtr(nil) | |
31 | assertNullInt(t, null, "IntFromPtr(nil)") | |
32 | } | |
33 | ||
34 | func TestUnmarshalInt(t *testing.T) { | |
35 | var i Int | |
36 | err := json.Unmarshal(intJSON, &i) | |
37 | maybePanic(err) | |
38 | assertInt(t, i, "int json") | |
39 | ||
40 | var ni Int | |
41 | err = json.Unmarshal(nullIntJSON, &ni) | |
42 | maybePanic(err) | |
43 | assertInt(t, ni, "sq.NullInt64 json") | |
44 | ||
45 | var null Int | |
46 | err = json.Unmarshal(nullJSON, &null) | |
47 | maybePanic(err) | |
48 | assertNullInt(t, null, "null json") | |
49 | ||
50 | var badType Int | |
51 | err = json.Unmarshal(boolJSON, &badType) | |
52 | if err == nil { | |
53 | panic("err should not be nil") | |
54 | } | |
55 | assertNullInt(t, badType, "wrong type json") | |
56 | ||
57 | var invalid Int | |
58 | err = invalid.UnmarshalJSON(invalidJSON) | |
59 | if _, ok := err.(*json.SyntaxError); !ok { | |
60 | t.Errorf("expected json.SyntaxError, not %T", err) | |
61 | } | |
62 | assertNullInt(t, invalid, "invalid json") | |
63 | } | |
64 | ||
65 | func TestUnmarshalNonIntegerNumber(t *testing.T) { | |
66 | var i Int | |
67 | err := json.Unmarshal(floatJSON, &i) | |
68 | if err == nil { | |
69 | panic("err should be present; non-integer number coerced to int") | |
70 | } | |
71 | } | |
72 | ||
73 | func TestUnmarshalInt64Overflow(t *testing.T) { | |
74 | int64Overflow := uint64(math.MaxInt64) | |
75 | ||
76 | // Max int64 should decode successfully | |
77 | var i Int | |
78 | err := json.Unmarshal([]byte(strconv.FormatUint(int64Overflow, 10)), &i) | |
79 | maybePanic(err) | |
80 | ||
81 | // Attempt to overflow | |
82 | int64Overflow++ | |
83 | err = json.Unmarshal([]byte(strconv.FormatUint(int64Overflow, 10)), &i) | |
84 | if err == nil { | |
85 | panic("err should be present; decoded value overflows int64") | |
86 | } | |
87 | } | |
88 | ||
89 | func TestTextUnmarshalInt(t *testing.T) { | |
90 | var i Int | |
91 | err := i.UnmarshalText([]byte("12345")) | |
92 | maybePanic(err) | |
93 | assertInt(t, i, "UnmarshalText() int") | |
94 | ||
95 | var blank Int | |
96 | err = blank.UnmarshalText([]byte("")) | |
97 | maybePanic(err) | |
98 | assertNullInt(t, blank, "UnmarshalText() empty int") | |
99 | ||
100 | var null Int | |
101 | err = null.UnmarshalText([]byte("null")) | |
102 | maybePanic(err) | |
103 | assertNullInt(t, null, `UnmarshalText() "null"`) | |
104 | } | |
105 | ||
106 | func TestMarshalInt(t *testing.T) { | |
107 | i := IntFrom(12345) | |
108 | data, err := json.Marshal(i) | |
109 | maybePanic(err) | |
110 | assertJSONEquals(t, data, "12345", "non-empty json marshal") | |
111 | ||
112 | // invalid values should be encoded as null | |
113 | null := NewInt(0, false) | |
114 | data, err = json.Marshal(null) | |
115 | maybePanic(err) | |
116 | assertJSONEquals(t, data, "null", "null json marshal") | |
117 | } | |
118 | ||
119 | func TestMarshalIntText(t *testing.T) { | |
120 | i := IntFrom(12345) | |
121 | data, err := i.MarshalText() | |
122 | maybePanic(err) | |
123 | assertJSONEquals(t, data, "12345", "non-empty text marshal") | |
124 | ||
125 | // invalid values should be encoded as null | |
126 | null := NewInt(0, false) | |
127 | data, err = null.MarshalText() | |
128 | maybePanic(err) | |
129 | assertJSONEquals(t, data, "", "null text marshal") | |
130 | } | |
131 | ||
132 | func TestIntPointer(t *testing.T) { | |
133 | i := IntFrom(12345) | |
134 | ptr := i.Ptr() | |
135 | if *ptr != 12345 { | |
136 | t.Errorf("bad %s int: %#v ≠ %d\n", "pointer", ptr, 12345) | |
137 | } | |
138 | ||
139 | null := NewInt(0, false) | |
140 | ptr = null.Ptr() | |
141 | if ptr != nil { | |
142 | t.Errorf("bad %s int: %#v ≠ %s\n", "nil pointer", ptr, "nil") | |
143 | } | |
144 | } | |
145 | ||
146 | func TestIntIsZero(t *testing.T) { | |
147 | i := IntFrom(12345) | |
148 | if i.IsZero() { | |
149 | t.Errorf("IsZero() should be false") | |
150 | } | |
151 | ||
152 | null := NewInt(0, false) | |
153 | if !null.IsZero() { | |
154 | t.Errorf("IsZero() should be true") | |
155 | } | |
156 | ||
157 | zero := NewInt(0, true) | |
158 | if zero.IsZero() { | |
159 | t.Errorf("IsZero() should be false") | |
160 | } | |
161 | } | |
162 | ||
163 | func TestIntSetValid(t *testing.T) { | |
164 | change := NewInt(0, false) | |
165 | assertNullInt(t, change, "SetValid()") | |
166 | change.SetValid(12345) | |
167 | assertInt(t, change, "SetValid()") | |
168 | } | |
169 | ||
170 | func TestIntScan(t *testing.T) { | |
171 | var i Int | |
172 | err := i.Scan(12345) | |
173 | maybePanic(err) | |
174 | assertInt(t, i, "scanned int") | |
175 | ||
176 | var null Int | |
177 | err = null.Scan(nil) | |
178 | maybePanic(err) | |
179 | assertNullInt(t, null, "scanned null") | |
180 | } | |
181 | ||
182 | func assertInt(t *testing.T, i Int, from string) { | |
183 | if i.Int64 != 12345 { | |
184 | t.Errorf("bad %s int: %d ≠ %d\n", from, i.Int64, 12345) | |
185 | } | |
186 | if !i.Valid { | |
187 | t.Error(from, "is invalid, but should be valid") | |
188 | } | |
189 | } | |
190 | ||
191 | func assertNullInt(t *testing.T, i Int, from string) { | |
192 | if i.Valid { | |
193 | t.Error(from, "is valid, but should be invalid") | |
194 | } | |
195 | } |
0 | // Package null contains SQL types that consider zero input and null input as separate values, | |
1 | // with convenient support for JSON and text marshaling. | |
2 | // Types in this package will always encode to their null value if null. | |
3 | // Use the zero subpackage if you want zero values and null to be treated the same. | |
4 | package null | |
5 | ||
6 | import ( | |
7 | "database/sql" | |
8 | "encoding/json" | |
9 | "fmt" | |
10 | "reflect" | |
11 | ) | |
12 | ||
13 | // String is a nullable string. It supports SQL and JSON serialization. | |
14 | // It will marshal to null if null. Blank string input will be considered null. | |
15 | type String struct { | |
16 | sql.NullString | |
17 | } | |
18 | ||
19 | // StringFrom creates a new String that will never be blank. | |
20 | func StringFrom(s string) String { | |
21 | return NewString(s, true) | |
22 | } | |
23 | ||
24 | // StringFromPtr creates a new String that be null if s is nil. | |
25 | func StringFromPtr(s *string) String { | |
26 | if s == nil { | |
27 | return NewString("", false) | |
28 | } | |
29 | return NewString(*s, true) | |
30 | } | |
31 | ||
32 | // NewString creates a new String | |
33 | func NewString(s string, valid bool) String { | |
34 | return String{ | |
35 | NullString: sql.NullString{ | |
36 | String: s, | |
37 | Valid: valid, | |
38 | }, | |
39 | } | |
40 | } | |
41 | ||
42 | // UnmarshalJSON implements json.Unmarshaler. | |
43 | // It supports string and null input. Blank string input does not produce a null String. | |
44 | // It also supports unmarshalling a sql.NullString. | |
45 | func (s *String) UnmarshalJSON(data []byte) error { | |
46 | var err error | |
47 | var v interface{} | |
48 | if err = json.Unmarshal(data, &v); err != nil { | |
49 | return err | |
50 | } | |
51 | switch x := v.(type) { | |
52 | case string: | |
53 | s.String = x | |
54 | case map[string]interface{}: | |
55 | err = json.Unmarshal(data, &s.NullString) | |
56 | case nil: | |
57 | s.Valid = false | |
58 | return nil | |
59 | default: | |
60 | err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.String", reflect.TypeOf(v).Name()) | |
61 | } | |
62 | s.Valid = err == nil | |
63 | return err | |
64 | } | |
65 | ||
66 | // MarshalJSON implements json.Marshaler. | |
67 | // It will encode null if this String is null. | |
68 | func (s String) MarshalJSON() ([]byte, error) { | |
69 | if !s.Valid { | |
70 | return []byte("null"), nil | |
71 | } | |
72 | return json.Marshal(s.String) | |
73 | } | |
74 | ||
75 | // MarshalText implements encoding.TextMarshaler. | |
76 | // It will encode a blank string when this String is null. | |
77 | func (s String) MarshalText() ([]byte, error) { | |
78 | if !s.Valid { | |
79 | return []byte{}, nil | |
80 | } | |
81 | return []byte(s.String), nil | |
82 | } | |
83 | ||
84 | // UnmarshalText implements encoding.TextUnmarshaler. | |
85 | // It will unmarshal to a null String if the input is a blank string. | |
86 | func (s *String) UnmarshalText(text []byte) error { | |
87 | s.String = string(text) | |
88 | s.Valid = s.String != "" | |
89 | return nil | |
90 | } | |
91 | ||
92 | // SetValid changes this String's value and also sets it to be non-null. | |
93 | func (s *String) SetValid(v string) { | |
94 | s.String = v | |
95 | s.Valid = true | |
96 | } | |
97 | ||
98 | // Ptr returns a pointer to this String's value, or a nil pointer if this String is null. | |
99 | func (s String) Ptr() *string { | |
100 | if !s.Valid { | |
101 | return nil | |
102 | } | |
103 | return &s.String | |
104 | } | |
105 | ||
106 | // IsZero returns true for null strings, for potential future omitempty support. | |
107 | func (s String) IsZero() bool { | |
108 | return !s.Valid | |
109 | } |
0 | package null | |
1 | ||
2 | import ( | |
3 | "encoding/json" | |
4 | "testing" | |
5 | ) | |
6 | ||
7 | var ( | |
8 | stringJSON = []byte(`"test"`) | |
9 | blankStringJSON = []byte(`""`) | |
10 | nullStringJSON = []byte(`{"String":"test","Valid":true}`) | |
11 | ||
12 | nullJSON = []byte(`null`) | |
13 | invalidJSON = []byte(`:)`) | |
14 | ) | |
15 | ||
16 | type stringInStruct struct { | |
17 | Test String `json:"test,omitempty"` | |
18 | } | |
19 | ||
20 | func TestStringFrom(t *testing.T) { | |
21 | str := StringFrom("test") | |
22 | assertStr(t, str, "StringFrom() string") | |
23 | ||
24 | zero := StringFrom("") | |
25 | if !zero.Valid { | |
26 | t.Error("StringFrom(0)", "is invalid, but should be valid") | |
27 | } | |
28 | } | |
29 | ||
30 | func TestStringFromPtr(t *testing.T) { | |
31 | s := "test" | |
32 | sptr := &s | |
33 | str := StringFromPtr(sptr) | |
34 | assertStr(t, str, "StringFromPtr() string") | |
35 | ||
36 | null := StringFromPtr(nil) | |
37 | assertNullStr(t, null, "StringFromPtr(nil)") | |
38 | } | |
39 | ||
40 | func TestUnmarshalString(t *testing.T) { | |
41 | var str String | |
42 | err := json.Unmarshal(stringJSON, &str) | |
43 | maybePanic(err) | |
44 | assertStr(t, str, "string json") | |
45 | ||
46 | var ns String | |
47 | err = json.Unmarshal(nullStringJSON, &ns) | |
48 | maybePanic(err) | |
49 | assertStr(t, ns, "sql.NullString json") | |
50 | ||
51 | var blank String | |
52 | err = json.Unmarshal(blankStringJSON, &blank) | |
53 | maybePanic(err) | |
54 | if !blank.Valid { | |
55 | t.Error("blank string should be valid") | |
56 | } | |
57 | ||
58 | var null String | |
59 | err = json.Unmarshal(nullJSON, &null) | |
60 | maybePanic(err) | |
61 | assertNullStr(t, null, "null json") | |
62 | ||
63 | var badType String | |
64 | err = json.Unmarshal(boolJSON, &badType) | |
65 | if err == nil { | |
66 | panic("err should not be nil") | |
67 | } | |
68 | assertNullStr(t, badType, "wrong type json") | |
69 | ||
70 | var invalid String | |
71 | err = invalid.UnmarshalJSON(invalidJSON) | |
72 | if _, ok := err.(*json.SyntaxError); !ok { | |
73 | t.Errorf("expected json.SyntaxError, not %T", err) | |
74 | } | |
75 | assertNullStr(t, invalid, "invalid json") | |
76 | } | |
77 | ||
78 | func TestTextUnmarshalString(t *testing.T) { | |
79 | var str String | |
80 | err := str.UnmarshalText([]byte("test")) | |
81 | maybePanic(err) | |
82 | assertStr(t, str, "UnmarshalText() string") | |
83 | ||
84 | var null String | |
85 | err = null.UnmarshalText([]byte("")) | |
86 | maybePanic(err) | |
87 | assertNullStr(t, null, "UnmarshalText() empty string") | |
88 | } | |
89 | ||
90 | func TestMarshalString(t *testing.T) { | |
91 | str := StringFrom("test") | |
92 | data, err := json.Marshal(str) | |
93 | maybePanic(err) | |
94 | assertJSONEquals(t, data, `"test"`, "non-empty json marshal") | |
95 | data, err = str.MarshalText() | |
96 | maybePanic(err) | |
97 | assertJSONEquals(t, data, "test", "non-empty text marshal") | |
98 | ||
99 | // empty values should be encoded as an empty string | |
100 | zero := StringFrom("") | |
101 | data, err = json.Marshal(zero) | |
102 | maybePanic(err) | |
103 | assertJSONEquals(t, data, `""`, "empty json marshal") | |
104 | data, err = zero.MarshalText() | |
105 | maybePanic(err) | |
106 | assertJSONEquals(t, data, "", "string marshal text") | |
107 | ||
108 | null := StringFromPtr(nil) | |
109 | data, err = json.Marshal(null) | |
110 | maybePanic(err) | |
111 | assertJSONEquals(t, data, `null`, "null json marshal") | |
112 | data, err = null.MarshalText() | |
113 | maybePanic(err) | |
114 | assertJSONEquals(t, data, "", "string marshal text") | |
115 | } | |
116 | ||
117 | // Tests omitempty... broken until Go 1.4 | |
118 | // func TestMarshalStringInStruct(t *testing.T) { | |
119 | // obj := stringInStruct{Test: StringFrom("")} | |
120 | // data, err := json.Marshal(obj) | |
121 | // maybePanic(err) | |
122 | // assertJSONEquals(t, data, `{}`, "null string in struct") | |
123 | // } | |
124 | ||
125 | func TestStringPointer(t *testing.T) { | |
126 | str := StringFrom("test") | |
127 | ptr := str.Ptr() | |
128 | if *ptr != "test" { | |
129 | t.Errorf("bad %s string: %#v ≠ %s\n", "pointer", ptr, "test") | |
130 | } | |
131 | ||
132 | null := NewString("", false) | |
133 | ptr = null.Ptr() | |
134 | if ptr != nil { | |
135 | t.Errorf("bad %s string: %#v ≠ %s\n", "nil pointer", ptr, "nil") | |
136 | } | |
137 | } | |
138 | ||
139 | func TestStringIsZero(t *testing.T) { | |
140 | str := StringFrom("test") | |
141 | if str.IsZero() { | |
142 | t.Errorf("IsZero() should be false") | |
143 | } | |
144 | ||
145 | blank := StringFrom("") | |
146 | if blank.IsZero() { | |
147 | t.Errorf("IsZero() should be false") | |
148 | } | |
149 | ||
150 | empty := NewString("", true) | |
151 | if empty.IsZero() { | |
152 | t.Errorf("IsZero() should be false") | |
153 | } | |
154 | ||
155 | null := StringFromPtr(nil) | |
156 | if !null.IsZero() { | |
157 | t.Errorf("IsZero() should be true") | |
158 | } | |
159 | } | |
160 | ||
161 | func TestStringSetValid(t *testing.T) { | |
162 | change := NewString("", false) | |
163 | assertNullStr(t, change, "SetValid()") | |
164 | change.SetValid("test") | |
165 | assertStr(t, change, "SetValid()") | |
166 | } | |
167 | ||
168 | func TestStringScan(t *testing.T) { | |
169 | var str String | |
170 | err := str.Scan("test") | |
171 | maybePanic(err) | |
172 | assertStr(t, str, "scanned string") | |
173 | ||
174 | var null String | |
175 | err = null.Scan(nil) | |
176 | maybePanic(err) | |
177 | assertNullStr(t, null, "scanned null") | |
178 | } | |
179 | ||
180 | func maybePanic(err error) { | |
181 | if err != nil { | |
182 | panic(err) | |
183 | } | |
184 | } | |
185 | ||
186 | func assertStr(t *testing.T, s String, from string) { | |
187 | if s.String != "test" { | |
188 | t.Errorf("bad %s string: %s ≠ %s\n", from, s.String, "test") | |
189 | } | |
190 | if !s.Valid { | |
191 | t.Error(from, "is invalid, but should be valid") | |
192 | } | |
193 | } | |
194 | ||
195 | func assertNullStr(t *testing.T, s String, from string) { | |
196 | if s.Valid { | |
197 | t.Error(from, "is valid, but should be invalid") | |
198 | } | |
199 | } | |
200 | ||
201 | func assertJSONEquals(t *testing.T, data []byte, cmp string, from string) { | |
202 | if string(data) != cmp { | |
203 | t.Errorf("bad %s data: %s ≠ %s\n", from, data, cmp) | |
204 | } | |
205 | } |
0 | package null | |
1 | ||
2 | import ( | |
3 | "database/sql/driver" | |
4 | "encoding/json" | |
5 | "fmt" | |
6 | "reflect" | |
7 | "time" | |
8 | ) | |
9 | ||
10 | // Time is a nullable time.Time. It supports SQL and JSON serialization. | |
11 | // It will marshal to null if null. | |
12 | type Time struct { | |
13 | Time time.Time | |
14 | Valid bool | |
15 | } | |
16 | ||
17 | // Scan implements the Scanner interface. | |
18 | func (t *Time) Scan(value interface{}) error { | |
19 | var err error | |
20 | switch x := value.(type) { | |
21 | case time.Time: | |
22 | t.Time = x | |
23 | case nil: | |
24 | t.Valid = false | |
25 | return nil | |
26 | default: | |
27 | err = fmt.Errorf("null: cannot scan type %T into null.Time: %v", value, value) | |
28 | } | |
29 | t.Valid = err == nil | |
30 | return err | |
31 | } | |
32 | ||
33 | // Value implements the driver Valuer interface. | |
34 | func (t Time) Value() (driver.Value, error) { | |
35 | if !t.Valid { | |
36 | return nil, nil | |
37 | } | |
38 | return t.Time, nil | |
39 | } | |
40 | ||
41 | // NewTime creates a new Time. | |
42 | func NewTime(t time.Time, valid bool) Time { | |
43 | return Time{ | |
44 | Time: t, | |
45 | Valid: valid, | |
46 | } | |
47 | } | |
48 | ||
49 | // TimeFrom creates a new Time that will always be valid. | |
50 | func TimeFrom(t time.Time) Time { | |
51 | return NewTime(t, true) | |
52 | } | |
53 | ||
54 | // TimeFromPtr creates a new Time that will be null if t is nil. | |
55 | func TimeFromPtr(t *time.Time) Time { | |
56 | if t == nil { | |
57 | return NewTime(time.Time{}, false) | |
58 | } | |
59 | return NewTime(*t, true) | |
60 | } | |
61 | ||
62 | // MarshalJSON implements json.Marshaler. | |
63 | // It will encode null if this time is null. | |
64 | func (t Time) MarshalJSON() ([]byte, error) { | |
65 | if !t.Valid { | |
66 | return []byte("null"), nil | |
67 | } | |
68 | return t.Time.MarshalJSON() | |
69 | } | |
70 | ||
71 | // UnmarshalJSON implements json.Unmarshaler. | |
72 | // It supports string, object (e.g. pq.NullTime and friends) | |
73 | // and null input. | |
74 | func (t *Time) UnmarshalJSON(data []byte) error { | |
75 | var err error | |
76 | var v interface{} | |
77 | if err = json.Unmarshal(data, &v); err != nil { | |
78 | return err | |
79 | } | |
80 | switch x := v.(type) { | |
81 | case string: | |
82 | err = t.Time.UnmarshalJSON(data) | |
83 | case map[string]interface{}: | |
84 | ti, tiOK := x["Time"].(string) | |
85 | valid, validOK := x["Valid"].(bool) | |
86 | if !tiOK || !validOK { | |
87 | 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"]) | |
88 | } | |
89 | err = t.Time.UnmarshalText([]byte(ti)) | |
90 | t.Valid = valid | |
91 | return err | |
92 | case nil: | |
93 | t.Valid = false | |
94 | return nil | |
95 | default: | |
96 | err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Time", reflect.TypeOf(v).Name()) | |
97 | } | |
98 | t.Valid = err == nil | |
99 | return err | |
100 | } | |
101 | ||
102 | func (t Time) MarshalText() ([]byte, error) { | |
103 | if !t.Valid { | |
104 | return []byte("null"), nil | |
105 | } | |
106 | return t.Time.MarshalText() | |
107 | } | |
108 | ||
109 | func (t *Time) UnmarshalText(text []byte) error { | |
110 | str := string(text) | |
111 | if str == "" || str == "null" { | |
112 | t.Valid = false | |
113 | return nil | |
114 | } | |
115 | if err := t.Time.UnmarshalText(text); err != nil { | |
116 | return err | |
117 | } | |
118 | t.Valid = true | |
119 | return nil | |
120 | } | |
121 | ||
122 | // SetValid changes this Time's value and sets it to be non-null. | |
123 | func (t *Time) SetValid(v time.Time) { | |
124 | t.Time = v | |
125 | t.Valid = true | |
126 | } | |
127 | ||
128 | // Ptr returns a pointer to this Time's value, or a nil pointer if this Time is null. | |
129 | func (t Time) Ptr() *time.Time { | |
130 | if !t.Valid { | |
131 | return nil | |
132 | } | |
133 | return &t.Time | |
134 | } |
0 | package null | |
1 | ||
2 | import ( | |
3 | "encoding/json" | |
4 | "testing" | |
5 | "time" | |
6 | ) | |
7 | ||
8 | var ( | |
9 | timeString = "2012-12-21T21:21:21Z" | |
10 | timeJSON = []byte(`"` + timeString + `"`) | |
11 | nullTimeJSON = []byte(`null`) | |
12 | timeValue, _ = time.Parse(time.RFC3339, timeString) | |
13 | timeObject = []byte(`{"Time":"2012-12-21T21:21:21Z","Valid":true}`) | |
14 | nullObject = []byte(`{"Time":"0001-01-01T00:00:00Z","Valid":false}`) | |
15 | badObject = []byte(`{"hello": "world"}`) | |
16 | ) | |
17 | ||
18 | func TestUnmarshalTimeJSON(t *testing.T) { | |
19 | var ti Time | |
20 | err := json.Unmarshal(timeJSON, &ti) | |
21 | maybePanic(err) | |
22 | assertTime(t, ti, "UnmarshalJSON() json") | |
23 | ||
24 | var null Time | |
25 | err = json.Unmarshal(nullTimeJSON, &null) | |
26 | maybePanic(err) | |
27 | assertNullTime(t, null, "null time json") | |
28 | ||
29 | var fromObject Time | |
30 | err = json.Unmarshal(timeObject, &fromObject) | |
31 | maybePanic(err) | |
32 | assertTime(t, fromObject, "time from object json") | |
33 | ||
34 | var nullFromObj Time | |
35 | err = json.Unmarshal(nullObject, &nullFromObj) | |
36 | maybePanic(err) | |
37 | assertNullTime(t, nullFromObj, "null from object json") | |
38 | ||
39 | var invalid Time | |
40 | err = invalid.UnmarshalJSON(invalidJSON) | |
41 | if _, ok := err.(*json.SyntaxError); !ok { | |
42 | t.Errorf("expected json.SyntaxError, not %T", err) | |
43 | } | |
44 | assertNullTime(t, invalid, "invalid from object json") | |
45 | ||
46 | var bad Time | |
47 | err = json.Unmarshal(badObject, &bad) | |
48 | if err == nil { | |
49 | t.Errorf("expected error: bad object") | |
50 | } | |
51 | assertNullTime(t, bad, "bad from object json") | |
52 | ||
53 | var wrongType Time | |
54 | err = json.Unmarshal(intJSON, &wrongType) | |
55 | if err == nil { | |
56 | t.Errorf("expected error: wrong type JSON") | |
57 | } | |
58 | assertNullTime(t, wrongType, "wrong type object json") | |
59 | } | |
60 | ||
61 | func TestUnmarshalTimeText(t *testing.T) { | |
62 | ti := TimeFrom(timeValue) | |
63 | txt, err := ti.MarshalText() | |
64 | maybePanic(err) | |
65 | assertJSONEquals(t, txt, timeString, "marshal text") | |
66 | ||
67 | var unmarshal Time | |
68 | err = unmarshal.UnmarshalText(txt) | |
69 | maybePanic(err) | |
70 | assertTime(t, unmarshal, "unmarshal text") | |
71 | ||
72 | var null Time | |
73 | err = null.UnmarshalText(nullJSON) | |
74 | maybePanic(err) | |
75 | assertNullTime(t, null, "unmarshal null text") | |
76 | txt, err = null.MarshalText() | |
77 | maybePanic(err) | |
78 | assertJSONEquals(t, txt, string(nullJSON), "marshal null text") | |
79 | ||
80 | var invalid Time | |
81 | err = invalid.UnmarshalText([]byte("hello world")) | |
82 | if err == nil { | |
83 | t.Error("expected error") | |
84 | } | |
85 | assertNullTime(t, invalid, "bad string") | |
86 | } | |
87 | ||
88 | func TestMarshalTime(t *testing.T) { | |
89 | ti := TimeFrom(timeValue) | |
90 | data, err := json.Marshal(ti) | |
91 | maybePanic(err) | |
92 | assertJSONEquals(t, data, string(timeJSON), "non-empty json marshal") | |
93 | ||
94 | ti.Valid = false | |
95 | data, err = json.Marshal(ti) | |
96 | maybePanic(err) | |
97 | assertJSONEquals(t, data, string(nullJSON), "null json marshal") | |
98 | } | |
99 | ||
100 | func TestTimeFrom(t *testing.T) { | |
101 | ti := TimeFrom(timeValue) | |
102 | assertTime(t, ti, "TimeFrom() time.Time") | |
103 | } | |
104 | ||
105 | func TestTimeFromPtr(t *testing.T) { | |
106 | ti := TimeFromPtr(&timeValue) | |
107 | assertTime(t, ti, "TimeFromPtr() time") | |
108 | ||
109 | null := TimeFromPtr(nil) | |
110 | assertNullTime(t, null, "TimeFromPtr(nil)") | |
111 | } | |
112 | ||
113 | func TestTimeSetValid(t *testing.T) { | |
114 | var ti time.Time | |
115 | change := NewTime(ti, false) | |
116 | assertNullTime(t, change, "SetValid()") | |
117 | change.SetValid(timeValue) | |
118 | assertTime(t, change, "SetValid()") | |
119 | } | |
120 | ||
121 | func TestTimePointer(t *testing.T) { | |
122 | ti := TimeFrom(timeValue) | |
123 | ptr := ti.Ptr() | |
124 | if *ptr != timeValue { | |
125 | t.Errorf("bad %s time: %#v ≠ %v\n", "pointer", ptr, timeValue) | |
126 | } | |
127 | ||
128 | var nt time.Time | |
129 | null := NewTime(nt, false) | |
130 | ptr = null.Ptr() | |
131 | if ptr != nil { | |
132 | t.Errorf("bad %s time: %#v ≠ %s\n", "nil pointer", ptr, "nil") | |
133 | } | |
134 | } | |
135 | ||
136 | func TestTimeScanValue(t *testing.T) { | |
137 | var ti Time | |
138 | err := ti.Scan(timeValue) | |
139 | maybePanic(err) | |
140 | assertTime(t, ti, "scanned time") | |
141 | if v, err := ti.Value(); v != timeValue || err != nil { | |
142 | t.Error("bad value or err:", v, err) | |
143 | } | |
144 | ||
145 | var null Time | |
146 | err = null.Scan(nil) | |
147 | maybePanic(err) | |
148 | assertNullTime(t, null, "scanned null") | |
149 | if v, err := null.Value(); v != nil || err != nil { | |
150 | t.Error("bad value or err:", v, err) | |
151 | } | |
152 | ||
153 | var wrong Time | |
154 | err = wrong.Scan(int64(42)) | |
155 | if err == nil { | |
156 | t.Error("expected error") | |
157 | } | |
158 | assertNullTime(t, wrong, "scanned wrong") | |
159 | } | |
160 | ||
161 | func assertTime(t *testing.T, ti Time, from string) { | |
162 | if ti.Time != timeValue { | |
163 | t.Errorf("bad %v time: %v ≠ %v\n", from, ti.Time, timeValue) | |
164 | } | |
165 | if !ti.Valid { | |
166 | t.Error(from, "is invalid, but should be valid") | |
167 | } | |
168 | } | |
169 | ||
170 | func assertNullTime(t *testing.T, ti Time, from string) { | |
171 | if ti.Valid { | |
172 | t.Error(from, "is valid, but should be invalid") | |
173 | } | |
174 | } |
0 | package zero | |
1 | ||
2 | import ( | |
3 | "database/sql" | |
4 | "encoding/json" | |
5 | "errors" | |
6 | "fmt" | |
7 | "reflect" | |
8 | ) | |
9 | ||
10 | // Bool is a nullable bool. False input is considered null. | |
11 | // JSON marshals to false if null. | |
12 | // Considered null to SQL unmarshaled from a false value. | |
13 | type Bool struct { | |
14 | sql.NullBool | |
15 | } | |
16 | ||
17 | // NewBool creates a new Bool | |
18 | func NewBool(b bool, valid bool) Bool { | |
19 | return Bool{ | |
20 | NullBool: sql.NullBool{ | |
21 | Bool: b, | |
22 | Valid: valid, | |
23 | }, | |
24 | } | |
25 | } | |
26 | ||
27 | // BoolFrom creates a new Bool that will be null if false. | |
28 | func BoolFrom(b bool) Bool { | |
29 | return NewBool(b, b) | |
30 | } | |
31 | ||
32 | // BoolFromPtr creates a new Bool that be null if b is nil. | |
33 | func BoolFromPtr(b *bool) Bool { | |
34 | if b == nil { | |
35 | return NewBool(false, false) | |
36 | } | |
37 | return NewBool(*b, true) | |
38 | } | |
39 | ||
40 | // UnmarshalJSON implements json.Unmarshaler. | |
41 | // "false" will be considered a null Bool. | |
42 | // It also supports unmarshalling a sql.NullBool. | |
43 | func (b *Bool) UnmarshalJSON(data []byte) error { | |
44 | var err error | |
45 | var v interface{} | |
46 | if err = json.Unmarshal(data, &v); err != nil { | |
47 | return err | |
48 | } | |
49 | switch x := v.(type) { | |
50 | case bool: | |
51 | b.Bool = x | |
52 | case map[string]interface{}: | |
53 | err = json.Unmarshal(data, &b.NullBool) | |
54 | case nil: | |
55 | b.Valid = false | |
56 | return nil | |
57 | default: | |
58 | err = fmt.Errorf("json: cannot unmarshal %v into Go value of type zero.Bool", reflect.TypeOf(v).Name()) | |
59 | } | |
60 | b.Valid = (err == nil) && b.Bool | |
61 | return err | |
62 | } | |
63 | ||
64 | // UnmarshalText implements encoding.TextUnmarshaler. | |
65 | // It will unmarshal to a null Bool if the input is a false or not a bool. | |
66 | // It will return an error if the input is not a float, blank, or "null". | |
67 | func (b *Bool) UnmarshalText(text []byte) error { | |
68 | str := string(text) | |
69 | switch str { | |
70 | case "", "null": | |
71 | b.Valid = false | |
72 | return nil | |
73 | case "true": | |
74 | b.Bool = true | |
75 | case "false": | |
76 | b.Bool = false | |
77 | default: | |
78 | b.Valid = false | |
79 | return errors.New("invalid input:" + str) | |
80 | } | |
81 | b.Valid = b.Bool | |
82 | return nil | |
83 | } | |
84 | ||
85 | // MarshalJSON implements json.Marshaler. | |
86 | // It will encode null if this Bool is null. | |
87 | func (b Bool) MarshalJSON() ([]byte, error) { | |
88 | if !b.Valid || !b.Bool { | |
89 | return []byte("false"), nil | |
90 | } | |
91 | return []byte("true"), nil | |
92 | } | |
93 | ||
94 | // MarshalText implements encoding.TextMarshaler. | |
95 | // It will encode a zero if this Bool is null. | |
96 | func (b Bool) MarshalText() ([]byte, error) { | |
97 | if !b.Valid || !b.Bool { | |
98 | return []byte("false"), nil | |
99 | } | |
100 | return []byte("true"), nil | |
101 | } | |
102 | ||
103 | // SetValid changes this Bool's value and also sets it to be non-null. | |
104 | func (b *Bool) SetValid(v bool) { | |
105 | b.Bool = v | |
106 | b.Valid = true | |
107 | } | |
108 | ||
109 | // Ptr returns a poBooler to this Bool's value, or a nil poBooler if this Bool is null. | |
110 | func (b Bool) Ptr() *bool { | |
111 | if !b.Valid { | |
112 | return nil | |
113 | } | |
114 | return &b.Bool | |
115 | } | |
116 | ||
117 | // IsZero returns true for null or zero Bools, for future omitempty support (Go 1.4?) | |
118 | func (b Bool) IsZero() bool { | |
119 | return !b.Valid || !b.Bool | |
120 | } |
0 | package zero | |
1 | ||
2 | import ( | |
3 | "encoding/json" | |
4 | "testing" | |
5 | ) | |
6 | ||
7 | var ( | |
8 | boolJSON = []byte(`true`) | |
9 | falseJSON = []byte(`false`) | |
10 | nullBoolJSON = []byte(`{"Bool":true,"Valid":true}`) | |
11 | ) | |
12 | ||
13 | func TestBoolFrom(t *testing.T) { | |
14 | b := BoolFrom(true) | |
15 | assertBool(t, b, "BoolFrom()") | |
16 | ||
17 | zero := BoolFrom(false) | |
18 | if zero.Valid { | |
19 | t.Error("BoolFrom(false)", "is valid, but should be invalid") | |
20 | } | |
21 | } | |
22 | ||
23 | func TestBoolFromPtr(t *testing.T) { | |
24 | v := true | |
25 | bptr := &v | |
26 | b := BoolFromPtr(bptr) | |
27 | assertBool(t, b, "BoolFromPtr()") | |
28 | ||
29 | null := BoolFromPtr(nil) | |
30 | assertNullBool(t, null, "BoolFromPtr(nil)") | |
31 | } | |
32 | ||
33 | func TestUnmarshalBool(t *testing.T) { | |
34 | var b Bool | |
35 | err := json.Unmarshal(boolJSON, &b) | |
36 | maybePanic(err) | |
37 | assertBool(t, b, "float json") | |
38 | ||
39 | var nb Bool | |
40 | err = json.Unmarshal(nullBoolJSON, &nb) | |
41 | maybePanic(err) | |
42 | assertBool(t, nb, "sql.NullBool json") | |
43 | ||
44 | var zero Bool | |
45 | err = json.Unmarshal(falseJSON, &zero) | |
46 | maybePanic(err) | |
47 | assertNullBool(t, zero, "zero json") | |
48 | ||
49 | var null Bool | |
50 | err = json.Unmarshal(nullJSON, &null) | |
51 | maybePanic(err) | |
52 | assertNullBool(t, null, "null json") | |
53 | ||
54 | var invalid Bool | |
55 | err = invalid.UnmarshalJSON(invalidJSON) | |
56 | if _, ok := err.(*json.SyntaxError); !ok { | |
57 | t.Errorf("expected json.SyntaxError, not %T: %v", err, err) | |
58 | } | |
59 | assertNullBool(t, invalid, "invalid json") | |
60 | ||
61 | var badType Bool | |
62 | err = json.Unmarshal(intJSON, &badType) | |
63 | if err == nil { | |
64 | panic("err should not be nil") | |
65 | } | |
66 | assertNullBool(t, badType, "wrong type json") | |
67 | } | |
68 | ||
69 | func TestTextUnmarshalBool(t *testing.T) { | |
70 | var b Bool | |
71 | err := b.UnmarshalText(boolJSON) | |
72 | maybePanic(err) | |
73 | assertBool(t, b, "UnmarshalText() bool") | |
74 | ||
75 | var zero Bool | |
76 | err = zero.UnmarshalText(falseJSON) | |
77 | maybePanic(err) | |
78 | assertNullBool(t, zero, "UnmarshalText() zero bool") | |
79 | ||
80 | var blank Bool | |
81 | err = blank.UnmarshalText([]byte("")) | |
82 | maybePanic(err) | |
83 | assertNullBool(t, blank, "UnmarshalText() empty bool") | |
84 | ||
85 | var null Bool | |
86 | err = null.UnmarshalText(nullJSON) | |
87 | maybePanic(err) | |
88 | assertNullBool(t, null, `UnmarshalText() "null"`) | |
89 | ||
90 | var invalid Bool | |
91 | err = invalid.UnmarshalText(invalidJSON) | |
92 | if err == nil { | |
93 | panic("err should not be nil") | |
94 | } | |
95 | } | |
96 | ||
97 | func TestMarshalBool(t *testing.T) { | |
98 | b := BoolFrom(true) | |
99 | data, err := json.Marshal(b) | |
100 | maybePanic(err) | |
101 | assertJSONEquals(t, data, "true", "non-empty json marshal") | |
102 | ||
103 | // invalid values should be encoded as false | |
104 | null := NewBool(false, false) | |
105 | data, err = json.Marshal(null) | |
106 | maybePanic(err) | |
107 | assertJSONEquals(t, data, "false", "null json marshal") | |
108 | } | |
109 | ||
110 | func TestMarshalBoolText(t *testing.T) { | |
111 | b := BoolFrom(true) | |
112 | data, err := b.MarshalText() | |
113 | maybePanic(err) | |
114 | assertJSONEquals(t, data, "true", "non-empty text marshal") | |
115 | ||
116 | // invalid values should be encoded as zero | |
117 | null := NewBool(false, false) | |
118 | data, err = null.MarshalText() | |
119 | maybePanic(err) | |
120 | assertJSONEquals(t, data, "false", "null text marshal") | |
121 | } | |
122 | ||
123 | func TestBoolPointer(t *testing.T) { | |
124 | b := BoolFrom(true) | |
125 | ptr := b.Ptr() | |
126 | if *ptr != true { | |
127 | t.Errorf("bad %s bool: %#v ≠ %v\n", "pointer", ptr, true) | |
128 | } | |
129 | ||
130 | null := NewBool(false, false) | |
131 | ptr = null.Ptr() | |
132 | if ptr != nil { | |
133 | t.Errorf("bad %s bool: %#v ≠ %s\n", "nil pointer", ptr, "nil") | |
134 | } | |
135 | } | |
136 | ||
137 | func TestBoolIsZero(t *testing.T) { | |
138 | b := BoolFrom(true) | |
139 | if b.IsZero() { | |
140 | t.Errorf("IsZero() should be false") | |
141 | } | |
142 | ||
143 | null := NewBool(false, false) | |
144 | if !null.IsZero() { | |
145 | t.Errorf("IsZero() should be true") | |
146 | } | |
147 | ||
148 | zero := NewBool(false, true) | |
149 | if !zero.IsZero() { | |
150 | t.Errorf("IsZero() should be true") | |
151 | } | |
152 | } | |
153 | ||
154 | func TestBoolSetValid(t *testing.T) { | |
155 | change := NewBool(false, false) | |
156 | assertNullBool(t, change, "SetValid()") | |
157 | change.SetValid(true) | |
158 | assertBool(t, change, "SetValid()") | |
159 | } | |
160 | ||
161 | func TestBoolScan(t *testing.T) { | |
162 | var b Bool | |
163 | err := b.Scan(true) | |
164 | maybePanic(err) | |
165 | assertBool(t, b, "scanned bool") | |
166 | ||
167 | var null Bool | |
168 | err = null.Scan(nil) | |
169 | maybePanic(err) | |
170 | assertNullBool(t, null, "scanned null") | |
171 | } | |
172 | ||
173 | func assertBool(t *testing.T, b Bool, from string) { | |
174 | if b.Bool != true { | |
175 | t.Errorf("bad %s bool: %d ≠ %v\n", from, b.Bool, true) | |
176 | } | |
177 | if !b.Valid { | |
178 | t.Error(from, "is invalid, but should be valid") | |
179 | } | |
180 | } | |
181 | ||
182 | func assertNullBool(t *testing.T, b Bool, from string) { | |
183 | if b.Valid { | |
184 | t.Error(from, "is valid, but should be invalid") | |
185 | } | |
186 | } |
0 | package zero | |
1 | ||
2 | import ( | |
3 | "database/sql" | |
4 | "encoding/json" | |
5 | "fmt" | |
6 | "reflect" | |
7 | "strconv" | |
8 | ) | |
9 | ||
10 | // Float is a nullable float64. Zero input will be considered null. | |
11 | // JSON marshals to zero if null. | |
12 | // Considered null to SQL if zero. | |
13 | type Float struct { | |
14 | sql.NullFloat64 | |
15 | } | |
16 | ||
17 | // NewFloat creates a new Float | |
18 | func NewFloat(f float64, valid bool) Float { | |
19 | return Float{ | |
20 | NullFloat64: sql.NullFloat64{ | |
21 | Float64: f, | |
22 | Valid: valid, | |
23 | }, | |
24 | } | |
25 | } | |
26 | ||
27 | // FloatFrom creates a new Float that will be null if zero. | |
28 | func FloatFrom(f float64) Float { | |
29 | return NewFloat(f, f != 0) | |
30 | } | |
31 | ||
32 | // FloatFromPtr creates a new Float that be null if f is nil. | |
33 | func FloatFromPtr(f *float64) Float { | |
34 | if f == nil { | |
35 | return NewFloat(0, false) | |
36 | } | |
37 | return NewFloat(*f, true) | |
38 | } | |
39 | ||
40 | // UnmarshalJSON implements json.Unmarshaler. | |
41 | // It supports number and null input. | |
42 | // 0 will be considered a null Float. | |
43 | // It also supports unmarshalling a sql.NullFloat64. | |
44 | func (f *Float) UnmarshalJSON(data []byte) error { | |
45 | var err error | |
46 | var v interface{} | |
47 | if err = json.Unmarshal(data, &v); err != nil { | |
48 | return err | |
49 | } | |
50 | switch x := v.(type) { | |
51 | case float64: | |
52 | f.Float64 = x | |
53 | case map[string]interface{}: | |
54 | err = json.Unmarshal(data, &f.NullFloat64) | |
55 | case nil: | |
56 | f.Valid = false | |
57 | return nil | |
58 | default: | |
59 | err = fmt.Errorf("json: cannot unmarshal %v into Go value of type zero.Float", reflect.TypeOf(v).Name()) | |
60 | } | |
61 | f.Valid = (err == nil) && (f.Float64 != 0) | |
62 | return err | |
63 | } | |
64 | ||
65 | // UnmarshalText implements encoding.TextUnmarshaler. | |
66 | // It will unmarshal to a null Float if the input is a blank, zero, or not a float. | |
67 | // It will return an error if the input is not a float, blank, or "null". | |
68 | func (f *Float) UnmarshalText(text []byte) error { | |
69 | str := string(text) | |
70 | if str == "" || str == "null" { | |
71 | f.Valid = false | |
72 | return nil | |
73 | } | |
74 | var err error | |
75 | f.Float64, err = strconv.ParseFloat(string(text), 64) | |
76 | f.Valid = (err == nil) && (f.Float64 != 0) | |
77 | return err | |
78 | } | |
79 | ||
80 | // MarshalJSON implements json.Marshaler. | |
81 | // It will encode null if this Float is null. | |
82 | func (f Float) MarshalJSON() ([]byte, error) { | |
83 | n := f.Float64 | |
84 | if !f.Valid { | |
85 | n = 0 | |
86 | } | |
87 | return []byte(strconv.FormatFloat(n, 'f', -1, 64)), nil | |
88 | } | |
89 | ||
90 | // MarshalText implements encoding.TextMarshaler. | |
91 | // It will encode a zero if this Float is null. | |
92 | func (f Float) MarshalText() ([]byte, error) { | |
93 | n := f.Float64 | |
94 | if !f.Valid { | |
95 | n = 0 | |
96 | } | |
97 | return []byte(strconv.FormatFloat(n, 'f', -1, 64)), nil | |
98 | } | |
99 | ||
100 | // SetValid changes this Float's value and also sets it to be non-null. | |
101 | func (f *Float) SetValid(v float64) { | |
102 | f.Float64 = v | |
103 | f.Valid = true | |
104 | } | |
105 | ||
106 | // Ptr returns a poFloater to this Float's value, or a nil poFloater if this Float is null. | |
107 | func (f Float) Ptr() *float64 { | |
108 | if !f.Valid { | |
109 | return nil | |
110 | } | |
111 | return &f.Float64 | |
112 | } | |
113 | ||
114 | // IsZero returns true for null or zero Floats, for future omitempty support (Go 1.4?) | |
115 | func (f Float) IsZero() bool { | |
116 | return !f.Valid || f.Float64 == 0 | |
117 | } |
0 | package zero | |
1 | ||
2 | import ( | |
3 | "encoding/json" | |
4 | "testing" | |
5 | ) | |
6 | ||
7 | var ( | |
8 | floatJSON = []byte(`1.2345`) | |
9 | nullFloatJSON = []byte(`{"Float64":1.2345,"Valid":true}`) | |
10 | ) | |
11 | ||
12 | func TestFloatFrom(t *testing.T) { | |
13 | f := FloatFrom(1.2345) | |
14 | assertFloat(t, f, "FloatFrom()") | |
15 | ||
16 | zero := FloatFrom(0) | |
17 | if zero.Valid { | |
18 | t.Error("FloatFrom(0)", "is valid, but should be invalid") | |
19 | } | |
20 | } | |
21 | ||
22 | func TestFloatFromPtr(t *testing.T) { | |
23 | n := float64(1.2345) | |
24 | iptr := &n | |
25 | f := FloatFromPtr(iptr) | |
26 | assertFloat(t, f, "FloatFromPtr()") | |
27 | ||
28 | null := FloatFromPtr(nil) | |
29 | assertNullFloat(t, null, "FloatFromPtr(nil)") | |
30 | } | |
31 | ||
32 | func TestUnmarshalFloat(t *testing.T) { | |
33 | var f Float | |
34 | err := json.Unmarshal(floatJSON, &f) | |
35 | maybePanic(err) | |
36 | assertFloat(t, f, "float json") | |
37 | ||
38 | var nf Float | |
39 | err = json.Unmarshal(nullFloatJSON, &nf) | |
40 | maybePanic(err) | |
41 | assertFloat(t, nf, "sql.NullFloat64 json") | |
42 | ||
43 | var zero Float | |
44 | err = json.Unmarshal(zeroJSON, &zero) | |
45 | maybePanic(err) | |
46 | assertNullFloat(t, zero, "zero json") | |
47 | ||
48 | var null Float | |
49 | err = json.Unmarshal(nullJSON, &null) | |
50 | maybePanic(err) | |
51 | assertNullFloat(t, null, "null json") | |
52 | ||
53 | var badType Float | |
54 | err = json.Unmarshal(boolJSON, &badType) | |
55 | if err == nil { | |
56 | panic("err should not be nil") | |
57 | } | |
58 | assertNullFloat(t, badType, "wrong type json") | |
59 | ||
60 | var invalid Float | |
61 | err = invalid.UnmarshalJSON(invalidJSON) | |
62 | if _, ok := err.(*json.SyntaxError); !ok { | |
63 | t.Errorf("expected json.SyntaxError, not %T", err) | |
64 | } | |
65 | assertNullFloat(t, invalid, "invalid json") | |
66 | } | |
67 | ||
68 | func TestTextUnmarshalFloat(t *testing.T) { | |
69 | var f Float | |
70 | err := f.UnmarshalText([]byte("1.2345")) | |
71 | maybePanic(err) | |
72 | assertFloat(t, f, "UnmarshalText() float") | |
73 | ||
74 | var zero Float | |
75 | err = zero.UnmarshalText([]byte("0")) | |
76 | maybePanic(err) | |
77 | assertNullFloat(t, zero, "UnmarshalText() zero float") | |
78 | ||
79 | var blank Float | |
80 | err = blank.UnmarshalText([]byte("")) | |
81 | maybePanic(err) | |
82 | assertNullFloat(t, blank, "UnmarshalText() empty float") | |
83 | ||
84 | var null Float | |
85 | err = null.UnmarshalText([]byte("null")) | |
86 | maybePanic(err) | |
87 | assertNullFloat(t, null, `UnmarshalText() "null"`) | |
88 | } | |
89 | ||
90 | func TestMarshalFloat(t *testing.T) { | |
91 | f := FloatFrom(1.2345) | |
92 | data, err := json.Marshal(f) | |
93 | maybePanic(err) | |
94 | assertJSONEquals(t, data, "1.2345", "non-empty json marshal") | |
95 | ||
96 | // invalid values should be encoded as 0 | |
97 | null := NewFloat(0, false) | |
98 | data, err = json.Marshal(null) | |
99 | maybePanic(err) | |
100 | assertJSONEquals(t, data, "0", "null json marshal") | |
101 | } | |
102 | ||
103 | func TestMarshalFloatText(t *testing.T) { | |
104 | f := FloatFrom(1.2345) | |
105 | data, err := f.MarshalText() | |
106 | maybePanic(err) | |
107 | assertJSONEquals(t, data, "1.2345", "non-empty text marshal") | |
108 | ||
109 | // invalid values should be encoded as zero | |
110 | null := NewFloat(0, false) | |
111 | data, err = null.MarshalText() | |
112 | maybePanic(err) | |
113 | assertJSONEquals(t, data, "0", "null text marshal") | |
114 | } | |
115 | ||
116 | func TestFloatPointer(t *testing.T) { | |
117 | f := FloatFrom(1.2345) | |
118 | ptr := f.Ptr() | |
119 | if *ptr != 1.2345 { | |
120 | t.Errorf("bad %s Float: %#v ≠ %v\n", "pointer", ptr, 1.2345) | |
121 | } | |
122 | ||
123 | null := NewFloat(0, false) | |
124 | ptr = null.Ptr() | |
125 | if ptr != nil { | |
126 | t.Errorf("bad %s Float: %#v ≠ %s\n", "nil pointer", ptr, "nil") | |
127 | } | |
128 | } | |
129 | ||
130 | func TestFloatIsZero(t *testing.T) { | |
131 | f := FloatFrom(1.2345) | |
132 | if f.IsZero() { | |
133 | t.Errorf("IsZero() should be false") | |
134 | } | |
135 | ||
136 | null := NewFloat(0, false) | |
137 | if !null.IsZero() { | |
138 | t.Errorf("IsZero() should be true") | |
139 | } | |
140 | ||
141 | zero := NewFloat(0, true) | |
142 | if !zero.IsZero() { | |
143 | t.Errorf("IsZero() should be true") | |
144 | } | |
145 | } | |
146 | ||
147 | func TestFloatSetValid(t *testing.T) { | |
148 | change := NewFloat(0, false) | |
149 | assertNullFloat(t, change, "SetValid()") | |
150 | change.SetValid(1.2345) | |
151 | assertFloat(t, change, "SetValid()") | |
152 | } | |
153 | ||
154 | func TestFloatScan(t *testing.T) { | |
155 | var f Float | |
156 | err := f.Scan(1.2345) | |
157 | maybePanic(err) | |
158 | assertFloat(t, f, "scanned float") | |
159 | ||
160 | var null Float | |
161 | err = null.Scan(nil) | |
162 | maybePanic(err) | |
163 | assertNullFloat(t, null, "scanned null") | |
164 | } | |
165 | ||
166 | func assertFloat(t *testing.T, f Float, from string) { | |
167 | if f.Float64 != 1.2345 { | |
168 | t.Errorf("bad %s float: %f ≠ %f\n", from, f.Float64, 1.2345) | |
169 | } | |
170 | if !f.Valid { | |
171 | t.Error(from, "is invalid, but should be valid") | |
172 | } | |
173 | } | |
174 | ||
175 | func assertNullFloat(t *testing.T, f Float, from string) { | |
176 | if f.Valid { | |
177 | t.Error(from, "is valid, but should be invalid") | |
178 | } | |
179 | } |
0 | package zero | |
1 | ||
2 | import ( | |
3 | "database/sql" | |
4 | "encoding/json" | |
5 | "fmt" | |
6 | "reflect" | |
7 | "strconv" | |
8 | ) | |
9 | ||
10 | // Int is a nullable int64. | |
11 | // JSON marshals to zero if null. | |
12 | // Considered null to SQL if zero. | |
13 | type Int struct { | |
14 | sql.NullInt64 | |
15 | } | |
16 | ||
17 | // NewInt creates a new Int | |
18 | func NewInt(i int64, valid bool) Int { | |
19 | return Int{ | |
20 | NullInt64: sql.NullInt64{ | |
21 | Int64: i, | |
22 | Valid: valid, | |
23 | }, | |
24 | } | |
25 | } | |
26 | ||
27 | // IntFrom creates a new Int that will be null if zero. | |
28 | func IntFrom(i int64) Int { | |
29 | return NewInt(i, i != 0) | |
30 | } | |
31 | ||
32 | // IntFromPtr creates a new Int that be null if i is nil. | |
33 | func IntFromPtr(i *int64) Int { | |
34 | if i == nil { | |
35 | return NewInt(0, false) | |
36 | } | |
37 | n := NewInt(*i, true) | |
38 | return n | |
39 | } | |
40 | ||
41 | // UnmarshalJSON implements json.Unmarshaler. | |
42 | // It supports number and null input. | |
43 | // 0 will be considered a null Int. | |
44 | // It also supports unmarshalling a sql.NullInt64. | |
45 | func (i *Int) UnmarshalJSON(data []byte) error { | |
46 | var err error | |
47 | var v interface{} | |
48 | if err = json.Unmarshal(data, &v); err != nil { | |
49 | return err | |
50 | } | |
51 | switch v.(type) { | |
52 | case float64: | |
53 | // Unmarshal again, directly to int64, to avoid intermediate float64 | |
54 | err = json.Unmarshal(data, &i.Int64) | |
55 | case map[string]interface{}: | |
56 | err = json.Unmarshal(data, &i.NullInt64) | |
57 | case nil: | |
58 | i.Valid = false | |
59 | return nil | |
60 | default: | |
61 | err = fmt.Errorf("json: cannot unmarshal %v into Go value of type zero.Int", reflect.TypeOf(v).Name()) | |
62 | } | |
63 | i.Valid = (err == nil) && (i.Int64 != 0) | |
64 | return err | |
65 | } | |
66 | ||
67 | // UnmarshalText implements encoding.TextUnmarshaler. | |
68 | // It will unmarshal to a null Int if the input is a blank, zero, or not an integer. | |
69 | // It will return an error if the input is not an integer, blank, or "null". | |
70 | func (i *Int) UnmarshalText(text []byte) error { | |
71 | str := string(text) | |
72 | if str == "" || str == "null" { | |
73 | i.Valid = false | |
74 | return nil | |
75 | } | |
76 | var err error | |
77 | i.Int64, err = strconv.ParseInt(string(text), 10, 64) | |
78 | i.Valid = (err == nil) && (i.Int64 != 0) | |
79 | return err | |
80 | } | |
81 | ||
82 | // MarshalJSON implements json.Marshaler. | |
83 | // It will encode 0 if this Int is null. | |
84 | func (i Int) MarshalJSON() ([]byte, error) { | |
85 | n := i.Int64 | |
86 | if !i.Valid { | |
87 | n = 0 | |
88 | } | |
89 | return []byte(strconv.FormatInt(n, 10)), nil | |
90 | } | |
91 | ||
92 | // MarshalText implements encoding.TextMarshaler. | |
93 | // It will encode a zero if this Int is null. | |
94 | func (i Int) MarshalText() ([]byte, error) { | |
95 | n := i.Int64 | |
96 | if !i.Valid { | |
97 | n = 0 | |
98 | } | |
99 | return []byte(strconv.FormatInt(n, 10)), nil | |
100 | } | |
101 | ||
102 | // SetValid changes this Int's value and also sets it to be non-null. | |
103 | func (i *Int) SetValid(n int64) { | |
104 | i.Int64 = n | |
105 | i.Valid = true | |
106 | } | |
107 | ||
108 | // Ptr returns a pointer to this Int's value, or a nil pointer if this Int is null. | |
109 | func (i Int) Ptr() *int64 { | |
110 | if !i.Valid { | |
111 | return nil | |
112 | } | |
113 | return &i.Int64 | |
114 | } | |
115 | ||
116 | // IsZero returns true for null or zero Ints, for future omitempty support (Go 1.4?) | |
117 | func (i Int) IsZero() bool { | |
118 | return !i.Valid || i.Int64 == 0 | |
119 | } |
0 | package zero | |
1 | ||
2 | import ( | |
3 | "encoding/json" | |
4 | "math" | |
5 | "strconv" | |
6 | "testing" | |
7 | ) | |
8 | ||
9 | var ( | |
10 | intJSON = []byte(`12345`) | |
11 | nullIntJSON = []byte(`{"Int64":12345,"Valid":true}`) | |
12 | zeroJSON = []byte(`0`) | |
13 | ) | |
14 | ||
15 | func TestIntFrom(t *testing.T) { | |
16 | i := IntFrom(12345) | |
17 | assertInt(t, i, "IntFrom()") | |
18 | ||
19 | zero := IntFrom(0) | |
20 | if zero.Valid { | |
21 | t.Error("IntFrom(0)", "is valid, but should be invalid") | |
22 | } | |
23 | } | |
24 | ||
25 | func TestIntFromPtr(t *testing.T) { | |
26 | n := int64(12345) | |
27 | iptr := &n | |
28 | i := IntFromPtr(iptr) | |
29 | assertInt(t, i, "IntFromPtr()") | |
30 | ||
31 | null := IntFromPtr(nil) | |
32 | assertNullInt(t, null, "IntFromPtr(nil)") | |
33 | } | |
34 | ||
35 | func TestUnmarshalInt(t *testing.T) { | |
36 | var i Int | |
37 | err := json.Unmarshal(intJSON, &i) | |
38 | maybePanic(err) | |
39 | assertInt(t, i, "int json") | |
40 | ||
41 | var ni Int | |
42 | err = json.Unmarshal(nullIntJSON, &ni) | |
43 | maybePanic(err) | |
44 | assertInt(t, ni, "sql.NullInt64 json") | |
45 | ||
46 | var zero Int | |
47 | err = json.Unmarshal(zeroJSON, &zero) | |
48 | maybePanic(err) | |
49 | assertNullInt(t, zero, "zero json") | |
50 | ||
51 | var null Int | |
52 | err = json.Unmarshal(nullJSON, &null) | |
53 | maybePanic(err) | |
54 | assertNullInt(t, null, "null json") | |
55 | ||
56 | var badType Int | |
57 | err = json.Unmarshal(boolJSON, &badType) | |
58 | if err == nil { | |
59 | panic("err should not be nil") | |
60 | } | |
61 | assertNullInt(t, badType, "wrong type json") | |
62 | ||
63 | var invalid Int | |
64 | err = invalid.UnmarshalJSON(invalidJSON) | |
65 | if _, ok := err.(*json.SyntaxError); !ok { | |
66 | t.Errorf("expected json.SyntaxError, not %T", err) | |
67 | } | |
68 | assertNullInt(t, invalid, "invalid json") | |
69 | } | |
70 | ||
71 | func TestUnmarshalNonIntegerNumber(t *testing.T) { | |
72 | var i Int | |
73 | err := json.Unmarshal(floatJSON, &i) | |
74 | if err == nil { | |
75 | panic("err should be present; non-integer number coerced to int") | |
76 | } | |
77 | } | |
78 | ||
79 | func TestUnmarshalInt64Overflow(t *testing.T) { | |
80 | int64Overflow := uint64(math.MaxInt64) | |
81 | ||
82 | // Max int64 should decode successfully | |
83 | var i Int | |
84 | err := json.Unmarshal([]byte(strconv.FormatUint(int64Overflow, 10)), &i) | |
85 | maybePanic(err) | |
86 | ||
87 | // Attempt to overflow | |
88 | int64Overflow++ | |
89 | err = json.Unmarshal([]byte(strconv.FormatUint(int64Overflow, 10)), &i) | |
90 | if err == nil { | |
91 | panic("err should be present; decoded value overflows int64") | |
92 | } | |
93 | } | |
94 | ||
95 | func TestTextUnmarshalInt(t *testing.T) { | |
96 | var i Int | |
97 | err := i.UnmarshalText([]byte("12345")) | |
98 | maybePanic(err) | |
99 | assertInt(t, i, "UnmarshalText() int") | |
100 | ||
101 | var zero Int | |
102 | err = zero.UnmarshalText([]byte("0")) | |
103 | maybePanic(err) | |
104 | assertNullInt(t, zero, "UnmarshalText() zero int") | |
105 | ||
106 | var blank Int | |
107 | err = blank.UnmarshalText([]byte("")) | |
108 | maybePanic(err) | |
109 | assertNullInt(t, blank, "UnmarshalText() empty int") | |
110 | ||
111 | var null Int | |
112 | err = null.UnmarshalText([]byte("null")) | |
113 | maybePanic(err) | |
114 | assertNullInt(t, null, `UnmarshalText() "null"`) | |
115 | } | |
116 | ||
117 | func TestMarshalInt(t *testing.T) { | |
118 | i := IntFrom(12345) | |
119 | data, err := json.Marshal(i) | |
120 | maybePanic(err) | |
121 | assertJSONEquals(t, data, "12345", "non-empty json marshal") | |
122 | ||
123 | // invalid values should be encoded as 0 | |
124 | null := NewInt(0, false) | |
125 | data, err = json.Marshal(null) | |
126 | maybePanic(err) | |
127 | assertJSONEquals(t, data, "0", "null json marshal") | |
128 | } | |
129 | ||
130 | func TestMarshalIntText(t *testing.T) { | |
131 | i := IntFrom(12345) | |
132 | data, err := i.MarshalText() | |
133 | maybePanic(err) | |
134 | assertJSONEquals(t, data, "12345", "non-empty text marshal") | |
135 | ||
136 | // invalid values should be encoded as zero | |
137 | null := NewInt(0, false) | |
138 | data, err = null.MarshalText() | |
139 | maybePanic(err) | |
140 | assertJSONEquals(t, data, "0", "null text marshal") | |
141 | } | |
142 | ||
143 | func TestIntPointer(t *testing.T) { | |
144 | i := IntFrom(12345) | |
145 | ptr := i.Ptr() | |
146 | if *ptr != 12345 { | |
147 | t.Errorf("bad %s int: %#v ≠ %d\n", "pointer", ptr, 12345) | |
148 | } | |
149 | ||
150 | null := NewInt(0, false) | |
151 | ptr = null.Ptr() | |
152 | if ptr != nil { | |
153 | t.Errorf("bad %s int: %#v ≠ %s\n", "nil pointer", ptr, "nil") | |
154 | } | |
155 | } | |
156 | ||
157 | func TestIntIsZero(t *testing.T) { | |
158 | i := IntFrom(12345) | |
159 | if i.IsZero() { | |
160 | t.Errorf("IsZero() should be false") | |
161 | } | |
162 | ||
163 | null := NewInt(0, false) | |
164 | if !null.IsZero() { | |
165 | t.Errorf("IsZero() should be true") | |
166 | } | |
167 | ||
168 | zero := NewInt(0, true) | |
169 | if !zero.IsZero() { | |
170 | t.Errorf("IsZero() should be true") | |
171 | } | |
172 | } | |
173 | ||
174 | func TestIntScan(t *testing.T) { | |
175 | var i Int | |
176 | err := i.Scan(12345) | |
177 | maybePanic(err) | |
178 | assertInt(t, i, "scanned int") | |
179 | ||
180 | var null Int | |
181 | err = null.Scan(nil) | |
182 | maybePanic(err) | |
183 | assertNullInt(t, null, "scanned null") | |
184 | } | |
185 | ||
186 | func TestIntSetValid(t *testing.T) { | |
187 | change := NewInt(0, false) | |
188 | assertNullInt(t, change, "SetValid()") | |
189 | change.SetValid(12345) | |
190 | assertInt(t, change, "SetValid()") | |
191 | } | |
192 | ||
193 | func assertInt(t *testing.T, i Int, from string) { | |
194 | if i.Int64 != 12345 { | |
195 | t.Errorf("bad %s int: %d ≠ %d\n", from, i.Int64, 12345) | |
196 | } | |
197 | if !i.Valid { | |
198 | t.Error(from, "is invalid, but should be valid") | |
199 | } | |
200 | } | |
201 | ||
202 | func assertNullInt(t *testing.T, i Int, from string) { | |
203 | if i.Valid { | |
204 | t.Error(from, "is valid, but should be invalid") | |
205 | } | |
206 | } |
0 | // Package zero contains SQL types that consider zero input and null input to be equivalent | |
1 | // with convenient support for JSON and text marshaling. | |
2 | // Types in this package will JSON marshal to their zero value, even if null. | |
3 | // Use the null parent package if you don't want this. | |
4 | package zero | |
5 | ||
6 | import ( | |
7 | "database/sql" | |
8 | "encoding/json" | |
9 | "fmt" | |
10 | "reflect" | |
11 | ) | |
12 | ||
13 | // String is a nullable string. | |
14 | // JSON marshals to a blank string if null. | |
15 | // Considered null to SQL if zero. | |
16 | type String struct { | |
17 | sql.NullString | |
18 | } | |
19 | ||
20 | // NewString creates a new String | |
21 | func NewString(s string, valid bool) String { | |
22 | return String{ | |
23 | NullString: sql.NullString{ | |
24 | String: s, | |
25 | Valid: valid, | |
26 | }, | |
27 | } | |
28 | } | |
29 | ||
30 | // StringFrom creates a new String that will be null if s is blank. | |
31 | func StringFrom(s string) String { | |
32 | return NewString(s, s != "") | |
33 | } | |
34 | ||
35 | // StringFromPtr creates a new String that be null if s is nil or blank. | |
36 | // It will make s point to the String's value. | |
37 | func StringFromPtr(s *string) String { | |
38 | if s == nil { | |
39 | return NewString("", false) | |
40 | } | |
41 | return NewString(*s, *s != "") | |
42 | } | |
43 | ||
44 | // UnmarshalJSON implements json.Unmarshaler. | |
45 | // It supports string and null input. Blank string input produces a null String. | |
46 | // It also supports unmarshalling a sql.NullString. | |
47 | func (s *String) UnmarshalJSON(data []byte) error { | |
48 | var err error | |
49 | var v interface{} | |
50 | if err = json.Unmarshal(data, &v); err != nil { | |
51 | return err | |
52 | } | |
53 | switch x := v.(type) { | |
54 | case string: | |
55 | s.String = x | |
56 | case map[string]interface{}: | |
57 | err = json.Unmarshal(data, &s.NullString) | |
58 | case nil: | |
59 | s.Valid = false | |
60 | return nil | |
61 | default: | |
62 | err = fmt.Errorf("json: cannot unmarshal %v into Go value of type zero.String", reflect.TypeOf(v).Name()) | |
63 | } | |
64 | s.Valid = (err == nil) && (s.String != "") | |
65 | return err | |
66 | } | |
67 | ||
68 | // MarshalText implements encoding.TextMarshaler. | |
69 | // It will encode a blank string when this String is null. | |
70 | func (s String) MarshalText() ([]byte, error) { | |
71 | if !s.Valid { | |
72 | return []byte{}, nil | |
73 | } | |
74 | return []byte(s.String), nil | |
75 | } | |
76 | ||
77 | // UnmarshalText implements encoding.TextUnmarshaler. | |
78 | // It will unmarshal to a null String if the input is a blank string. | |
79 | func (s *String) UnmarshalText(text []byte) error { | |
80 | s.String = string(text) | |
81 | s.Valid = s.String != "" | |
82 | return nil | |
83 | } | |
84 | ||
85 | // SetValid changes this String's value and also sets it to be non-null. | |
86 | func (s *String) SetValid(v string) { | |
87 | s.String = v | |
88 | s.Valid = true | |
89 | } | |
90 | ||
91 | // Ptr returns a pointer to this String's value, or a nil pointer if this String is null. | |
92 | func (s String) Ptr() *string { | |
93 | if !s.Valid { | |
94 | return nil | |
95 | } | |
96 | return &s.String | |
97 | } | |
98 | ||
99 | // IsZero returns true for null or empty strings, for potential future omitempty support. | |
100 | func (s String) IsZero() bool { | |
101 | return !s.Valid || s.String == "" | |
102 | } |
0 | package zero | |
1 | ||
2 | import ( | |
3 | "encoding/json" | |
4 | "testing" | |
5 | ) | |
6 | ||
7 | var ( | |
8 | stringJSON = []byte(`"test"`) | |
9 | blankStringJSON = []byte(`""`) | |
10 | nullStringJSON = []byte(`{"String":"test","Valid":true}`) | |
11 | ||
12 | nullJSON = []byte(`null`) | |
13 | invalidJSON = []byte(`:)`) | |
14 | ) | |
15 | ||
16 | type stringInStruct struct { | |
17 | Test String `json:"test,omitempty"` | |
18 | } | |
19 | ||
20 | func TestStringFrom(t *testing.T) { | |
21 | str := StringFrom("test") | |
22 | assertStr(t, str, "StringFrom() string") | |
23 | ||
24 | null := StringFrom("") | |
25 | assertNullStr(t, null, "StringFrom() empty string") | |
26 | } | |
27 | ||
28 | func TestUnmarshalString(t *testing.T) { | |
29 | var str String | |
30 | err := json.Unmarshal(stringJSON, &str) | |
31 | maybePanic(err) | |
32 | assertStr(t, str, "string json") | |
33 | ||
34 | var ns String | |
35 | err = json.Unmarshal(nullStringJSON, &ns) | |
36 | maybePanic(err) | |
37 | assertStr(t, ns, "sql.NullString json") | |
38 | ||
39 | var blank String | |
40 | err = json.Unmarshal(blankStringJSON, &blank) | |
41 | maybePanic(err) | |
42 | assertNullStr(t, blank, "blank string json") | |
43 | ||
44 | var null String | |
45 | err = json.Unmarshal(nullJSON, &null) | |
46 | maybePanic(err) | |
47 | assertNullStr(t, null, "null json") | |
48 | ||
49 | var badType String | |
50 | err = json.Unmarshal(boolJSON, &badType) | |
51 | if err == nil { | |
52 | panic("err should not be nil") | |
53 | } | |
54 | assertNullStr(t, badType, "wrong type json") | |
55 | ||
56 | var invalid String | |
57 | err = invalid.UnmarshalJSON(invalidJSON) | |
58 | if _, ok := err.(*json.SyntaxError); !ok { | |
59 | t.Errorf("expected json.SyntaxError, not %T", err) | |
60 | } | |
61 | assertNullStr(t, invalid, "invalid json") | |
62 | } | |
63 | ||
64 | func TestTextUnmarshalString(t *testing.T) { | |
65 | var str String | |
66 | err := str.UnmarshalText([]byte("test")) | |
67 | maybePanic(err) | |
68 | assertStr(t, str, "UnmarshalText() string") | |
69 | ||
70 | var null String | |
71 | err = null.UnmarshalText([]byte("")) | |
72 | maybePanic(err) | |
73 | assertNullStr(t, null, "UnmarshalText() empty string") | |
74 | } | |
75 | ||
76 | func TestMarshalString(t *testing.T) { | |
77 | str := StringFrom("test") | |
78 | data, err := json.Marshal(str) | |
79 | maybePanic(err) | |
80 | assertJSONEquals(t, data, `"test"`, "non-empty json marshal") | |
81 | ||
82 | // invalid values should be encoded as an empty string | |
83 | null := StringFrom("") | |
84 | data, err = json.Marshal(null) | |
85 | maybePanic(err) | |
86 | assertJSONEquals(t, data, `""`, "empty json marshal") | |
87 | } | |
88 | ||
89 | // Tests omitempty... broken until Go 1.4 | |
90 | // func TestMarshalStringInStruct(t *testing.T) { | |
91 | // obj := stringInStruct{Test: StringFrom("")} | |
92 | // data, err := json.Marshal(obj) | |
93 | // maybePanic(err) | |
94 | // assertJSONEquals(t, data, `{}`, "null string in struct") | |
95 | // } | |
96 | ||
97 | func TestStringPointer(t *testing.T) { | |
98 | str := StringFrom("test") | |
99 | ptr := str.Ptr() | |
100 | if *ptr != "test" { | |
101 | t.Errorf("bad %s string: %#v ≠ %s\n", "pointer", ptr, "test") | |
102 | } | |
103 | ||
104 | null := StringFrom("") | |
105 | ptr = null.Ptr() | |
106 | if ptr != nil { | |
107 | t.Errorf("bad %s string: %#v ≠ %s\n", "nil pointer", ptr, "nil") | |
108 | } | |
109 | } | |
110 | ||
111 | func TestStringFromPointer(t *testing.T) { | |
112 | test := "test" | |
113 | testptr := &test | |
114 | str := StringFromPtr(testptr) | |
115 | assertStr(t, str, "StringFromPtr()") | |
116 | ||
117 | testptr = nil | |
118 | null := StringFromPtr(testptr) | |
119 | assertNullStr(t, null, "StringFromPtr()") | |
120 | ||
121 | ptr := null.Ptr() | |
122 | if ptr != nil { | |
123 | t.Errorf("bad %s string: %#v ≠ %s\n", "nil pointer", ptr, "nil") | |
124 | } | |
125 | } | |
126 | ||
127 | func TestStringIsZero(t *testing.T) { | |
128 | str := StringFrom("test") | |
129 | if str.IsZero() { | |
130 | t.Errorf("IsZero() should be false") | |
131 | } | |
132 | ||
133 | null := StringFrom("") | |
134 | if !null.IsZero() { | |
135 | t.Errorf("IsZero() should be true") | |
136 | } | |
137 | ||
138 | empty := NewString("", true) | |
139 | if !empty.IsZero() { | |
140 | t.Errorf("IsZero() should be true") | |
141 | } | |
142 | } | |
143 | ||
144 | func TestStringScan(t *testing.T) { | |
145 | var str String | |
146 | err := str.Scan("test") | |
147 | maybePanic(err) | |
148 | assertStr(t, str, "scanned string") | |
149 | ||
150 | var null String | |
151 | err = null.Scan(nil) | |
152 | maybePanic(err) | |
153 | assertNullStr(t, null, "scanned null") | |
154 | } | |
155 | ||
156 | func TestStringSetValid(t *testing.T) { | |
157 | change := NewString("", false) | |
158 | assertNullStr(t, change, "SetValid()") | |
159 | change.SetValid("test") | |
160 | assertStr(t, change, "SetValid()") | |
161 | } | |
162 | ||
163 | func maybePanic(err error) { | |
164 | if err != nil { | |
165 | panic(err) | |
166 | } | |
167 | } | |
168 | ||
169 | func assertStr(t *testing.T, s String, from string) { | |
170 | if s.String != "test" { | |
171 | t.Errorf("bad %s string: %s ≠ %s\n", from, s.String, "test") | |
172 | } | |
173 | if !s.Valid { | |
174 | t.Error(from, "is invalid, but should be valid") | |
175 | } | |
176 | } | |
177 | ||
178 | func assertNullStr(t *testing.T, s String, from string) { | |
179 | if s.Valid { | |
180 | t.Error(from, "is valid, but should be invalid") | |
181 | } | |
182 | } | |
183 | ||
184 | func assertJSONEquals(t *testing.T, data []byte, cmp string, from string) { | |
185 | if string(data) != cmp { | |
186 | t.Errorf("bad %s data: %s ≠ %s\n", from, data, cmp) | |
187 | } | |
188 | } |
0 | package zero | |
1 | ||
2 | import ( | |
3 | "database/sql/driver" | |
4 | "encoding/json" | |
5 | "fmt" | |
6 | "reflect" | |
7 | "time" | |
8 | ) | |
9 | ||
10 | // Time is a nullable time.Time. | |
11 | // JSON marshals to the zero value for time.Time if null. | |
12 | // Considered to be null to SQL if zero. | |
13 | type Time struct { | |
14 | Time time.Time | |
15 | Valid bool | |
16 | } | |
17 | ||
18 | // Scan implements Scanner interface. | |
19 | func (t *Time) Scan(value interface{}) error { | |
20 | var err error | |
21 | switch x := value.(type) { | |
22 | case time.Time: | |
23 | t.Time = x | |
24 | case nil: | |
25 | t.Valid = false | |
26 | return nil | |
27 | default: | |
28 | err = fmt.Errorf("null: cannot scan type %T into null.Time: %v", value, value) | |
29 | } | |
30 | t.Valid = err == nil | |
31 | return err | |
32 | } | |
33 | ||
34 | // Value implements the driver Valuer interface. | |
35 | func (t Time) Value() (driver.Value, error) { | |
36 | if !t.Valid { | |
37 | return nil, nil | |
38 | } | |
39 | return t.Time, nil | |
40 | } | |
41 | ||
42 | // NewTime creates a new Time. | |
43 | func NewTime(t time.Time, valid bool) Time { | |
44 | return Time{ | |
45 | Time: t, | |
46 | Valid: valid, | |
47 | } | |
48 | } | |
49 | ||
50 | // TimeFrom creates a new Time that will | |
51 | // be null if t is the zero value. | |
52 | func TimeFrom(t time.Time) Time { | |
53 | return NewTime(t, !t.IsZero()) | |
54 | } | |
55 | ||
56 | // TimeFromPtr creates a new Time that will | |
57 | // be null if t is nil or *t is the zero value. | |
58 | func TimeFromPtr(t *time.Time) Time { | |
59 | if t == nil { | |
60 | return NewTime(time.Time{}, false) | |
61 | } | |
62 | return TimeFrom(*t) | |
63 | } | |
64 | ||
65 | // MarshalJSON implements json.Marshaler. | |
66 | // It will encode the zero value of time.Time | |
67 | // if this time is invalid. | |
68 | func (t Time) MarshalJSON() ([]byte, error) { | |
69 | if !t.Valid { | |
70 | return (time.Time{}).MarshalJSON() | |
71 | } | |
72 | return t.Time.MarshalJSON() | |
73 | } | |
74 | ||
75 | // UnmarshalJSON implements json.Unmarshaler. | |
76 | // It supports string, object (e.g. pq.NullTime and friends) | |
77 | // and null input. | |
78 | func (t *Time) UnmarshalJSON(data []byte) error { | |
79 | var err error | |
80 | var v interface{} | |
81 | if err = json.Unmarshal(data, &v); err != nil { | |
82 | return err | |
83 | } | |
84 | switch x := v.(type) { | |
85 | case string: | |
86 | var ti time.Time | |
87 | if err = ti.UnmarshalJSON(data); err != nil { | |
88 | return err | |
89 | } | |
90 | *t = TimeFrom(ti) | |
91 | return nil | |
92 | case map[string]interface{}: | |
93 | ti, tiOK := x["Time"].(string) | |
94 | valid, validOK := x["Valid"].(bool) | |
95 | if !tiOK || !validOK { | |
96 | 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"]) | |
97 | } | |
98 | err = t.Time.UnmarshalText([]byte(ti)) | |
99 | t.Valid = valid | |
100 | return err | |
101 | case nil: | |
102 | t.Valid = false | |
103 | return nil | |
104 | default: | |
105 | return fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Time", reflect.TypeOf(v).Name()) | |
106 | } | |
107 | } | |
108 | ||
109 | func (t Time) MarshalText() ([]byte, error) { | |
110 | ti := t.Time | |
111 | if !t.Valid { | |
112 | ti = time.Time{} | |
113 | } | |
114 | return ti.MarshalText() | |
115 | } | |
116 | ||
117 | func (t *Time) UnmarshalText(text []byte) error { | |
118 | str := string(text) | |
119 | if str == "" || str == "null" { | |
120 | t.Valid = false | |
121 | return nil | |
122 | } | |
123 | if err := t.Time.UnmarshalText(text); err != nil { | |
124 | return err | |
125 | } | |
126 | t.Valid = true | |
127 | return nil | |
128 | } | |
129 | ||
130 | // SetValid changes this Time's value and | |
131 | // sets it to be non-null. | |
132 | func (t *Time) SetValid(v time.Time) { | |
133 | t.Time = v | |
134 | t.Valid = true | |
135 | } | |
136 | ||
137 | // Ptr returns a pointer to this Time's value, | |
138 | // or a nil pointer if this Time is zero. | |
139 | func (t Time) Ptr() *time.Time { | |
140 | if !t.Valid { | |
141 | return nil | |
142 | } | |
143 | return &t.Time | |
144 | } |
0 | package zero | |
1 | ||
2 | import ( | |
3 | "encoding/json" | |
4 | "testing" | |
5 | "time" | |
6 | ) | |
7 | ||
8 | var ( | |
9 | timeString = "2012-12-21T21:21:21Z" | |
10 | timeJSON = []byte(`"` + timeString + `"`) | |
11 | zeroTimeStr = "0001-01-01T00:00:00Z" | |
12 | zeroTimeJSON = []byte(`"0001-01-01T00:00:00Z"`) | |
13 | blankTimeJSON = []byte(`null`) | |
14 | timeValue, _ = time.Parse(time.RFC3339, timeString) | |
15 | timeObject = []byte(`{"Time":"2012-12-21T21:21:21Z","Valid":true}`) | |
16 | nullObject = []byte(`{"Time":"0001-01-01T00:00:00Z","Valid":false}`) | |
17 | badObject = []byte(`{"hello": "world"}`) | |
18 | ) | |
19 | ||
20 | func TestUnmarshalTimeJSON(t *testing.T) { | |
21 | var ti Time | |
22 | err := json.Unmarshal(timeObject, &ti) | |
23 | maybePanic(err) | |
24 | assertTime(t, ti, "UnmarshalJSON() json") | |
25 | ||
26 | var blank Time | |
27 | err = json.Unmarshal(blankTimeJSON, &blank) | |
28 | maybePanic(err) | |
29 | assertNullTime(t, blank, "blank time json") | |
30 | ||
31 | var zero Time | |
32 | err = json.Unmarshal(zeroTimeJSON, &zero) | |
33 | maybePanic(err) | |
34 | assertNullTime(t, zero, "zero time json") | |
35 | ||
36 | var fromObject Time | |
37 | err = json.Unmarshal(timeObject, &fromObject) | |
38 | maybePanic(err) | |
39 | assertTime(t, fromObject, "map time json") | |
40 | ||
41 | var null Time | |
42 | err = json.Unmarshal(nullObject, &null) | |
43 | maybePanic(err) | |
44 | assertNullTime(t, null, "map null time json") | |
45 | ||
46 | var nullFromObj Time | |
47 | err = json.Unmarshal(nullObject, &nullFromObj) | |
48 | maybePanic(err) | |
49 | assertNullTime(t, nullFromObj, "null from object json") | |
50 | ||
51 | var invalid Time | |
52 | err = invalid.UnmarshalJSON(invalidJSON) | |
53 | if _, ok := err.(*json.SyntaxError); !ok { | |
54 | t.Errorf("expected json.SyntaxError, not %T", err) | |
55 | } | |
56 | assertNullTime(t, invalid, "invalid from object json") | |
57 | ||
58 | var bad Time | |
59 | err = json.Unmarshal(badObject, &bad) | |
60 | if err == nil { | |
61 | t.Errorf("expected error: bad object") | |
62 | } | |
63 | assertNullTime(t, bad, "bad from object json") | |
64 | ||
65 | var wrongType Time | |
66 | err = json.Unmarshal(intJSON, &wrongType) | |
67 | if err == nil { | |
68 | t.Errorf("expected error: wrong type JSON") | |
69 | } | |
70 | assertNullTime(t, wrongType, "wrong type object json") | |
71 | ||
72 | var wrongString Time | |
73 | err = json.Unmarshal(stringJSON, &wrongString) | |
74 | if err == nil { | |
75 | t.Errorf("expected error: wrong string JSON") | |
76 | } | |
77 | assertNullTime(t, wrongString, "wrong string object json") | |
78 | } | |
79 | ||
80 | func TestMarshalTime(t *testing.T) { | |
81 | ti := TimeFrom(timeValue) | |
82 | data, err := json.Marshal(ti) | |
83 | maybePanic(err) | |
84 | assertJSONEquals(t, data, string(timeJSON), "non-empty json marshal") | |
85 | ||
86 | null := TimeFromPtr(nil) | |
87 | data, err = json.Marshal(null) | |
88 | maybePanic(err) | |
89 | assertJSONEquals(t, data, string(zeroTimeJSON), "empty json marshal") | |
90 | } | |
91 | ||
92 | func TestUnmarshalTimeText(t *testing.T) { | |
93 | ti := TimeFrom(timeValue) | |
94 | txt, err := ti.MarshalText() | |
95 | maybePanic(err) | |
96 | assertJSONEquals(t, txt, timeString, "marshal text") | |
97 | ||
98 | var unmarshal Time | |
99 | err = unmarshal.UnmarshalText(txt) | |
100 | maybePanic(err) | |
101 | assertTime(t, unmarshal, "unmarshal text") | |
102 | ||
103 | var null Time | |
104 | err = null.UnmarshalText(nullJSON) | |
105 | maybePanic(err) | |
106 | assertNullTime(t, null, "unmarshal null text") | |
107 | txt, err = null.MarshalText() | |
108 | maybePanic(err) | |
109 | assertJSONEquals(t, txt, zeroTimeStr, "marshal null text") | |
110 | ||
111 | var invalid Time | |
112 | err = invalid.UnmarshalText([]byte("hello world")) | |
113 | if err == nil { | |
114 | t.Error("expected error") | |
115 | } | |
116 | assertNullTime(t, invalid, "bad string") | |
117 | } | |
118 | ||
119 | func TestTimeFrom(t *testing.T) { | |
120 | ti := TimeFrom(timeValue) | |
121 | assertTime(t, ti, "TimeFrom() time.Time") | |
122 | ||
123 | var nt time.Time | |
124 | null := TimeFrom(nt) | |
125 | assertNullTime(t, null, "TimeFrom() empty time.Time") | |
126 | } | |
127 | ||
128 | func TestTimeFromPtr(t *testing.T) { | |
129 | ti := TimeFromPtr(&timeValue) | |
130 | assertTime(t, ti, "TimeFromPtr() time") | |
131 | ||
132 | null := TimeFromPtr(nil) | |
133 | assertNullTime(t, null, "TimeFromPtr(nil)") | |
134 | } | |
135 | ||
136 | func TestTimeSetValid(t *testing.T) { | |
137 | var ti time.Time | |
138 | change := TimeFrom(ti) | |
139 | assertNullTime(t, change, "SetValid()") | |
140 | change.SetValid(timeValue) | |
141 | assertTime(t, change, "SetValid()") | |
142 | } | |
143 | ||
144 | func TestTimePointer(t *testing.T) { | |
145 | ti := TimeFrom(timeValue) | |
146 | ptr := ti.Ptr() | |
147 | if *ptr != timeValue { | |
148 | t.Errorf("bad %s time: %#v ≠ %v\n", "pointer", ptr, timeValue) | |
149 | } | |
150 | ||
151 | var nt time.Time | |
152 | null := TimeFrom(nt) | |
153 | ptr = null.Ptr() | |
154 | if ptr != nil { | |
155 | t.Errorf("bad %s time: %#v ≠ %s\n", "nil pointer", ptr, "nil") | |
156 | } | |
157 | } | |
158 | ||
159 | func TestTimeScan(t *testing.T) { | |
160 | var ti Time | |
161 | err := ti.Scan(timeValue) | |
162 | maybePanic(err) | |
163 | assertTime(t, ti, "scanned time") | |
164 | ||
165 | var null Time | |
166 | err = null.Scan(nil) | |
167 | maybePanic(err) | |
168 | assertNullTime(t, null, "scanned null") | |
169 | ||
170 | var wrong Time | |
171 | err = wrong.Scan(int64(42)) | |
172 | if err == nil { | |
173 | t.Error("expected error") | |
174 | } | |
175 | assertNullTime(t, wrong, "scanned wrong") | |
176 | } | |
177 | ||
178 | func TestTimeValue(t *testing.T) { | |
179 | ti := TimeFrom(timeValue) | |
180 | v, err := ti.Value() | |
181 | maybePanic(err) | |
182 | if ti.Time != timeValue { | |
183 | t.Errorf("bad time.Time value: %v ≠ %v", ti.Time, timeValue) | |
184 | } | |
185 | ||
186 | var nt time.Time | |
187 | zero := TimeFrom(nt) | |
188 | v, err = zero.Value() | |
189 | maybePanic(err) | |
190 | if v != nil { | |
191 | t.Errorf("bad %s time.Time value: %v ≠ %v", "zero", v, nil) | |
192 | } | |
193 | } | |
194 | ||
195 | func assertTime(t *testing.T, ti Time, from string) { | |
196 | if ti.Time != timeValue { | |
197 | t.Errorf("bad %v time: %v ≠ %v\n", from, ti.Time, timeValue) | |
198 | } | |
199 | if !ti.Valid { | |
200 | t.Error(from, "is invalid, but should be valid") | |
201 | } | |
202 | } | |
203 | ||
204 | func assertNullTime(t *testing.T, ti Time, from string) { | |
205 | if ti.Valid { | |
206 | t.Error(from, "is valid, but should be invalid") | |
207 | } | |
208 | } |