Documentation
¶
Overview ¶
Package features is the feature core of the phpboyscout Go toolkit: what a binary offers (declared at init into a Registry), what a tool has chosen (a Set, resolved once from an immutable Snapshot and the tool's states), and how a request asks (an Evaluator, static by default and dynamic over a Backend when a feature opts in).
Declaration is process-wide because a blank import can only reach package state; everything after main works with values. A reader takes a Snapshot and is never surprised by a later registration, so nothing seals and nothing after main panics.
The static/dynamic rule: anything that shapes a command tree, help, completion or a generated artefact is static and reads Set.Enabled, fixed at construction. A Descriptor that reports IsDynamic may be overridden at evaluation time by a Backend through Dynamic, which answers the static state as the fallback for any error, a not-ready backend, or a flag the backend does not know or has disabled. No vendor SDK is here; a backend is an adapter module implementing Backend.
Extracted from go-tool-base (spec 0199), which builds its command registration, doctor inventory and code generator on it.
Index ¶
- Variables
- func ContributionsOf[T any](s Set, id ID, slot Slot) ([]T, error)
- func MustDeclare(r Registry, d Descriptor)
- type Backend
- type Change
- type Decision
- type Descriptor
- type DynamicEvaluator
- func (e *DynamicEvaluator) Close() error
- func (e *DynamicEvaluator) Evaluate(ctx context.Context, id ID, ec EvalContext) (Decision, error)
- func (e *DynamicEvaluator) Init(ctx context.Context) error
- func (e *DynamicEvaluator) Ready() bool
- func (e *DynamicEvaluator) Watch(ctx context.Context) <-chan Change
- type DynamicOption
- type EvalContext
- type Evaluator
- type ID
- type Kind
- type MemoryRegistry
- type Mutator
- type Ranked
- type Reason
- type Registry
- type Resolver
- type Set
- type Slot
- type Snapshot
- type State
- type StateResolver
- type StaticBackend
Constants ¶
This section is empty.
Variables ¶
var ( // ErrInvalidDescriptor reports a descriptor with no ID or no kind. ErrInvalidDescriptor = errors.NewSentinel("features.invalid_descriptor", "features: descriptor is incomplete") // ErrDuplicateFeature reports a second declaration for one ID. ErrDuplicateFeature = errors.NewSentinel("features.duplicate_feature", "features: feature is already declared") // ErrUnknownFeature reports an ID the snapshot does not hold. ErrUnknownFeature = errors.NewSentinel("features.unknown_feature", "features: unknown feature") // ErrContributionType reports a contribution that is not the type the // reader asked for. ErrContributionType = errors.NewSentinel("features.contribution_type", "features: contribution has an unexpected type") )
var ErrBackendNotReady = errors.NewSentinel("features.backend_not_ready", "features: flag backend is not ready")
ErrBackendNotReady reports a Backend that has not finished Init (or has gone stale); the Set answered instead.
Functions ¶
func ContributionsOf ¶
ContributionsOf returns the contributions under id and slot that are a T, and an error naming the feature and slot when any is not. The well-typed values are returned either way so a reader can carry on.
func MustDeclare ¶
func MustDeclare(r Registry, d Descriptor)
MustDeclare declares d on r and panics on a rejected descriptor. It is for init, where there is no caller to hand an error to and a silently dropped feature would surface much later as a missing entry somewhere else.
Types ¶
type Backend ¶
type Backend interface {
Resolve(ctx context.Context, id ID, fallback bool, ec EvalContext) (Decision, error)
Init(ctx context.Context) error
Ready() bool
Watch(ctx context.Context) <-chan Change
Close() error
}
Backend resolves a dynamic feature given the caller's fallback: the value it hands back for a flag it does not know or has disabled, which is OpenFeature's defaultValue and this package's Set state (go-tool-base spec 0199 D10). It carries the lifecycle a vendor SDK needs: Init before the first Resolve, Ready while answers are trustworthy, Watch for pushed updates (nil when it does not push), Close on shutdown.
type Change ¶
type Change struct {
ID ID
}
Change is a Backend's notice that a flag's value may have moved. An empty ID means everything may have changed.
type Decision ¶
type Decision struct {
Enabled bool
Reason Reason
// Variant is empty for a plain toggle; a multivariate backend fills it.
Variant string
}
Decision is an Evaluator's answer.
type Descriptor ¶
type Descriptor interface {
FeatureID() ID
FeatureKind() Kind
// DefaultOn reports whether the feature is enabled when the tool expresses
// no preference.
DefaultOn() bool
// IsDynamic reports whether a Backend may override the static state at
// evaluation time (go-tool-base spec 0199 D7). A false answer keeps the feature off
// every backend's path.
IsDynamic() bool
}
Descriptor is what the core needs to know about a feature. It is an interface so a consumer carries its own facts alongside (go-tool-base adds the identifiers its code generator emits).
type DynamicEvaluator ¶
type DynamicEvaluator struct {
// contains filtered or unexported fields
}
DynamicEvaluator is the production Evaluator: a static-only feature is answered from the Set without consulting the backend; a dynamic one is resolved by the backend with the Set state as its fallback, and on any error, a not-ready backend, or an unknown flag the Set answers with ReasonFallback and the error alongside (go-tool-base spec 0199 D10). It is exported so a consumer can embed it.
func Dynamic ¶
func Dynamic(set Set, backend Backend, opts ...DynamicOption) *DynamicEvaluator
Dynamic builds the production Evaluator over set and backend. Which features are dynamic is fixed here from the Set's descriptors, so an evaluation is a map lookup before it decides whether to ask the backend.
func (*DynamicEvaluator) Evaluate ¶
func (e *DynamicEvaluator) Evaluate(ctx context.Context, id ID, ec EvalContext) (Decision, error)
Evaluate implements Evaluator.
func (*DynamicEvaluator) Init ¶
func (e *DynamicEvaluator) Init(ctx context.Context) error
Init initialises the backend within the configured timeout. An error leaves the evaluator usable: it answers with fallbacks until the backend is Ready.
func (*DynamicEvaluator) Ready ¶
func (e *DynamicEvaluator) Ready() bool
Ready reports the backend's readiness.
type DynamicOption ¶
type DynamicOption func(*DynamicEvaluator)
DynamicOption configures Dynamic.
func WithInitTimeout ¶
func WithInitTimeout(d time.Duration) DynamicOption
WithInitTimeout bounds Init: a short-lived process gives its backend a short budget and evaluates with fallbacks if it is not met. Zero means no bound.
func WithOnFallback ¶
func WithOnFallback(fn func(id ID, err error)) DynamicOption
WithOnFallback is called each time an evaluation falls back to the Set because the backend errored, was not ready, or did not know the flag; a service logs it.
type EvalContext ¶
EvalContext is who is asking: a targeting key and attributes, the shape every flag system and the OpenFeature specification use. Empty for a static answer.
type Evaluator ¶
Evaluator answers whether a feature is on for this request. A Set is one; a Dynamic evaluator over a Backend is the other (go-tool-base spec 0199 D10).
type ID ¶
type ID string
ID is a feature's identity: its config and manifest name.
const Global ID = ""
Global is the contribution target that applies to every feature, used for middleware that wraps every command.
type Kind ¶
type Kind string
Kind classifies a feature so "every forge" is a query rather than a list.
type MemoryRegistry ¶
type MemoryRegistry struct {
// contains filtered or unexported fields
}
MemoryRegistry is the default Registry: append-only, mutex-guarded, never sealed. It is exported so a consumer can embed it.
func (*MemoryRegistry) Contribute ¶
func (r *MemoryRegistry) Contribute(id ID, slot Slot, v any)
Contribute records v under id and slot. A contribution for an ID nobody has declared is kept: declaration and contribution may come from different packages in either init order, and a Set only hands out the contributions of enabled (hence declared) features.
func (*MemoryRegistry) Declare ¶
func (r *MemoryRegistry) Declare(d Descriptor) error
Declare records d, refusing an incomplete descriptor or a second one for the same ID.
func (*MemoryRegistry) Snapshot ¶
func (r *MemoryRegistry) Snapshot() Snapshot
Snapshot returns an immutable copy of everything declared and contributed so far, in the total order Snapshot documents.
type Mutator ¶
Mutator edits a list of states; Enable and Disable are the two.
type Ranked ¶
Ranked is an optional Descriptor capability: a descriptor that reports a rank (ok true) sorts before every unranked one, lowest first. It is how a consumer keeps a declared order for its own features while everything else sorts by kind and ID.
type Reason ¶
type Reason string
Reason says how a Decision was reached.
const ( // ReasonStatic is a static-only feature, or a Set, answering from the // resolved state. ReasonStatic Reason = "static" // ReasonDefault is a backend answering with its default rule. ReasonDefault Reason = "default" // ReasonTargeting is a backend matching the EvalContext. ReasonTargeting Reason = "targeting" // ReasonDisabled is a backend that has the flag disabled: the fallback // (the Set state) applies, and that is a clean answer. ReasonDisabled Reason = "disabled" // ReasonFallback is a backend error or a backend not ready: the Set // answered, and the error travels alongside. ReasonFallback Reason = "fallback" // ReasonError is no answer possible (an ID nobody declared). ReasonError Reason = "error" )
type Registry ¶
type Registry interface {
Declare(d Descriptor) error
Contribute(id ID, slot Slot, v any)
Snapshot() Snapshot
}
Registry is where a package declares its features and contributions, and where a root takes its Snapshot. Default is the init-time instance; a test or a host builds its own with NewRegistry.
type Resolver ¶
Resolver turns a Snapshot and a tool's states into a Set. The default applies every descriptor's DefaultOn, then the states in order; a consumer with another precedence supplies its own.
type Set ¶
type Set interface {
Evaluator
Enabled(id ID) bool
Descriptors() []Descriptor
EnabledDescriptors() []Descriptor
// Contributions returns the values registered under id and slot when id
// is enabled, and nothing otherwise.
Contributions(id ID, slot Slot) []any
// Ignored lists the disable states that named an ID the snapshot lacks
// (go-tool-base spec 0199 OQ2): you can switch off what you did not link,
// and a diagnostic can report that you did.
Ignored() []State
}
Set is what a tool has: the resolved enabled state of every declared feature, the contributions of the enabled ones, and a static Evaluator.
type Slot ¶
type Slot string
Slot names a contribution channel. The core knows a slot is a name and a value; the consumer defines the slots and asserts the types.
type Snapshot ¶
type Snapshot interface {
Descriptors() []Descriptor
OfKind(k Kind) []Descriptor
Lookup(id ID) (Descriptor, bool)
Contributions(id ID, slot Slot) []any
// Contributed lists every ID that has at least one contribution, sorted,
// Global included when it has any. It is how a reader enumerates without
// assuming every contributor was also declared.
Contributed() []ID
}
Snapshot is an immutable view of a Registry at one moment. Descriptors are in a total order derived from data, never from init sequencing: Ranked descriptors first by rank, then the rest by kind and ID. Go orders init by dependency then filename, which is stable for one build but moves with the import graph, and reports and golden files depend on this order.
type StateResolver ¶
type StateResolver struct{}
StateResolver is the default Resolver.
func DefaultResolver ¶
func DefaultResolver() *StateResolver
DefaultResolver returns the default Resolver.
type StaticBackend ¶
type StaticBackend struct {
// contains filtered or unexported fields
}
StaticBackend is a Backend over a Set: every answer is the static one. It is what lets Dynamic be tested and wired with no vendor at all.
func (*StaticBackend) Resolve ¶
func (b *StaticBackend) Resolve(ctx context.Context, id ID, _ bool, ec EvalContext) (Decision, error)
Resolve answers from the Set.