tailscale/types/prefs/map.go
Brad Fitzpatrick 2a64c03c95
Some checks failed
checklocks / checklocks (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
Dockerfile build / deploy (push) Has been cancelled
natlab-integrationtest / natlab-integrationtest (push) Has been cancelled
CI / gomod-cache (push) Has been cancelled
CI / fuzz (push) Has been cancelled
update-flake / update-flake (push) Has been cancelled
tailscale.com/cmd/vet / vet (push) Has been cancelled
CI / race-root-integration (1/4) (push) Has been cancelled
CI / race-root-integration (2/4) (push) Has been cancelled
CI / race-root-integration (3/4) (push) Has been cancelled
CI / race-root-integration (4/4) (push) Has been cancelled
CI / test (-race, amd64, 1/3) (push) Has been cancelled
CI / test (-race, amd64, 2/3) (push) Has been cancelled
CI / test (-race, amd64, 3/3) (push) Has been cancelled
CI / test (386) (push) Has been cancelled
CI / test (amd64) (push) Has been cancelled
CI / Windows (${{ matrix.name || matrix.shard}}) (win-bench, benchmarks) (push) Has been cancelled
CI / Windows (${{ matrix.name || matrix.shard}}) (win-shard-1-2, 1/2) (push) Has been cancelled
CI / Windows (${{ matrix.name || matrix.shard}}) (win-shard-2-2, 2/2) (push) Has been cancelled
CI / Windows (win-tool-go) (push) Has been cancelled
CI / macos (push) Has been cancelled
CI / privileged (push) Has been cancelled
CI / vm (push) Has been cancelled
CI / cross (386, linux) (push) Has been cancelled
CI / cross (amd64, darwin) (push) Has been cancelled
CI / cross (amd64, freebsd) (push) Has been cancelled
CI / cross (amd64, openbsd) (push) Has been cancelled
CI / cross (amd64, windows) (push) Has been cancelled
CI / cross (arm, 5, linux) (push) Has been cancelled
CI / cross (arm, 7, linux) (push) Has been cancelled
CI / cross (arm64, darwin) (push) Has been cancelled
CI / cross (arm64, linux) (push) Has been cancelled
CI / cross (arm64, windows) (push) Has been cancelled
CI / cross (loong64, linux) (push) Has been cancelled
CI / ios (push) Has been cancelled
CI / crossmin (amd64, illumos) (push) Has been cancelled
CI / crossmin (amd64, plan9) (push) Has been cancelled
CI / crossmin (amd64, solaris) (push) Has been cancelled
CI / crossmin (ppc64, aix) (push) Has been cancelled
CI / android (push) Has been cancelled
CI / wasm (push) Has been cancelled
CI / tailscale_go (push) Has been cancelled
CI / depaware (push) Has been cancelled
CI / go_generate (push) Has been cancelled
CI / make_tidy (push) Has been cancelled
CI / licenses (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--with-tags-all=darwin, arm64, darwin, macOS) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--with-tags-all=linux, amd64, linux, Linux) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--with-tags-all=windows, amd64, windows, Windows) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--without-tags-any=windows,darwin,linux --shard=1/4, amd64, linux, Portable (1/4)) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--without-tags-any=windows,darwin,linux --shard=2/4, amd64, linux, Portable (2/4)) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--without-tags-any=windows,darwin,linux --shard=3/4, amd64, linux, Portable (3/4)) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--without-tags-any=windows,darwin,linux --shard=4/4, amd64, linux, Portable (4/4)) (push) Has been cancelled
CI / notify_slack (push) Has been cancelled
CI / merge_blocker (push) Has been cancelled
CI / check_mergeability_strict (push) Has been cancelled
CI / check_mergeability (push) Has been cancelled
types/ptr: deprecate ptr.To, use Go 1.26 new
Updates #18682

Change-Id: I62f6aa0de2a15ef8c1435032c6aa74a181c25f8f
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-03-05 20:13:18 -08:00

