Confispex (confispex v1.2.0)
A tool which allows defining specs for runtime configuration, cast values according to specified types and inspect them.
Workflow
- Define a schema - Create a module with
defvariables/1describing your configuration - Initialize - Call
init/1inconfig/runtime.exswith your schema and context - Get values - Use
get/1to retrieve typed and validated configuration values - Inspect - Run
mix confispex.reportto see all variables and their status
Key Concepts
- Schema - Defines variables with types, defaults, validation rules, and grouping
- Context - Runtime environment info (e.g.,
%{env: :prod, target: :host}) - Store - Source of raw configuration values (default:
System.get_env/0) - Groups - Logical organization of related variables for reporting
- Invocations - Tracked variable access for comprehensive error reporting
Example
# 1. Define schema
defmodule MyApp.ConfigSchema do
import Confispex.Schema
@behaviour Confispex.Schema
defvariables(%{
"DATABASE_URL" => %{
cast: Confispex.Type.URL,
required: [:database],
groups: [:database]
}
})
end
# 2. Initialize in config/runtime.exs
Confispex.init(%{
schema: MyApp.ConfigSchema,
context: %{env: config_env()}
})
# 3. Use values
config :my_app, MyApp.Repo,
url: Confispex.get("DATABASE_URL")
# 4. Inspect (command line)
# $ mix confispex.report --mode=detailedSee the Getting Started guide for more details.
Summary
Types
Runtime context information used for conditional defaults and requirements.
A map containing configuration values, typically environment variables.
Functions
Returns true if all required variables in specified group are present in store.
Returns true if any required variable in specified group is present in store.
Get a value from store by specified variable name (key) and cast it according to schema.
Initialize or reinitialize a state in server
Initialize a state in server if it hasn't already been initialized.
Print report with variables usage to STDOUT.
Update the store at runtime by applying a function to the current store.
Types
Runtime context information used for conditional defaults and requirements.
Commonly includes :env (:dev, :test, :prod) and :target (:host, :docker).
@type store() :: map()
A map containing configuration values, typically environment variables.
Keys are variable names (usually strings) and values are their string representations.
Functions
@spec all_required_touched?(group_name :: atom(), GenServer.server()) :: boolean()
Returns true if all required variables in specified group are present in store.
Use this when you want to ensure ALL required variables are configured before
enabling a feature. This is stricter than any_required_touched?/1.
Example
# Only configure database if ALL required variables are provided
if Confispex.all_required_touched?(:database) do
config :my_app, MyApp.Repo,
url: Confispex.get("DATABASE_URL"),
pool_size: Confispex.get("DATABASE_POOL_SIZE"),
ssl: Confispex.get("DATABASE_SSL")
endCompare with any_required_touched?/1: if even ONE required variable is present,
any_required_touched?/1 returns true. This function requires ALL of them.
@spec any_required_touched?(group_name :: atom(), GenServer.server()) :: boolean()
Returns true if any required variable in specified group is present in store.
Use this to detect if the user is trying to configure a group, even if the configuration is incomplete. This is useful for conditional configuration of services that crash on invalid config (better to skip than crash).
Example
# Configure APNS only if user provided at least one APNS variable
if Confispex.any_required_touched?(:apns) do
config :pigeon, :apns,
sandbox: %{
cert: Confispex.get("APNS_CERT"),
key: Confispex.get("APNS_KEY"),
mode: :dev
}
endDifference from all_required_touched?/1
any_required_touched?/1- returnstrueif at least one required variable is present (user is trying to configure this group)all_required_touched?/1- returnstrueonly if all required variables are present (configuration is complete)
Use any_required_touched?/1 to decide "should I configure this at all?" and
all_required_touched?/1 to decide "is configuration complete and valid?"
@spec get(Confispex.Schema.variable_name(), GenServer.server()) :: any()
Get a value from store by specified variable name (key) and cast it according to schema.
Returns the casted value on success, or nil on any error (variable not found, type
casting failed, etc.). Errors are collected and can be viewed with report/1.
Example
config :my_app, MyApp.Repo, url: Confispex.get("DATABASE_URL")Behavior
- Variable found and valid: returns the casted value
- Variable found via alias: returns the casted value (e.g.,
DB_URLwhenDATABASE_URLnot found) - Variable not found but has default: returns the default value (casted)
- Type casting fails: returns
niland saves error for later reporting - Variable not in schema: returns
niland shows warning in report
To see all errors, run:
mix confispex.report --mode=detailed
# or
Confispex.report(:detailed)This design allows your application to start even with configuration errors, so you can see ALL problems at once in the report, rather than fixing them one-at-a-time.
@spec init( %{ :schema => module(), :context => context(), optional(:store) => store() | (-> store()) }, GenServer.server() ) :: :ok
Initialize or reinitialize a state in server
Example
Confispex.init(%{
schema: MyApp.RuntimeConfigSchema,
context: %{env: config_env(), target: config_target()}
})By default, Confispex uses System.get_env/0 to setup the store.
@spec init_once( %{ :schema => module(), :context => context(), optional(:store) => store() | (-> store()) }, GenServer.server() ) :: :ok
Initialize a state in server if it hasn't already been initialized.
Use this instead of init/1 when you want to initialize only once, ignoring
subsequent calls. This is useful when configuration file is re-read with
Config.Reader.read!/1 to prevent overwriting the existing state.
Example
# In config/runtime.exs - initializes only on first call
Confispex.init_once(%{
schema: MyApp.RuntimeConfigSchema,
context: %{env: config_env(), target: config_target()}
})By default, Confispex uses System.get_env/0 to setup the store.
@spec report( :detailed | :brief, keyword() ) :: :ok
Print report with variables usage to STDOUT.
The report shows all variables organized by groups with color-coded status:
- Green groups: all required variables present and valid
- Red groups: required variables missing or invalid
- Blue groups: functional (no required variables or all have defaults)
Modes
:detailed- shows actual values from the store (may contain sensitive data):brief- hides values, only shows variable status (safe for logs)
Options
:server(atom/0) - The GenServer to query for report data. Defaults to the internal server.:emit_ansi?(boolean/0) - Whether to emit ANSI color codes. Defaults toIO.ANSI.enabled?().
Examples
# Show full report with values
Confispex.report(:detailed)
# Show report without values (safe for CI/logs)
Confispex.report(:brief)
# Force colors on remote shell
Confispex.report(:detailed, emit_ansi?: true)
# Custom server with colors disabled
Confispex.report(:brief, server: MyApp.ConfigServer, emit_ansi?: false)You can also use the mix task:
mix confispex.report --mode=detailed
mix confispex.report --mode=brief
@spec update_store((store() -> store()), GenServer.server()) :: :ok
Update the store at runtime by applying a function to the current store.
Example
# Add or override specific variables
new_values = %{"FEATURE_X_ENABLED" => "true", "API_KEY" => "new-key"}
Confispex.update_store(&Map.merge(&1, new_values))
# Remove a variable
Confispex.update_store(&Map.delete(&1, "TEMP_CONFIG"))