Help us shape the future of Go! Take the 2026 Go Developer Survey

settings

package
v1.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 2, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package settings provides a dynamic, plugin-extensible configuration system for AuthSome. It supports typed setting definitions, multi-scope overrides (global, app, org, user), enforcement (locking settings at higher scopes), and change notifications.

Settings are declared by plugins via [SettingsProvider] and resolved through a cascade: code default → global DB → app DB → org DB → user DB. Enforcement flags prevent lower scopes from overriding locked values.

Use Define to create typed setting definitions and Get to resolve them:

var MinLength = settings.Define("password.min_length", 8,
    settings.WithScopes(settings.ScopeGlobal, settings.ScopeApp),
    settings.WithEnforceable(),
)

val, err := settings.Get(ctx, mgr, MinLength, settings.ResolveOpts{AppID: appID})

Index

Constants

View Source
const DefaultCacheTTL = 30 * time.Second

DefaultCacheTTL is how long a resolved setting is served from memory.

Variables

View Source
var (
	ErrNotFound             = errors.New("settings: not found")
	ErrKeyAlreadyRegistered = errors.New("settings: key already registered")
	ErrUnknownKey           = errors.New("settings: unknown key")
	ErrScopeNotAllowed      = errors.New("settings: scope not allowed for this setting")
	ErrEnforcedAtHigher     = errors.New("settings: setting is enforced at a higher scope")
	ErrNotEnforceable       = errors.New("settings: setting is not marked as enforceable")
	ErrValidation           = errors.New("settings: validation failed")
)

Sentinel errors.

Functions

func Get

func Get[T any](ctx context.Context, m *Manager, def DefinitionTyped[T], opts ResolveOpts) (T, error)

Get is the type-safe generic resolver. It resolves the setting and unmarshals it into the Go type T declared in the Definition.

func RegisterTyped

func RegisterTyped[T any](m *Manager, namespace string, def DefinitionTyped[T]) error

RegisterTyped registers a typed setting definition under the given namespace.

func ScopePriority

func ScopePriority(s Scope) int

ScopePriority returns the numeric priority of a scope (lower = less specific).

Types

type ChangeEvent

type ChangeEvent struct {
	Key      string          `json:"key"`
	Scope    Scope           `json:"scope"`
	ScopeID  string          `json:"scope_id"`
	AppID    string          `json:"app_id"`
	OrgID    string          `json:"org_id"`
	Enforced bool            `json:"enforced"`
	OldValue json.RawMessage `json:"old_value,omitempty"`
	NewValue json.RawMessage `json:"new_value"`
}

ChangeEvent is emitted when a setting value changes.

type ChangeListener

type ChangeListener func(event ChangeEvent)

ChangeListener is called when a setting changes.

type DefOption

type DefOption func(*Definition)

DefOption configures a Definition.

func WithCategory

func WithCategory(cat string) DefOption

WithCategory sets the UI grouping category.

func WithDescription

func WithDescription(desc string) DefOption

WithDescription sets the description.

func WithDisplayName

func WithDisplayName(name string) DefOption

WithDisplayName sets the human-readable label.

func WithEnforceable

func WithEnforceable() DefOption

WithEnforceable marks the setting as lockable at higher scopes.

func WithHelpText

func WithHelpText(h string) DefOption

WithHelpText sets descriptive text shown below the input field.

func WithInputType

func WithInputType(t formconfig.FieldType) DefOption

WithInputType sets the form field type (text, number, switch, select, …).

func WithObjectFields

func WithObjectFields(fields ...ObjectFieldDef) DefOption

WithObjectFields sets the schema for nested object fields in an object_array setting. This implicitly sets InputType to FieldObjectArray when no explicit InputType is set or when the auto-inferred type is FieldTextarea (the default for slices).

func WithOptions

func WithOptions(opts ...formconfig.SelectOption) DefOption

WithOptions sets the choices for select/radio/checkbox fields.

func WithOrder

func WithOrder(o int) DefOption

WithOrder sets the display order within a category (lower = first).

func WithPlaceholder

func WithPlaceholder(p string) DefOption

WithPlaceholder sets placeholder text for the input field.

func WithReadOnly

func WithReadOnly() DefOption

WithReadOnly marks the setting as non-editable in the UI.

func WithScopes

func WithScopes(scopes ...Scope) DefOption

WithScopes sets which scopes allow override.

func WithSection

func WithSection(s string) DefOption

WithSection sets a sub-group label within a category.

