use std::path::{Path, PathBuf};
use crate::error::NpxcError;
pub fn validate_path(raw_path: &str, cwd: &Path) -> Result<PathBuf, NpxcError> {
let stripped = raw_path.strip_prefix("file://").unwrap_or(raw_path);
let expanded: PathBuf = if let Some(rest) = stripped.strip_prefix("~/") {
let home = PathBuf::from(std::env::var_os("HOME").unwrap_or_default());
home.join(rest)
} else {
PathBuf::from(stripped)
};
let canonical = std::fs::canonicalize(&expanded)
.map_err(|_| NpxcError::PathNotFound(raw_path.to_owned()))?;
let canonical_cwd = std::fs::canonicalize(cwd)?;
if !canonical.starts_with(&canonical_cwd) {
return Err(NpxcError::PathOutOfScope {
path: raw_path.to_owned(),
cwd: cwd.to_string_lossy().into_owned(),
});
}
Ok(canonical)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
fn setup() -> TempDir {
tempfile::tempdir().unwrap()
}
#[test]
fn valid_path_within_cwd() {
let tmp = setup();
let file = tmp.path().join("hello.txt");
fs::write(&file, b"hi").unwrap();
let result = validate_path(file.to_str().unwrap(), tmp.path());
assert!(result.is_ok());
assert_eq!(result.unwrap(), fs::canonicalize(&file).unwrap());
}
#[test]
fn nonexistent_path_returns_not_found() {
let tmp = setup();
let raw = "/this/definitely/does/not/exist/anywhere";
let err = validate_path(raw, tmp.path()).unwrap_err();
assert!(matches!(err, NpxcError::PathNotFound(p) if p == raw));
}
#[test]
fn path_outside_cwd_returns_out_of_scope() {
let tmp = setup();
let outside = tempfile::tempdir().unwrap();
let file = outside.path().join("secret.txt");
fs::write(&file, b"data").unwrap();
let raw = file.to_str().unwrap();
let err = validate_path(raw, tmp.path()).unwrap_err();
assert!(matches!(err, NpxcError::PathOutOfScope { path, .. } if path == raw));
}
#[test]
fn file_uri_prefix_is_stripped() {
let tmp = setup();
let file = tmp.path().join("doc.md");
fs::write(&file, b"").unwrap();
let raw = format!("file://{}", file.to_str().unwrap());
let result = validate_path(&raw, tmp.path());
assert!(result.is_ok(), "{result:?}");
}
#[test]
fn error_preserves_original_raw_path_with_file_uri() {
let tmp = setup();
let raw = "file:///no/such/path/anywhere";
let err = validate_path(raw, tmp.path()).unwrap_err();
assert!(matches!(err, NpxcError::PathNotFound(p) if p == raw));
}
}