Bandera (bandera v0.4.0)

Copy Markdown View Source

Runtime-configured feature flags, API-compatible with fun_with_flags.

The active store is resolved at runtime (Bandera.Store.active/0), so nothing about persistence or caching is fixed at compile time.

Summary

Functions

Returns {:ok, names} with every known flag name, or {:error, reason}.

Returns {:ok, flags} with every stored Bandera.Flag, or {:error, reason}.

Removes gates from flag_name, returning :ok.

Disables flag_name, optionally scoped by an option, and returns {:ok, enabled?}.

Enables flag_name, optionally scoped by an option, and returns {:ok, enabled?}.

Returns whether flag_name is enabled.

Looks up a single flag, returning {:ok, %Bandera.Flag{}} or {:error, reason}.

Stores a reusable named constraint set (a segment) under the reserved key :"bandera_segment:<name>".

Stores a :variant gate for flag_name with the given weights map.

Re-read application env into the runtime config snapshot.

List flags whose last evaluation is older than older_than days (or never evaluated). Requires Bandera.Usage to be running and attached.

Returns the variant chosen for the flag named flag_name (bucketed by the actor passed via for:), or options[:default] (nil if not given) when the flag is missing, has no variant gate, or for: is absent or nil.

Functions

all_flag_names()

@spec all_flag_names() :: {:ok, [atom()]} | {:error, term()}

Returns {:ok, names} with every known flag name, or {:error, reason}.

Examples

iex> Bandera.enable(:checkout)
iex> Bandera.all_flag_names()
{:ok, [:checkout]}

all_flags()

@spec all_flags() :: {:ok, [Bandera.Flag.t()]} | {:error, term()}

Returns {:ok, flags} with every stored Bandera.Flag, or {:error, reason}.

Examples

iex> Bandera.enable(:checkout)
iex> {:ok, flags} = Bandera.all_flags()
iex> Enum.map(flags, & &1.name)
[:checkout]

clear(flag_name, options \\ [])

@spec clear(
  atom(),
  keyword()
) :: :ok | {:error, term()}

Removes gates from flag_name, returning :ok.

With no options the whole flag (all its gates) is deleted. A scope removes just that gate, letting evaluation fall through to whatever remains:

  • boolean: true — clear the boolean gate
  • for_actor: actor — clear one actor gate
  • for_group: group — clear one group gate
  • for_percentage: true — clear the percentage gate
  • variant: true — clear the variant gate
  • rule: true — clear the rule gate
  • for_segment: name — clear one segment gate
  • requires: parent — clear one prerequisite gate
  • schedule: true — clear the schedule gate

Accepts by: identity to record who made the change (see Bandera.Audit).

Returns {:error, reason} if the store delete fails.

Examples

iex> Bandera.enable(:checkout)
iex> Bandera.clear(:checkout)
:ok
iex> Bandera.enabled?(:checkout)
false

disable(flag_name, options \\ [])

@spec disable(
  atom(),
  keyword()
) :: {:ok, boolean()} | {:error, term()}

Disables flag_name, optionally scoped by an option, and returns {:ok, enabled?}.

Accepts the negatable scopes for_actor:, for_group:, and for_percentage_of: (for a percentage scope, disabling for ratio is equivalent to enabling for 1.0 - ratio). To remove a grant-only gate (variant, rule, segment, prerequisite, schedule), use clear/2; passing one of those scopes here returns {:error, :unsupported_scope}. Returns {:error, reason} on a store write failure.

Accepts by: identity to record who made the change (see Bandera.Audit).

Examples

iex> Bandera.disable(:checkout)
{:ok, false}

iex> Bandera.enable(:beta)
iex> Bandera.disable(:beta)
{:ok, false}

enable(flag_name, options \\ [])

@spec enable(
  atom(),
  keyword()
) :: {:ok, boolean()} | {:error, term()}

Enables flag_name, optionally scoped by an option, and returns {:ok, enabled?}.

With no options the boolean gate is turned on. Supported scopes:

  • for_actor: actor — enable for one actor
  • for_group: group — enable for a named group
  • for_percentage_of: {:time, ratio} — enable for a ratio of calls
  • for_percentage_of: {:actors, ratio} — enable for a ratio of actors
  • when: constraints — enable when the evaluation context matches a rule
  • for_segment: name — enable for a reusable named segment
  • requires: parent (or {parent, required_state}) — add a prerequisite
  • schedule: {from, until} — enable inside an ISO-8601 time window

