codex_mobile_bridge/
directory.rs1use std::fs;
2use std::path::{Path, PathBuf};
3
4use anyhow::{Context, Result, bail};
5
6pub fn canonicalize_directory(path: &Path) -> Result<PathBuf> {
7 let canonical = fs::canonicalize(path)
8 .with_context(|| format!("目录不存在或不可访问: {}", path.display()))?;
9
10 if !canonical.is_dir() {
11 bail!("目标必须是目录: {}", canonical.display());
12 }
13
14 Ok(trim_trailing_separator(&canonical))
15}
16
17pub fn normalize_absolute_directory(path: &Path) -> Result<PathBuf> {
18 if !path.is_absolute() {
19 bail!("目录路径必须为绝对路径: {}", path.display());
20 }
21
22 Ok(trim_trailing_separator(path))
23}
24
25pub fn default_display_name(path: &Path) -> String {
26 path.file_name()
27 .and_then(|name| name.to_str())
28 .map(ToOwned::to_owned)
29 .filter(|value| !value.is_empty())
30 .unwrap_or_else(|| "directory".to_string())
31}
32
33pub fn directory_contains(base: &Path, candidate: &Path) -> bool {
34 candidate.starts_with(base)
35}
36
37pub fn parent_directory(path: &Path) -> Option<PathBuf> {
38 path.parent()
39 .filter(|parent| parent != &path)
40 .map(trim_trailing_separator)
41}
42
43fn trim_trailing_separator(path: &Path) -> PathBuf {
44 if path == Path::new("/") {
45 return PathBuf::from("/");
46 }
47
48 let display = path.to_string_lossy();
49 let trimmed = display.trim_end_matches('/');
50 if trimmed.is_empty() {
51 PathBuf::from("/")
52 } else {
53 PathBuf::from(trimmed)
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use std::fs;
60
61 use super::{canonicalize_directory, directory_contains, parent_directory};
62
63 #[test]
64 fn canonicalize_directory_accepts_existing_dir() {
65 let temp = tempfile_dir("directory-ok");
66 fs::create_dir_all(&temp).unwrap();
67
68 let result = canonicalize_directory(&temp).unwrap();
69 assert!(result.is_dir());
70 }
71
72 #[test]
73 fn directory_contains_respects_path_boundaries() {
74 assert!(directory_contains(
75 std::path::Path::new("/srv/work"),
76 std::path::Path::new("/srv/work/project"),
77 ));
78 assert!(!directory_contains(
79 std::path::Path::new("/srv/work"),
80 std::path::Path::new("/srv/workspace"),
81 ));
82 }
83
84 #[test]
85 fn parent_directory_returns_none_for_root() {
86 assert!(parent_directory(std::path::Path::new("/")).is_none());
87 }
88
89 fn tempfile_dir(name: &str) -> std::path::PathBuf {
90 std::env::temp_dir().join(format!("{}-{}", name, uuid::Uuid::new_v4().simple()))
91 }
92}