mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-02-13 08:32:47 +00:00
118 lines
3.2 KiB
Go
118 lines
3.2 KiB
Go
package nullable
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
)
|
|
|
|
// Nullable is a generic type, which implements a field that can be one of three states:
|
|
//
|
|
// - field is not set in the request
|
|
// - field is explicitly set to `null` in the request
|
|
// - field is explicitly set to a valid value in the request
|
|
//
|
|
// Nullable is intended to be used with JSON marshalling and unmarshalling.
|
|
//
|
|
// Internal implementation details:
|
|
//
|
|
// - map[true]T means a value was provided
|
|
// - map[false]T means an explicit null was provided
|
|
// - nil or zero map means the field was not provided
|
|
//
|
|
// If the field is expected to be optional, add the `omitempty` JSON tags. Do NOT use `*Nullable`!
|
|
//
|
|
// Adapted from https://github.com/golang/go/issues/64515#issuecomment-1841057182
|
|
type Nullable[T any] map[bool]T
|
|
|
|
// NewNullableWithValue is a convenience helper to allow constructing a `Nullable` with a given value, for instance to construct a field inside a struct, without introducing an intermediate variable
|
|
func NewNullableWithValue[T any](t T) Nullable[T] {
|
|
var n Nullable[T]
|
|
n.Set(t)
|
|
return n
|
|
}
|
|
|
|
// NewNullNullable is a convenience helper to allow constructing a `Nullable` with an explicit `null`, for instance to construct a field inside a struct, without introducing an intermediate variable
|
|
func NewNullNullable[T any]() Nullable[T] {
|
|
var n Nullable[T]
|
|
n.SetNull()
|
|
return n
|
|
}
|
|
|
|
// Get retrieves the underlying value, if present, and returns an error if the value was not present
|
|
func (t Nullable[T]) Get() (T, error) {
|
|
var empty T
|
|
if t.IsNull() {
|
|
return empty, errors.New("value is null")
|
|
}
|
|
if !t.IsSpecified() {
|
|
return empty, errors.New("value is not specified")
|
|
}
|
|
return t[true], nil
|
|
}
|
|
|
|
// MustGet retrieves the underlying value, if present, and panics if the value was not present
|
|
func (t Nullable[T]) MustGet() T {
|
|
v, err := t.Get()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return v
|
|
}
|
|
|
|
// Set sets the underlying value to a given value
|
|
func (t *Nullable[T]) Set(value T) {
|
|
*t = map[bool]T{true: value}
|
|
}
|
|
|
|
// IsNull indicate whether the field was sent, and had a value of `null`
|
|
func (t Nullable[T]) IsNull() bool {
|
|
_, foundNull := t[false]
|
|
return foundNull
|
|
}
|
|
|
|
// SetNull indicate that the field was sent, and had a value of `null`
|
|
func (t *Nullable[T]) SetNull() {
|
|
var empty T
|
|
*t = map[bool]T{false: empty}
|
|
}
|
|
|
|
// IsSpecified indicates whether the field was sent
|
|
func (t Nullable[T]) IsSpecified() bool {
|
|
return len(t) != 0
|
|
}
|
|
|
|
// SetUnspecified indicate whether the field was sent
|
|
func (t *Nullable[T]) SetUnspecified() {
|
|
*t = map[bool]T{}
|
|
}
|
|
|
|
func (t Nullable[T]) MarshalJSON() ([]byte, error) {
|
|
// if field was specified, and `null`, marshal it
|
|
if t.IsNull() {
|
|
return []byte("null"), nil
|
|
}
|
|
|
|
// if field was unspecified, and `omitempty` is set on the field's tags, `json.Marshal` will omit this field
|
|
|
|
// otherwise: we have a value, so marshal it
|
|
return json.Marshal(t[true])
|
|
}
|
|
|
|
func (t *Nullable[T]) UnmarshalJSON(data []byte) error {
|
|
// if field is unspecified, UnmarshalJSON won't be called
|
|
|
|
// if field is specified, and `null`
|
|
if bytes.Equal(data, []byte("null")) {
|
|
t.SetNull()
|
|
return nil
|
|
}
|
|
// otherwise, we have an actual value, so parse it
|
|
var v T
|
|
if err := json.Unmarshal(data, &v); err != nil {
|
|
return err
|
|
}
|
|
t.Set(v)
|
|
return nil
|
|
}
|