/
omit.go
326 lines (287 loc) · 7.5 KB
/
omit.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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
// Package null exposes a Val(ue) type that wraps a regular value with the
// ability to be 'omitted' or 'unset'.
package omit
import (
"bytes"
"database/sql/driver"
"encoding"
"errors"
"reflect"
"github.com/aarondl/opt"
"github.com/aarondl/opt/internal/globaldata"
)
// state is the state of the omittable object
type state int
const (
StateUnset state = 0
StateSet state = 1
)
// String -er interface implementation
func (s state) String() string {
switch s {
case StateUnset:
return "unset"
case StateSet:
return "set"
default:
panic("unknown")
}
}
// Val allows representing a value with a state of "unset" or "set".
// Its zero value is usfel and initially "unset".
type Val[T any] struct {
value T
state state
}
// From a value which is considered 'set'
func From[T any](val T) Val[T] {
return Val[T]{
value: val,
state: StateSet,
}
}
// FromPtr creates a value from a pointer, if the pointer is null it will be
// 'unset', if it has a value the deferenced value is stored.
func FromPtr[T any](val *T) Val[T] {
if val == nil {
return Val[T]{state: StateUnset}
}
return Val[T]{
value: *val,
state: StateSet,
}
}
// FromCond conditionally creates a 'set' value if the bool is true, else
// it will return an omitted value.
func FromCond[T any](val T, ok bool) Val[T] {
if !ok {
return Val[T]{}
}
return Val[T]{
value: val,
state: StateSet,
}
}
// Get the underlying value, if one exists.
func (v Val[T]) Get() (T, bool) {
if v.state == StateSet {
return v.value, true
}
var empty T
return empty, false
}
// GetOr gets the value or returns a fallback if the value does not exist.
func (v Val[T]) GetOr(fallback T) T {
if v.state == StateSet {
return v.value
}
return fallback
}
// GetOrZero returns the zero value for T if the value was omitted.
func (v Val[T]) GetOrZero() T {
if v.state != StateSet {
var t T
return t
}
return v.value
}
// MustGet retrieves the value or panics if it's null
func (v Val[T]) MustGet() T {
val, ok := v.Get()
if !ok {
panic("no value present")
}
return val
}
// Or returns v or other depending on their states. In general
// set > unset and therefore the one with the state highest in that
// area will win out.
//
// v | other | result
// ------------- | -------
// set | _ | v
// unset | set | other
// unset | unset | v
func (v Val[T]) Or(other Val[T]) Val[T] {
switch {
case v.state == StateUnset && other.state == StateSet:
return other
default:
return v
}
}
// Map transforms the value inside if it is set, else it returns a value of the
// same state.
//
// Until a later Go version adds type parameters to methods, it is not possible
// to map to a different type. See the non-method function Map if you need
// another type.
func (v Val[T]) Map(fn func(T) T) Val[T] {
if v.state == StateSet {
return From(fn(v.value))
}
return Val[T]{state: v.state}
}
// Map transforms the value inside if it is set, else it returns value of the
// same state.
func Map[A any, B any](v Val[A], fn func(A) B) Val[B] {
if v.state == StateSet {
return From(fn(v.value))
}
return Val[B]{state: v.state}
}
// Set the value (and the state to 'set')
func (v *Val[T]) Set(val T) {
v.value = val
v.state = StateSet
}
// Unset the value (state is set to 'unset')
func (v *Val[T]) Unset() {
var empty T
v.value = empty
v.state = StateUnset
}
// IsSet returns true if v contains a non-null value
func (v Val[T]) IsSet() bool {
return v.state == StateSet
}
// IsUnset returns true if v contains no value
func (v Val[T]) IsUnset() bool {
return v.state == StateUnset
}
// State retrieves the internal state, mostly useful for testing.
func (v Val[T]) State() state {
return v.state
}
// UnmarshalJSON implements json.Unmarshaler. Notably will fail to unmarshal
// if given a null.
func (v *Val[T]) UnmarshalJSON(data []byte) error {
switch {
case len(data) == 0:
var zero T
v.value = zero
v.state = StateUnset
return nil
case bytes.Equal(data, globaldata.JSONNull):
return errors.New("cannot unmarshal 'null' value into omit value")
default:
err := opt.JSONUnmarshal(data, &v.value)
if err != nil {
return err
}
v.state = StateSet
return nil
}
}
// MarshalJSON implements json.Marshaler.
//
// Note that this type cannot possibly work with the stdlib json package due
// to there being no way for the json package to omit a value based on its
// internals.
//
// That's to say even if you have an `omitempty` tag with this type, it will
// still show up in outputs as {"val": null} because this functionality is not
// supported.
//
// For a package that works well with this package see github.com/aarondl/json.
func (v Val[T]) MarshalJSON() ([]byte, error) {
switch v.state {
case StateSet:
return opt.JSONMarshal(v.value)
default:
return globaldata.JSONNull, nil
}
}
// MarshalJSONIsZero returns true if this value should be omitted by the json
// marshaler.
//
// There is a special case in which we omit the value even if the value is `set`
// which is when the value is going to write out `nil` (pointers, maps
// and slices that are nil) when marshaled.
//
// The reason this is important is if we marshal(From[[]int](nil)) with the
// special json fork, it will emit `null` without this override. This is bad
// because this same package even with the json fork cannot consume a null.
//
// In order to achieve symmetry in encoding/decoding we'll quietly omit nil
// maps, slices, and ptrs as it was likely a mistake to try to .From(nil)
// for this type of value anyway.
func (v Val[T]) MarshalJSONIsZero() bool {
if v.state == StateUnset {
return true
}
switch reflect.TypeOf(v.value).Kind() {
case reflect.Map, reflect.Slice, reflect.Ptr:
if reflect.ValueOf(v.value).IsNil() {
return true
}
}
return false
}
// MarshalText implements encoding.TextMarshaler.
func (v Val[T]) MarshalText() ([]byte, error) {
if v.state != StateSet {
return nil, nil
}
refVal := reflect.ValueOf(v.value)
if refVal.Type().Implements(globaldata.EncodingTextMarshalerIntf) {
valuer := refVal.Interface().(encoding.TextMarshaler)
return valuer.MarshalText()
}
var text string
if err := opt.ConvertAssign(&text, v.value); err != nil {
return nil, err
}
return []byte(text), nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (v *Val[T]) UnmarshalText(text []byte) error {
if len(text) == 0 {
var zero T
v.value = zero
v.state = StateUnset
return nil
}
refVal := reflect.ValueOf(&v.value)
if refVal.Type().Implements(globaldata.EncodingTextUnmarshalerIntf) {
valuer := refVal.Interface().(encoding.TextUnmarshaler)
if err := valuer.UnmarshalText(text); err != nil {
return err
}
v.state = StateSet
return nil
}
if err := opt.ConvertAssign(&v.value, string(text)); err != nil {
return err
}
v.state = StateSet
return nil
}
// Scan implements the sql.Scanner interface. If the wrapped type implements
// sql.Scanner then it will call that.
func (v *Val[T]) Scan(value any) error {
if value == nil {
return errors.New("cannot store 'null' value in omit value")
}
v.state = StateSet
return opt.ConvertAssign(&v.value, value)
}
// Value implements the driver.Valuer interface. If the underlying type
// implements the driver.Valuer it will call that (when not unset).
// Go primitive types will be converted where possible.
//
// Because sql doesn't have an analog to unset it will marshal as null in these
// cases.
//
// int64
// float64
// bool
// []byte
// string
// time.Time
func (v Val[T]) Value() (driver.Value, error) {
if v.state != StateSet {
return nil, nil
}
return opt.ToDriverValue(v.value)
}