std::config
Settings read out of the environment, through a schema, and reported all at once.
The environment is a source like any other: a JSON body, a query string
and a database row are all read by one Schema<A>, and so is this. What
this module adds is the walk – which variables a shape needs, and what to
call them – and the report, which spells a path the way a deployment
does.
derive(Show, Decode)type Listen = { host: String, port: Int };
derive(Show, Decode)type Settings = { listen: Listen, password: Redacted<String>, debug: Option<Bool> };
fn settings() -> Validated<Settings, Rejection> with { env: Env } { read(Settings::schema())}reads LISTEN_HOST, LISTEN_PORT, PASSWORD and DEBUG, and answers
every problem rather than the first: a person fixing a deployment wants
the list, not one line of it per restart.
How a shape becomes variable names
Section titled “How a shape becomes variable names”A field is its name in upper case; a nested record’s fields are joined
to it with _, so listen.port is LISTEN_PORT. A list is one variable
split on commas, TAGS=a,b. A variant is one variable holding the case,
MODE=Remote, and its payload fields beside it, MODE_URL. A key given
by key("http_host", ..) replaces the field’s name in that scheme and
nothing else. An optional nested record is present when any of its
variables is.
Every variable a shape needs is variables, which is the question a
deployment asks and a program should not have to be started to answer.
Why every value arrives Untyped
Section titled “Why every value arrives Untyped”The environment cannot say whether 8080 was meant as a number, so it
does not: every value is Raw::Untyped, which every primitive reads.
Strictness is a fact about the source, and this source has none to offer.
Denied is not missing
Section titled “Denied is not missing”A variable the manifest does not grant is a different thing from one
nobody set, and the fix is a line in khora.toml rather than a line in a
deployment script. Env::variable raises for it; this catches the raise
into Raw::Denied, which decodes as Problem::Denied, and report
names the file.
API at a glance
Section titled “API at a glance”Functions: read · variables · describe · report
Functions
Section titled “Functions”pub fn read<A>(schema: Schema<A>) -> Validated<A, Rejection> with { env: Env }Reads the settings a schema describes, every problem reported.
The schema’s shape decides which variables are read and what they are
called; the schema’s decoder decides what they mean. A record schema is
what this is for: a bare int() at the top level has no name to read.
import std::config::{read, report};import std::env::{Env};import std::schema::{Decode};
derive(Decode)pub type Settings = { listen_port: Int, database_url: String };
pub fn main() -> Int { with { env: Env::real() } { match read(Settings::schema()) { Validated::Valid(settings) => start(settings), Validated::Invalid(problems) => { print(report(problems)); 1 }, } }}The shape decides the variable names: listen_port is read from
LISTEN_PORT. A record written once with derive(Decode) reads from the
environment, a request body and a test fixture alike, which is what it
means for the schema to be a property of the type.
variables
Section titled “variables”pub fn variables(shape: Shape) -> List<String>The variables a shape needs, in the order they would be read.
[LISTEN_HOST, LISTEN_PORT, PASSWORD, DEBUG] for the settings above.
describe
Section titled “describe”pub fn describe(problem: Rejection) -> StringOne line a person can act on, with the path spelled as the variable it
came from: LISTEN_PORT should be a whole number, and is "eighty".
report
Section titled “report”pub fn report(problems: List<Rejection>) -> StringEvery reason, one per line, in the order they were found.
What a service prints just before it stops, and the reason the whole
module answers Validated: this list is allowed to have more than one
thing in it.
Khora 0.4.0built frombd964738afb6on 2026-10-17 20:41ZVersioningSecurityContributingReleasesSource