use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
pub fn resolve_paths_against_workspace(
paths: &[PathBuf],
workspace: &Path,
) -> Result<Vec<PathBuf>> {
paths
.iter()
.map(|p| {
crate::path_safety::validate_path(p, workspace).with_context(|| {
format!(
"G121: path '{}' escapes workspace jail; use --workspace to set a different root",
p.display()
)
})
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn resolves_relative_path_against_workspace() {
let dir = tempdir().expect("tempdir");
let workspace = dir.path();
let sub = workspace.join("src");
std::fs::create_dir(&sub).expect("mkdir");
let resolved =
resolve_paths_against_workspace(&[PathBuf::from("src")], workspace).expect("resolve");
assert_eq!(resolved.len(), 1);
assert!(resolved[0].ends_with("src"));
assert!(resolved[0].is_absolute());
}
#[test]
fn resolves_absolute_path_inside_workspace() {
let dir = tempdir().expect("tempdir");
let workspace = dir.path();
let target = workspace.join("data");
std::fs::create_dir(&target).expect("mkdir");
let resolved = resolve_paths_against_workspace(std::slice::from_ref(&target), workspace)
.expect("resolve");
assert_eq!(
resolved[0],
resolve_paths_against_workspace(std::slice::from_ref(&target), workspace).unwrap()[0]
);
}
#[test]
fn rejects_path_outside_workspace() {
let dir = tempdir().expect("tempdir");
let result = resolve_paths_against_workspace(&[PathBuf::from("/etc/passwd")], dir.path());
assert!(result.is_err(), "out-of-jail path must fail");
}
#[test]
fn preserves_input_count() {
let dir = tempdir().expect("tempdir");
let p1 = dir.path().join("a");
let p2 = dir.path().join("b");
std::fs::create_dir(&p1).expect("mkdir a");
std::fs::create_dir(&p2).expect("mkdir b");
let resolved =
resolve_paths_against_workspace(&[PathBuf::from("a"), PathBuf::from("b")], dir.path())
.expect("resolve");
assert_eq!(resolved.len(), 2);
}
}