-
Notifications
You must be signed in to change notification settings - Fork 0
/
gt_null_string.go
165 lines (134 loc) · 4.02 KB
/
gt_null_string.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package gt
import (
"database/sql/driver"
"encoding/json"
)
/*
Variant of `string` where zero value is considered empty in text, and null in
JSON and SQL. Use this for fields where an empty string is not allowed, such as
enums or text foreign keys.
Unlike `string`, encoding/decoding is not always reversible:
JSON "" → Go "" → JSON null
SQL '' → Go "" → SQL null
Differences from `"database/sql".NullString`:
* Much easier to use.
* Supports text.
* Supports JSON.
* Fewer states: null and empty string are one.
In your data model, text fields should be either:
* Non-nullable, zero value = empty string -> use `string`.
* Nullable, zero value = `null`, empty string is not allowed -> use `gt.NullString`.
Avoid `*string` or `sql.NullString`.
*/
type NullString string
var (
_ = Encodable(NullString(``))
_ = Decodable((*NullString)(nil))
)
// Implement `gt.Zeroable`. Equivalent to `reflect.ValueOf(self).IsZero()`.
func (self NullString) IsZero() bool { return self == `` }
// Implement `gt.Nullable`. True if zero.
func (self NullString) IsNull() bool { return self.IsZero() }
// Implement `gt.PtrGetter`, returning `*string`.
func (self *NullString) GetPtr() any { return (*string)(self) }
// Implement `gt.Getter`. If zero, returns `nil`, otherwise returns `string`.
func (self NullString) Get() any {
if self.IsNull() {
return nil
}
return string(self)
}
// Implement `gt.Setter`, using `.Scan`. Panics on error.
func (self *NullString) Set(src any) { try(self.Scan(src)) }
// Implement `gt.Zeroer`, zeroing the receiver.
func (self *NullString) Zero() {
if self != nil {
*self = ``
}
}
// Implement `fmt.Stringer`, returning the string as-is.
func (self NullString) String() string {
return string(self)
}
// Implement `gt.Parser`, assigning the string as-is.
func (self *NullString) Parse(src string) error {
*self = NullString(src)
return nil
}
// Implement `gt.AppenderTo`, appending the string as-is.
func (self NullString) AppendTo(buf []byte) []byte {
return append(buf, self...)
}
/*
Implement `encoding.TextMarhaler`. If zero, returns nil. Otherwise returns the
string as-is.
*/
func (self NullString) MarshalText() ([]byte, error) {
if self.IsNull() {
return nil, nil
}
return self.AppendTo(nil), nil
}
// Implement `encoding.TextUnmarshaler`, assigning the string as-is.
func (self *NullString) UnmarshalText(src []byte) error {
// This makes a copy, which is intentional because streaming decoders tend to
// reuse one buffer for different content.
*self = NullString(src)
return nil
}
/*
Implement `json.Marshaler`. If zero, returns bytes representing `null`.
Otherwise uses the default `json.Marshal` behavior for `string`.
*/
func (self NullString) MarshalJSON() ([]byte, error) {
if self.IsNull() {
return bytesNull, nil
}
return json.Marshal(self.Get())
}
/*
Implement `json.Unmarshaler`. If the input is empty or represents JSON `null`,
zeroes the receiver. Otherwise uses the default `json.Unmarshal` behavior
for `*string`.
*/
func (self *NullString) UnmarshalJSON(src []byte) error {
if isJsonEmpty(src) {
self.Zero()
return nil
}
return json.Unmarshal(src, self.GetPtr())
}
// Implement `driver.Valuer`, using `.Get`.
func (self NullString) Value() (driver.Value, error) { return self.Get(), nil }
/*
Implement `sql.Scanner`, converting an arbitrary input to `gt.NullString` and
modifying the receiver. Acceptable inputs:
* `nil` -> use `.Zero`
* `string` -> use `.Parse`
* `[]byte` -> use `.UnmarshalText`
* `NullString` -> assign
* `gt.Getter` -> scan underlying value
*/
func (self *NullString) Scan(src any) error {
switch src := src.(type) {
case nil:
self.Zero()
return nil
case string:
return self.Parse(src)
case []byte:
return self.UnmarshalText(src)
case NullString:
*self = src
return nil
default:
val, ok := get(src)
if ok {
return self.Scan(val)
}
return errScanType(self, src)
}
}
// Same as `len(self)`. Sometimes handy when embedding `gt.NullString` in
// single-field structs.
func (self NullString) Len() int { return len(self) }