mirror of https://github.com/tailscale/tailscale/
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
180 lines
5.8 KiB
Go
180 lines
5.8 KiB
Go
4 months ago
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||
|
|
||
|
// Package prefs contains types and functions to work with arbitrary
|
||
|
// preference hierarchies.
|
||
|
//
|
||
|
// Specifically, the package provides [Item], [List], [Map], [StructList] and [StructMap]
|
||
|
// types which represent individual preferences in a user-defined prefs struct.
|
||
|
// A valid prefs struct must contain one or more exported fields of the preference types,
|
||
|
// either directly or within nested structs, but not pointers to these types.
|
||
|
// Additionally to preferences, a prefs struct may contain any number of
|
||
|
// non-preference fields that will be marshalled and unmarshalled but are
|
||
|
// otherwise ignored by the prefs package.
|
||
|
//
|
||
|
// The preference types are compatible with the [tailscale.com/cmd/viewer] and
|
||
|
// [tailscale.com/cmd/cloner] utilities. It is recommended to generate a read-only view
|
||
|
// of the user-defined prefs structure and use it in place of prefs whenever the prefs
|
||
|
// should not be modified.
|
||
|
package prefs
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
|
||
|
jsonv2 "github.com/go-json-experiment/json"
|
||
|
"github.com/go-json-experiment/json/jsontext"
|
||
|
"tailscale.com/types/opt"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
// ErrManaged is the error returned when attempting to modify a managed preference.
|
||
|
ErrManaged = errors.New("cannot modify a managed preference")
|
||
|
// ErrReadOnly is the error returned when attempting to modify a readonly preference.
|
||
|
ErrReadOnly = errors.New("cannot modify a readonly preference")
|
||
|
)
|
||
|
|
||
|
// metadata holds type-agnostic preference metadata.
|
||
|
type metadata struct {
|
||
|
// Managed indicates whether the preference is managed via MDM, Group Policy, or other means.
|
||
|
Managed bool `json:",omitzero"`
|
||
|
|
||
|
// ReadOnly indicates whether the preference is read-only due to any other reasons,
|
||
|
// such as user's access rights.
|
||
|
ReadOnly bool `json:",omitzero"`
|
||
|
}
|
||
|
|
||
|
// serializable is a JSON-serializable preference data.
|
||
|
type serializable[T any] struct {
|
||
|
// Value is an optional preference value that is set when the preference is
|
||
|
// configured by the user or managed by an admin.
|
||
|
Value opt.Value[T] `json:",omitzero"`
|
||
|
// Default is the default preference value to be used
|
||
|
// when the preference has not been configured.
|
||
|
Default T `json:",omitzero"`
|
||
|
// Metadata is any additional type-agnostic preference metadata to be serialized.
|
||
|
Metadata metadata `json:",inline"`
|
||
|
}
|
||
|
|
||
|
// preference is an embeddable type that provides a common implementation for
|
||
|
// concrete preference types, such as [Item], [List], [Map], [StructList] and [StructMap].
|
||
|
type preference[T any] struct {
|
||
|
s serializable[T]
|
||
|
}
|
||
|
|
||
|
// preferenceOf returns a preference with the specified value and/or [Options].
|
||
|
func preferenceOf[T any](v opt.Value[T], opts ...Options) preference[T] {
|
||
|
var m metadata
|
||
|
for _, o := range opts {
|
||
|
o(&m)
|
||
|
}
|
||
|
return preference[T]{serializable[T]{Value: v, Metadata: m}}
|
||
|
}
|
||
|
|
||
|
// IsSet reports whether p has a value set.
|
||
|
func (p preference[T]) IsSet() bool {
|
||
|
return p.s.Value.IsSet()
|
||
|
}
|
||
|
|
||
|
// Value returns the value of p if the preference has a value set.
|
||
|
// Otherwise, it returns its default value.
|
||
|
func (p preference[T]) Value() T {
|
||
|
val, _ := p.ValueOk()
|
||
|
return val
|
||
|
}
|
||
|
|
||
|
// ValueOk returns the value of p and true if the preference has a value set.
|
||
|
// Otherwise, it returns its default value and false.
|
||
|
func (p preference[T]) ValueOk() (val T, ok bool) {
|
||
|
if val, ok = p.s.Value.GetOk(); ok {
|
||
|
return val, true
|
||
|
}
|
||
|
return p.DefaultValue(), false
|
||
|
}
|
||
|
|
||
|
// SetValue configures the preference with the specified value.
|
||
|
// It fails and returns [ErrManaged] if p is a managed preference,
|
||
|
// and [ErrReadOnly] if p is a read-only preference.
|
||
|
func (p *preference[T]) SetValue(val T) error {
|
||
|
switch {
|
||
|
case p.s.Metadata.Managed:
|
||
|
return ErrManaged
|
||
|
case p.s.Metadata.ReadOnly:
|
||
|
return ErrReadOnly
|
||
|
default:
|
||
|
p.s.Value.Set(val)
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ClearValue resets the preference to an unconfigured state.
|
||
|
// It fails and returns [ErrManaged] if p is a managed preference,
|
||
|
// and [ErrReadOnly] if p is a read-only preference.
|
||
|
func (p *preference[T]) ClearValue() error {
|
||
|
switch {
|
||
|
case p.s.Metadata.Managed:
|
||
|
return ErrManaged
|
||
|
case p.s.Metadata.ReadOnly:
|
||
|
return ErrReadOnly
|
||
|
default:
|
||
|
p.s.Value.Clear()
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// DefaultValue returns the default value of p.
|
||
|
func (p preference[T]) DefaultValue() T {
|
||
|
return p.s.Default
|
||
|
}
|
||
|
|
||
|
// SetDefaultValue sets the default value of p.
|
||
|
func (p *preference[T]) SetDefaultValue(def T) {
|
||
|
p.s.Default = def
|
||
|
}
|
||
|
|
||
|
// IsManaged reports whether p is managed via MDM, Group Policy, or similar means.
|
||
|
func (p preference[T]) IsManaged() bool {
|
||
|
return p.s.Metadata.Managed
|
||
|
}
|
||
|
|
||
|
// SetManagedValue configures the preference with the specified value
|
||
|
// and marks the preference as managed.
|
||
|
func (p *preference[T]) SetManagedValue(val T) {
|
||
|
p.s.Value.Set(val)
|
||
|
p.s.Metadata.Managed = true
|
||
|
}
|
||
|
|
||
|
// ClearManaged clears the managed flag of the preference without altering its value.
|
||
|
func (p *preference[T]) ClearManaged() {
|
||
|
p.s.Metadata.Managed = false
|
||
|
}
|
||
|
|
||
|
// IsReadOnly reports whether p is read-only and cannot be changed by user.
|
||
|
func (p preference[T]) IsReadOnly() bool {
|
||
|
return p.s.Metadata.ReadOnly || p.s.Metadata.Managed
|
||
|
}
|
||
|
|
||
|
// SetReadOnly sets the read-only status of p, preventing changes by a user if set to true.
|
||
|
func (p *preference[T]) SetReadOnly(readonly bool) {
|
||
|
p.s.Metadata.ReadOnly = readonly
|
||
|
}
|
||
|
|
||
|
// MarshalJSONV2 implements [jsonv2.MarshalerV2].
|
||
|
func (p preference[T]) MarshalJSONV2(out *jsontext.Encoder, opts jsonv2.Options) error {
|
||
|
return jsonv2.MarshalEncode(out, &p.s, opts)
|
||
|
}
|
||
|
|
||
|
// UnmarshalJSONV2 implements [jsonv2.UnmarshalerV2].
|
||
|
func (p *preference[T]) UnmarshalJSONV2(in *jsontext.Decoder, opts jsonv2.Options) error {
|
||
|
return jsonv2.UnmarshalDecode(in, &p.s, opts)
|
||
|
}
|
||
|
|
||
|
// MarshalJSON implements [json.Marshaler].
|
||
|
func (p preference[T]) MarshalJSON() ([]byte, error) {
|
||
|
return jsonv2.Marshal(p) // uses MarshalJSONV2
|
||
|
}
|
||
|
|
||
|
// UnmarshalJSON implements [json.Unmarshaler].
|
||
|
func (p *preference[T]) UnmarshalJSON(b []byte) error {
|
||
|
return jsonv2.Unmarshal(b, p) // uses UnmarshalJSONV2
|
||
|
}
|