ratio is a float in 0.0 < r < 1.0. The write goes to the persistent store and busts/refreshes the cache; returns {:error, reason} if the store write fails.

The returned enabled? is the immediate state for unconditional/percentage gates. For the conditional scopes (when:, for_segment:, requires:, schedule:) it is true to signal a successful write — those gates are evaluated per call by enabled?/2 against the relevant context, actor, time, or parent flag.

Pass by: identity to record who made the change; it is carried in the write telemetry metadata (see Bandera.Audit) and does not affect the gate written.

Examples

iex> Bandera.enable(:checkout)
{:ok, true}

iex> Bandera.enable(:beta, for_actor: "user-1")
{:ok, true}

iex> Bandera.enable(:gradual, for_percentage_of: {:actors, 0.25})
{:ok, true}

enabled?(flag_name, options \\ [])

@spec enabled?(
  atom(),
  keyword()
) :: boolean()

Returns whether flag_name is enabled.

Pass for: actor to evaluate actor, group, and percentage-of-actors gates against a specific subject (the actor is identified via the Bandera.Actor/Bandera.Group protocols). The flag is read through the active store (cache included). A missing flag, or a store lookup error, resolves to false (the error is logged).

Pass default: true to fail open (return true) when the store is unreachable; the default is false.

Examples

iex> Bandera.enabled?(:unknown_flag)
false

iex> Bandera.enable(:checkout)
iex> Bandera.enabled?(:checkout)
true

iex> Bandera.enable(:beta, for_actor: "user-1")
iex> Bandera.enabled?(:beta, for: "user-1")
true
iex> Bandera.enabled?(:beta, for: "user-2")
false

get_flag(flag_name)

@spec get_flag(atom()) :: {:ok, Bandera.Flag.t()} | {:error, term()}

Looks up a single flag, returning {:ok, %Bandera.Flag{}} or {:error, reason}.

An unknown flag still returns {:ok, flag} with an empty gate list (a disabled flag), not an error.

Examples

iex> Bandera.enable(:checkout)
iex> {:ok, flag} = Bandera.get_flag(:checkout)
iex> flag.gates
[%Bandera.Gate{type: :boolean, for: nil, enabled: true}]

iex> {:ok, flag} = Bandera.get_flag(:unknown_flag)
iex> flag.gates
[]

put_segment(name, constraints)

@spec put_segment(atom(), [tuple() | Bandera.Constraint.t()]) ::
  {:ok, Bandera.Flag.t()} | {:error, term()}

Stores a reusable named constraint set (a segment) under the reserved key :"bandera_segment:<name>".

Segments are referenced from flags via enable(flag, for_segment: name) and are expanded at evaluation time so that Flag stays pure. name must be a developer-defined atom — never untrusted user input.

Examples

iex> {:ok, _} = Bandera.put_segment(:premium, [{"plan", :eq, "premium"}])
iex> {:ok, _flag} = Bandera.get_flag(:"bandera_segment:premium")

put_variants(flag_name, weights, options \\ [])

@spec put_variants(atom(), %{optional(String.t()) => number()}, keyword()) ::
  {:ok, Bandera.Flag.t()} | {:error, term()}

Stores a :variant gate for flag_name with the given weights map.

weights is a %{variant_name => weight} map; actors are bucketed proportionally by weight using a stable SHA-256 hash per actor+flag. Returns {:ok, flag} on success, {:error, reason} on a store write failure.

The optional third argument is accepted for API uniformity but is ignored. put_variants does not support by: and is not audited by Bandera.Audit.

Examples

iex> {:ok, flag} = Bandera.put_variants(:hero, %{"blue" => 1, "green" => 1})
iex> flag.name
:hero

reload_config()

@spec reload_config() :: :ok

Re-read application env into the runtime config snapshot.

stale_flags(opts \\ [])

@spec stale_flags(keyword()) :: [atom()]

List flags whose last evaluation is older than older_than days (or never evaluated). Requires Bandera.Usage to be running and attached.

variant(flag_name, options \\ [])

@spec variant(
  atom(),
  keyword()
) :: term()

Returns the variant chosen for the flag named flag_name (bucketed by the actor passed via for:), or options[:default] (nil if not given) when the flag is missing, has no variant gate, or for: is absent or nil.

Looks up the flag from the active store and delegates to Flag.variant/2. A missing flag or store lookup error returns options[:default] (the error is logged).

Examples

iex> Bandera.put_variants(:ab_test, %{"a" => 1, "b" => 1})
iex> Bandera.variant(:ab_test, for: %{id: 1}) in ["a", "b"]
true