func WithSensitive

func WithSensitive() DefOption

WithSensitive marks the setting value as sensitive (redacted in API).

func WithUI

func WithUI(ui UIMetadata) DefOption

WithUI sets the entire UIMetadata at once.

func WithUIValidation

func WithUIValidation(v formconfig.Validation) DefOption

WithUIValidation sets client-side validation hints for the field.

func WithValidation

func WithValidation(fn func(json.RawMessage) error) DefOption

WithValidation sets a validation function called at write time.

func WithVisibleWhen

func WithVisibleWhen(key string, value any, operator ...string) DefOption

WithVisibleWhen sets a conditional visibility rule. The setting field is only shown when the referenced key's value matches. Supported operators are "eq" (default), "ne", and "in".

type Definition

type Definition struct {
	// Key is the unique dot-separated key, e.g. "password.min_length".
	Key string `json:"key"`

	// DisplayName is the human-readable label for UI surfaces.
	DisplayName string `json:"display_name"`

	// Description explains what this setting controls.
	Description string `json:"description"`

	// Type is the JSON schema type of the value.
	Type ValueType `json:"type"`

	// Default is the JSON-encoded default value (the "code default" in the cascade).
	Default json.RawMessage `json:"default"`

	// Scopes lists which scopes this setting can be overridden at.
	// An empty slice means only global.
	Scopes []Scope `json:"scopes"`

	// Enforceable marks this setting as lockable at higher scopes.
	// When enforced, lower scopes cannot override the value.
	Enforceable bool `json:"enforceable"`

	// Sensitive marks values that should be redacted in API responses.
	Sensitive bool `json:"sensitive"`

	// Validate is an optional validation function that receives the raw JSON
	// value and returns an error if the value is invalid. Called at write time.
	Validate func(value json.RawMessage) error `json:"-"`

	// Category groups related settings in UI displays.
	Category string `json:"category"`

	// Namespace is the owning plugin name (auto-set at registration).
	Namespace string `json:"namespace"`

	// UI holds rendering metadata for auto-generated settings forms.
	// When non-nil, the dashboard can auto-render this setting.
	UI *UIMetadata `json:"ui,omitempty"`
}

Definition describes a single setting that can be configured. Plugins register definitions at init time to declare their configurable knobs.

func (*Definition) HasScope

func (d *Definition) HasScope(scope Scope) bool

HasScope returns true if the definition allows the given scope.

type DefinitionTyped

type DefinitionTyped[T any] struct {
	Def Definition
}

DefinitionTyped is a generic wrapper that pairs a Definition with its Go type. This enables type-safe reading of resolved values via Get.

func Define

func Define[T any](key string, defaultVal T, opts ...DefOption) DefinitionTyped[T]

Define creates a typed setting definition. The Go type T determines the default value type and is used by the generic Get accessor.

If any UI-related DefOption is used (e.g. WithInputType, WithHelpText) and no explicit InputType is set, the input type is inferred from T:

bool   → switch
int    → number
float  → number
string → text
[]T    → textarea

type EnforcementProvider

type EnforcementProvider interface {
	// EnforcementRules returns the current enforcement rules.
	// Called during settings resolution to check for forced values.
	EnforcementRules(ctx context.Context) ([]EnforcementRule, error)
}

EnforcementProvider is implemented by plugins that programmatically enforce setting values (e.g., compliance, security hardening).

type EnforcementRule

type EnforcementRule struct {
	// Key is the setting key to enforce.
	Key string `json:"key"`

	// Value is the enforced value (JSON-encoded).
	Value json.RawMessage `json:"value"`

	// Scope is the scope at which enforcement applies.
	Scope Scope `json:"scope"`

	// ScopeID is the target scope ID (empty for global).
	ScopeID string `json:"scope_id"`

	// Reason explains why this enforcement exists (e.g., "SOC2 compliance").
	Reason string `json:"reason"`

	// Source is the plugin that created this rule.
	Source string `json:"source"`
}

EnforcementRule represents a programmatic enforcement applied by a plugin (e.g., a compliance plugin). It forces a setting value at a given scope and prevents lower scopes from overriding it.

type ListOpts

type ListOpts struct {
	Limit     int
	Offset    int
	Namespace string
	Scope     Scope
	ScopeID   string
	AppID     string
	OrgID     string
}

ListOpts controls pagination and filtering for setting queries.

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager is the central settings coordinator. It holds the definition registry, provides the resolution API, and emits change events.

