generated from TBD54566975/tbd-project-template
-
Notifications
You must be signed in to change notification settings - Fork 5
/
option.go
228 lines (199 loc) 路 4.85 KB
/
option.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
//nolint:wrapcheck
package ftl
import (
"bytes"
"context"
"database/sql"
"database/sql/driver"
"encoding"
"encoding/json"
"fmt"
"reflect"
)
// Stdlib interfaces types implement.
type stdlib interface {
fmt.Stringer
fmt.GoStringer
}
// An Option type is a type that can contain a value or nothing.
type Option[T any] struct {
value T
ok bool
}
var _ driver.Valuer = (*Option[int])(nil)
var _ sql.Scanner = (*Option[int])(nil)
func (o *Option[T]) Scan(src any) error {
if src == nil {
o.ok = false
var zero T
o.value = zero
return nil
}
if value, ok := src.(T); ok {
o.value = value
o.ok = true
return nil
}
var value T
switch scan := any(&value).(type) {
case sql.Scanner:
if err := scan.Scan(src); err != nil {
return fmt.Errorf("cannot scan %T into Option[%T]: %w", src, o.value, err)
}
o.value = value
o.ok = true
case encoding.TextUnmarshaler:
switch src := src.(type) {
case string:
if err := scan.UnmarshalText([]byte(src)); err != nil {
return fmt.Errorf("unmarshal from %T into Option[%T] failed: %w", src, o.value, err)
}
o.value = value
o.ok = true
case []byte:
if err := scan.UnmarshalText(src); err != nil {
return fmt.Errorf("cannot scan %T into Option[%T]: %w", src, o.value, err)
}
o.value = value
o.ok = true
default:
return fmt.Errorf("cannot unmarshal %T into Option[%T]", src, o.value)
}
default:
return fmt.Errorf("no decoding mechanism found for %T into Option[%T]", src, o.value)
}
return nil
}
func (o Option[T]) Value() (driver.Value, error) {
if !o.ok {
return nil, nil
}
switch value := any(o.value).(type) {
case driver.Valuer:
return value.Value()
case encoding.TextMarshaler:
return value.MarshalText()
}
return o.value, nil
}
var _ stdlib = (*Option[int])(nil)
// Some returns an Option that contains a value.
func Some[T any](value T) Option[T] { return Option[T]{value: value, ok: true} }
// None returns an Option that contains nothing.
func None[T any]() Option[T] { return Option[T]{} }
// Ptr returns an Option that is invalid if the pointer is nil, otherwise the dereferenced pointer.
func Ptr[T any](ptr *T) Option[T] {
if ptr == nil {
return None[T]()
}
return Some(*ptr)
}
// Nil returns an Option that is invalid if the value is nil, otherwise the value.
//
// If the type is not nillable (slice, map, chan, ptr, interface) this will panic.
func Nil[T any](ptr T) Option[T] {
rv := reflect.ValueOf(ptr)
if !rv.IsValid() {
return None[T]()
}
switch rv.Type().Kind() {
case reflect.Slice, reflect.Map, reflect.Chan, reflect.Ptr, reflect.Interface:
if rv.IsNil() {
return None[T]()
}
return Some(ptr)
default:
panic("type is not nillable")
}
}
// Zero returns an Option that is invalid if the value is the zero value, otherwise the value.
func Zero[T any](value T) Option[T] {
rv := reflect.ValueOf(value)
if !rv.IsValid() || rv.IsZero() {
return None[T]()
}
return Some(value)
}
// Ptr returns a pointer to the value if the Option contains a value, otherwise nil.
func (o Option[T]) Ptr() *T {
if o.ok {
return &o.value
}
return nil
}
// Ok returns true if the Option contains a value.
func (o Option[T]) Ok() bool { return o.ok }
// MustGet returns the value. It panics if the Option contains nothing.
func (o Option[T]) MustGet() T {
if !o.ok {
var t T
panic(fmt.Sprintf("Option[%T] contains nothing", t))
}
return o.value
}
// Get returns the value and a boolean indicating if the Option contains a value.
func (o Option[T]) Get() (T, bool) { return o.value, o.ok }
// Default returns the Option value if it is present, otherwise it returns the
// value passed.
func (o Option[T]) Default(value T) T {
if o.ok {
return o.value
}
return value
}
func (o Option[T]) MarshalJSON() ([]byte, error) {
if o.ok {
return json.Marshal(o.value)
}
return []byte("null"), nil
}
func (o *Option[T]) UnmarshalJSON(data []byte) error {
if string(data) == "null" {
o.ok = false
return nil
}
if err := json.Unmarshal(data, &o.value); err != nil {
return err
}
o.ok = true
return nil
}
func (o Option[T]) String() string {
if o.ok {
return fmt.Sprintf("%v", o.value)
}
return "None"
}
func (o Option[T]) GoString() string {
if o.ok {
return fmt.Sprintf("Some[%T](%#v)", o.value, o.value)
}
return fmt.Sprintf("None[%T]()", o.value)
}
func (o Option[T]) Marshal(
ctx context.Context,
w *bytes.Buffer,
encode func(ctx context.Context, v reflect.Value, w *bytes.Buffer) error,
) error {
if o.ok {
return encode(ctx, reflect.ValueOf(&o.value).Elem(), w)
}
w.WriteString("null")
return nil
}
func (o *Option[T]) Unmarshal(
ctx context.Context,
d *json.Decoder,
isNull bool,
decode func(ctx context.Context, d *json.Decoder, v reflect.Value) error,
) error {
if isNull {
o.ok = false
return nil
}
if err := decode(ctx, d, reflect.ValueOf(&o.value).Elem()); err != nil {
return err
}
o.ok = true
return nil
}