5.3 What the Air Carries
A good instruction describes the general; the details belong to the workshop. Where to store finished work is not a question for the tool - it is a question of place. In one shop, one corner; in another, another; in a temporary one, nowhere and not for long. A configuration file cannot speak for itself. It has to be changed before a move. Moves happen more often than planned.
What You Need
tq reads config.toml - takes data_dir from there, or uses the current directory by
default. That works for one location. A developer with data in ~/tq-data and a CI job
with data in /tmp/ci-tq must either edit the file before each run or maintain two files
and swap them in as needed. Both options require manual work.
An environment variable solves the problem differently: it does not touch the file, does not require versioning, and lives exactly as long as needed - one run, one session, one CI job.
The Build
std::env::var("TQ_DATA_DIR") returns Ok(String) if the variable is set, and Err if
not. if let Ok(dir) takes the value only on success; if the variable is not set, the
block is skipped.
Two changes to src/config/mod.rs - config becomes mut, and after loading the file a
check for the environment variable is added:
// src/config/mod.rs - CHANGED
pub fn load(path: &Path) -> TqResult<Config> {
let mut config = if !path.exists() { // CHANGED: let -> let mut
Config::default()
} else {
let content = fs::read_to_string(path).map_err(|e| TqError::Parse(e.to_string()))?;
toml::from_str(&content).map_err(|e| TqError::Parse(e.to_string()))?
};
if let Ok(dir) = std::env::var("TQ_DATA_DIR") { // NEW
config.data_dir = PathBuf::from(dir); // NEW
} // NEW
if !config.data_dir.exists() {
return Err(TqError::Parse(format!(
"data_dir '{}' does not exist; create it with: mkdir -p {}",
config.data_dir.display(),
config.data_dir.display()
)));
}
Ok(config)
}
The file is loaded first (or the default taken), then - if the variable is set - data_dir
is overwritten. The existence check comes last, after all sources have been consulted.
Priority: TQ_DATA_DIR > config.toml > default.
Uppercase letters and the TQ_ prefix are a Unix convention: environment variables are
readable by any tool, and the name must be distinctive enough not to collide with someone
else’s.
The test for the new path lives in src/config/tests.rs. In Rust 2024, set_var requires
unsafe (details in M6.x), and the variable is removed before unwrap() - if the
assertion fails, remove_var has already run:
#[test]
fn env_var_overrides_data_dir() {
let tmp = std::env::temp_dir().join("tq-m53-env");
std::fs::create_dir_all(&tmp).unwrap();
unsafe { std::env::set_var("TQ_DATA_DIR", &tmp) };
let result = Config::load(Path::new("nonexistent.toml"));
unsafe { std::env::remove_var("TQ_DATA_DIR") };
assert_eq!(result.unwrap().data_dir, tmp);
}
Note.
env_var_overrides_data_dirsetsTQ_DATA_DIR- a process-wide environment variable shared across all threads. When tests run in parallel, two tests can set different values simultaneously and interfere with each other. In this book the simple path is chosen:make ciruns tests with--test-threads=1, which eliminates the potential problem.
# Makefile - CHANGED
ci:
@cargo fmt --check && cargo clippy -- -D warnings && cargo test -- --test-threads=1
The Result
$ mkdir -p /tmp/tq-test
$ TQ_DATA_DIR=/tmp/tq-test cargo run -- list
The list is empty: /tmp/tq-test contains no tasks.json. That is expected - the
variable changes where tq looks, not what is there.
An invalid path:
$ TQ_DATA_DIR=/nonexistent cargo run -- list
tq: parse error: data_dir '/nonexistent' does not exist; create it with: mkdir -p /nonexistent
make ci passes.
The complete
tqcode for this chapter is in5-interface/03-what-the-air-carries/.