docs.rs failed to build tier-0.1.3
Please check the
build logs for more information.
See
Builds for ideas on how to fix a failed build,
or
Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault,
open an issue.
tier
tier is a Rust configuration library for typed, layered application config.
It is designed for projects that want one serde config type fed by code
defaults, TOML files, environment variables, and CLI overrides, without
falling back to untyped value trees.
By default, tier only enables TOML file support. derive, clap, schema,
watch, json, and yaml are opt-in features.
Use tier when you want:
- a Rust config library built around
serde types
- predictable layered config from defaults, files, env, and CLI
- source tracing and validation instead of silent config drift
- optional schema, docs, and reload support without a heavy default feature set
Feature Flags
toml: TOML file parsing and commented TOML examples
derive: #[derive(TierConfig)] metadata generation
clap: reusable config flags and diagnostics commands
schema: JSON Schema, env docs, and machine-readable reports
watch: native filesystem watcher backend
json: JSON file parsing
yaml: YAML file parsing
Input Semantics
- Env values and
--set key=value overrides are string-first inputs
- Primitive targets such as
bool, integers, floats, and Option<T> are coerced during deserialization
- Use explicit JSON syntax for arrays, objects, or quoted strings when you need structured inline values
Quick Start
The smallest useful setup is defaults plus a TOML file:
use serde::{Deserialize, Serialize};
use tier::ConfigLoader;
#[derive(Debug, Clone, Serialize, Deserialize)]
struct AppConfig {
port: u16,
}
impl Default for AppConfig {
fn default() -> Self {
Self { port: 3000 }
}
}
let loaded = ConfigLoader::new(AppConfig::default())
.file("config/app.toml")
.load()?;
assert!(loaded.report().doctor().contains("Sources:"));
# Ok::<(), tier::ConfigError>(())
Examples
The crate ships with focused examples under examples/:
basic.rs: defaults + env + CLI layering
manual-metadata.rs: explicit ConfigMetadata
derive.rs: derive metadata and declarative validation
schema.rs: schema, env docs, and commented TOML examples
clap.rs: embedding TierCli
reload.rs: polling reload
application.rs: a fuller application setup
Core Types
ConfigLoader<T> builds a deterministic pipeline from defaults, files, env,
CLI, and custom layers.
ConfigMetadata carries env names, aliases, secrets, examples, merge rules,
and declared validations.
LoadedConfig<T> returns the final typed value with a ConfigReport.
ReloadHandle<T> reuses the same loader closure for polling or native file
watching.
Highlights
- Typed loading with deterministic merge order and unknown field governance
- Metadata-driven env mapping, secret handling, and validation
- Field-level tracing, doctor output, and machine-readable audit/report data
- Optional schema/docs export, commented TOML examples,
clap, and reload
Example
# #[cfg(feature = "derive")] {
use serde::{Deserialize, Serialize};
use tier::{ArgsSource, ConfigLoader, EnvSource, Secret, TierConfig, ValidationErrors};
#[derive(Debug, Clone, Serialize, Deserialize, TierConfig)]
struct AppConfig {
server: ServerConfig,
db: DbConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize, TierConfig)]
struct ServerConfig {
#[tier(
env = "APP_SERVER_HOST",
doc = "IP address or hostname to bind",
example = "0.0.0.0"
)]
host: String,
#[tier(deprecated = "use server.bind_port instead")]
port: u16,
}
#[derive(Debug, Clone, Serialize, Deserialize, TierConfig)]
struct DbConfig {
password: Secret<String>,
}
impl Default for AppConfig {
fn default() -> Self {
Self {
server: ServerConfig {
host: "127.0.0.1".into(),
port: 3000,
},
db: DbConfig {
password: Secret::new("secret".into()),
},
}
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let loaded = ConfigLoader::new(AppConfig::default())
.derive_metadata()
.file("config/default.toml")
.optional_file("config/{profile}.toml")
.env(EnvSource::prefixed("APP"))
.args(ArgsSource::from_env())
.profile("prod")
.validator("port-range", |config| {
if config.server.port == 0 {
return Err(ValidationErrors::from_message(
"server.port",
"port must be greater than zero",
));
}
Ok(())
})
.load()?;
println!("{}", loaded.report().doctor());
Ok(())
}
# }
derive_metadata() applies metadata generated by TierConfig, including env
names, aliases, secret handling, serde(default) awareness, merge strategies,
declared validation rules, env docs, and deprecation warnings.
Declarative Validation
tier supports metadata-driven field and cross-field validation alongside
custom validator hooks. Declared rules feed the loader, schema annotations,
env docs, and commented TOML examples from the same metadata source.
# #[cfg(feature = "derive")] {
use serde::{Deserialize, Serialize};
use tier::{ConfigError, ConfigLoader, TierConfig};
#[derive(Debug, Clone, Serialize, Deserialize, TierConfig)]
struct AppConfig {
#[tier(non_empty, min_length = 3)]
service_name: String,
#[tier(min = 1, max = 65535)]
port: u16,
}
impl Default for AppConfig {
fn default() -> Self {
Self {
service_name: "api".to_owned(),
port: 8080,
}
}
}
let error = ConfigLoader::new(AppConfig::default())
.derive_metadata()
.args(tier::ArgsSource::from_args([
"app",
"--set",
r#"service_name="""#,
"--set",
"port=0",
]))
.load()
.expect_err("declared validation must fail");
assert!(matches!(error, ConfigError::DeclaredValidation { .. }));
# }
Reload
tier always includes ReloadHandle and a polling watcher. With watch
enabled, it also exposes a native filesystem watcher. Reloads can emit
structured diffs and events, and watchers can either keep running or stop
after a failed reload.
# #[cfg(all(feature = "toml", feature = "watch"))] {
use std::time::Duration;
use serde::{Deserialize, Serialize};
use tier::{ConfigError, ConfigLoader, ReloadEvent, ReloadHandle};
#[derive(Clone, Serialize, Deserialize)]
struct AppConfig {
port: u16,
}
impl Default for AppConfig {
fn default() -> Self {
Self { port: 3000 }
}
}
fn main() -> Result<(), ConfigError> {
let handle =
ReloadHandle::new(|| ConfigLoader::new(AppConfig::default()).file("app.toml").load())?;
let _events = handle.subscribe();
let _summary = handle.reload_detailed()?;
let watcher = handle.start_native(["app.toml"], Duration::from_millis(100))?;
watcher.stop();
Ok(())
}
# }
Schema Export
With schema enabled, tier can export a JSON Schema for a config type:
# #[cfg(feature = "schema")] {
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tier::json_schema_pretty;
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
struct AppConfig {
port: u16,
}
let schema = json_schema_pretty::<AppConfig>();
assert!(schema.contains("\"type\": \"object\""));
# }
Clap Integration
With clap enabled, tier provides a reusable config flag group:
# #[cfg(feature = "clap")] {
use clap::Parser;
use tier::TierCli;
#[derive(Debug, Parser)]
struct AppCli {
#[command(flatten)]
config: TierCli,
}
let cli = AppCli::parse_from(["app", "--validate-config"]);
assert!(matches!(cli.config.command(), tier::TierCliCommand::ValidateConfig));
# #[cfg(feature = "schema")] {
let example = AppCli::parse_from(["app", "--print-config-example"]);
assert!(matches!(
example.config.command(),
tier::TierCliCommand::PrintConfigExample
));
# }
# }
Environment Variable Docs
With schema enabled, tier can generate environment variable docs and
annotated JSON Schema. When toml is also enabled, it can render a commented
TOML example configuration:
# #[cfg(all(feature = "schema", feature = "derive", feature = "toml"))] {
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tier::{
EnvDocOptions, TierConfig, annotated_json_schema_pretty, config_example_toml, env_docs_json,
env_docs_markdown,
};
#[derive(Debug, Serialize, Deserialize, JsonSchema, TierConfig)]
struct AppConfig {
server: ServerConfig,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema, TierConfig)]
struct ServerConfig {
#[tier(
env = "APP_SERVER_PORT",
doc = "Port used for incoming traffic",
example = "8080"
)]
port: u16,
}
let docs = env_docs_markdown::<AppConfig>(&EnvDocOptions::prefixed("APP"));
assert!(docs.contains("APP_SERVER_PORT"));
let docs_json = env_docs_json::<AppConfig>(&EnvDocOptions::prefixed("APP"));
assert!(docs_json.is_array());
let schema = annotated_json_schema_pretty::<AppConfig>();
assert!(schema.contains("\"x-tier-env\""));
let example = config_example_toml::<AppConfig>();
assert!(example.contains("[server]"));
# }
Secrets
tier::Secret<T> is a strong typed wrapper for sensitive values. It redacts
Debug and Display output, and with the schema feature it marks fields as
writeOnly so the loader can auto-discover secret paths.
use serde::{Deserialize, Serialize};
use tier::Secret;
#[derive(Debug, Serialize, Deserialize)]
struct DbConfig {
password: Secret<String>,
}
let password = Secret::new("super-secret".to_owned());
assert_eq!(format!("{password}"), "***redacted***");
Status
This crate focuses on typed layered loading, metadata, diagnostics, validation,
schema/docs output, and reload support.
Deliberately out of scope in the current crate line:
- remote configuration backends
- derive-driven full CLI generation for application-specific flags