func NewManager

func NewManager(store Store, logger log.Logger, opts ...ManagerOption) *Manager

NewManager creates a new settings manager.

func (*Manager) AddEnforcementProvider

func (m *Manager) AddEnforcementProvider(p EnforcementProvider)

AddEnforcementProvider registers a plugin that provides programmatic enforcement rules (e.g., compliance plugin).

func (*Manager) BatchResolve

func (m *Manager) BatchResolve(ctx context.Context, keys []string, opts ResolveOpts) (map[string]json.RawMessage, error)

BatchResolve resolves multiple settings in a single store call.

func (*Manager) Definition

func (m *Manager) Definition(key string) *Definition

Definition returns a single definition by key, or nil if not found.

func (*Manager) Definitions

func (m *Manager) Definitions() []*Definition

Definitions returns all registered definitions.

func (*Manager) DefinitionsForNamespace

func (m *Manager) DefinitionsForNamespace(ns string) []*Definition

DefinitionsForNamespace returns definitions registered by a specific plugin.

func (*Manager) Delete

func (m *Manager) Delete(ctx context.Context, key string, scope Scope, scopeID string) error

Delete removes a setting override at a specific scope.

func (*Manager) Enforce

func (m *Manager) Enforce(ctx context.Context, key string, value json.RawMessage, scope Scope, scopeID, appID, orgID, updatedBy string) error

Enforce sets a value AND marks it as enforced, preventing lower scopes from overriding it. Only allowed if the definition has Enforceable=true.

func (*Manager) InvalidateCache

func (m *Manager) InvalidateCache()

InvalidateCache drops every cached row, for callers that changed the store behind the manager's back.

func (*Manager) IsEnforced

func (m *Manager) IsEnforced(ctx context.Context, key string, opts ResolveOpts) (bool, *Setting, error)

IsEnforced checks if a setting is enforced at any scope in the resolution chain for the given context. Returns the enforcing setting if found.

func (*Manager) Namespaces

func (m *Manager) Namespaces() []string

Namespaces returns a sorted list of all registered namespace names.

func (*Manager) OnChange

func (m *Manager) OnChange(key string, fn ChangeListener)

OnChange registers a listener for changes to a specific key. Pass "*" to listen for all setting changes.

func (*Manager) Register

func (m *Manager) Register(def Definition) error

Register adds a setting definition to the registry.

func (*Manager) Resolve

func (m *Manager) Resolve(ctx context.Context, key string, opts ResolveOpts) (json.RawMessage, error)

Resolve returns the effective value for a setting key, performing the full enforcement-aware cascade:

code default → global → app → org → user

If any scope has enforced=true, the cascade stops there. EnforcementProviders are checked for programmatic rules.

func (*Manager) ResolveAllForNamespace

func (m *Manager) ResolveAllForNamespace(ctx context.Context, namespace string, opts ResolveOpts) ([]*ResolvedSetting, error)

ResolveAllForNamespace resolves all settings in a namespace with full cascade details. This is the primary endpoint for rendering an entire plugin's settings page in the dashboard.

func (*Manager) ResolveWithDetails

func (m *Manager) ResolveWithDetails(ctx context.Context, key string, opts ResolveOpts) (*ResolvedSetting, error)

ResolveWithDetails returns the full resolution result for a setting key, including the definition, effective value, the value at each scope that has an override, and the enforcement state. This provides all the context a UI needs to render a scope-aware settings form.

func (*Manager) Set

func (m *Manager) Set(ctx context.Context, key string, value json.RawMessage, scope Scope, scopeID, appID, orgID, updatedBy string) error

Set writes a setting value at the given scope. It validates the value, checks scope permissions, checks enforcement, and emits a change event.

func (*Manager) Store

func (m *Manager) Store() Store

Store returns the underlying settings store.

func (*Manager) Unenforce

func (m *Manager) Unenforce(ctx context.Context, key string, scope Scope, scopeID string) error

Unenforce removes the enforcement flag from a setting without changing its value. Lower scopes can then override it again.

type ManagerOption

type ManagerOption func(*Manager)

ManagerOption tunes a Manager.

func WithCacheTTL

func WithCacheTTL(d time.Duration) ManagerOption

WithCacheTTL sets how long resolved rows are cached; zero or less disables the cache.

type NilStore

type NilStore struct{}

NilStore is a no-op settings store used when the backing store does not support settings. All reads return nil (causing the manager to fall through to code defaults), and all writes are silently discarded.

