Defines and validates the internal configuration needed by Bier processes.
The options given to Bier.start_link/1 are validated via new!/2 using the
internal schema, if the given options are valid, the configuration will be
wrapped in a Elixir.Bier.Config struct, which subsequently will be stored in the
Bier.Registry. Internal modules will also consume this configuration to work
properly.
Summary
Functions
Decode a base64 jwt-secret (jwt-secret-is-base64), accepting URL-safe
characters the way PostgREST does. Shared with the CLI so --dump-config
rejects an undecodable secret identically (case 1718).
Translate a server-host value into a :gen_tcp bind address. PostgREST's
key is a Warp HostPreference: "*"/"*4"/"!4" bind any IPv4 interface
and "*6"/"!6" any IPv6 one (the IPv4-vs-IPv6 preference the * forms
express has no counterpart in a single bind call). Anything else is an IP
literal or a host name resolved at boot; an unresolvable name raises, which
surfaces as a boot failure.
Non-raising variant of new!/2: validates the given options against the
schema plus the semantic validators and returns {:ok, config} or
{:error, message}. The standalone/CLI boot path uses this to turn a bad
config into a clean fatal message instead of a raised exception.
Validates the given options based on the internal schema definition
Parse a socket-mode value the way PostgREST does (Haskell's readOct): the
longest leading run of octal digits is the value — so "599" reads as 5
(range error) while "800" has no octal prefix at all — and the result must
lie between 0o600 and 0o777. Returns the integer file mode for
File.chmod/2.
Render a list of schema names as a search_path GUC value: each name quoted
as an identifier, comma-separated — PostgREST's escapeIdentList
(Query/PreQuery.hs). search_path takes an identifier list, so the names
cannot be bound individually; the value is pre-quoted here and then bound as
a single parameter (or sent as a connection parameter).
admin-server-port must differ from the main server port. Mirrors PostgREST
(test_cli.py:test_server_port_and_admin_port_same_value; conformance case
1717). NimbleOptions validates fields independently, so this cross-field
check lives here, shared by new!/2 and the CLI. Either port may be nil
when not configured.
db-channel must be a non-empty channel name of at most 63 bytes (the
Postgres identifier limit) and must not contain a null byte.
Postgrex.Notifications.listen/3 enforces both the length bound and the
null-byte restriction at runtime by raising — validating at boot turns a
would-be listener crash-loop into a fast ArgumentError. Library-enforced
(PostgREST does not validate this key), like the admin-port collision rule.
db-schemas may not expose Postgres' own catalog schemas. PostgREST v16.0
rejects pg_catalog and information_schema while parsing the key — after
the comma split, so a restricted name anywhere in the list aborts startup
(Config.hs parseDbSchemas). v14.12 accepted them and failed per request
instead. Mirrors conformance cases 1733/1734.
Each events-channels entry must be a usable Postgres notification channel
name: non-empty, at most 63 bytes (the identifier limit), no null bytes, and
no double quotes (Postgrex.Notifications.listen/3 wraps the name in double
quotes without escaping). Validated at boot so a bad entry is a fast
ArgumentError instead of a listener crash-loop. Bier-specific key.
events-path is the reserved top-level path segment for the SSE endpoint,
so it must be non-empty and must not contain /.
jwt-aud may be any plain string, but a value containing ':' must parse as a
valid absolute URI. Mirrors PostgREST conformance case 1709.
A symmetric (text) JWT secret must be at least 32 bytes long — PostgREST
counts the secret's octets (BS.length in Config.hs), not characters, so
Bier does too. nil (no secret configured) is allowed. Mirrors PostgREST
conformance case 1708.
openapi-server-proxy-uri must be an absolute http(s) URI with a host —
PostgREST's isMalformedProxyUri check. nil (not configured) is allowed.
Mirrors conformance case 1716.
Boolean-style wrapper over parse_socket_mode/2 for the validation chain.
Types
@type router_opts() :: [port: pos_integer(), scheme: :http | :https]
Options given to Bandit
@type t() :: %Bier.Config{ admin_server_port: pos_integer() | nil, app_settings: %{optional(String.t()) => String.t()}, cancel_on_disconnect: boolean(), client_error_verbosity: String.t(), database: String.t(), db_anon_role: String.t() | nil, db_channel: String.t(), db_channel_enabled: boolean(), db_extra_search_path: [String.t()], db_max_rows: pos_integer() | nil, db_max_rows_by_schema: %{optional(String.t()) => pos_integer()}, db_plan_enabled: boolean(), db_pool_max_idletime: pos_integer() | nil, db_pre_request: String.t() | nil, db_profile_default: String.t() | nil, db_profile_schemas: [String.t()] | nil, db_root_spec: String.t() | nil, db_safe_update_tables: [String.t()], db_schema_aliases: %{optional(String.t()) => String.t()}, db_schemas: [String.t(), ...], db_tx_end: :commit | :rollback, events_channels: [String.t()], events_heartbeat_interval: pos_integer(), events_path: String.t(), hostname: String.t(), jwt_aud: String.t() | nil, jwt_cache_max_entries: integer(), jwt_role_claim_path: Bier.JWT.RoleClaim.path(), jwt_secret: String.t() | nil, jwt_secret_is_base64: boolean(), log_level: :crit | :error | :warn | :info | :debug, log_query: boolean(), name: module(), openapi_mode: String.t(), openapi_security_active: boolean(), openapi_server_proxy_uri: String.t() | nil, openapi_version: String.t(), password: String.t() | nil, pool_size: pos_integer(), port: pos_integer(), router: router_opts(), server_cors_allowed_origins: String.t() | nil, server_host: String.t(), server_timing_enabled: boolean(), server_trace_header: String.t() | nil, server_unix_socket: String.t() | nil, server_unix_socket_mode: String.t(), ssl: boolean(), url_use_legacy_target_names: boolean(), username: String.t() | nil }
Functions
Decode a base64 jwt-secret (jwt-secret-is-base64), accepting URL-safe
characters the way PostgREST does. Shared with the CLI so --dump-config
rejects an undecodable secret identically (case 1718).
@spec host_address(String.t()) :: :inet.socket_address()
Translate a server-host value into a :gen_tcp bind address. PostgREST's
key is a Warp HostPreference: "*"/"*4"/"!4" bind any IPv4 interface
and "*6"/"!6" any IPv6 one (the IPv4-vs-IPv6 preference the * forms
express has no counterpart in a single bind call). Anything else is an IP
literal or a host name resolved at boot; an unresolvable name raises, which
surfaces as a boot failure.
Non-raising variant of new!/2: validates the given options against the
schema plus the semantic validators and returns {:ok, config} or
{:error, message}. The standalone/CLI boot path uses this to turn a bad
config into a clean fatal message instead of a raised exception.
Validates the given options based on the internal schema definition
In case the given options are valid, it returns a Bier.Config struct,
otherwise it will raise an exception.
@spec parse_socket_mode(String.t(), String.t()) :: {:ok, non_neg_integer()} | {:error, String.t()}
Parse a socket-mode value the way PostgREST does (Haskell's readOct): the
longest leading run of octal digits is the value — so "599" reads as 5
(range error) while "800" has no octal prefix at all — and the result must
lie between 0o600 and 0o777. Returns the integer file mode for
File.chmod/2.
key names the config key the value came from. PostgREST v16.0 applies the
same parseSocketFileMode to server-unix-socket-mode and the new
admin-server-unix-socket-mode, building both failure messages from the key
(Config.hs parseSocketFileMode), so the caller decides which key is
reported. Mirrors conformance cases 1714/1715 (server) and 1738 (admin).
Render a list of schema names as a search_path GUC value: each name quoted
as an identifier, comma-separated — PostgREST's escapeIdentList
(Query/PreQuery.hs). search_path takes an identifier list, so the names
cannot be bound individually; the value is pre-quoted here and then bound as
a single parameter (or sent as a connection parameter).
Duplicates are dropped, keeping first position: the request's schema leads and
an extra-search-path entry naming the same schema must not shift it. Returns
nil for an empty list, which callers read as "leave search_path alone".
@spec validate_admin_server_port(pos_integer() | nil, pos_integer() | nil) :: :ok | {:error, String.t()}
admin-server-port must differ from the main server port. Mirrors PostgREST
(test_cli.py:test_server_port_and_admin_port_same_value; conformance case
1717). NimbleOptions validates fields independently, so this cross-field
check lives here, shared by new!/2 and the CLI. Either port may be nil
when not configured.
db-channel must be a non-empty channel name of at most 63 bytes (the
Postgres identifier limit) and must not contain a null byte.
Postgrex.Notifications.listen/3 enforces both the length bound and the
null-byte restriction at runtime by raising — validating at boot turns a
would-be listener crash-loop into a fast ArgumentError. Library-enforced
(PostgREST does not validate this key), like the admin-port collision rule.
db-schemas may not expose Postgres' own catalog schemas. PostgREST v16.0
rejects pg_catalog and information_schema while parsing the key — after
the comma split, so a restricted name anywhere in the list aborts startup
(Config.hs parseDbSchemas). v14.12 accepted them and failed per request
instead. Mirrors conformance cases 1733/1734.
Each events-channels entry must be a usable Postgres notification channel
name: non-empty, at most 63 bytes (the identifier limit), no null bytes, and
no double quotes (Postgrex.Notifications.listen/3 wraps the name in double
quotes without escaping). Validated at boot so a bad entry is a fast
ArgumentError instead of a listener crash-loop. Bier-specific key.
events-path is the reserved top-level path segment for the SSE endpoint,
so it must be non-empty and must not contain /.
jwt-aud may be any plain string, but a value containing ':' must parse as a
valid absolute URI. Mirrors PostgREST conformance case 1709.
A symmetric (text) JWT secret must be at least 32 bytes long — PostgREST
counts the secret's octets (BS.length in Config.hs), not characters, so
Bier does too. nil (no secret configured) is allowed. Mirrors PostgREST
conformance case 1708.
openapi-server-proxy-uri must be an absolute http(s) URI with a host —
PostgREST's isMalformedProxyUri check. nil (not configured) is allowed.
Mirrors conformance case 1716.
Boolean-style wrapper over parse_socket_mode/2 for the validation chain.