EndPointBlank.Config (end_point_blank_elixir v0.9.0)

Copy Markdown View Source

Singleton configuration store for the EndPointBlank library.

Update via EndPointBlank.configure/1.

Reads do not go through a process

This config is read-mostly to an extreme degree: it is written by EndPointBlank.configure/1, usually once at boot, and read on every inbound request (the authorization plug, the version finder) and every outbound write (each writer, the masking layer, every URL builder). The two sides are therefore stored differently.

Writes go through an Agent, which is the writer of record. It serialises concurrent update/1 calls so each one's read-modify-write is atomic, and it owns the ETS table below, so the config still dies with the process — a restart starts from a blank %EndPointBlank.Config{} exactly as it did when the Agent was the only store.

Reads go straight to that table: a :protected, read_concurrency set that only the Agent writes. get/0 is a lock-free lookup performed in the calling process, so config reads no longer serialise behind one mailbox — and, the reason this matters more than the speed, a slow, wedged or restarting Agent can no longer take its readers with it. Agent.get/2 carries a 5000 ms default timeout and a timeout exits the caller: until sc-350, every authorization plug call and every writer was one busy Agent away from dying. sc-331 had to catch that exit in EndPointBlank.Writers.DelayedWriter for precisely this reason.

There is deliberately no fallback

If the store is unavailable, get/0 raises. It does not quietly return a default %EndPointBlank.Config{}: that would authorize requests against nil credentials and ship telemetry to the public default base URL because a process happened to be down, which is a far worse failure than a raise and exactly the silent fallback this library refuses.

Environment variables

Several settings also fall back to ENDPOINTBLANK_* environment variables when not explicitly configured, in order to support Rails-free (and Elixir-config-free) deployments. Precedence for each such setting is: explicit value set via EndPointBlank.configure/1 > System.get_env/1 > built-in default. The environment variable is read at getter time (not cached), so it can be changed at runtime.

Summary

Functions

Returns a specification to start this module under a supervisor.

The organization slug a client_id names, or nil for one without it (issued before sc-1463).

The current configuration, with ENDPOINTBLANK_* fallbacks applied.

Merges opts into the stored config.

Max number of concurrent writes DelayedWriter performs per flush tick. Falls back to the struct default (4) if unset or invalid.

Functions

access_token_url()

authorize_url()

child_spec(arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

client_id_slug(client_id)

@spec client_id_slug(term()) :: String.t() | nil

The organization slug a client_id names, or nil for one without it (issued before sc-1463).

The same rule as app_portal's Credentials.client_id_slug/1, and the rule all five SDKs will share: the part before the first . must have the exact shape of an organization slug, and something must follow the dot. "Contains a ." is not enough, because app_portal has always accepted a typed client_id, so a legacy my.client can exist and must keep calling the default intake.

endpoint_update_url()

errors_url()

get()

The current configuration, with ENDPOINTBLANK_* fallbacks applied.

A direct ETS read in the calling process — no message is sent to the Agent, so this can neither queue behind a config write nor exit on a call timeout.

Raises if the store is unavailable; see the module doc for why it does not fall back to a default config.

logs_url()

mask_hook()

masking_rules()

requests_url()

responses_url()

start_link(opts)

update(opts)

Merges opts into the stored config.

Raises ArgumentError if opts contains any key that is not a setting — including :__struct__, which is otherwise a "valid" key of the struct but would corrupt it if put. The check is all-or-nothing: if any key is unknown, none of opts is applied, including the keys that were fine.

A misspelled or obsolete key (client_secert:, base_uri:) used to be dropped silently, which left the host running with nil credentials or the public default base URL and no indication why. This library does not do silent fallbacks; a bad configure/1 call is a boot-time bug and should crash the boot.

Also raises ArgumentError if :cache_ttl is given with a value that is not a non-negative integer — an explicit nil, a negative number, a float or a string (sc-970). Omit :cache_ttl for the default of 300 seconds; 0 disables the authorization cache. This is all-or-nothing in the same way. Before sc-970, nil and strings were stored as given and silently treated as 300 when the cache was first used, a float was used as a fractional TTL, and a negative number silently disabled the cache.

Both checks run in the calling process, before Agent.update/2 is called — never inside the function passed to it. update/1 is called from arbitrary host code (usually EndPointBlank.configure/1 at boot), and an exception raised inside Agent.update/2's function crashes the Agent itself, not just the caller. That Agent is this config store's only writer and the owner of the read path's ETS table (see the moduledoc), so crashing it over one bad configure/1 call would take the whole store down for every reader in the host app, not just reject the one bad call.

Serialised through the Agent, and synchronous: once this returns, every process reading get/0 sees the new value.

worker_count()

Max number of concurrent writes DelayedWriter performs per flush tick. Falls back to the struct default (4) if unset or invalid.