pub trait Load {
// Required methods
fn name(&self) -> &str;
fn supported_source_list(&self) -> Vec<String>;
fn load(&self, source: Source) -> Result<Vec<Payload>, Error>;
}Expand description
Loads raw configuration bytes from a declared source.
Implement this to add a new source kind (protocol, service, database, …). This is the first
stage of the pipeline: it only fetches bytes, it does not parse them — Payload::content
is handed to the parser stage unchanged.
§Contract
loadtakes ownership of oneSourceand returns onePayloadper configuration entry found. A single source may expand to many entries (e.g. every file in a directory) → return many payloads; finding nothing isOk(vec![]), not an error.- Set
Payload::sourceon each entry to the concrete resource loaded, not the original source — clone it and narrow withSource::with_resource. This keeps diagnostics precise. - Use
Payload::maybe_namefor the entry name (Nonemerges into the root with all other unnamed entries) andPayload::maybe_formatas a parser hint (e.g.json). - Follow the lowercase convention: when your
lowercaseoption istrue(recommended default), lower-case names and formats so entries merge predictably across sources.
§Reading options
Options declared on the source (e.g. file(ignore=[not-found])) are available via
Source::options. Look each up with Options::get, convert with the typed accessors
(OptionValue::as_bool, OptionValue::as_string, OptionValue::as_list, …), and on a
type mismatch build the reason from OptionValue::type_name. It is good practice to reject
unknown keys by iterating Options::keys and returning Error::InvalidOption — see the
file loader’s load for a complete worked pattern.
§Choosing an error
Error::InvalidResource— the resource string is empty/malformed for this loader.Error::InvalidOption— an option is unknown, or has the wrong type/value.Error::NotFound— the resource/entry doesn’t exist (and isn’t being ignored).Error::NoAccess— permission denied by the backend.Error::Timeout— a deadline was exceeded.Error::Duplicate— two entries collide on the same name with different formats.Error::Load— any other backend failure (descriptioncompletes “could not …”).Error::Other— bridge for an opaque error via?.
§Registering
Pass an instance to tanzim::Config::with_loader. The pipeline dispatches each source to the
first loader whose supported_source_list contains the source
string, so it may advertise several (e.g. ["http", "https"]). For a one-off loader you don’t
want to define a type for, use closure::Closure instead of implementing this trait.
§Example — collecting specific environment variables
A loader that reads the variable names listed in its keys option and returns them as one
env-format payload. It shows the whole contract: reading a typed option, mapping failures to
the right Error variant, and building a Payload.
use std::env;
use tanzim_load::{Error, Load, Payload, Source};
struct SelectedEnv;
impl Load for SelectedEnv {
fn name(&self) -> &str { "selected-env" }
fn supported_source_list(&self) -> Vec<String> { vec!["selected-env".into()] }
fn load(&self, source: Source) -> Result<Vec<Payload>, Error> {
// Read the `keys` option — a required list of variable names.
let value = source.options().get("keys").ok_or_else(|| Error::InvalidOption {
loader: self.name().into(),
key: "keys".into(),
reason: "required".into(),
})?;
let keys = value.as_list().ok_or_else(|| Error::InvalidOption {
loader: self.name().into(),
key: "keys".into(),
reason: format!("expected list, found {}", value.type_name()),
})?;
// Collect each requested variable into a `KEY="value"` line.
let mut lines = Vec::new();
for item in keys {
let key = item.as_string().ok_or_else(|| Error::InvalidOption {
loader: self.name().into(),
key: "keys".into(),
reason: format!("expected string, found {}", item.type_name()),
})?;
let val = env::var(key).map_err(|_| Error::NotFound {
loader: self.name().into(),
resource: source.resource().into(),
item: format!("environment variable `{key}`"),
})?;
lines.push(format!("{key}={val:?}"));
}
Ok(vec![Payload {
source,
maybe_name: None, // unnamed → merges into the config root
maybe_format: Some("env".into()), // parsed by the `env` parser
content: lines.join("\n").into_bytes(),
}])
}
}
// SAFETY: example-only; single-threaded doctest env vars.
unsafe {
env::set_var("DB_HOST", "localhost");
env::set_var("DB_PORT", "5432");
}
let source = Source::parse("selected-env(keys=[DB_HOST,DB_PORT])").unwrap();
let payloads = SelectedEnv.load(source).unwrap();
let content = String::from_utf8_lossy(&payloads[0].content);
assert!(content.contains(r#"DB_HOST="localhost""#));
assert!(content.contains(r#"DB_PORT="5432""#));Required Methods§
Sourcefn supported_source_list(&self) -> Vec<String>
fn supported_source_list(&self) -> Vec<String>
Source strings this loader handles (e.g. ["env"], ["file"], ["http", "https"]).
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".