tailscale/types/opt/value.go
Joe Tsai ae303d41dd
go.mod: bump github.com/go-json-experiment/json (#15010)
The upstream module has seen significant work making
the v1 emulation layer a high fidelity re-implementation
of v1 "encoding/json".

This addresses several upstream breaking changes:
* MarshalJSONV2 renamed as MarshalJSONTo
* UnmarshalJSONV2 renamed as UnmarshalJSONFrom
* Options argument removed from MarshalJSONV2
* Options argument removed from UnmarshalJSONV2

Updates tailscale/corp#791

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2025-02-27 11:35:54 -08:00

131 lines
3.4 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package opt
import (
"fmt"
"reflect"
jsonv2 "github.com/go-json-experiment/json"
"github.com/go-json-experiment/json/jsontext"
)
// Value is an optional value to be JSON-encoded.
// With [encoding/json], a zero Value is marshaled as a JSON null.
// With [github.com/go-json-experiment/json], a zero Value is omitted from the
// JSON object if the Go struct field specified with omitzero.
// The omitempty tag option should never be used with Value fields.
type Value[T any] struct {
value T
set bool
}
// Equal reports whether the receiver and the other value are equal.
// If the template type T in Value[T] implements an Equal method, it will be used
// instead of the == operator for comparing values.
type equatable[T any] interface {
// Equal reports whether the receiver and the other values are equal.
Equal(other T) bool
}
// ValueOf returns an optional Value containing the specified value.
// It treats nil slices and maps as empty slices and maps.
func ValueOf[T any](v T) Value[T] {
return Value[T]{value: v, set: true}
}
// String implements [fmt.Stringer].
func (o Value[T]) String() string {
if !o.set {
return fmt.Sprintf("(empty[%T])", o.value)
}
return fmt.Sprint(o.value)
}
// Set assigns the specified value to the optional value o.
func (o *Value[T]) Set(v T) {
*o = ValueOf(v)
}
// Clear resets o to an empty state.
func (o *Value[T]) Clear() {
*o = Value[T]{}
}
// IsSet reports whether o has a value set.
func (o *Value[T]) IsSet() bool {
return o.set
}
// Get returns the value of o.
// If a value hasn't been set, a zero value of T will be returned.
func (o Value[T]) Get() T {
return o.value
}
// GetOr returns the value of o or def if a value hasn't been set.
func (o Value[T]) GetOr(def T) T {
if o.set {
return o.value
}
return def
}
// Get returns the value and a flag indicating whether the value is set.
func (o Value[T]) GetOk() (v T, ok bool) {
return o.value, o.set
}
// Equal reports whether o is equal to v.
// Two optional values are equal if both are empty,
// or if both are set and the underlying values are equal.
// If the template type T implements an Equal(T) bool method, it will be used
// instead of the == operator for value comparison.
// If T is not comparable, it returns false.
func (o Value[T]) Equal(v Value[T]) bool {
if o.set != v.set {
return false
}
if !o.set {
return true
}
ov := any(o.value)
if eq, ok := ov.(equatable[T]); ok {
return eq.Equal(v.value)
}
if reflect.TypeFor[T]().Comparable() {
return ov == any(v.value)
}
return false
}
// MarshalJSONTo implements [jsonv2.MarshalerTo].
func (o Value[T]) MarshalJSONTo(enc *jsontext.Encoder) error {
if !o.set {
return enc.WriteToken(jsontext.Null)
}
return jsonv2.MarshalEncode(enc, &o.value)
}
// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
func (o *Value[T]) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
if dec.PeekKind() == 'n' {
*o = Value[T]{}
_, err := dec.ReadToken() // read null
return err
}
o.set = true
return jsonv2.UnmarshalDecode(dec, &o.value)
}
// MarshalJSON implements [json.Marshaler].
func (o Value[T]) MarshalJSON() ([]byte, error) {
return jsonv2.Marshal(o) // uses MarshalJSONTo
}
// UnmarshalJSON implements [json.Unmarshaler].
func (o *Value[T]) UnmarshalJSON(b []byte) error {
return jsonv2.Unmarshal(b, o) // uses UnmarshalJSONFrom
}