Skip to main content

mars_agents/cli/
target.rs

1//! Shared target-name validation for `link` and `unlink` commands.
2
3use crate::error::MarsError;
4
5/// Normalize and validate a target directory name.
6///
7/// Strips trailing slashes, rejects paths (containing `/` or `\`),
8/// and rejects empty/dot names.
9pub fn normalize_target_name(target: &str) -> Result<String, MarsError> {
10    let normalized = target.trim_end_matches('/').trim_end_matches('\\');
11    if normalized.contains('/') || normalized.contains('\\') {
12        return Err(MarsError::Link {
13            target: target.to_string(),
14            message: "target must be a directory name, not a path".to_string(),
15        });
16    }
17    if normalized.is_empty() || normalized == "." || normalized == ".." {
18        return Err(MarsError::Link {
19            target: target.to_string(),
20            message: "invalid target name".to_string(),
21        });
22    }
23    Ok(normalized.to_string())
24}
25
26#[cfg(test)]
27mod tests {
28    use super::normalize_target_name;
29
30    #[test]
31    fn normalize_strips_trailing_slash() {
32        assert_eq!(normalize_target_name(".claude/").unwrap(), ".claude");
33    }
34
35    #[test]
36    fn normalize_rejects_path() {
37        assert!(normalize_target_name("foo/bar").is_err());
38    }
39
40    #[test]
41    fn normalize_rejects_empty() {
42        assert!(normalize_target_name("").is_err());
43    }
44
45    #[test]
46    fn normalize_rejects_dots() {
47        assert!(normalize_target_name(".").is_err());
48        assert!(normalize_target_name("..").is_err());
49    }
50}