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
- Variables
- func Get[T any](ctx context.Context, m *Manager, def DefinitionTyped[T], opts ResolveOpts) (T, error)
- func RegisterTyped[T any](m *Manager, namespace string, def DefinitionTyped[T]) error
- func ScopePriority(s Scope) int
- type ChangeEvent
- type ChangeListener
- type DefOption
- func WithCategory(cat string) DefOption
- func WithDescription(desc string) DefOption
- func WithDisplayName(name string) DefOption
- func WithEnforceable() DefOption
- func WithHelpText(h string) DefOption
- func WithInputType(t formconfig.FieldType) DefOption
- func WithObjectFields(fields ...ObjectFieldDef) DefOption
- func WithOptions(opts ...formconfig.SelectOption) DefOption
- func WithOrder(o int) DefOption
- func WithPlaceholder(p string) DefOption
- func WithReadOnly() DefOption
- func WithScopes(scopes ...Scope) DefOption
- func WithSection(s string) DefOption
- func WithSensitive() DefOption
- func WithUI(ui UIMetadata) DefOption
- func WithUIValidation(v formconfig.Validation) DefOption
- func WithValidation(fn func(json.RawMessage) error) DefOption
- func WithVisibleWhen(key string, value any, operator ...string) DefOption
- type Definition
- type DefinitionTyped
- type EnforcementProvider
- type EnforcementRule
- type ListOpts
- type Manager
- func (m *Manager) AddEnforcementProvider(p EnforcementProvider)
- func (m *Manager) BatchResolve(ctx context.Context, keys []string, opts ResolveOpts) (map[string]json.RawMessage, error)
- func (m *Manager) Definition(key string) *Definition
- func (m *Manager) Definitions() []*Definition
- func (m *Manager) DefinitionsForNamespace(ns string) []*Definition
- func (m *Manager) Delete(ctx context.Context, key string, scope Scope, scopeID string) error
- func (m *Manager) Enforce(ctx context.Context, key string, value json.RawMessage, scope Scope, ...) error
- func (m *Manager) InvalidateCache()
- func (m *Manager) IsEnforced(ctx context.Context, key string, opts ResolveOpts) (bool, *Setting, error)
- func (m *Manager) Namespaces() []string
- func (m *Manager) OnChange(key string, fn ChangeListener)
- func (m *Manager) Register(def Definition) error
- func (m *Manager) Resolve(ctx context.Context, key string, opts ResolveOpts) (json.RawMessage, error)
- func (m *Manager) ResolveAllForNamespace(ctx context.Context, namespace string, opts ResolveOpts) ([]*ResolvedSetting, error)
- func (m *Manager) ResolveWithDetails(ctx context.Context, key string, opts ResolveOpts) (*ResolvedSetting, error)
- func (m *Manager) Set(ctx context.Context, key string, value json.RawMessage, scope Scope, ...) error
- func (m *Manager) Store() Store
- func (m *Manager) Unenforce(ctx context.Context, key string, scope Scope, scopeID string) error
- type ManagerOption
- type NilStore
- func (NilStore) BatchResolve(_ context.Context, _ []string, _ ResolveOpts) (map[string][]*Setting, error)
- func (NilStore) DeleteSetting(_ context.Context, _ string, _ Scope, _ string) error
- func (NilStore) DeleteSettingsByNamespace(_ context.Context, _ string) error
- func (NilStore) GetSetting(_ context.Context, _ string, _ Scope, _ string) (*Setting, error)
- func (NilStore) ListSettings(_ context.Context, _ ListOpts) ([]*Setting, error)
- func (NilStore) ResolveSettings(_ context.Context, _ string, _ ResolveOpts) ([]*Setting, error)
- func (NilStore) SetSetting(_ context.Context, _ *Setting) error
- type ObjectFieldDef
- type ResolveOpts
- type ResolvedSetting
- type Scope
- type ScopeValue
- type Setting
- type Store
- type UIMetadata
- type ValueType
- type VisibilityCondition
Constants ¶
const DefaultCacheTTL = 30 * time.Second
DefaultCacheTTL is how long a resolved setting is served from memory.
Variables ¶
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 ¶
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 ¶
WithCategory sets the UI grouping category.
func WithDescription ¶
WithDescription sets the description.
func WithDisplayName ¶
WithDisplayName sets the human-readable label.
func WithEnforceable ¶
func WithEnforceable() DefOption
WithEnforceable marks the setting as lockable at higher scopes.
func WithHelpText ¶
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 WithPlaceholder ¶
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 ¶
WithScopes sets which scopes allow override.
func WithSection ¶
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 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.
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) 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 ¶
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.
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) DeleteSetting ¶
func (NilStore) DeleteSettingsByNamespace ¶
func (NilStore) GetSetting ¶
func (NilStore) ListSettings ¶
func (NilStore) ResolveSettings ¶
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 ¶
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.
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.