Documentation
¶
Overview ¶
Package feature provides a vendor-neutral, read-only feature flag contract and a typed facade.
A Driver answers one question: what is the value of a flag key for a Subject? Flags are created and changed in the vendor's own tools, never through this package. Flags sits on top of a driver and turns the answer into a Go value with Flags.Value or, with the variant and Reason, Flags.ValueDetail. Four kinds exist: bool, string, int, and float. A request for any integer or float type, or a named type such as type Plan string, maps onto them. Declare a shared immutable Flag with NewFlag to bind the provider name, Go type, and safe fallback. Definitions do not register or validate an external provider's schema; driver type checks still run on every answer. Raw Driver.Evaluate calls check cancellation first, then reject empty or unknown Kind values with ErrTypeMismatch before readiness, flag lookup, disabled/default shortcuts, or provider I/O. The only accepted kinds are KindBool, KindString, KindInt, and KindFloat. Unknown string representations remain unchanged in JSON and String; typed zero definitions still fail with ErrInvalidFlag before driver access.
flags := feature.New(driver, func(flags *feature.Builder) {
flags.Telemetry(app.Telemetry())
})
subject := feature.Subject{Key: user.ID, Attributes: map[string]any{"plan": "pro"}}
checkout := feature.NewFlag("new-checkout", false)
if on, _ := flags.Value(ctx, checkout, subject); on {
// serve the new checkout
}
Default guarantee ¶
Every call uses its definition's fallback and the driver never sees it. The returned value is the default whenever the error is non-nil, and also, with a nil error, when the flag is off or has no value for the subject. Ignoring the error is therefore safe: the code takes the old path. Failures are ErrNotFound, ErrTypeMismatch, ErrNotReady, and ErrInvalidSubject. An invalid or zero definition fails with ErrInvalidFlag before the driver is called. An integer request accepts a float result holding a whole number; 3.5, or a value that overflows the requested type, fails with ErrTypeMismatch.
Subjects ¶
A Subject is passed explicitly on every call. Its Key identifies the subject stably, such as a user ID, and percentage rollouts hash it. An empty Key is an anonymous subject and is valid; the driver then returns the flag's default or a value chosen without bucketing. Attributes are matched by targeting rules and must be string, bool, int64, float64, []string, or time.Time; the facade rejects anything else with ErrInvalidSubject before calling the driver.
Events ¶
With Builder.Events, Flags publishes EvaluationFailed ("feature.evaluation.failed") with the flag key and the error when the driver fails an evaluation and the caller gets its default value. Normal evaluations publish nothing, and the payload never carries the subject or flag values.
Telemetry ¶
With telemetry enabled, every evaluation emits one internal span named "feature evaluate" and two metrics, and costs nothing when telemetry is off. The span is internal rather than a client span because the facade cannot know whether the driver calls a remote service. It carries feature_flag.key, feature_flag.provider.name (the Builder.Name value), feature_flag.result.reason, feature_flag.result.variant when the driver gave one, and error.type with an error status on failure. The counter arkoren.feature.evaluations ({evaluation}) is labeled with the key, provider, reason, and error.type; the histogram arkoren.feature.evaluation.duration (s) with the key, provider, and error.type. The reason of a failed evaluation is "error", and error.type names the package sentinel, such as feature.ErrNotFound.
Flag keys are recorded because developers choose them and their number is bounded, unlike cache keys. Subject keys, subject attributes, flag values, and error text are never recorded, because they can identify people.
New takes Builder policies for Telemetry, Name and Events. They run directly once in order on a fresh draft; nil entries panic and expanded nil/empty slices supply no edits. Publication detaches settings, not the live driver or shared services. Retained drafts cannot reconfigure Flags. Nil telemetry resolves the process default on each evaluation.
Index ¶
- Constants
- Variables
- type Builder
- type BuilderFunc
- type Detail
- type Driver
- type EvaluationFailedEvent
- type Flag
- type Flags
- func (flags *Flags) Driver() Driver
- func (flags *Flags) DriverAs[T any]() (T, bool)
- func (flags *Flags) Value[T Primitive](ctx context.Context, definition Flag[T], subject Subject) (T, error)
- func (flags *Flags) ValueDetail[T Primitive](ctx context.Context, definition Flag[T], subject Subject) (Detail[T], error)
- type Kind
- type Namespace
- func (namespace Namespace[T]) Derive(configure ...NamespaceBuilderFunc) Namespace[T]
- func (namespace Namespace[T]) Err() error
- func (namespace Namespace[T]) Fallback() T
- func (namespace Namespace[T]) Flag(name string) Flag[T]
- func (namespace Namespace[T]) Format(state fmt.State, _ rune)
- func (namespace Namespace[T]) LogValue() slog.Value
- func (namespace Namespace[T]) Name() string
- func (namespace Namespace[T]) Separator() string
- func (namespace Namespace[T]) Subnamespace(name string, configure ...NamespaceBuilderFunc) Namespace[T]
- type NamespaceBuilder
- type NamespaceBuilderFunc
- type Primitive
- type Reason
- type Request
- type Result
- type Subject
Constants ¶
const DefaultName = "default"
DefaultName is the provider name reported in telemetry when Builder.Name is empty.
Example:
name := feature.DefaultName
feature.New(driver, func(flags *feature.Builder) {
flags.Name(name)
})
Variables ¶
var ( // ErrNotFound indicates that no flag with the requested key exists. // Typed evaluation still returns the definition's fallback with this error. // // Example: missing := errors.Is(err, feature.ErrNotFound) ErrNotFound = errors.New("feature flag not found") // ErrTypeMismatch indicates that the flag's value cannot be represented // as the requested kind or Go type. // // Example: incompatible := errors.Is(err, feature.ErrTypeMismatch) ErrTypeMismatch = errors.New("feature flag type mismatch") // ErrNotReady indicates that the driver has not loaded its flags yet, // for example before its first sync completes. // // Example: loading := errors.Is(err, feature.ErrNotReady) ErrNotReady = errors.New("feature flag driver not ready") // ErrInvalidSubject indicates that the subject cannot be evaluated, such // as an attribute of an unsupported type. // // Example: invalid := errors.Is(err, feature.ErrInvalidSubject) ErrInvalidSubject = errors.New("feature flag invalid subject") )
var ErrInvalidFlag = errors.New("feature flag invalid definition")
ErrInvalidFlag identifies a missing flag or namespace name, including zero definitions and flags descended from an invalid namespace. Names are provider-owned literal identifiers; they are not normalized.
Example: invalid := errors.Is(feature.NewFlag("", false).Err(), feature.ErrInvalidFlag)
var EvaluationFailed = event.NewTopic[EvaluationFailedEvent]("feature.evaluation.failed")
EvaluationFailed is published by Flags built with Builder.Events when the driver's Evaluate fails and the caller therefore gets its default value together with the error. Successful evaluations, flags that are off, and failures the facade detects itself (an invalid subject, or a driver value that does not convert to the requested type) publish nothing. Every failing call publishes, so a missing flag read on a hot path publishes at the rate of that path.
Example:
stop, err := bus.Listen(feature.EvaluationFailed, func(_ context.Context, payload feature.EvaluationFailedEvent) error {
_ = errors.Is(payload.Err, feature.ErrNotFound)
return nil
})
if err != nil {
return err
}
defer stop()
Functions ¶
This section is empty.
Types ¶
type Builder ¶
type Builder struct {
// contains filtered or unexported fields
}
Builder configures New before publication. Callbacks run directly once, synchronously in order on one fresh draft. Nil callback entries panic; expanded nil or empty callback slices supply no edits. Retaining the draft cannot change published settings. Methods return the same draft.
Example:
feature.New(driver, func(flags *feature.Builder) {
flags.Name("primary")
})
func (*Builder) Events ¶
Events selects the shared bus receiving EvaluationFailed after a driver failure. Nil publishes nothing; listeners cannot change the evaluated result.
This setting replaces the previous value on the construction draft. Services and loggers remain shared; publication detaches owned settings.
Example:
feature.New(driver, func(flags *feature.Builder) {
flags.Events(nil)
})
func (*Builder) Name ¶
Name sets the provider name reported as feature_flag.provider.name.
This setting replaces the previous value on the construction draft. Services and loggers remain shared; publication detaches owned settings.
Example:
feature.New(driver, func(flags *feature.Builder) {
flags.Name("primary")
})
func (*Builder) Telemetry ¶
Telemetry sets the telemetry that records evaluations. Without it the facade follows telemetry.Default.
This setting replaces the previous value on the construction draft. Services and loggers remain shared; publication detaches owned settings.
Example:
feature.New(driver, func(flags *feature.Builder) {
flags.Telemetry(nil)
})
type BuilderFunc ¶
type BuilderFunc = func(*Builder)
BuilderFunc configures a Builder. Example:
var policy feature.BuilderFunc = func(flags *feature.Builder) {
flags.Name("memory")
}
_ = feature.New(driver, policy)
type Detail ¶
type Detail[T Primitive] struct { // Value is the flag's value, or the caller's default when the flag is // off, has no value for the subject, or evaluation failed. // // Example: value := detail.Value Value T // Variant is the vendor variant name, empty when the driver gave none. // // Example: variant := detail.Variant Variant string // Reason explains the result: [ReasonError] on failure, otherwise the // driver's reason. // // Example: failed := detail.Reason == feature.ReasonError Reason Reason }
Detail is an evaluated value with its explanation. On failure Value is the definition's fallback and Reason is ReasonError.
Example:
detail, err := flags.ValueDetail(ctx, feature.NewFlag("plan", "free"), subject)
if err != nil {
_ = detail.Value // "free" on failure.
}
type Driver ¶
type Driver interface {
// Evaluate resolves the requested flag for its subject and value kind.
// Cancellation takes precedence over invalid-kind rejection. An empty or
// unknown kind wraps ErrTypeMismatch before readiness, lookup, any
// disabled/default shortcut, or provider I/O, as described by Driver.
//
// Example:
//
// result, err := driver.Evaluate(ctx, feature.Request{Key: "checkout", Kind: feature.KindBool})
// if err != nil {
// return err
// }
// _ = result.Reason
Evaluate(ctx context.Context, request Request) (Result, error)
}
Driver evaluates feature flags. Implementations are read-only: flags are managed in the vendor's own tools.
Evaluate first checks cancellation, then accepts only KindBool, KindString, KindInt, or KindFloat. An empty or unknown kind fails with an error wrapping ErrTypeMismatch before readiness checks, flag lookup, disabled/default shortcuts, or provider I/O. This validation does not change Kind serialization.
Evaluate returns the value of request.Key for request.Subject as a valid request.Kind:
- an unknown key fails with an error wrapping ErrNotFound;
- a flag whose value cannot be expressed as Kind fails with an error wrapping ErrTypeMismatch;
- before the driver has loaded its flags it fails with an error wrapping ErrNotReady;
- a subject the backend rejects fails with an error wrapping ErrInvalidSubject;
- a flag that exists but is off returns Value nil, Reason Disabled, nil error;
- a flag with no value for this subject returns Value nil, Reason Default.
An empty Subject.Key is valid and means an anonymous subject. A percentage rollout then returns its default (Value nil, Reason Default) or a value chosen without bucketing, whichever the backend does. Every call fails with an error wrapping the context's error when ctx is already canceled. Evaluate is safe for concurrent use.
Example:
var driver feature.Driver = memoryfeature.New()
flags := feature.New(driver)
value, err := flags.Value(ctx, feature.NewFlag("checkout", false), feature.Subject{})
if err != nil {
_ = value // The declared fallback is still available.
}
type EvaluationFailedEvent ¶
type EvaluationFailedEvent struct {
// Key is the flag key.
// It is application-owned identity, not the subject's key.
//
// Example: name := payload.Key
Key string
// Err is the failure, such as [ErrNotFound] or [ErrNotReady], wrapped
// with the key.
// The original cause is retained and its text is not sanitized.
//
// Example: unavailable := errors.Is(payload.Err, feature.ErrNotReady)
Err error
}
EvaluationFailedEvent is the payload of EvaluationFailed. It has no structured subject, attribute, fallback, or evaluated-value fields. Err remains an explicit raw diagnostic, not a redacted telemetry field; provider error text can contain sensitive details. Inspect error identities instead of forwarding the entire payload to untrusted logs or transports.
Example: missing := errors.Is(payload.Err, feature.ErrNotFound)
type Flag ¶
type Flag[T Primitive] struct { // contains filtered or unexported fields }
Flag binds a provider name, Go value type, and fallback in one immutable definition. Share it between evaluation and typed memory setup. It does not register a flag or enforce an external provider's schema. The zero value is invalid; construct definitions with NewFlag.
Example:
checkout := feature.NewFlag("new-checkout", false)
on, err := flags.Value(ctx, checkout, subject)
if err != nil {
log.Print(err) // on is checkout.Fallback().
}
func NewFlag ¶
NewFlag publishes an immutable definition without I/O or registration. An empty name is invalid; Err reports it at construction or use. All Primitive types, including named types, retain their exact Go identity.
Example: checkout := feature.NewFlag("new-checkout", false)
func (Flag[T]) Err ¶
Err validates the definition without contacting a driver. Empty names, including the zero Flag, and inherited namespace errors wrap ErrInvalidFlag; other names are literal. A nonempty joined name cannot repair an invalid namespace or an empty leaf name.
Example:
if err := feature.NewFlag("new-checkout", false).Err(); err != nil {
log.Print(err)
}
type Flags ¶
type Flags struct {
// contains filtered or unexported fields
}
Flags evaluates feature flags through a Driver. Construct with New and share Flag definitions between evaluation and setup. The facade adds validation, fallback conversion, and optional telemetry/events.
Example:
flags := feature.New(memoryfeature.New())
value, err := flags.Value(ctx, feature.NewFlag("checkout", false), subject)
if err != nil {
_ = value // The fallback remains false.
}
func New ¶
func New(driver Driver, configure ...BuilderFunc) *Flags
New creates Flags that delegate evaluation to driver. Construction policies configure its telemetry: every evaluation emits one internal span and the arkoren.feature.evaluations and arkoren.feature.evaluation.duration metrics when the telemetry is enabled, and costs nothing otherwise. Flag keys are recorded because developers choose them and their number is bounded; subject keys, attributes, values, and error text never are.
flags := feature.New(driver, func(flags *feature.Builder) {
flags.Telemetry(app.Telemetry())
},
func(flags *feature.Builder) {
flags.Name("launchdarkly")
},
)
Construction callbacks run directly once, synchronously in order on one fresh draft. Nil entries panic; expanded nil or empty slices omit edits. Publication detaches owned settings, not live dependencies or captures.
func (*Flags) Driver ¶
Driver returns the underlying Driver. Direct Evaluate calls bypass facade validation, fallback conversion, and facade telemetry/events; supply a complete raw Request yourself.
Example: driver := flags.Driver()
func (*Flags) DriverAs ¶
DriverAs returns the first driver of type T in the chain of the underlying driver. A driver that wraps another exposes it with an Unwrap() Driver method, so wrappers do not hide the concrete driver.
Example:
driver, ok := flags.DriverAs[*memoryfeature.Driver]()
if ok {
_ = driver // For example, application-owned memory setup.
}
func (*Flags) Value ¶
func (flags *Flags) Value[T Primitive](ctx context.Context, definition Flag[T], subject Subject) (T, error)
Value evaluates definition for subject and returns its value as T. It returns the definition's fallback when the flag is off, has no value for subject, or evaluation fails. A non-nil error always comes with definition.Fallback(). Invalid definitions fail with ErrInvalidFlag before driver access.
An unknown key fails with ErrNotFound, a value that does not fit T with ErrTypeMismatch (an int8 request for 300, an int request for 3.5), an attribute of an unsupported type with ErrInvalidSubject, and a driver that is still loading with ErrNotReady. A flag that is off is not an error.
on, err := flags.Value(ctx, feature.NewFlag("new-checkout", false), subject)
if err != nil {
log.Print(err) // on retains the declared fallback.
}
func (*Flags) ValueDetail ¶
func (flags *Flags) ValueDetail[T Primitive](ctx context.Context, definition Flag[T], subject Subject) (Detail[T], error)
ValueDetail is Flags.Value plus the variant and reason. On failure it returns the definition's fallback with ReasonError together with the error; when the flag has no value for subject it returns the fallback with the driver's variant and reason and a nil error.
detail, err := flags.ValueDetail(ctx, feature.NewFlag("plan", "free"), subject)
if err != nil {
log.Print(err) // detail.Value is "free".
}
type Kind ¶
type Kind string
Kind is the value type a caller requests from a Driver. The facade derives it from the Go type parameter of Flags.Value. Only KindBool, KindString, KindInt, and KindFloat are valid for evaluation. Empty and unknown values fail with ErrTypeMismatch at driver boundaries; their JSON and String representations still preserve the supplied bytes.
Example: _ = feature.Request{Key: "checkout", Kind: feature.KindBool}
const ( // KindBool requests a bool; [Result.Value] holds a bool. // // Example: _ = feature.Request{Key: "checkout", Kind: feature.KindBool} KindBool Kind = "bool" // KindString requests a string; [Result.Value] holds a string. // // Example: _ = feature.Request{Key: "plan", Kind: feature.KindString} KindString Kind = "string" // KindInt requests a whole number; [Result.Value] holds an int64, or a // float64 holding a whole number. // // Example: _ = feature.Request{Key: "seats", Kind: feature.KindInt} KindInt Kind = "int" // KindFloat requests a floating-point number; [Result.Value] holds a // float64. // // Example: _ = feature.Request{Key: "ratio", Kind: feature.KindFloat} KindFloat Kind = "float" )
type Namespace ¶
type Namespace[T Primitive] struct { // contains filtered or unexported fields }
Namespace groups literal provider names under an immutable prefix and an exact typed fallback. It neither registers flags nor isolates provider state. Use NewFlag for standalone names. A zero Namespace is invalid; errors remain attached to all descendants, even when their assembled names are nonempty. Formatting and structured logging redact the prefix and fallback.
Example:
checkout := feature.NewNamespace("checkout", false)
on, err := flags.Value(ctx, checkout.Flag("enabled"), subject)
if err != nil {
log.Print(err) // on retains checkout.Fallback().
}
func NewNamespace ¶
func NewNamespace[T Primitive](prefix string, fallback T, configure ...NamespaceBuilderFunc) Namespace[T]
NewNamespace publishes a literal prefix and typed fallback without I/O. The default separator is ":". Empty prefixes wrap ErrInvalidFlag; whitespace and separators in names remain unchanged. Callbacks follow NamespaceBuilderFunc's ordering and misuse rules, including invalid prefixes.
Example:
checkout := feature.NewNamespace("checkout", false)
flag := checkout.Flag("enabled") // checkout:enabled, fallback false
func (Namespace[T]) Derive ¶
func (namespace Namespace[T]) Derive(configure ...NamespaceBuilderFunc) Namespace[T]
Derive publishes an independent variant using the existing separator, fallback, prefix, and first error. Defaults are not reapplied. All callbacks edit one fresh draft under NamespaceBuilderFunc's rules, even when invalid.
Example:
slash := feature.NewNamespace("checkout", false).Derive(func(names *feature.NamespaceBuilder) {
names.Separator("/")
})
flag := slash.Flag("enabled") // checkout/enabled
func (Namespace[T]) Err ¶
Err reports the first definition error without I/O. An empty prefix, including a zero Namespace, wraps ErrInvalidFlag. Errors survive derivation and child creation; diagnostics never include names or fallback values.
Example:
if err := feature.NewNamespace("checkout", false).Err(); err != nil {
log.Print(err)
}
func (Namespace[T]) Fallback ¶
func (namespace Namespace[T]) Fallback() T
Fallback returns the exact typed value inherited by every child definition. It is not installed in the provider and cannot be changed by draft callbacks.
Example: fallback := feature.NewNamespace("uploads", int64(25)).Fallback()
func (Namespace[T]) Flag ¶
Flag publishes a typed leaf with this namespace's fallback and a literal joined name. Empty leaf names and inherited namespace errors remain invalid, so evaluation and memory setup reject them before accessing a driver.
Example:
checkout := feature.NewNamespace("checkout", false).Flag("enabled")
on, err := flags.Value(ctx, checkout, subject)
if err != nil {
log.Print(err) // on is false.
}
func (Namespace[T]) Format ¶
Format redacts the prefix and fallback for every fmt verb, including Go-syntax output. Use Name and Fallback explicitly to obtain those values.
Example:
description := fmt.Sprintf("%#v", feature.NewNamespace("checkout", false))
_ = description // "feature.Namespace([redacted])"
func (Namespace[T]) LogValue ¶
LogValue redacts the prefix and fallback in structured logs independently of fmt. Explicit Name and Fallback access is not sanitized.
Example: logger.Info("flags configured", "namespace", feature.NewNamespace("checkout", false))
func (Namespace[T]) Name ¶
Name returns the exact composed provider prefix. A zero Namespace returns an empty prefix; a nonempty prefix alone does not guarantee validity.
Example: prefix := feature.NewNamespace("checkout", false).Name()
func (Namespace[T]) Separator ¶
Separator returns the literal join string for future children. An explicit empty separator remains empty through Derive and Subnamespace.
Example: separator := feature.NewNamespace("checkout", false).Separator()
func (Namespace[T]) Subnamespace ¶
func (namespace Namespace[T]) Subnamespace(name string, configure ...NamespaceBuilderFunc) Namespace[T]
Subnamespace appends name using the parent's separator, then applies child callbacks to future joins. Empty child names are invalid even if the assembled prefix is nonempty. The fallback and first error are inherited unchanged.
Example:
checkout := feature.NewNamespace("checkout", false)
admin := checkout.Subnamespace("admin", func(names *feature.NamespaceBuilder) {
names.Separator("/")
})
flag := admin.Flag("enabled") // checkout:admin/enabled
type NamespaceBuilder ¶
type NamespaceBuilder struct {
// contains filtered or unexported fields
}
NamespaceBuilder configures future joins, not the namespace's type or fallback. Constructors publish a detached copy, so retaining a draft cannot change an existing Namespace. Drafts are only for synchronous construction.
Example:
checkout := feature.NewNamespace("checkout", false, func(names *feature.NamespaceBuilder) {
names.Separator("/")
})
flag := checkout.Flag("enabled") // checkout/enabled
func (*NamespaceBuilder) Separator ¶
func (names *NamespaceBuilder) Separator(separator string) *NamespaceBuilder
Separator selects the literal string used to join future child names. Empty concatenates names directly; no escaping, trimming, or validation of separator content occurs. It returns the same construction draft for chaining.
Example:
checkout := feature.NewNamespace("checkout", false, func(names *feature.NamespaceBuilder) {
names.Separator("")
})
flag := checkout.Flag("Enabled") // checkoutEnabled
type NamespaceBuilderFunc ¶
type NamespaceBuilderFunc = func(*NamespaceBuilder)
NamespaceBuilderFunc edits a shared construction draft synchronously. Callbacks run once in order, even for invalid definitions. Nil entries panic; expanding a nil or empty slice supplies no callbacks.
Example:
var slash feature.NamespaceBuilderFunc = func(names *feature.NamespaceBuilder) {
names.Separator("/")
}
checkout := feature.NewNamespace("checkout", false, slash)
type Primitive ¶
type Primitive interface {
~bool | ~string | ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~float32 | ~float64
}
Primitive is the set of Go types a flag value can be read as. Named types with one of these underlying types, such as type Plan string, qualify. Integer types read KindInt flags and float types read KindFloat flags; the facade converts between them where the value allows. This constraint is for generic definitions and evaluation, not a runtime value container or an external provider schema.
Example:
func evaluate[T feature.Primitive](ctx context.Context, flags *feature.Flags,
definition feature.Flag[T], subject feature.Subject) (T, error) {
return flags.Value(ctx, definition, subject)
}
type Reason ¶
type Reason string
Reason explains how a driver chose a result. The values match OpenFeature's resolution reasons so that drivers can pass them through. Empty input means unknown; the facade normalizes it to ReasonUnknown.
Example: _ = feature.Result{Value: true, Reason: feature.ReasonStatic}
const ( // ReasonUnknown means the driver gave no reason. Empty input has the same // meaning and is normalized by the facade. // // Example: _ = feature.Result{Reason: feature.ReasonUnknown} ReasonUnknown Reason = "unknown" // ReasonStatic means the flag has one value for everyone. // // Example: _ = feature.Result{Value: true, Reason: feature.ReasonStatic} ReasonStatic Reason = "static" // ReasonDefault means no rule matched, so the result is the flag's // default or, with a nil value, the caller's. // // Example: _ = feature.Result{Reason: feature.ReasonDefault} ReasonDefault Reason = "default" // ReasonTargetingMatch means a targeting rule or subject override // matched. // // Example: _ = feature.Result{Value: true, Reason: feature.ReasonTargetingMatch} ReasonTargetingMatch Reason = "targeting_match" // ReasonSplit means a percentage rollout chose the value. // // Example: _ = feature.Result{Value: true, Reason: feature.ReasonSplit} ReasonSplit Reason = "split" // ReasonCached means the value was served from a cache and may be stale. // // Example: _ = feature.Result{Value: true, Reason: feature.ReasonCached} ReasonCached Reason = "cached" // ReasonDisabled means the flag exists but is off. // // Example: _ = feature.Result{Value: false, Reason: feature.ReasonDisabled} ReasonDisabled Reason = "disabled" // ReasonError means evaluation failed. The facade reports it in // [Detail.Reason] together with the error. // // Example: _ = feature.Result{Reason: feature.ReasonError} ReasonError Reason = "error" )
type Request ¶
type Request struct {
// Key is the flag key. The facade treats it as opaque.
//
// Example: request := feature.Request{Key: "checkout", Kind: feature.KindBool}
Key string
// Kind is the value type the caller wants.
// Only KindBool, KindString, KindInt, and KindFloat are accepted. After
// cancellation, drivers reject empty or unknown kinds before other work.
//
// Example: request := feature.Request{Key: "quota", Kind: feature.KindInt}
Kind Kind
// Subject is the entity to evaluate for. The facade has already
// validated its attributes; drivers must not modify them.
//
// Example: request := feature.Request{Key: "checkout", Kind: feature.KindBool, Subject: subject}
Subject Subject
}
Request is one evaluation handed to a Driver. The typed facade derives Kind and validates the subject before handing it to the adapter. Direct callers supply those values themselves.
Example:
result, err := driver.Evaluate(ctx, feature.Request{Key: "checkout", Kind: feature.KindBool})
if err != nil {
return err
}
_ = result.Value
type Result ¶
type Result struct {
// Value holds a bool, string, int64 or float64 for the requested Kind,
// or nil when the flag has no value for this subject (Disabled,
// Default). An Int request may be answered with a float64 holding a
// whole number; the facade converts it, so drivers never truncate.
//
// Example: result := feature.Result{Value: int64(25), Reason: feature.ReasonStatic}
Value any
// Variant is the optional vendor variant name.
//
// Example: result := feature.Result{Value: true, Variant: "treatment"}
Variant string
// Reason explains how the driver chose the result.
//
// Example: result := feature.Result{Value: true, Reason: feature.ReasonTargetingMatch}
Reason Reason
}
Result is a Driver's answer. A nil Value selects the caller's fallback; the facade otherwise validates and converts the representation requested by Kind.
Example: result := feature.Result{Value: true, Reason: feature.ReasonStatic}
type Subject ¶
type Subject struct {
// Key identifies the subject stably, such as a user ID. Percentage
// rollouts hash it. Empty means anonymous.
//
// Example: subject := feature.Subject{Key: "user-42"}
Key string
// Attributes are matched by targeting rules. Values must be string, bool,
// int64, float64, []string, or time.Time; the facade rejects anything
// else with [ErrInvalidSubject] before calling the driver. Drivers must
// not modify the map.
//
// Example: subject := feature.Subject{Attributes: map[string]any{"plan": "pro"}}
Attributes map[string]any
}
Subject is the entity a flag is evaluated for, usually a user.
The zero Subject is anonymous and valid.
subject := feature.Subject{
Key: user.ID,
Attributes: map[string]any{"plan": "pro", "seats": int64(5)},
}
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package featuretest provides a conformance suite for feature.Driver implementations, modeled on testing/slogtest and testing/fstest.
|
Package featuretest provides a conformance suite for feature.Driver implementations, modeled on testing/slogtest and testing/fstest. |
|
Package memoryfeature provides an in-memory feature.Driver for tests, local development, and single-process applications.
|
Package memoryfeature provides an in-memory feature.Driver for tests, local development, and single-process applications. |