types/views: add JSON marshal/unmarshal and AsMap to Map

This allows cloning a Map as well as marshaling the Map
as JSON.

Updates tailscale/corp#12754

Signed-off-by: Maisem Ali <maisem@tailscale.com>
This commit is contained in:
Maisem Ali 2023-06-29 08:06:57 -07:00 committed by Maisem Ali
parent e42be5a060
commit 2e19790f61
4 changed files with 31 additions and 4 deletions

View File

@ -182,6 +182,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+ golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
golang.org/x/exp/constraints from golang.org/x/exp/slices golang.org/x/exp/constraints from golang.org/x/exp/slices
golang.org/x/exp/maps from tailscale.com/types/views
golang.org/x/exp/slices from tailscale.com/net/tsaddr+ golang.org/x/exp/slices from tailscale.com/net/tsaddr+
L golang.org/x/net/bpf from github.com/mdlayher/netlink+ L golang.org/x/net/bpf from github.com/mdlayher/netlink+
golang.org/x/net/dns/dnsmessage from net+ golang.org/x/net/dns/dnsmessage from net+

View File

@ -173,6 +173,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
golang.org/x/crypto/pbkdf2 from software.sslmate.com/src/go-pkcs12 golang.org/x/crypto/pbkdf2 from software.sslmate.com/src/go-pkcs12
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+ golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
golang.org/x/exp/constraints from golang.org/x/exp/slices golang.org/x/exp/constraints from golang.org/x/exp/slices
golang.org/x/exp/maps from tailscale.com/types/views
golang.org/x/exp/slices from tailscale.com/net/tsaddr+ golang.org/x/exp/slices from tailscale.com/net/tsaddr+
golang.org/x/net/bpf from github.com/mdlayher/netlink+ golang.org/x/net/bpf from github.com/mdlayher/netlink+
golang.org/x/net/dns/dnsmessage from net+ golang.org/x/net/dns/dnsmessage from net+

View File

@ -379,7 +379,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+ golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
LD golang.org/x/crypto/ssh from tailscale.com/ssh/tailssh+ LD golang.org/x/crypto/ssh from tailscale.com/ssh/tailssh+
golang.org/x/exp/constraints from golang.org/x/exp/slices+ golang.org/x/exp/constraints from golang.org/x/exp/slices+
golang.org/x/exp/maps from tailscale.com/wgengine golang.org/x/exp/maps from tailscale.com/wgengine+
golang.org/x/exp/slices from tailscale.com/ipn/ipnlocal+ golang.org/x/exp/slices from tailscale.com/ipn/ipnlocal+
golang.org/x/net/bpf from github.com/mdlayher/genetlink+ golang.org/x/net/bpf from github.com/mdlayher/genetlink+
golang.org/x/net/dns/dnsmessage from net+ golang.org/x/net/dns/dnsmessage from net+

View File

@ -10,11 +10,12 @@
"errors" "errors"
"net/netip" "net/netip"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"tailscale.com/net/tsaddr" "tailscale.com/net/tsaddr"
) )
func unmarshalJSON[T any](b []byte, x *[]T) error { func unmarshalSliceFromJSON[T any](b []byte, x *[]T) error {
if *x != nil { if *x != nil {
return errors.New("already initialized") return errors.New("already initialized")
} }
@ -64,7 +65,7 @@ type SliceView[T ViewCloner[T, V], V StructView[T]] struct {
func (v SliceView[T, V]) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) } func (v SliceView[T, V]) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
// UnmarshalJSON implements json.Unmarshaler. // UnmarshalJSON implements json.Unmarshaler.
func (v *SliceView[T, V]) UnmarshalJSON(b []byte) error { return unmarshalJSON(b, &v.ж) } func (v *SliceView[T, V]) UnmarshalJSON(b []byte) error { return unmarshalSliceFromJSON(b, &v.ж) }
// IsNil reports whether the underlying slice is nil. // IsNil reports whether the underlying slice is nil.
func (v SliceView[T, V]) IsNil() bool { return v.ж == nil } func (v SliceView[T, V]) IsNil() bool { return v.ж == nil }
@ -119,7 +120,7 @@ func (v Slice[T]) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements json.Unmarshaler. // UnmarshalJSON implements json.Unmarshaler.
func (v *Slice[T]) UnmarshalJSON(b []byte) error { func (v *Slice[T]) UnmarshalJSON(b []byte) error {
return unmarshalJSON(b, &v.ж) return unmarshalSliceFromJSON(b, &v.ж)
} }
// IsNil reports whether the underlying slice is nil. // IsNil reports whether the underlying slice is nil.
@ -332,6 +333,30 @@ func (m Map[K, V]) GetOk(k K) (V, bool) {
return v, ok return v, ok
} }
// MarshalJSON implements json.Marshaler.
func (m Map[K, V]) MarshalJSON() ([]byte, error) {
return json.Marshal(m.ж)
}
// UnmarshalJSON implements json.Unmarshaler.
// It should only be called on an uninitialized Map.
func (m *Map[K, V]) UnmarshalJSON(b []byte) error {
if m.ж != nil {
return errors.New("already initialized")
}
return json.Unmarshal(b, &m.ж)
}
// AsMap returns a shallow-clone of the underlying map.
// If V is a pointer type, it is the caller's responsibility to make sure
// the values are immutable.
func (m *Map[K, V]) AsMap() map[K]V {
if m == nil {
return nil
}
return maps.Clone(m.ж)
}
// MapRangeFn is the func called from a Map.Range call. // MapRangeFn is the func called from a Map.Range call.
// Implementations should return false to stop range. // Implementations should return false to stop range.
type MapRangeFn[K comparable, V any] func(k K, v V) (cont bool) type MapRangeFn[K comparable, V any] func(k K, v V) (cont bool)