159 lines
4.6 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package prefs
import (
"maps"
"net/netip"
jsonv2 "github.com/go-json-experiment/json"
"github.com/go-json-experiment/json/jsontext"
"golang.org/x/exp/constraints"
"tailscale.com/types/opt"
"tailscale.com/types/views"
)
// MapKeyType is a constraint allowing types that can be used as [Map] and [StructMap] keys.
// To satisfy this requirement, a type must be comparable and must encode as a JSON string.
// See [jsonv2.Marshal] for more details.
type MapKeyType interface {
~string | constraints.Integer | netip.Addr | netip.Prefix | netip.AddrPort
}
// Map is a preference type that holds immutable key-value pairs.
type Map[K MapKeyType, V ImmutableType] struct {
preference[map[K]V]
}
// MapOf returns a map configured with the specified value and [Options].
func MapOf[K MapKeyType, V ImmutableType](v map[K]V, opts ...Options) Map[K, V] {
return Map[K, V]{preferenceOf(opt.ValueOf(v), opts...)}
}
// MapWithOpts returns an unconfigured [Map] with the specified [Options].
func MapWithOpts[K MapKeyType, V ImmutableType](opts ...Options) Map[K, V] {
return Map[K, V]{preferenceOf(opt.Value[map[K]V]{}, opts...)}
}
// View returns a read-only view of m.
func (m *Map[K, V]) View() MapView[K, V] {
return MapView[K, V]{m}
}
// Clone returns a copy of m that aliases no memory with m.
func (m Map[K, V]) Clone() *Map[K, V] {
res := new(m)
if v, ok := m.s.Value.GetOk(); ok {
res.s.Value.Set(maps.Clone(v))
}
return res
}
// Equal reports whether m and m2 are equal.
func (m Map[K, V]) Equal(m2 Map[K, V]) bool {
if m.s.Metadata != m2.s.Metadata {
return false
}
v1, ok1 := m.s.Value.GetOk()
v2, ok2 := m2.s.Value.GetOk()
if ok1 != ok2 {
return false
}
return !ok1 || maps.Equal(v1, v2)
}
// MapView is a read-only view of a [Map].
type MapView[K MapKeyType, V ImmutableType] struct {
// ж is the underlying mutable value, named with a hard-to-type
// character that looks pointy like a pointer.
// It is named distinctively to make you think of how dangerous it is to escape
// to callers. You must not let callers be able to mutate it.
ж *Map[K, V]
}
// Valid reports whether the underlying [Map] is non-nil.
func (mv MapView[K, V]) Valid() bool {
return mv.ж != nil
}
// AsStruct implements [views.StructView] by returning a clone of the [Map]
// which aliases no memory with the original.
func (mv MapView[K, V]) AsStruct() *Map[K, V] {
if mv.ж == nil {
return nil
}
return mv.ж.Clone()
}
// IsSet reports whether the preference has a value set.
func (mv MapView[K, V]) IsSet() bool {
return mv.ж.IsSet()
}
// Value returns a read-only view of the value if the preference has a value set.
// Otherwise, it returns a read-only view of its default value.
func (mv MapView[K, V]) Value() views.Map[K, V] {
return views.MapOf(mv.ж.Value())
}
// ValueOk returns a read-only view of the value and true if the preference has a value set.
// Otherwise, it returns an invalid view and false.
func (mv MapView[K, V]) ValueOk() (val views.Map[K, V], ok bool) {
if v, ok := mv.ж.ValueOk(); ok {
return views.MapOf(v), true
}
return views.Map[K, V]{}, false
}
// DefaultValue returns a read-only view of the default value of the preference.
func (mv MapView[K, V]) DefaultValue() views.Map[K, V] {
return views.MapOf(mv.ж.DefaultValue())
}
// Managed reports whether the preference is managed via MDM, Group Policy, or similar means.
func (mv MapView[K, V]) Managed() bool {
return mv.ж.IsManaged()
}
// ReadOnly reports whether the preference is read-only and cannot be changed by user.
func (mv MapView[K, V]) ReadOnly() bool {
return mv.ж.IsReadOnly()
}
// Equal reports whether mv and mv2 are equal.
func (mv MapView[K, V]) Equal(mv2 MapView[K, V]) bool {
if !mv.Valid() && !mv2.Valid() {
return true
}
if mv.Valid() != mv2.Valid() {
return false
}
return mv.ж.Equal(*mv2.ж)
}
// MarshalJSONTo implements [jsonv2.MarshalerTo].
func (mv MapView[K, V]) MarshalJSONTo(out *jsontext.Encoder) error {
return mv.ж.MarshalJSONTo(out)
}
// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
func (mv *MapView[K, V]) UnmarshalJSONFrom(in *jsontext.Decoder) error {
var x Map[K, V]
if err := x.UnmarshalJSONFrom(in); err != nil {
return err
}
mv.ж = &x
return nil
}
// MarshalJSON implements [json.Marshaler].
func (mv MapView[K, V]) MarshalJSON() ([]byte, error) {
return jsonv2.Marshal(mv) // uses MarshalJSONTo
}
// UnmarshalJSON implements [json.Unmarshaler].
func (mv *MapView[K, V]) UnmarshalJSON(b []byte) error {
return jsonv2.Unmarshal(b, mv) // uses UnmarshalJSONFrom
}