use anyhow::Result;
pub const MANIFEST: &str = "manifest.json";
pub const FILES_PREFIX: &str = "files/";
pub const OUTPUTS_PREFIX: &str = "outputs/";
pub const CASSETTES_PREFIX: &str = "cassettes/";
pub(crate) fn validate_entry_path(path: &str) -> Result<String> {
let normalized = path.replace('\\', "/").trim_start_matches('/').to_string();
if normalized.is_empty() {
anyhow::bail!("invalid bundle path: empty path");
}
let segments: Vec<&str> = normalized.split('/').collect();
if segments[0].contains(':') {
anyhow::bail!(
"invalid bundle path: drive-letter or ':' in first segment (path: {})",
path
);
}
for seg in &segments {
if seg.is_empty() {
anyhow::bail!("invalid bundle path: empty segment (path: {})", path);
}
if *seg == "." || *seg == ".." {
anyhow::bail!(
"invalid bundle path: traversal segment '.' or '..' (path: {})",
path
);
}
}
let has_canonical_prefix = normalized.starts_with(FILES_PREFIX)
|| normalized.starts_with(OUTPUTS_PREFIX)
|| normalized.starts_with(CASSETTES_PREFIX);
if !has_canonical_prefix {
anyhow::bail!(
"invalid bundle path prefix: must be files/, outputs/, or cassettes/ (path: {})",
path
);
}
Ok(normalized)
}