func (NilStore) BatchResolve

func (NilStore) BatchResolve(_ context.Context, _ []string, _ ResolveOpts) (map[string][]*Setting, error)

func (NilStore) DeleteSetting

func (NilStore) DeleteSetting(_ context.Context, _ string, _ Scope, _ string) error

func (NilStore) DeleteSettingsByNamespace

func (NilStore) DeleteSettingsByNamespace(_ context.Context, _ string) error

func (NilStore) GetSetting

func (NilStore) GetSetting(_ context.Context, _ string, _ Scope, _ string) (*Setting, error)

func (NilStore) ListSettings

func (NilStore) ListSettings(_ context.Context, _ ListOpts) ([]*Setting, error)

func (NilStore) ResolveSettings

func (NilStore) ResolveSettings(_ context.Context, _ string, _ ResolveOpts) ([]*Setting, error)

func (NilStore) SetSetting

func (NilStore) SetSetting(_ context.Context, _ *Setting) error

type ObjectFieldDef

type ObjectFieldDef struct {
	// Key is the JSON field name (must match the struct's json tag).
	Key string `json:"key"`

	// DisplayName is the human-readable label.
	DisplayName string `json:"display_name"`

	// InputType is the form field type for this nested field.
	InputType formconfig.FieldType `json:"input_type"`

	// Placeholder is placeholder text for the input.
	Placeholder string `json:"placeholder,omitempty"`

	// Required marks this field as mandatory within each object.
	Required bool `json:"required,omitempty"`

	// Sensitive marks this field as a secret (renders as password input).
	Sensitive bool `json:"sensitive,omitempty"`

	// Options lists choices if InputType is select.
	Options []formconfig.SelectOption `json:"options,omitempty"`

	// HelpText is descriptive text shown below the nested input.
	HelpText string `json:"help_text,omitempty"`
}

ObjectFieldDef describes a single field within a structured object. Used by the object_array renderer to know how to render each nested input.

type ResolveOpts

type ResolveOpts struct {
	AppID  string
	OrgID  string
	UserID string
}

ResolveOpts provides the full scope context for cascade resolution.

type ResolvedSetting

type ResolvedSetting struct {
	// Definition is the full setting definition including UI metadata.
	Definition *Definition `json:"definition"`

	// EffectiveValue is the final resolved value after cascade + enforcement.
	EffectiveValue json.RawMessage `json:"effective_value"`

	// ScopeValues lists the value at each scope that has an override.
	// Ordered from least specific (global) to most specific (user).
	ScopeValues []ScopeValue `json:"scope_values"`

	// CanOverride is true if the current scope context allows setting a value.
	// False when a higher scope has this setting enforced.
	CanOverride bool `json:"can_override"`

	// EnforcedAt is the scope that has this setting locked, if any.
	EnforcedAt *Scope `json:"enforced_at,omitempty"`
}

ResolvedSetting is the full resolution result for a setting key, including the definition, effective value, value at each scope, and enforcement state. This enables UIs to render scope-aware settings forms with override indicators.

type Scope

type Scope string

Scope represents the specificity level of a setting value. Scopes are ordered from least specific (global) to most specific (user).

const (
	// ScopeGlobal applies to the entire AuthSome instance.
	ScopeGlobal Scope = "global"
	// ScopeApp applies to a specific application.
	ScopeApp Scope = "app"
	// ScopeOrg applies to a specific organization within an app.
	ScopeOrg Scope = "org"
	// ScopeUser applies to a specific user within an org/app.
	ScopeUser Scope = "user"
)

type ScopeValue

type ScopeValue struct {
	// Scope is the specificity level (global, app, org, user).
	Scope Scope `json:"scope"`

	// ScopeID is the ID of the entity at this scope (app ID, org ID, user ID).
	ScopeID string `json:"scope_id,omitempty"`

	// Value is the JSON-encoded setting value at this scope.
	Value json.RawMessage `json:"value"`

	// Enforced indicates whether this scope has the value locked.
	Enforced bool `json:"enforced"`

	// Version is the setting version at this scope.
	Version int64 `json:"version,omitempty"`

	// UpdatedBy is who last changed the value at this scope.
	UpdatedBy string `json:"updated_by,omitempty"`

	// UpdatedAt is when the value was last changed at this scope.
	UpdatedAt time.Time `json:"updated_at,omitempty"`
}

