Documentation
¶
Overview ¶
Package featureflag is a minimal feature-flag primitive for GoFastr apps.
The package name avoids collision with the standard library's `flag`; users who want a shorter local name can alias on import:
import flag "github.com/DonaldMurillo/gofastr/core/featureflag"
The model is intentionally small: a flag has a global on/off switch, an optional percentage rollout, and explicit user/tenant allow lists. Rules are evaluated against an EvalContext that you build from the request (user, tenant, env, attributes).
Anything more elaborate — variants with weights, dependent flags, scheduled ramps — should be a separate evaluator. This package's only job is to make "is feature X on for this caller right now" trivial to express in handler code.
Wiring:
store := featureflag.NewMemoryStore()
store.Set(featureflag.Flag{Key: "new-checkout", Enabled: true, Rollout: 25})
featureflag.SetDefault(featureflag.NewEvaluator(store))
// later, in a handler
if featureflag.Bool(ctx, "new-checkout") {
return newCheckout(w, r)
}
return oldCheckout(w, r)
Concurrency: the bundled memory store is safe for concurrent reads and writes. Evaluator and the package-level helpers are safe for concurrent use as long as the underlying store is.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func SetDefault ¶
func SetDefault(e *Evaluator)
SetDefault installs the process-wide evaluator used by the package- level helpers (Bool). Pass nil to disable and have Bool return false for every call.
func WithContext ¶
func WithContext(ctx context.Context, ec EvalContext) context.Context
WithContext attaches an EvalContext to ctx. Middleware that knows the user and tenant should call this once per request; handlers then just call flag.Bool(ctx, ...).
Types ¶
type EvalContext ¶
EvalContext is the per-call subject identity used to evaluate a flag. Build one from the request — typically the authenticated user id and the resolved tenant id.
The Env field is matched by string equality against any rule that references it; this is how "dev only" or "staging only" flags work without coupling the flag definition to deployment plumbing.
Attrs is open-ended additional context (region, plan tier, etc.). The default evaluator doesn't consult Attrs, but a custom Store / Evaluator wrapper can. Future versions of this package may grow attribute-rule matching; storing them now keeps the migration cheap.
func FromContext ¶
func FromContext(ctx context.Context) EvalContext
FromContext returns the attached EvalContext, or a zero value if none.
type Evaluator ¶
type Evaluator struct {
// contains filtered or unexported fields
}
Evaluator is the entry point for runtime checks.
func NewEvaluator ¶
NewEvaluator wraps a Store. Passing nil yields an evaluator that answers every question with the caller's default — useful for tests that don't want to wire a store but still call featureflag.Bool.
func NewEvaluatorWithSalt ¶
NewEvaluatorWithSalt wraps a Store with a process-private salt mixed into the rollout bucket hash. Set this when the rollout cohort needs to be unpredictable to attackers (kill switches whose evasion would re-enable a payments flow, etc.) — without a salt, FNV-1a buckets are deterministic from public flag keys.
The salt is process-private by convention; rotating it shuffles every cohort so use only when that's acceptable.
func (*Evaluator) Bool ¶
Bool returns whether the named flag is on for the supplied context. Missing keys, storage errors, and disabled flags all return false.
The context's user / tenant lists are checked first, then the rollout percentage. The decision is stable across processes — same key + same subject id always hashes the same way.
func (*Evaluator) BoolDefault ¶
BoolDefault returns the supplied fallback when the named flag is genuinely absent from the store; existing flags evaluate normally (so an explicitly-disabled flag returns false, ignoring fallback).
Use this for kill switches where a typo or accidental delete must not silently re-enable the protected path. Example:
if app.Flags().BoolDefault(ctx, "kill-payments", true) {
return payment.Block(...) // safe default: block on missing flag
}
type Flag ¶
type Flag struct {
Key string
Enabled bool
Rollout int
Users []string
Tenants []string
Envs []string
}
Flag is the rule definition stored for a key.
Enabled is the global kill switch; when false, Bool always returns false regardless of the other fields.
Rollout is a percentage (0–100) of subjects that see the flag as on, computed from a stable hash of the flag key and subject id. Set 0 for "off-by-default" and 100 to enable everywhere the kill switch allows.
Users and Tenants are explicit allow lists. A subject matching either list short-circuits to true (regardless of rollout), so flags can be force-enabled for testers and beta tenants.
Envs restricts the flag to specific deployment environments. When non-empty, the EvalContext's Env must match one of the listed values or the flag evaluates to false — even for explicitly allow-listed users. When empty, no environment restriction applies. The match is case-sensitive string equality.
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore is the bundled in-memory MutableStore implementation. Suitable for single-instance apps, tests, and as a cache layer in front of a persistent store.
func NewMemoryStore ¶
func NewMemoryStore() *MemoryStore
NewMemoryStore creates an empty MemoryStore.
func (*MemoryStore) All ¶
func (m *MemoryStore) All() []Flag
All returns a snapshot of every defined flag, suitable for /admin listings. The result is a copy — mutations don't affect the store.
func (*MemoryStore) Delete ¶
func (m *MemoryStore) Delete(key string) error
Delete removes a flag. Missing keys are not an error.
func (*MemoryStore) Set ¶
func (m *MemoryStore) Set(f Flag) error
Set creates or updates a flag definition. Empty Key returns an error.
type MutableStore ¶
MutableStore extends Store with write operations. The memory store implements it; persistent stores (db, redis) typically should too so admin tools can edit flags at runtime.
type SQLOption ¶
type SQLOption func(*SQLStore)
SQLOption configures the SQL store.
func WithSQLDialect ¶
WithSQLDialect pins the dialect ("postgres" or "sqlite") instead of running the SELECT version() probe. Use this when the probe is known to fail (e.g. embedded sqlite with restricted features) or when you want construction to avoid any DB read at all.
func WithSQLTable ¶
WithSQLTable overrides the default "feature_flags" table name.
type SQLStore ¶
type SQLStore struct {
// contains filtered or unexported fields
}
SQLStore is a SQL-backed MutableStore. Flags are persisted as individual rows; allow lists (Users, Tenants, Envs) live in JSON columns so the schema stays a single table.
Dialect is either set explicitly via WithSQLDialect or detected at construction via SELECT version() (matches Postgres; everything else is treated as sqlite-compatible).
Schema:
feature_flags(
key TEXT PRIMARY KEY,
enabled INTEGER NOT NULL,
rollout INTEGER NOT NULL,
users TEXT NOT NULL, -- JSON array
tenants TEXT NOT NULL, -- JSON array
envs TEXT NOT NULL -- JSON array
)
func NewSQLStore ¶
NewSQLStore constructs a SQLStore and ensures the table exists.