vyre-build-scan 0.1.0

Build-time filesystem scanner for flat trait-impl registries
Documentation
//! Path validation for generated build output.

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"));
    }
}