ScopeValue represents the value of a setting at a particular scope. Used in ResolvedSetting to show the full cascade breakdown to UIs.

type Setting

type Setting struct {
	ID        id.SettingID    `json:"id"`
	Key       string          `json:"key"`
	Value     json.RawMessage `json:"value"`
	Scope     Scope           `json:"scope"`
	ScopeID   string          `json:"scope_id"`
	AppID     string          `json:"app_id"`
	OrgID     string          `json:"org_id"`
	Enforced  bool            `json:"enforced"`
	Namespace string          `json:"namespace"`
	Version   int64           `json:"version"`
	UpdatedBy string          `json:"updated_by"`
	CreatedAt time.Time       `json:"created_at"`
	UpdatedAt time.Time       `json:"updated_at"`
}

Setting is a persisted setting override stored in the database. Each row represents one (key, scope, scope_id) combination.

type Store

type Store interface {
	// GetSetting retrieves a single setting by key + scope + scope_id.
	// Returns ErrNotFound if no setting exists for the given combination.
	GetSetting(ctx context.Context, key string, scope Scope, scopeID string) (*Setting, error)

	// SetSetting creates or updates a setting. Upsert semantics on (key, scope, scope_id).
	SetSetting(ctx context.Context, s *Setting) error

	// DeleteSetting removes a specific setting override.
	DeleteSetting(ctx context.Context, key string, scope Scope, scopeID string) error

	// ListSettings returns all settings matching the filter.
	ListSettings(ctx context.Context, opts ListOpts) ([]*Setting, error)

	// ResolveSettings performs cascade lookup for a key across all scopes.
	// Returns all matching settings ordered by specificity (global → app → org → user).
	// The Manager uses these to apply enforcement logic.
	ResolveSettings(ctx context.Context, key string, opts ResolveOpts) ([]*Setting, error)

	// BatchResolve resolves multiple keys at once for performance.
	// Returns a map of key → list of matching settings (ordered by specificity).
	BatchResolve(ctx context.Context, keys []string, opts ResolveOpts) (map[string][]*Setting, error)

	// DeleteSettingsByNamespace removes all settings for a given namespace.
	DeleteSettingsByNamespace(ctx context.Context, namespace string) error
}

Store defines the persistence contract for settings. Each backend (postgres, sqlite, mongo, memory) implements this.

type UIMetadata

type UIMetadata struct {
	// InputType is the form field type (text, number, switch, select, etc.).
	InputType formconfig.FieldType `json:"input_type"`

	// Placeholder is placeholder text for the input field.
	Placeholder string `json:"placeholder,omitempty"`

	// HelpText is descriptive text shown below the input field.
	HelpText string `json:"help_text,omitempty"`

	// Options lists choices for select/radio/checkbox fields.
	Options []formconfig.SelectOption `json:"options,omitempty"`

	// Validation defines client-side validation hints.
	Validation *formconfig.Validation `json:"validation,omitempty"`

	// Order controls display ordering within a category (lower = first).
	Order int `json:"order"`

	// ReadOnly marks the field as non-editable in the UI.
	ReadOnly bool `json:"read_only,omitempty"`

	// Condition defines conditional visibility based on another setting.
	Condition *VisibilityCondition `json:"condition,omitempty"`

	// Section is an optional sub-group label within a category.
	Section string `json:"section,omitempty"`

	// ObjectFields describes the schema for each object in an object_array field.
	// Only used when InputType is FieldObjectArray.
	ObjectFields []ObjectFieldDef `json:"object_fields,omitempty"`
}

UIMetadata describes how a setting should be rendered in a form UI. It reuses types from the formconfig package for consistency with other authsome UI schema definitions.

type ValueType

type ValueType string

ValueType describes the JSON-compatible type of a setting value.

const (
	TypeString ValueType = "string"
	TypeInt    ValueType = "int"
	TypeFloat  ValueType = "float"
	TypeBool   ValueType = "bool"
	TypeObject ValueType = "object"
	TypeArray  ValueType = "array"
)

type VisibilityCondition

type VisibilityCondition struct {
	// Key is the setting key to evaluate.
	Key string `json:"key"`

	// Value is the expected value (JSON-encoded).
	Value json.RawMessage `json:"value"`

	// Operator is the comparison operator: "eq" (default), "ne", "in".
	Operator string `json:"operator,omitempty"`
}

VisibilityCondition controls when a setting field is visible in the UI. The field is shown only when the referenced setting matches the condition.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL