use std::path::{Component, Path, PathBuf};
pub(crate) fn output_path_in_out_dir(out_dir: &Path, output_file: &str) -> Result<PathBuf, String> {
let output_path = Path::new(output_file);
let mut components = output_path.components();
let is_bare_file_name = matches!(components.next(), Some(Component::Normal(name)) if name == output_file)
&& components.next().is_none();
if is_bare_file_name {
Ok(out_dir.join(output_file))
} else {
Err(format!(
"build_scan: output_file must be a bare file name, got `{output_file}`. \
Fix: use a name like `gates_registry.rs`; vyre-build-scan always writes it under OUT_DIR."
))
}
}
#[cfg(test)]
mod tests {
use super::output_path_in_out_dir;
use std::path::Path;
#[test]
fn output_file_is_anchored_under_out_dir() {
let out_dir = Path::new("/tmp/out-dir");
let got = output_path_in_out_dir(out_dir, "gates_registry.rs").unwrap();
assert_eq!(got, out_dir.join("gates_registry.rs"));
}
#[test]
fn rejects_output_paths_that_escape_out_dir() {
let out_dir = Path::new("/tmp/out-dir");
let err = output_path_in_out_dir(out_dir, "../src/gates_registry.rs").unwrap_err();
assert!(err.contains("Fix: use a name like `gates_registry.rs`"));
}
#[test]
fn rejects_absolute_output_paths() {
let out_dir = Path::new("/tmp/out-dir");
let err = output_path_in_out_dir(out_dir, "/tmp/gates_registry.rs").unwrap_err();
assert!(err.contains("output_file must be a bare file name"));
}
#[test]
fn rejects_nested_output_paths() {
let out_dir = Path::new("/tmp/out-dir");
let err = output_path_in_out_dir(out_dir, "src/gates_registry.rs").unwrap_err();
assert!(err.contains("output_file must be a bare file name"));
}
}