mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-22 04:48:39 +00:00
types/opt: add generic Value[T any] for optional values of any types
Updates #12736 Signed-off-by: Nick Khyl <nickk@tailscale.com>
This commit is contained in:
parent
5576972261
commit
e21d8768f9
@ -10,6 +10,12 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
|||||||
L github.com/coreos/go-iptables/iptables from tailscale.com/util/linuxfw
|
L github.com/coreos/go-iptables/iptables from tailscale.com/util/linuxfw
|
||||||
W 💣 github.com/dblohm7/wingoes from tailscale.com/util/winutil
|
W 💣 github.com/dblohm7/wingoes from tailscale.com/util/winutil
|
||||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||||
|
github.com/go-json-experiment/json from tailscale.com/types/opt
|
||||||
|
github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json+
|
||||||
|
github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json+
|
||||||
|
github.com/go-json-experiment/json/internal/jsonopts from github.com/go-json-experiment/json+
|
||||||
|
github.com/go-json-experiment/json/internal/jsonwire from github.com/go-json-experiment/json+
|
||||||
|
github.com/go-json-experiment/json/jsontext from github.com/go-json-experiment/json+
|
||||||
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
||||||
L github.com/google/nftables from tailscale.com/util/linuxfw
|
L github.com/google/nftables from tailscale.com/util/linuxfw
|
||||||
L 💣 github.com/google/nftables/alignedbuff from github.com/google/nftables/xt
|
L 💣 github.com/google/nftables/alignedbuff from github.com/google/nftables/xt
|
||||||
|
@ -98,11 +98,12 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
|||||||
💣 github.com/fsnotify/fsnotify from sigs.k8s.io/controller-runtime/pkg/certwatcher
|
💣 github.com/fsnotify/fsnotify from sigs.k8s.io/controller-runtime/pkg/certwatcher
|
||||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||||
github.com/gaissmai/bart from tailscale.com/net/ipset+
|
github.com/gaissmai/bart from tailscale.com/net/ipset+
|
||||||
|
github.com/go-json-experiment/json from tailscale.com/types/opt
|
||||||
github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json/internal/jsonflags+
|
github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json/internal/jsonflags+
|
||||||
github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json/internal/jsonopts+
|
github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json/internal/jsonopts+
|
||||||
github.com/go-json-experiment/json/internal/jsonopts from github.com/go-json-experiment/json/jsontext
|
github.com/go-json-experiment/json/internal/jsonopts from github.com/go-json-experiment/json/jsontext+
|
||||||
github.com/go-json-experiment/json/internal/jsonwire from github.com/go-json-experiment/json/jsontext
|
github.com/go-json-experiment/json/internal/jsonwire from github.com/go-json-experiment/json/jsontext+
|
||||||
github.com/go-json-experiment/json/jsontext from tailscale.com/logtail
|
github.com/go-json-experiment/json/jsontext from tailscale.com/logtail+
|
||||||
github.com/go-logr/logr from github.com/go-logr/logr/slogr+
|
github.com/go-logr/logr from github.com/go-logr/logr/slogr+
|
||||||
github.com/go-logr/logr/slogr from github.com/go-logr/zapr
|
github.com/go-logr/logr/slogr from github.com/go-logr/zapr
|
||||||
github.com/go-logr/zapr from sigs.k8s.io/controller-runtime/pkg/log/zap+
|
github.com/go-logr/zapr from sigs.k8s.io/controller-runtime/pkg/log/zap+
|
||||||
@ -957,7 +958,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
|||||||
math/big from crypto/dsa+
|
math/big from crypto/dsa+
|
||||||
math/bits from compress/flate+
|
math/bits from compress/flate+
|
||||||
math/rand from github.com/google/go-cmp/cmp+
|
math/rand from github.com/google/go-cmp/cmp+
|
||||||
math/rand/v2 from database/sql+
|
math/rand/v2 from tailscale.com/derp+
|
||||||
mime from github.com/prometheus/common/expfmt+
|
mime from github.com/prometheus/common/expfmt+
|
||||||
mime/multipart from github.com/go-openapi/swag+
|
mime/multipart from github.com/go-openapi/swag+
|
||||||
mime/quotedprintable from mime/multipart
|
mime/quotedprintable from mime/multipart
|
||||||
|
@ -2,6 +2,12 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
|
|||||||
|
|
||||||
github.com/beorn7/perks/quantile from github.com/prometheus/client_golang/prometheus
|
github.com/beorn7/perks/quantile from github.com/prometheus/client_golang/prometheus
|
||||||
💣 github.com/cespare/xxhash/v2 from github.com/prometheus/client_golang/prometheus
|
💣 github.com/cespare/xxhash/v2 from github.com/prometheus/client_golang/prometheus
|
||||||
|
github.com/go-json-experiment/json from tailscale.com/types/opt
|
||||||
|
github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json+
|
||||||
|
github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json+
|
||||||
|
github.com/go-json-experiment/json/internal/jsonopts from github.com/go-json-experiment/json+
|
||||||
|
github.com/go-json-experiment/json/internal/jsonwire from github.com/go-json-experiment/json+
|
||||||
|
github.com/go-json-experiment/json/jsontext from github.com/go-json-experiment/json+
|
||||||
github.com/google/uuid from tailscale.com/util/fastuuid
|
github.com/google/uuid from tailscale.com/util/fastuuid
|
||||||
💣 github.com/prometheus/client_golang/prometheus from tailscale.com/tsweb/promvarz
|
💣 github.com/prometheus/client_golang/prometheus from tailscale.com/tsweb/promvarz
|
||||||
github.com/prometheus/client_golang/prometheus/internal from github.com/prometheus/client_golang/prometheus
|
github.com/prometheus/client_golang/prometheus/internal from github.com/prometheus/client_golang/prometheus
|
||||||
@ -128,6 +134,7 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
|
|||||||
embed from crypto/internal/nistec+
|
embed from crypto/internal/nistec+
|
||||||
encoding from encoding/json+
|
encoding from encoding/json+
|
||||||
encoding/asn1 from crypto/x509+
|
encoding/asn1 from crypto/x509+
|
||||||
|
encoding/base32 from github.com/go-json-experiment/json
|
||||||
encoding/base64 from encoding/json+
|
encoding/base64 from encoding/json+
|
||||||
encoding/binary from compress/gzip+
|
encoding/binary from compress/gzip+
|
||||||
encoding/hex from crypto/x509+
|
encoding/hex from crypto/x509+
|
||||||
|
@ -9,6 +9,12 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
W 💣 github.com/dblohm7/wingoes from github.com/dblohm7/wingoes/pe+
|
W 💣 github.com/dblohm7/wingoes from github.com/dblohm7/wingoes/pe+
|
||||||
W 💣 github.com/dblohm7/wingoes/pe from tailscale.com/util/winutil/authenticode
|
W 💣 github.com/dblohm7/wingoes/pe from tailscale.com/util/winutil/authenticode
|
||||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||||
|
github.com/go-json-experiment/json from tailscale.com/types/opt
|
||||||
|
github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json+
|
||||||
|
github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json+
|
||||||
|
github.com/go-json-experiment/json/internal/jsonopts from github.com/go-json-experiment/json+
|
||||||
|
github.com/go-json-experiment/json/internal/jsonwire from github.com/go-json-experiment/json+
|
||||||
|
github.com/go-json-experiment/json/jsontext from github.com/go-json-experiment/json+
|
||||||
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
||||||
L github.com/google/nftables from tailscale.com/util/linuxfw
|
L github.com/google/nftables from tailscale.com/util/linuxfw
|
||||||
L 💣 github.com/google/nftables/alignedbuff from github.com/google/nftables/xt
|
L 💣 github.com/google/nftables/alignedbuff from github.com/google/nftables/xt
|
||||||
|
@ -90,11 +90,12 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
💣 github.com/djherbis/times from tailscale.com/drive/driveimpl
|
💣 github.com/djherbis/times from tailscale.com/drive/driveimpl
|
||||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||||
github.com/gaissmai/bart from tailscale.com/net/tstun+
|
github.com/gaissmai/bart from tailscale.com/net/tstun+
|
||||||
|
github.com/go-json-experiment/json from tailscale.com/types/opt
|
||||||
github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json/internal/jsonflags+
|
github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json/internal/jsonflags+
|
||||||
github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json/internal/jsonopts+
|
github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json/internal/jsonopts+
|
||||||
github.com/go-json-experiment/json/internal/jsonopts from github.com/go-json-experiment/json/jsontext
|
github.com/go-json-experiment/json/internal/jsonopts from github.com/go-json-experiment/json/jsontext+
|
||||||
github.com/go-json-experiment/json/internal/jsonwire from github.com/go-json-experiment/json/jsontext
|
github.com/go-json-experiment/json/internal/jsonwire from github.com/go-json-experiment/json/jsontext+
|
||||||
github.com/go-json-experiment/json/jsontext from tailscale.com/logtail
|
github.com/go-json-experiment/json/jsontext from tailscale.com/logtail+
|
||||||
W 💣 github.com/go-ole/go-ole from github.com/go-ole/go-ole/oleutil+
|
W 💣 github.com/go-ole/go-ole from github.com/go-ole/go-ole/oleutil+
|
||||||
W 💣 github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet
|
W 💣 github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet
|
||||||
L 💣 github.com/godbus/dbus/v5 from tailscale.com/net/dns+
|
L 💣 github.com/godbus/dbus/v5 from tailscale.com/net/dns+
|
||||||
|
122
types/opt/value.go
Normal file
122
types/opt/value.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSONV2 implements [jsonv2.MarshalerV2].
|
||||||
|
func (o Value[T]) MarshalJSONV2(enc *jsontext.Encoder, opts jsonv2.Options) error {
|
||||||
|
if !o.set {
|
||||||
|
return enc.WriteToken(jsontext.Null)
|
||||||
|
}
|
||||||
|
return jsonv2.MarshalEncode(enc, &o.value, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSONV2 implements [jsonv2.UnmarshalerV2].
|
||||||
|
func (o *Value[T]) UnmarshalJSONV2(dec *jsontext.Decoder, opts jsonv2.Options) error {
|
||||||
|
if dec.PeekKind() == 'n' {
|
||||||
|
*o = Value[T]{}
|
||||||
|
_, err := dec.ReadToken() // read null
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
o.set = true
|
||||||
|
return jsonv2.UnmarshalDecode(dec, &o.value, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements [json.Marshaler].
|
||||||
|
func (o Value[T]) MarshalJSON() ([]byte, error) {
|
||||||
|
return jsonv2.Marshal(o) // uses MarshalJSONV2
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements [json.Unmarshaler].
|
||||||
|
func (o *Value[T]) UnmarshalJSON(b []byte) error {
|
||||||
|
return jsonv2.Unmarshal(b, o) // uses UnmarshalJSONV2
|
||||||
|
}
|
296
types/opt/value_test.go
Normal file
296
types/opt/value_test.go
Normal file
@ -0,0 +1,296 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package opt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
jsonv2 "github.com/go-json-experiment/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testStruct struct {
|
||||||
|
Int int `json:",omitempty,omitzero"`
|
||||||
|
Str string `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValue(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
in any
|
||||||
|
jsonv2 bool
|
||||||
|
want string // JSON
|
||||||
|
wantBack any
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "null_for_unset",
|
||||||
|
in: struct {
|
||||||
|
True Value[bool]
|
||||||
|
False Value[bool]
|
||||||
|
Unset Value[bool]
|
||||||
|
ExplicitUnset Value[bool]
|
||||||
|
}{
|
||||||
|
True: ValueOf(true),
|
||||||
|
False: ValueOf(false),
|
||||||
|
ExplicitUnset: Value[bool]{},
|
||||||
|
},
|
||||||
|
want: `{"True":true,"False":false,"Unset":null,"ExplicitUnset":null}`,
|
||||||
|
wantBack: struct {
|
||||||
|
True Value[bool]
|
||||||
|
False Value[bool]
|
||||||
|
Unset Value[bool]
|
||||||
|
ExplicitUnset Value[bool]
|
||||||
|
}{
|
||||||
|
True: ValueOf(true),
|
||||||
|
False: ValueOf(false),
|
||||||
|
Unset: Value[bool]{},
|
||||||
|
ExplicitUnset: Value[bool]{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "null_for_unset_jsonv2",
|
||||||
|
in: struct {
|
||||||
|
True Value[bool]
|
||||||
|
False Value[bool]
|
||||||
|
Unset Value[bool]
|
||||||
|
ExplicitUnset Value[bool]
|
||||||
|
}{
|
||||||
|
True: ValueOf(true),
|
||||||
|
False: ValueOf(false),
|
||||||
|
ExplicitUnset: Value[bool]{},
|
||||||
|
},
|
||||||
|
jsonv2: true,
|
||||||
|
want: `{"True":true,"False":false,"Unset":null,"ExplicitUnset":null}`,
|
||||||
|
wantBack: struct {
|
||||||
|
True Value[bool]
|
||||||
|
False Value[bool]
|
||||||
|
Unset Value[bool]
|
||||||
|
ExplicitUnset Value[bool]
|
||||||
|
}{
|
||||||
|
True: ValueOf(true),
|
||||||
|
False: ValueOf(false),
|
||||||
|
Unset: Value[bool]{},
|
||||||
|
ExplicitUnset: Value[bool]{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "null_for_unset_omitzero",
|
||||||
|
in: struct {
|
||||||
|
True Value[bool] `json:",omitzero"`
|
||||||
|
False Value[bool] `json:",omitzero"`
|
||||||
|
Unset Value[bool] `json:",omitzero"`
|
||||||
|
ExplicitUnset Value[bool] `json:",omitzero"`
|
||||||
|
}{
|
||||||
|
True: ValueOf(true),
|
||||||
|
False: ValueOf(false),
|
||||||
|
ExplicitUnset: Value[bool]{},
|
||||||
|
},
|
||||||
|
want: `{"True":true,"False":false,"Unset":null,"ExplicitUnset":null}`,
|
||||||
|
wantBack: struct {
|
||||||
|
True Value[bool] `json:",omitzero"`
|
||||||
|
False Value[bool] `json:",omitzero"`
|
||||||
|
Unset Value[bool] `json:",omitzero"`
|
||||||
|
ExplicitUnset Value[bool] `json:",omitzero"`
|
||||||
|
}{
|
||||||
|
True: ValueOf(true),
|
||||||
|
False: ValueOf(false),
|
||||||
|
Unset: Value[bool]{},
|
||||||
|
ExplicitUnset: Value[bool]{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "null_for_unset_omitzero_jsonv2",
|
||||||
|
in: struct {
|
||||||
|
True Value[bool] `json:",omitzero"`
|
||||||
|
False Value[bool] `json:",omitzero"`
|
||||||
|
Unset Value[bool] `json:",omitzero"`
|
||||||
|
ExplicitUnset Value[bool] `json:",omitzero"`
|
||||||
|
}{
|
||||||
|
True: ValueOf(true),
|
||||||
|
False: ValueOf(false),
|
||||||
|
ExplicitUnset: Value[bool]{},
|
||||||
|
},
|
||||||
|
jsonv2: true,
|
||||||
|
want: `{"True":true,"False":false}`,
|
||||||
|
wantBack: struct {
|
||||||
|
True Value[bool] `json:",omitzero"`
|
||||||
|
False Value[bool] `json:",omitzero"`
|
||||||
|
Unset Value[bool] `json:",omitzero"`
|
||||||
|
ExplicitUnset Value[bool] `json:",omitzero"`
|
||||||
|
}{
|
||||||
|
True: ValueOf(true),
|
||||||
|
False: ValueOf(false),
|
||||||
|
Unset: Value[bool]{},
|
||||||
|
ExplicitUnset: Value[bool]{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "string",
|
||||||
|
in: struct {
|
||||||
|
EmptyString Value[string]
|
||||||
|
NonEmpty Value[string]
|
||||||
|
Unset Value[string]
|
||||||
|
}{
|
||||||
|
EmptyString: ValueOf(""),
|
||||||
|
NonEmpty: ValueOf("value"),
|
||||||
|
Unset: Value[string]{},
|
||||||
|
},
|
||||||
|
want: `{"EmptyString":"","NonEmpty":"value","Unset":null}`,
|
||||||
|
wantBack: struct {
|
||||||
|
EmptyString Value[string]
|
||||||
|
NonEmpty Value[string]
|
||||||
|
Unset Value[string]
|
||||||
|
}{ValueOf(""), ValueOf("value"), Value[string]{}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "integer",
|
||||||
|
in: struct {
|
||||||
|
Zero Value[int]
|
||||||
|
NonZero Value[int]
|
||||||
|
Unset Value[int]
|
||||||
|
}{
|
||||||
|
Zero: ValueOf(0),
|
||||||
|
NonZero: ValueOf(42),
|
||||||
|
Unset: Value[int]{},
|
||||||
|
},
|
||||||
|
want: `{"Zero":0,"NonZero":42,"Unset":null}`,
|
||||||
|
wantBack: struct {
|
||||||
|
Zero Value[int]
|
||||||
|
NonZero Value[int]
|
||||||
|
Unset Value[int]
|
||||||
|
}{ValueOf(0), ValueOf(42), Value[int]{}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "struct",
|
||||||
|
in: struct {
|
||||||
|
Zero Value[testStruct]
|
||||||
|
NonZero Value[testStruct]
|
||||||
|
Unset Value[testStruct]
|
||||||
|
}{
|
||||||
|
Zero: ValueOf(testStruct{}),
|
||||||
|
NonZero: ValueOf(testStruct{Int: 42, Str: "String"}),
|
||||||
|
Unset: Value[testStruct]{},
|
||||||
|
},
|
||||||
|
want: `{"Zero":{},"NonZero":{"Int":42,"Str":"String"},"Unset":null}`,
|
||||||
|
wantBack: struct {
|
||||||
|
Zero Value[testStruct]
|
||||||
|
NonZero Value[testStruct]
|
||||||
|
Unset Value[testStruct]
|
||||||
|
}{ValueOf(testStruct{}), ValueOf(testStruct{Int: 42, Str: "String"}), Value[testStruct]{}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "struct_ptr",
|
||||||
|
in: struct {
|
||||||
|
Zero Value[*testStruct]
|
||||||
|
NonZero Value[*testStruct]
|
||||||
|
Unset Value[*testStruct]
|
||||||
|
}{
|
||||||
|
Zero: ValueOf(&testStruct{}),
|
||||||
|
NonZero: ValueOf(&testStruct{Int: 42, Str: "String"}),
|
||||||
|
Unset: Value[*testStruct]{},
|
||||||
|
},
|
||||||
|
want: `{"Zero":{},"NonZero":{"Int":42,"Str":"String"},"Unset":null}`,
|
||||||
|
wantBack: struct {
|
||||||
|
Zero Value[*testStruct]
|
||||||
|
NonZero Value[*testStruct]
|
||||||
|
Unset Value[*testStruct]
|
||||||
|
}{ValueOf(&testStruct{}), ValueOf(&testStruct{Int: 42, Str: "String"}), Value[*testStruct]{}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil-slice-and-map",
|
||||||
|
in: struct {
|
||||||
|
Slice Value[[]int]
|
||||||
|
Map Value[map[string]int]
|
||||||
|
}{
|
||||||
|
Slice: ValueOf[[]int](nil), // marshalled as []
|
||||||
|
Map: ValueOf[map[string]int](nil), // marshalled as {}
|
||||||
|
},
|
||||||
|
want: `{"Slice":[],"Map":{}}`,
|
||||||
|
wantBack: struct {
|
||||||
|
Slice Value[[]int]
|
||||||
|
Map Value[map[string]int]
|
||||||
|
}{ValueOf([]int{}), ValueOf(map[string]int{})},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var j []byte
|
||||||
|
var err error
|
||||||
|
if tt.jsonv2 {
|
||||||
|
j, err = jsonv2.Marshal(tt.in)
|
||||||
|
} else {
|
||||||
|
j, err = json.Marshal(tt.in)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(j) != tt.want {
|
||||||
|
t.Errorf("wrong JSON:\n got: %s\nwant: %s\n", j, tt.want)
|
||||||
|
}
|
||||||
|
|
||||||
|
wantBack := tt.in
|
||||||
|
if tt.wantBack != nil {
|
||||||
|
wantBack = tt.wantBack
|
||||||
|
}
|
||||||
|
// And back again:
|
||||||
|
newVal := reflect.New(reflect.TypeOf(tt.in))
|
||||||
|
out := newVal.Interface()
|
||||||
|
if tt.jsonv2 {
|
||||||
|
err = jsonv2.Unmarshal(j, out)
|
||||||
|
} else {
|
||||||
|
err = json.Unmarshal(j, out)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unmarshal %#q: %v", j, err)
|
||||||
|
}
|
||||||
|
got := newVal.Elem().Interface()
|
||||||
|
if !reflect.DeepEqual(got, wantBack) {
|
||||||
|
t.Errorf("value mismatch\n got: %+v\nwant: %+v\n", got, wantBack)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValueEqual(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
o Value[bool]
|
||||||
|
v Value[bool]
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{ValueOf(true), ValueOf(true), true},
|
||||||
|
{ValueOf(true), ValueOf(false), false},
|
||||||
|
{ValueOf(true), Value[bool]{}, false},
|
||||||
|
{ValueOf(false), ValueOf(false), true},
|
||||||
|
{ValueOf(false), ValueOf(true), false},
|
||||||
|
{ValueOf(false), Value[bool]{}, false},
|
||||||
|
{Value[bool]{}, Value[bool]{}, true},
|
||||||
|
{Value[bool]{}, ValueOf(true), false},
|
||||||
|
{Value[bool]{}, ValueOf(false), false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
if got := tt.o.Equal(tt.v); got != tt.want {
|
||||||
|
t.Errorf("(%v).Equals(%v) = %v; want %v", tt.o, tt.v, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIncomparableValueEqual(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
o Value[[]bool]
|
||||||
|
v Value[[]bool]
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{ValueOf([]bool{}), ValueOf([]bool{}), false},
|
||||||
|
{ValueOf([]bool{true}), ValueOf([]bool{true}), false},
|
||||||
|
{Value[[]bool]{}, ValueOf([]bool{}), false},
|
||||||
|
{ValueOf([]bool{}), Value[[]bool]{}, false},
|
||||||
|
{Value[[]bool]{}, Value[[]bool]{}, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
if got := tt.o.Equal(tt.v); got != tt.want {
|
||||||
|
t.Errorf("(%v).Equals(%v) = %v; want %v", tt.o, tt.v, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user