Update dependencies
This commit is contained in:
71
vendor/tailscale.com/util/syspolicy/setting/errors.go
generated
vendored
Normal file
71
vendor/tailscale.com/util/syspolicy/setting/errors.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"tailscale.com/types/ptr"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotConfigured is returned when the requested policy setting is not configured.
|
||||
ErrNotConfigured = errors.New("not configured")
|
||||
// ErrTypeMismatch is returned when there's a type mismatch between the actual type
|
||||
// of the setting value and the expected type.
|
||||
ErrTypeMismatch = errors.New("type mismatch")
|
||||
// ErrNoSuchKey is returned by [DefinitionOf] when no policy setting
|
||||
// has been registered with the specified key.
|
||||
//
|
||||
// Until 2024-08-02, this error was also returned by a [Handler] when the specified
|
||||
// key did not have a value set. While the package maintains compatibility with this
|
||||
// usage of ErrNoSuchKey, it is recommended to return [ErrNotConfigured] from newer
|
||||
// [source.Store] implementations.
|
||||
ErrNoSuchKey = errors.New("no such key")
|
||||
)
|
||||
|
||||
// ErrorText represents an error that occurs when reading or parsing a policy setting.
|
||||
// This includes errors due to permissions issues, value type and format mismatches,
|
||||
// and other platform- or source-specific errors. It does not include
|
||||
// [ErrNotConfigured] and [ErrNoSuchKey], as those correspond to unconfigured
|
||||
// policy settings rather than settings that cannot be read or parsed
|
||||
// due to an error.
|
||||
//
|
||||
// ErrorText is used to marshal errors when a policy setting is sent over the wire,
|
||||
// allowing the error to be logged or displayed. It does not preserve the
|
||||
// type information of the underlying error.
|
||||
type ErrorText string
|
||||
|
||||
// NewErrorText returns a [ErrorText] with the specified error message.
|
||||
func NewErrorText(text string) *ErrorText {
|
||||
return ptr.To(ErrorText(text))
|
||||
}
|
||||
|
||||
// MaybeErrorText returns an [ErrorText] with the text of the specified error,
|
||||
// or nil if err is nil, [ErrNotConfigured], or [ErrNoSuchKey].
|
||||
func MaybeErrorText(err error) *ErrorText {
|
||||
if err == nil || errors.Is(err, ErrNotConfigured) || errors.Is(err, ErrNoSuchKey) {
|
||||
return nil
|
||||
}
|
||||
if err, ok := err.(*ErrorText); ok {
|
||||
return err
|
||||
}
|
||||
return ptr.To(ErrorText(err.Error()))
|
||||
}
|
||||
|
||||
// Error implements error.
|
||||
func (e ErrorText) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
// MarshalText implements [encoding.TextMarshaler].
|
||||
func (e ErrorText) MarshalText() (text []byte, err error) {
|
||||
return []byte(e.Error()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements [encoding.TextUnmarshaler].
|
||||
func (e *ErrorText) UnmarshalText(text []byte) error {
|
||||
*e = ErrorText(text)
|
||||
return nil
|
||||
}
|
||||
13
vendor/tailscale.com/util/syspolicy/setting/key.go
generated
vendored
Normal file
13
vendor/tailscale.com/util/syspolicy/setting/key.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package setting
|
||||
|
||||
// Key is a string that uniquely identifies a policy and must remain unchanged
|
||||
// once established and documented for a given policy setting. It may contain
|
||||
// alphanumeric characters and zero or more [KeyPathSeparator]s to group
|
||||
// individual policy settings into categories.
|
||||
type Key string
|
||||
|
||||
// KeyPathSeparator allows logical grouping of policy settings into categories.
|
||||
const KeyPathSeparator = "/"
|
||||
71
vendor/tailscale.com/util/syspolicy/setting/origin.go
generated
vendored
Normal file
71
vendor/tailscale.com/util/syspolicy/setting/origin.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
jsonv2 "github.com/go-json-experiment/json"
|
||||
"github.com/go-json-experiment/json/jsontext"
|
||||
)
|
||||
|
||||
// Origin describes where a policy or a policy setting is configured.
|
||||
type Origin struct {
|
||||
data settingOrigin
|
||||
}
|
||||
|
||||
// settingOrigin is the marshallable data of an [Origin].
|
||||
type settingOrigin struct {
|
||||
Name string `json:",omitzero"`
|
||||
Scope PolicyScope
|
||||
}
|
||||
|
||||
// NewOrigin returns a new [Origin] with the specified scope.
|
||||
func NewOrigin(scope PolicyScope) *Origin {
|
||||
return NewNamedOrigin("", scope)
|
||||
}
|
||||
|
||||
// NewNamedOrigin returns a new [Origin] with the specified scope and name.
|
||||
func NewNamedOrigin(name string, scope PolicyScope) *Origin {
|
||||
return &Origin{settingOrigin{name, scope}}
|
||||
}
|
||||
|
||||
// Scope reports the policy [PolicyScope] where the setting is configured.
|
||||
func (s Origin) Scope() PolicyScope {
|
||||
return s.data.Scope
|
||||
}
|
||||
|
||||
// Name returns the name of the policy source where the setting is configured,
|
||||
// or "" if not available.
|
||||
func (s Origin) Name() string {
|
||||
return s.data.Name
|
||||
}
|
||||
|
||||
// String implements [fmt.Stringer].
|
||||
func (s Origin) String() string {
|
||||
if s.Name() != "" {
|
||||
return fmt.Sprintf("%s (%v)", s.Name(), s.Scope())
|
||||
}
|
||||
return s.Scope().String()
|
||||
}
|
||||
|
||||
// MarshalJSONV2 implements [jsonv2.MarshalerV2].
|
||||
func (s Origin) MarshalJSONV2(out *jsontext.Encoder, opts jsonv2.Options) error {
|
||||
return jsonv2.MarshalEncode(out, &s.data, opts)
|
||||
}
|
||||
|
||||
// UnmarshalJSONV2 implements [jsonv2.UnmarshalerV2].
|
||||
func (s *Origin) UnmarshalJSONV2(in *jsontext.Decoder, opts jsonv2.Options) error {
|
||||
return jsonv2.UnmarshalDecode(in, &s.data, opts)
|
||||
}
|
||||
|
||||
// MarshalJSON implements [json.Marshaler].
|
||||
func (s Origin) MarshalJSON() ([]byte, error) {
|
||||
return jsonv2.Marshal(s) // uses MarshalJSONV2
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements [json.Unmarshaler].
|
||||
func (s *Origin) UnmarshalJSON(b []byte) error {
|
||||
return jsonv2.Unmarshal(b, s) // uses UnmarshalJSONV2
|
||||
}
|
||||
189
vendor/tailscale.com/util/syspolicy/setting/policy_scope.go
generated
vendored
Normal file
189
vendor/tailscale.com/util/syspolicy/setting/policy_scope.go
generated
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"tailscale.com/types/lazy"
|
||||
)
|
||||
|
||||
var (
|
||||
lazyDefaultScope lazy.SyncValue[PolicyScope]
|
||||
|
||||
// DeviceScope indicates a scope containing device-global policies.
|
||||
DeviceScope = PolicyScope{kind: DeviceSetting}
|
||||
// CurrentProfileScope indicates a scope containing policies that apply to the
|
||||
// currently active Tailscale profile.
|
||||
CurrentProfileScope = PolicyScope{kind: ProfileSetting}
|
||||
// CurrentUserScope indicates a scope containing policies that apply to the
|
||||
// current user, for whatever that means on the current platform and
|
||||
// in the current application context.
|
||||
CurrentUserScope = PolicyScope{kind: UserSetting}
|
||||
)
|
||||
|
||||
// PolicyScope is a management scope.
|
||||
type PolicyScope struct {
|
||||
kind Scope
|
||||
userID string
|
||||
profileID string
|
||||
}
|
||||
|
||||
// DefaultScope returns the default [PolicyScope] to be used by a program
|
||||
// when querying policy settings.
|
||||
// It returns [DeviceScope], unless explicitly changed with [SetDefaultScope].
|
||||
func DefaultScope() PolicyScope {
|
||||
return lazyDefaultScope.Get(func() PolicyScope { return DeviceScope })
|
||||
}
|
||||
|
||||
// SetDefaultScope attempts to set the specified scope as the default scope
|
||||
// to be used by a program when querying policy settings.
|
||||
// It fails and returns false if called more than once, or if the [DefaultScope]
|
||||
// has already been used.
|
||||
func SetDefaultScope(scope PolicyScope) bool {
|
||||
return lazyDefaultScope.Set(scope)
|
||||
}
|
||||
|
||||
// UserScopeOf returns a policy [PolicyScope] of the user with the specified id.
|
||||
func UserScopeOf(uid string) PolicyScope {
|
||||
return PolicyScope{kind: UserSetting, userID: uid}
|
||||
}
|
||||
|
||||
// Kind reports the scope kind of s.
|
||||
func (s PolicyScope) Kind() Scope {
|
||||
return s.kind
|
||||
}
|
||||
|
||||
// IsApplicableSetting reports whether the specified setting applies to
|
||||
// and can be retrieved for this scope. Policy settings are applicable
|
||||
// to their own scopes as well as more specific scopes. For example,
|
||||
// device settings are applicable to device, profile and user scopes,
|
||||
// but user settings are only applicable to user scopes.
|
||||
// For instance, a menu visibility setting is inherently a user setting
|
||||
// and only makes sense in the context of a specific user.
|
||||
func (s PolicyScope) IsApplicableSetting(setting *Definition) bool {
|
||||
return setting != nil && setting.Scope() <= s.Kind()
|
||||
}
|
||||
|
||||
// IsConfigurableSetting reports whether the specified setting can be configured
|
||||
// by a policy at this scope. Policy settings are configurable at their own scopes
|
||||
// as well as broader scopes. For example, [UserSetting]s are configurable in
|
||||
// user, profile, and device scopes, but [DeviceSetting]s are only configurable
|
||||
// in the [DeviceScope]. For instance, the InstallUpdates policy setting
|
||||
// can only be configured in the device scope, as it controls whether updates
|
||||
// will be installed automatically on the device, rather than for specific users.
|
||||
func (s PolicyScope) IsConfigurableSetting(setting *Definition) bool {
|
||||
return setting != nil && setting.Scope() >= s.Kind()
|
||||
}
|
||||
|
||||
// Contains reports whether policy settings that apply to s also apply to s2.
|
||||
// For example, policy settings that apply to the [DeviceScope] also apply to
|
||||
// the [CurrentUserScope].
|
||||
func (s PolicyScope) Contains(s2 PolicyScope) bool {
|
||||
if s.Kind() > s2.Kind() {
|
||||
return false
|
||||
}
|
||||
switch s.Kind() {
|
||||
case DeviceSetting:
|
||||
return true
|
||||
case ProfileSetting:
|
||||
return s.profileID == s2.profileID
|
||||
case UserSetting:
|
||||
return s.userID == s2.userID
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
// StrictlyContains is like [PolicyScope.Contains], but returns false
|
||||
// when s and s2 is the same scope.
|
||||
func (s PolicyScope) StrictlyContains(s2 PolicyScope) bool {
|
||||
return s != s2 && s.Contains(s2)
|
||||
}
|
||||
|
||||
// String implements [fmt.Stringer].
|
||||
func (s PolicyScope) String() string {
|
||||
if s.profileID == "" && s.userID == "" {
|
||||
return s.kind.String()
|
||||
}
|
||||
return s.stringSlow()
|
||||
}
|
||||
|
||||
// MarshalText implements [encoding.TextMarshaler].
|
||||
func (s PolicyScope) MarshalText() ([]byte, error) {
|
||||
return []byte(s.String()), nil
|
||||
}
|
||||
|
||||
// MarshalText implements [encoding.TextUnmarshaler].
|
||||
func (s *PolicyScope) UnmarshalText(b []byte) error {
|
||||
*s = PolicyScope{}
|
||||
parts := strings.SplitN(string(b), "/", 2)
|
||||
for i, part := range parts {
|
||||
kind, id, err := parseScopeAndID(part)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if i > 0 && kind <= s.kind {
|
||||
return fmt.Errorf("invalid scope hierarchy: %s", b)
|
||||
}
|
||||
s.kind = kind
|
||||
switch kind {
|
||||
case DeviceSetting:
|
||||
if id != "" {
|
||||
return fmt.Errorf("the device scope must not have an ID: %s", b)
|
||||
}
|
||||
case ProfileSetting:
|
||||
s.profileID = id
|
||||
case UserSetting:
|
||||
s.userID = id
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s PolicyScope) stringSlow() string {
|
||||
var sb strings.Builder
|
||||
writeScopeWithID := func(s Scope, id string) {
|
||||
sb.WriteString(s.String())
|
||||
if id != "" {
|
||||
sb.WriteRune('(')
|
||||
sb.WriteString(id)
|
||||
sb.WriteRune(')')
|
||||
}
|
||||
}
|
||||
if s.kind == ProfileSetting || s.profileID != "" {
|
||||
writeScopeWithID(ProfileSetting, s.profileID)
|
||||
if s.kind != ProfileSetting {
|
||||
sb.WriteRune('/')
|
||||
}
|
||||
}
|
||||
if s.kind == UserSetting {
|
||||
writeScopeWithID(UserSetting, s.userID)
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func parseScopeAndID(s string) (scope Scope, id string, err error) {
|
||||
name, params, ok := extractScopeAndParams(s)
|
||||
if !ok {
|
||||
return 0, "", fmt.Errorf("%q is not a valid scope string", s)
|
||||
}
|
||||
if err := scope.UnmarshalText([]byte(name)); err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
return scope, params, nil
|
||||
}
|
||||
|
||||
func extractScopeAndParams(s string) (name, params string, ok bool) {
|
||||
paramsStart := strings.Index(s, "(")
|
||||
if paramsStart == -1 {
|
||||
return s, "", true
|
||||
}
|
||||
paramsEnd := strings.LastIndex(s, ")")
|
||||
if paramsEnd < paramsStart {
|
||||
return "", "", false
|
||||
}
|
||||
return s[0:paramsStart], s[paramsStart+1 : paramsEnd], true
|
||||
}
|
||||
67
vendor/tailscale.com/util/syspolicy/setting/raw_item.go
generated
vendored
Normal file
67
vendor/tailscale.com/util/syspolicy/setting/raw_item.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"tailscale.com/types/structs"
|
||||
)
|
||||
|
||||
// RawItem contains a raw policy setting value as read from a policy store, or an
|
||||
// error if the requested setting could not be read from the store. As a special
|
||||
// case, it may also hold a value of the [Visibility], [PreferenceOption],
|
||||
// or [time.Duration] types. While the policy store interface does not support
|
||||
// these types natively, and the values of these types have to be unmarshalled
|
||||
// or converted from strings, these setting types predate the typed policy
|
||||
// hierarchies, and must be supported at this layer.
|
||||
type RawItem struct {
|
||||
_ structs.Incomparable
|
||||
value any
|
||||
err *ErrorText
|
||||
origin *Origin // or nil
|
||||
}
|
||||
|
||||
// RawItemOf returns a [RawItem] with the specified value.
|
||||
func RawItemOf(value any) RawItem {
|
||||
return RawItemWith(value, nil, nil)
|
||||
}
|
||||
|
||||
// RawItemWith returns a [RawItem] with the specified value, error and origin.
|
||||
func RawItemWith(value any, err *ErrorText, origin *Origin) RawItem {
|
||||
return RawItem{value: value, err: err, origin: origin}
|
||||
}
|
||||
|
||||
// Value returns the value of the policy setting, or nil if the policy setting
|
||||
// is not configured, or an error occurred while reading it.
|
||||
func (i RawItem) Value() any {
|
||||
return i.value
|
||||
}
|
||||
|
||||
// Error returns the error that occurred when reading the policy setting,
|
||||
// or nil if no error occurred.
|
||||
func (i RawItem) Error() error {
|
||||
if i.err != nil {
|
||||
return i.err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Origin returns an optional [Origin] indicating where the policy setting is
|
||||
// configured.
|
||||
func (i RawItem) Origin() *Origin {
|
||||
return i.origin
|
||||
}
|
||||
|
||||
// String implements [fmt.Stringer].
|
||||
func (i RawItem) String() string {
|
||||
var suffix string
|
||||
if i.origin != nil {
|
||||
suffix = fmt.Sprintf(" - {%v}", i.origin)
|
||||
}
|
||||
if i.err != nil {
|
||||
return fmt.Sprintf("Error{%q}%s", i.err.Error(), suffix)
|
||||
}
|
||||
return fmt.Sprintf("%v%s", i.value, suffix)
|
||||
}
|
||||
348
vendor/tailscale.com/util/syspolicy/setting/setting.go
generated
vendored
Normal file
348
vendor/tailscale.com/util/syspolicy/setting/setting.go
generated
vendored
Normal file
@@ -0,0 +1,348 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package setting contains types for defining and representing policy settings.
|
||||
// It facilitates the registration of setting definitions using [Register] and [RegisterDefinition],
|
||||
// and the retrieval of registered setting definitions via [Definitions] and [DefinitionOf].
|
||||
// This package is intended for use primarily within the syspolicy package hierarchy.
|
||||
package setting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"tailscale.com/types/lazy"
|
||||
"tailscale.com/util/syspolicy/internal"
|
||||
)
|
||||
|
||||
// Scope indicates the broadest scope at which a policy setting may apply,
|
||||
// and the narrowest scope at which it may be configured.
|
||||
type Scope int8
|
||||
|
||||
const (
|
||||
// DeviceSetting indicates a policy setting that applies to a device, regardless of
|
||||
// which OS user or Tailscale profile is currently active, if any.
|
||||
// It can only be configured at a [DeviceScope].
|
||||
DeviceSetting Scope = iota
|
||||
// ProfileSetting indicates a policy setting that applies to a Tailscale profile.
|
||||
// It can only be configured for a specific profile or at a [DeviceScope],
|
||||
// in which case it applies to all profiles on the device.
|
||||
ProfileSetting
|
||||
// UserSetting indicates a policy setting that applies to users.
|
||||
// It can be configured for a user, profile, or the entire device.
|
||||
UserSetting
|
||||
|
||||
// NumScopes is the number of possible [Scope] values.
|
||||
NumScopes int = iota // must be the last value in the const block.
|
||||
)
|
||||
|
||||
// String implements [fmt.Stringer].
|
||||
func (s Scope) String() string {
|
||||
switch s {
|
||||
case DeviceSetting:
|
||||
return "Device"
|
||||
case ProfileSetting:
|
||||
return "Profile"
|
||||
case UserSetting:
|
||||
return "User"
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalText implements [encoding.TextMarshaler].
|
||||
func (s Scope) MarshalText() (text []byte, err error) {
|
||||
return []byte(s.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements [encoding.TextUnmarshaler].
|
||||
func (s *Scope) UnmarshalText(text []byte) error {
|
||||
switch strings.ToLower(string(text)) {
|
||||
case "device":
|
||||
*s = DeviceSetting
|
||||
case "profile":
|
||||
*s = ProfileSetting
|
||||
case "user":
|
||||
*s = UserSetting
|
||||
default:
|
||||
return fmt.Errorf("%q is not a valid scope", string(text))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type is a policy setting value type.
|
||||
// Except for [InvalidValue], which represents an invalid policy setting type,
|
||||
// and [PreferenceOptionValue], [VisibilityValue], and [DurationValue],
|
||||
// which have special handling due to their legacy status in the package,
|
||||
// SettingTypes represent the raw value types readable from policy stores.
|
||||
type Type int
|
||||
|
||||
const (
|
||||
// InvalidValue indicates an invalid policy setting value type.
|
||||
InvalidValue Type = iota
|
||||
// BooleanValue indicates a policy setting whose underlying type is a bool.
|
||||
BooleanValue
|
||||
// IntegerValue indicates a policy setting whose underlying type is a uint64.
|
||||
IntegerValue
|
||||
// StringValue indicates a policy setting whose underlying type is a string.
|
||||
StringValue
|
||||
// StringListValue indicates a policy setting whose underlying type is a []string.
|
||||
StringListValue
|
||||
// PreferenceOptionValue indicates a three-state policy setting whose
|
||||
// underlying type is a string, but the actual value is a [PreferenceOption].
|
||||
PreferenceOptionValue
|
||||
// VisibilityValue indicates a two-state boolean-like policy setting whose
|
||||
// underlying type is a string, but the actual value is a [Visibility].
|
||||
VisibilityValue
|
||||
// DurationValue indicates an interval/period/duration policy setting whose
|
||||
// underlying type is a string, but the actual value is a [time.Duration].
|
||||
DurationValue
|
||||
)
|
||||
|
||||
// String returns a string representation of t.
|
||||
func (t Type) String() string {
|
||||
switch t {
|
||||
case InvalidValue:
|
||||
return "Invalid"
|
||||
case BooleanValue:
|
||||
return "Boolean"
|
||||
case IntegerValue:
|
||||
return "Integer"
|
||||
case StringValue:
|
||||
return "String"
|
||||
case StringListValue:
|
||||
return "StringList"
|
||||
case PreferenceOptionValue:
|
||||
return "PreferenceOption"
|
||||
case VisibilityValue:
|
||||
return "Visibility"
|
||||
case DurationValue:
|
||||
return "Duration"
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
// ValueType is a constraint that allows Go types corresponding to [Type].
|
||||
type ValueType interface {
|
||||
bool | uint64 | string | []string | Visibility | PreferenceOption | time.Duration
|
||||
}
|
||||
|
||||
// Definition defines policy key, scope and value type.
|
||||
type Definition struct {
|
||||
key Key
|
||||
scope Scope
|
||||
typ Type
|
||||
platforms PlatformList
|
||||
}
|
||||
|
||||
// NewDefinition returns a new [Definition] with the specified
|
||||
// key, scope, type and supported platforms (see [PlatformList]).
|
||||
func NewDefinition(k Key, s Scope, t Type, platforms ...string) *Definition {
|
||||
return &Definition{key: k, scope: s, typ: t, platforms: platforms}
|
||||
}
|
||||
|
||||
// Key returns a policy setting's identifier.
|
||||
func (d *Definition) Key() Key {
|
||||
if d == nil {
|
||||
return ""
|
||||
}
|
||||
return d.key
|
||||
}
|
||||
|
||||
// Scope reports the broadest [Scope] the policy setting may apply to.
|
||||
func (d *Definition) Scope() Scope {
|
||||
if d == nil {
|
||||
return 0
|
||||
}
|
||||
return d.scope
|
||||
}
|
||||
|
||||
// Type reports the underlying value type of the policy setting.
|
||||
func (d *Definition) Type() Type {
|
||||
if d == nil {
|
||||
return InvalidValue
|
||||
}
|
||||
return d.typ
|
||||
}
|
||||
|
||||
// IsSupported reports whether the policy setting is supported on the current OS.
|
||||
func (d *Definition) IsSupported() bool {
|
||||
if d == nil {
|
||||
return false
|
||||
}
|
||||
return d.platforms.HasCurrent()
|
||||
}
|
||||
|
||||
// SupportedPlatforms reports platforms on which the policy setting is supported.
|
||||
// An empty [PlatformList] indicates that s is available on all platforms.
|
||||
func (d *Definition) SupportedPlatforms() PlatformList {
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
return d.platforms
|
||||
}
|
||||
|
||||
// String implements [fmt.Stringer].
|
||||
func (d *Definition) String() string {
|
||||
if d == nil {
|
||||
return "(nil)"
|
||||
}
|
||||
return fmt.Sprintf("%v(%q, %v)", d.scope, d.key, d.typ)
|
||||
}
|
||||
|
||||
// Equal reports whether d and d2 have the same key, type and scope.
|
||||
// It does not check whether both s and s2 are supported on the same platforms.
|
||||
func (d *Definition) Equal(d2 *Definition) bool {
|
||||
if d == d2 {
|
||||
return true
|
||||
}
|
||||
if d == nil || d2 == nil {
|
||||
return false
|
||||
}
|
||||
return d.key == d2.key && d.typ == d2.typ && d.scope == d2.scope
|
||||
}
|
||||
|
||||
// DefinitionMap is a map of setting [Definition] by [Key].
|
||||
type DefinitionMap map[Key]*Definition
|
||||
|
||||
var (
|
||||
definitions lazy.SyncValue[DefinitionMap]
|
||||
|
||||
definitionsMu sync.Mutex
|
||||
definitionsList []*Definition
|
||||
definitionsUsed bool
|
||||
)
|
||||
|
||||
// Register registers a policy setting with the specified key, scope, value type,
|
||||
// and an optional list of supported platforms. All policy settings must be
|
||||
// registered before any of them can be used. Register panics if called after
|
||||
// invoking any functions that use the registered policy definitions. This
|
||||
// includes calling [Definitions] or [DefinitionOf] directly, or reading any
|
||||
// policy settings via syspolicy.
|
||||
func Register(k Key, s Scope, t Type, platforms ...string) {
|
||||
RegisterDefinition(NewDefinition(k, s, t, platforms...))
|
||||
}
|
||||
|
||||
// RegisterDefinition is like [Register], but accepts a [Definition].
|
||||
func RegisterDefinition(d *Definition) {
|
||||
definitionsMu.Lock()
|
||||
defer definitionsMu.Unlock()
|
||||
registerLocked(d)
|
||||
}
|
||||
|
||||
func registerLocked(d *Definition) {
|
||||
if definitionsUsed {
|
||||
panic("policy definitions are already in use")
|
||||
}
|
||||
definitionsList = append(definitionsList, d)
|
||||
}
|
||||
|
||||
func settingDefinitions() (DefinitionMap, error) {
|
||||
return definitions.GetErr(func() (DefinitionMap, error) {
|
||||
definitionsMu.Lock()
|
||||
defer definitionsMu.Unlock()
|
||||
definitionsUsed = true
|
||||
return DefinitionMapOf(definitionsList)
|
||||
})
|
||||
}
|
||||
|
||||
// DefinitionMapOf returns a [DefinitionMap] with the specified settings,
|
||||
// or an error if any settings have the same key but different type or scope.
|
||||
func DefinitionMapOf(settings []*Definition) (DefinitionMap, error) {
|
||||
m := make(DefinitionMap, len(settings))
|
||||
for _, s := range settings {
|
||||
if existing, exists := m[s.key]; exists {
|
||||
if existing.Equal(s) {
|
||||
// Ignore duplicate setting definitions if they match. It is acceptable
|
||||
// if the same policy setting was registered more than once
|
||||
// (e.g. by the syspolicy package itself and by iOS/Android code).
|
||||
existing.platforms.mergeFrom(s.platforms)
|
||||
continue
|
||||
}
|
||||
return nil, fmt.Errorf("duplicate policy definition: %q", s.key)
|
||||
}
|
||||
m[s.key] = s
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// SetDefinitionsForTest allows to register the specified setting definitions
|
||||
// for the test duration. It is not concurrency-safe, but unlike [Register],
|
||||
// it does not panic and can be called anytime.
|
||||
// It returns an error if ds contains two different settings with the same [Key].
|
||||
func SetDefinitionsForTest(tb lazy.TB, ds ...*Definition) error {
|
||||
m, err := DefinitionMapOf(ds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
definitions.SetForTest(tb, m, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefinitionOf returns a setting definition by key,
|
||||
// or [ErrNoSuchKey] if the specified key does not exist,
|
||||
// or an error if there are conflicting policy definitions.
|
||||
func DefinitionOf(k Key) (*Definition, error) {
|
||||
ds, err := settingDefinitions()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d, ok := ds[k]; ok {
|
||||
return d, nil
|
||||
}
|
||||
return nil, ErrNoSuchKey
|
||||
}
|
||||
|
||||
// Definitions returns all registered setting definitions,
|
||||
// or an error if different policies were registered under the same name.
|
||||
func Definitions() ([]*Definition, error) {
|
||||
ds, err := settingDefinitions()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := make([]*Definition, 0, len(ds))
|
||||
for _, d := range ds {
|
||||
res = append(res, d)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// PlatformList is a list of OSes.
|
||||
// An empty list indicates that all possible platforms are supported.
|
||||
type PlatformList []string
|
||||
|
||||
// Has reports whether l contains the target platform.
|
||||
func (l PlatformList) Has(target string) bool {
|
||||
if len(l) == 0 {
|
||||
return true
|
||||
}
|
||||
return slices.ContainsFunc(l, func(os string) bool {
|
||||
return strings.EqualFold(os, target)
|
||||
})
|
||||
}
|
||||
|
||||
// HasCurrent is like Has, but for the current platform.
|
||||
func (l PlatformList) HasCurrent() bool {
|
||||
return l.Has(internal.OS())
|
||||
}
|
||||
|
||||
// mergeFrom merges l2 into l. Since an empty list indicates no platform restrictions,
|
||||
// if either l or l2 is empty, the merged result in l will also be empty.
|
||||
func (l *PlatformList) mergeFrom(l2 PlatformList) {
|
||||
switch {
|
||||
case len(*l) == 0:
|
||||
// No-op. An empty list indicates no platform restrictions.
|
||||
case len(l2) == 0:
|
||||
// Merging with an empty list results in an empty list.
|
||||
*l = l2
|
||||
default:
|
||||
// Append, sort and dedup.
|
||||
*l = append(*l, l2...)
|
||||
slices.Sort(*l)
|
||||
*l = slices.Compact(*l)
|
||||
}
|
||||
}
|
||||
170
vendor/tailscale.com/util/syspolicy/setting/snapshot.go
generated
vendored
Normal file
170
vendor/tailscale.com/util/syspolicy/setting/snapshot.go
generated
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"iter"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
xmaps "golang.org/x/exp/maps"
|
||||
"tailscale.com/util/deephash"
|
||||
)
|
||||
|
||||
// Snapshot is an immutable collection of ([Key], [RawItem]) pairs, representing
|
||||
// a set of policy settings applied at a specific moment in time.
|
||||
// A nil pointer to [Snapshot] is valid.
|
||||
type Snapshot struct {
|
||||
m map[Key]RawItem
|
||||
sig deephash.Sum // of m
|
||||
summary Summary
|
||||
}
|
||||
|
||||
// NewSnapshot returns a new [Snapshot] with the specified items and options.
|
||||
func NewSnapshot(items map[Key]RawItem, opts ...SummaryOption) *Snapshot {
|
||||
return &Snapshot{m: xmaps.Clone(items), sig: deephash.Hash(&items), summary: SummaryWith(opts...)}
|
||||
}
|
||||
|
||||
// All returns an iterator over policy settings in s. The iteration order is not
|
||||
// specified and is not guaranteed to be the same from one call to the next.
|
||||
func (s *Snapshot) All() iter.Seq2[Key, RawItem] {
|
||||
if s == nil {
|
||||
return func(yield func(Key, RawItem) bool) {}
|
||||
}
|
||||
return maps.All(s.m)
|
||||
}
|
||||
|
||||
// Get returns the value of the policy setting with the specified key
|
||||
// or nil if it is not configured or has an error.
|
||||
func (s *Snapshot) Get(k Key) any {
|
||||
v, _ := s.GetErr(k)
|
||||
return v
|
||||
}
|
||||
|
||||
// GetErr returns the value of the policy setting with the specified key,
|
||||
// [ErrNotConfigured] if it is not configured, or an error returned by
|
||||
// the policy Store if the policy setting could not be read.
|
||||
func (s *Snapshot) GetErr(k Key) (any, error) {
|
||||
if s != nil {
|
||||
if s, ok := s.m[k]; ok {
|
||||
return s.Value(), s.Error()
|
||||
}
|
||||
}
|
||||
return nil, ErrNotConfigured
|
||||
}
|
||||
|
||||
// GetSetting returns the untyped policy setting with the specified key and true
|
||||
// if a policy setting with such key has been configured;
|
||||
// otherwise, it returns zero, false.
|
||||
func (s *Snapshot) GetSetting(k Key) (setting RawItem, ok bool) {
|
||||
setting, ok = s.m[k]
|
||||
return setting, ok
|
||||
}
|
||||
|
||||
// Equal reports whether s and s2 are equal.
|
||||
func (s *Snapshot) Equal(s2 *Snapshot) bool {
|
||||
if !s.EqualItems(s2) {
|
||||
return false
|
||||
}
|
||||
return s.Summary() == s2.Summary()
|
||||
}
|
||||
|
||||
// EqualItems reports whether items in s and s2 are equal.
|
||||
func (s *Snapshot) EqualItems(s2 *Snapshot) bool {
|
||||
if s == s2 {
|
||||
return true
|
||||
}
|
||||
if s.Len() != s2.Len() {
|
||||
return false
|
||||
}
|
||||
if s.Len() == 0 {
|
||||
return true
|
||||
}
|
||||
return s.sig == s2.sig
|
||||
}
|
||||
|
||||
// Keys return an iterator over keys in s. The iteration order is not specified
|
||||
// and is not guaranteed to be the same from one call to the next.
|
||||
func (s *Snapshot) Keys() iter.Seq[Key] {
|
||||
if s.m == nil {
|
||||
return func(yield func(Key) bool) {}
|
||||
}
|
||||
return maps.Keys(s.m)
|
||||
}
|
||||
|
||||
// Len reports the number of [RawItem]s in s.
|
||||
func (s *Snapshot) Len() int {
|
||||
if s == nil {
|
||||
return 0
|
||||
}
|
||||
return len(s.m)
|
||||
}
|
||||
|
||||
// Summary returns information about s as a whole rather than about specific [RawItem]s in it.
|
||||
func (s *Snapshot) Summary() Summary {
|
||||
if s == nil {
|
||||
return Summary{}
|
||||
}
|
||||
return s.summary
|
||||
}
|
||||
|
||||
// String implements [fmt.Stringer]
|
||||
func (s *Snapshot) String() string {
|
||||
if s.Len() == 0 && s.Summary().IsEmpty() {
|
||||
return "{Empty}"
|
||||
}
|
||||
var sb strings.Builder
|
||||
if !s.summary.IsEmpty() {
|
||||
sb.WriteRune('{')
|
||||
if s.Len() == 0 {
|
||||
sb.WriteString("Empty, ")
|
||||
}
|
||||
sb.WriteString(s.summary.String())
|
||||
sb.WriteRune('}')
|
||||
}
|
||||
for _, k := range slices.Sorted(s.Keys()) {
|
||||
if sb.Len() != 0 {
|
||||
sb.WriteRune('\n')
|
||||
}
|
||||
sb.WriteString(string(k))
|
||||
sb.WriteString(" = ")
|
||||
sb.WriteString(s.m[k].String())
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// MergeSnapshots returns a [Snapshot] that contains all [RawItem]s
|
||||
// from snapshot1 and snapshot2 and the [Summary] with the narrower [PolicyScope].
|
||||
// If there's a conflict between policy settings in the two snapshots,
|
||||
// the policy settings from the snapshot with the broader scope take precedence.
|
||||
// In other words, policy settings configured for the [DeviceScope] win
|
||||
// over policy settings configured for a user scope.
|
||||
func MergeSnapshots(snapshot1, snapshot2 *Snapshot) *Snapshot {
|
||||
scope1, ok1 := snapshot1.Summary().Scope().GetOk()
|
||||
scope2, ok2 := snapshot2.Summary().Scope().GetOk()
|
||||
if ok1 && ok2 && scope1.StrictlyContains(scope2) {
|
||||
// Swap snapshots if snapshot1 has higher precedence than snapshot2.
|
||||
snapshot1, snapshot2 = snapshot2, snapshot1
|
||||
}
|
||||
if snapshot2.Len() == 0 {
|
||||
return snapshot1
|
||||
}
|
||||
summaryOpts := make([]SummaryOption, 0, 2)
|
||||
if scope, ok := snapshot1.Summary().Scope().GetOk(); ok {
|
||||
// Use the scope from snapshot1, if present, which is the more specific snapshot.
|
||||
summaryOpts = append(summaryOpts, scope)
|
||||
}
|
||||
if snapshot1.Len() == 0 {
|
||||
if origin, ok := snapshot2.Summary().Origin().GetOk(); ok {
|
||||
// Use the origin from snapshot2 if snapshot1 is empty.
|
||||
summaryOpts = append(summaryOpts, origin)
|
||||
}
|
||||
return &Snapshot{snapshot2.m, snapshot2.sig, SummaryWith(summaryOpts...)}
|
||||
}
|
||||
m := make(map[Key]RawItem, snapshot1.Len()+snapshot2.Len())
|
||||
xmaps.Copy(m, snapshot1.m)
|
||||
xmaps.Copy(m, snapshot2.m) // snapshot2 has higher precedence
|
||||
return &Snapshot{m, deephash.Hash(&m), SummaryWith(summaryOpts...)}
|
||||
}
|
||||
100
vendor/tailscale.com/util/syspolicy/setting/summary.go
generated
vendored
Normal file
100
vendor/tailscale.com/util/syspolicy/setting/summary.go
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
jsonv2 "github.com/go-json-experiment/json"
|
||||
"github.com/go-json-experiment/json/jsontext"
|
||||
"tailscale.com/types/opt"
|
||||
)
|
||||
|
||||
// Summary is an immutable [PolicyScope] and [Origin].
|
||||
type Summary struct {
|
||||
data summary
|
||||
}
|
||||
|
||||
type summary struct {
|
||||
Scope opt.Value[PolicyScope] `json:",omitzero"`
|
||||
Origin opt.Value[Origin] `json:",omitzero"`
|
||||
}
|
||||
|
||||
// SummaryWith returns a [Summary] with the specified options.
|
||||
func SummaryWith(opts ...SummaryOption) Summary {
|
||||
var summary Summary
|
||||
for _, o := range opts {
|
||||
o.applySummaryOption(&summary)
|
||||
}
|
||||
return summary
|
||||
}
|
||||
|
||||
// IsEmpty reports whether s is empty.
|
||||
func (s Summary) IsEmpty() bool {
|
||||
return s == Summary{}
|
||||
}
|
||||
|
||||
// Scope reports the [PolicyScope] in s.
|
||||
func (s Summary) Scope() opt.Value[PolicyScope] {
|
||||
return s.data.Scope
|
||||
}
|
||||
|
||||
// Origin reports the [Origin] in s.
|
||||
func (s Summary) Origin() opt.Value[Origin] {
|
||||
return s.data.Origin
|
||||
}
|
||||
|
||||
// String implements [fmt.Stringer].
|
||||
func (s Summary) String() string {
|
||||
if s.IsEmpty() {
|
||||
return "{Empty}"
|
||||
}
|
||||
if origin, ok := s.data.Origin.GetOk(); ok {
|
||||
return origin.String()
|
||||
}
|
||||
return s.data.Scope.String()
|
||||
}
|
||||
|
||||
// MarshalJSONV2 implements [jsonv2.MarshalerV2].
|
||||
func (s Summary) MarshalJSONV2(out *jsontext.Encoder, opts jsonv2.Options) error {
|
||||
return jsonv2.MarshalEncode(out, &s.data, opts)
|
||||
}
|
||||
|
||||
// UnmarshalJSONV2 implements [jsonv2.UnmarshalerV2].
|
||||
func (s *Summary) UnmarshalJSONV2(in *jsontext.Decoder, opts jsonv2.Options) error {
|
||||
return jsonv2.UnmarshalDecode(in, &s.data, opts)
|
||||
}
|
||||
|
||||
// MarshalJSON implements [json.Marshaler].
|
||||
func (s Summary) MarshalJSON() ([]byte, error) {
|
||||
return jsonv2.Marshal(s) // uses MarshalJSONV2
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements [json.Unmarshaler].
|
||||
func (s *Summary) UnmarshalJSON(b []byte) error {
|
||||
return jsonv2.Unmarshal(b, s) // uses UnmarshalJSONV2
|
||||
}
|
||||
|
||||
// SummaryOption is an option that configures [Summary]
|
||||
// The following are allowed options:
|
||||
//
|
||||
// - [Summary]
|
||||
// - [PolicyScope]
|
||||
// - [Origin]
|
||||
type SummaryOption interface {
|
||||
applySummaryOption(summary *Summary)
|
||||
}
|
||||
|
||||
func (s PolicyScope) applySummaryOption(summary *Summary) {
|
||||
summary.data.Scope.Set(s)
|
||||
}
|
||||
|
||||
func (o Origin) applySummaryOption(summary *Summary) {
|
||||
summary.data.Origin.Set(o)
|
||||
if !summary.data.Scope.IsSet() {
|
||||
summary.data.Scope.Set(o.Scope())
|
||||
}
|
||||
}
|
||||
|
||||
func (s Summary) applySummaryOption(summary *Summary) {
|
||||
*summary = s
|
||||
}
|
||||
136
vendor/tailscale.com/util/syspolicy/setting/types.go
generated
vendored
Normal file
136
vendor/tailscale.com/util/syspolicy/setting/types.go
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
)
|
||||
|
||||
// PreferenceOption is a policy that governs whether a boolean variable
|
||||
// is forcibly assigned an administrator-defined value, or allowed to receive
|
||||
// a user-defined value.
|
||||
type PreferenceOption byte
|
||||
|
||||
const (
|
||||
ShowChoiceByPolicy PreferenceOption = iota
|
||||
NeverByPolicy
|
||||
AlwaysByPolicy
|
||||
)
|
||||
|
||||
// Show returns if the UI option that controls the choice administered by this
|
||||
// policy should be shown. Currently this is true if and only if the policy is
|
||||
// [ShowChoiceByPolicy].
|
||||
func (p PreferenceOption) Show() bool {
|
||||
return p == ShowChoiceByPolicy
|
||||
}
|
||||
|
||||
// ShouldEnable checks if the choice administered by this policy should be
|
||||
// enabled. If the administrator has chosen a setting, the administrator's
|
||||
// setting is returned, otherwise userChoice is returned.
|
||||
func (p PreferenceOption) ShouldEnable(userChoice bool) bool {
|
||||
switch p {
|
||||
case NeverByPolicy:
|
||||
return false
|
||||
case AlwaysByPolicy:
|
||||
return true
|
||||
default:
|
||||
return userChoice
|
||||
}
|
||||
}
|
||||
|
||||
// IsAlways reports whether the preference should always be enabled.
|
||||
func (p PreferenceOption) IsAlways() bool {
|
||||
return p == AlwaysByPolicy
|
||||
}
|
||||
|
||||
// IsNever reports whether the preference should always be disabled.
|
||||
func (p PreferenceOption) IsNever() bool {
|
||||
return p == NeverByPolicy
|
||||
}
|
||||
|
||||
// WillOverride checks if the choice administered by the policy is different
|
||||
// from the user's choice.
|
||||
func (p PreferenceOption) WillOverride(userChoice bool) bool {
|
||||
return p.ShouldEnable(userChoice) != userChoice
|
||||
}
|
||||
|
||||
// String returns a string representation of p.
|
||||
func (p PreferenceOption) String() string {
|
||||
switch p {
|
||||
case AlwaysByPolicy:
|
||||
return "always"
|
||||
case NeverByPolicy:
|
||||
return "never"
|
||||
default:
|
||||
return "user-decides"
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalText implements [encoding.TextMarshaler].
|
||||
func (p *PreferenceOption) MarshalText() (text []byte, err error) {
|
||||
return []byte(p.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements [encoding.TextUnmarshaler].
|
||||
// It never fails and sets p to [ShowChoiceByPolicy] if the specified text
|
||||
// does not represent a valid [PreferenceOption].
|
||||
func (p *PreferenceOption) UnmarshalText(text []byte) error {
|
||||
switch string(text) {
|
||||
case "always":
|
||||
*p = AlwaysByPolicy
|
||||
case "never":
|
||||
*p = NeverByPolicy
|
||||
default:
|
||||
*p = ShowChoiceByPolicy
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Visibility is a policy that controls whether or not a particular
|
||||
// component of a user interface is to be shown.
|
||||
type Visibility byte
|
||||
|
||||
var (
|
||||
_ encoding.TextMarshaler = (*Visibility)(nil)
|
||||
_ encoding.TextUnmarshaler = (*Visibility)(nil)
|
||||
)
|
||||
|
||||
const (
|
||||
VisibleByPolicy Visibility = 'v'
|
||||
HiddenByPolicy Visibility = 'h'
|
||||
)
|
||||
|
||||
// Show reports whether the UI option administered by this policy should be shown.
|
||||
// Currently this is true if the policy is not [hiddenByPolicy].
|
||||
func (v Visibility) Show() bool {
|
||||
return v != HiddenByPolicy
|
||||
}
|
||||
|
||||
// String returns a string representation of v.
|
||||
func (v Visibility) String() string {
|
||||
switch v {
|
||||
case 'h':
|
||||
return "hide"
|
||||
default:
|
||||
return "show"
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalText implements [encoding.TextMarshaler].
|
||||
func (v Visibility) MarshalText() (text []byte, err error) {
|
||||
return []byte(v.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements [encoding.TextUnmarshaler].
|
||||
// It never fails and sets v to [VisibleByPolicy] if the specified text
|
||||
// does not represent a valid [Visibility].
|
||||
func (v *Visibility) UnmarshalText(text []byte) error {
|
||||
switch string(text) {
|
||||
case "hide":
|
||||
*v = HiddenByPolicy
|
||||
default:
|
||||
*v = VisibleByPolicy
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user