Rust dotyaml-equivalent crate (design)
Goals
- Mirror Python
dotyaml semantics: dotenv preload, env interpolation, flatten to env vars with prefix, optional override of existing env.
- Zero fallback logic: one YAML path, explicit options for dotenv path and override.
- Pure Rust crate suitable for reuse by Kanbus and other projects; publish on crates.io.
- Name:
dotyaml (if available) or kanbus-dotyaml (fallback)
- Edition: 2021
- Dependencies:
serde_yaml, serde, serde_json (for value handling), dotenvy (dotenv load), thiserror, indexmap (order preservation), once_cell (optional), path-absolutize (for resolved paths)
- License: MIT OR Apache-2.0
- Minimum supported Rust version: 1.72 (aligned with current repo)
API sketch
/// Options controlling how YAML is loaded and exported to the environment.
pub struct DotYamlOptions {
pub prefix: String, // e.g., "KANBUS_"; required, non-empty
pub yaml_path: PathBuf, // required; one file only
pub dotenv_path: Option<PathBuf>, // default Some(Path::new(".env"))
pub load_dotenv: bool, // default true
pub override_existing: bool, // default false (never clobber existing env)
}
/// Load YAML and export environment variables.
pub fn load_and_export(options: &DotYamlOptions) -> Result<BTreeMap<String, String>, DotYamlError>;
/// Load YAML into a structured value without exporting env vars.
pub fn load(options: &DotYamlOptions) -> Result<serde_yaml::Value, DotYamlError>;
Semantics
- Dotenv first (optional): if
load_dotenv, load dotenv file (default .env) but do not override already-set env vars.
- Parse YAML: read
yaml_path with UTF-8; errors are surfaced verbatim.
- Interpolation: resolve
{{ VAR|default }} using current env (after dotenv). If VAR missing and no default, treat as empty string.
- Flattening: nested keys become uppercase with
_ separators; arrays become comma-separated values; booleans become true/false; null becomes empty string.
- Export: set env vars only if not present OR if
override_existing is true. Return the map of variables actually written.
- No fallbacks: only
yaml_path is read; no search paths.
Error cases
- Missing YAML file →
DotYamlError::NotFound with the path.
- YAML parse error →
DotYamlError::Parse with message and location.
- Interpolation cycle or malformed template →
DotYamlError::Interpolation.
- Invalid prefix (empty / non-ASCII) →
DotYamlError::InvalidPrefix.
Testing strategy
- Golden tests comparing exported env maps against Python dotyaml outputs for the same fixtures.
- Interpolation precedence: env > dotenv > defaults > YAML literal.
- Override flag behavior: with and without
override_existing.
- Flattening edge cases: nested objects, arrays, booleans, nulls.
- No-fallback guarantee: ensure only the specified file is ever read.
Integration notes for Kanbus (Rust)
- Kanbus will call
load_and_export early in CLI startup with prefix KANBUS_ and yaml_path from --config (default kanbus.yml).
- Configuration loader will read from env (already set by dotyaml) and deserialize into
ProjectConfiguration without additional fallbacks.
- Be explicit in errors: e.g., "kanbus.yml not found" or interpolation failures.
Open decisions
- Final crate name depending on crates.io availability.
- Whether to expose a streaming iterator API for large YAML files (likely unnecessary for our size).
- Template syntax: we will mirror Python dotyaml minimal subset; no full Jinja.