codex_utils_home_dir/
lib.rs1use codex_utils_absolute_path::AbsolutePathBuf;
2use dirs::home_dir;
3use std::path::PathBuf;
4
5pub fn find_codex_home() -> std::io::Result<AbsolutePathBuf> {
14 let codex_home_env = std::env::var("CODEX_HOME")
15 .ok()
16 .filter(|val| !val.is_empty());
17 find_codex_home_from_env(codex_home_env.as_deref())
18}
19
20fn find_codex_home_from_env(codex_home_env: Option<&str>) -> std::io::Result<AbsolutePathBuf> {
21 match codex_home_env {
24 Some(val) => {
25 let path = PathBuf::from(val);
26 let metadata = std::fs::metadata(&path).map_err(|err| match err.kind() {
27 std::io::ErrorKind::NotFound => std::io::Error::new(
28 std::io::ErrorKind::NotFound,
29 format!("CODEX_HOME points to {val:?}, but that path does not exist"),
30 ),
31 _ => std::io::Error::new(
32 err.kind(),
33 format!("failed to read CODEX_HOME {val:?}: {err}"),
34 ),
35 })?;
36
37 if !metadata.is_dir() {
38 Err(std::io::Error::new(
39 std::io::ErrorKind::InvalidInput,
40 format!("CODEX_HOME points to {val:?}, but that path is not a directory"),
41 ))
42 } else {
43 let canonical = path.canonicalize().map_err(|err| {
44 std::io::Error::new(
45 err.kind(),
46 format!("failed to canonicalize CODEX_HOME {val:?}: {err}"),
47 )
48 })?;
49 AbsolutePathBuf::from_absolute_path(canonical)
50 }
51 }
52 None => {
53 let mut p = home_dir().ok_or_else(|| {
54 std::io::Error::new(
55 std::io::ErrorKind::NotFound,
56 "Could not find home directory",
57 )
58 })?;
59 p.push(".codex");
60 AbsolutePathBuf::from_absolute_path(p)
61 }
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::find_codex_home_from_env;
68 use codex_utils_absolute_path::AbsolutePathBuf;
69 use dirs::home_dir;
70 use pretty_assertions::assert_eq;
71 use std::fs;
72 use std::io::ErrorKind;
73 use tempfile::TempDir;
74
75 #[test]
76 fn find_codex_home_env_missing_path_is_fatal() {
77 let temp_home = TempDir::new().expect("temp home");
78 let missing = temp_home.path().join("missing-codex-home");
79 let missing_str = missing
80 .to_str()
81 .expect("missing codex home path should be valid utf-8");
82
83 let err = find_codex_home_from_env(Some(missing_str)).expect_err("missing CODEX_HOME");
84 assert_eq!(err.kind(), ErrorKind::NotFound);
85 assert!(
86 err.to_string().contains("CODEX_HOME"),
87 "unexpected error: {err}"
88 );
89 }
90
91 #[test]
92 fn find_codex_home_env_file_path_is_fatal() {
93 let temp_home = TempDir::new().expect("temp home");
94 let file_path = temp_home.path().join("codex-home.txt");
95 fs::write(&file_path, "not a directory").expect("write temp file");
96 let file_str = file_path
97 .to_str()
98 .expect("file codex home path should be valid utf-8");
99
100 let err = find_codex_home_from_env(Some(file_str)).expect_err("file CODEX_HOME");
101 assert_eq!(err.kind(), ErrorKind::InvalidInput);
102 assert!(
103 err.to_string().contains("not a directory"),
104 "unexpected error: {err}"
105 );
106 }
107
108 #[test]
109 fn find_codex_home_env_valid_directory_canonicalizes() {
110 let temp_home = TempDir::new().expect("temp home");
111 let temp_str = temp_home
112 .path()
113 .to_str()
114 .expect("temp codex home path should be valid utf-8");
115
116 let resolved = find_codex_home_from_env(Some(temp_str)).expect("valid CODEX_HOME");
117 let expected = temp_home
118 .path()
119 .canonicalize()
120 .expect("canonicalize temp home");
121 let expected = AbsolutePathBuf::from_absolute_path(expected).expect("absolute home");
122 assert_eq!(resolved, expected);
123 }
124
125 #[test]
126 fn find_codex_home_without_env_uses_default_home_dir() {
127 let resolved =
128 find_codex_home_from_env(None).expect("default CODEX_HOME");
129 let mut expected = home_dir().expect("home dir");
130 expected.push(".codex");
131 let expected = AbsolutePathBuf::from_absolute_path(expected).expect("absolute home");
132 assert_eq!(resolved, expected);
133 }
134}