Layered, validated configuration conventions for Baukit services.
[ConfigLoader] applies configuration in this order: serde-backed defaults,
an optional local file, then environment variables. Environment variables use
an application prefix and a double-underscore separator, such as
MYAPP__HTTP__PORT=8080. A local .env file is loaded only when the loader's
deployment [Environment] is [Environment::Local].
Collection-valued environment variables use JSON array syntax, for example
MYAPP__HTTP__CORS_ALLOWED_ORIGINS=["https://app.example.com"]. Scalar
environment values retain their exact source strings, including values that
look numeric, so secret-string preservation is unchanged.
http.cors_allowed_origins is registered automatically; product collection
fields must be declared with [ConfigLoader::environment_collection].
Durations in the standard sections are represented as [Duration] values and
deserialize from integer seconds.
Shared environment and log-format vocabulary lives in the dependency-light
baukit-core crate and is re-exported here. This lets configuration and
telemetry use identical types without making configuration depend on the
OpenTelemetry exporter stack.
Example
use baukit_config::{BaukitConfig, ConfigLoader, Environment, Validate, ValidationErrors};
use serde::Deserialize;
#[derive(Debug, Default, Deserialize)]
#[serde(default)]
struct ProductConfig {
feature_name: Option<String>,
}
impl Validate for ProductConfig {
fn validate(&self) -> Result<(), ValidationErrors> {
Ok(())
}
}
let config: BaukitConfig<ProductConfig> =
ConfigLoader::new("myapp", Environment::Local)?.load()?;
# Ok::<(), baukit_config::LoadError>(())