1use std::path::Path;
6
7use thiserror::Error;
8
9use super::entries::path_exists;
10
11#[derive(Clone, Debug, Eq, PartialEq)]
13pub struct SessionCwdIssue {
14 pub session_file: Option<String>,
16 pub session_cwd: String,
18 pub fallback_cwd: String,
20}
21
22pub trait SessionCwdSource {
24 fn get_cwd(&self) -> &str;
26 fn get_session_file(&self) -> Option<&str>;
28}
29
30#[must_use]
33pub fn get_missing_session_cwd_issue(
34 session_manager: &impl SessionCwdSource,
35 fallback_cwd: &str,
36) -> Option<SessionCwdIssue> {
37 let session_file = session_manager.get_session_file()?;
38 let session_cwd = session_manager.get_cwd();
39 if session_cwd.is_empty() || path_exists(Path::new(session_cwd)) {
40 return None;
41 }
42 Some(SessionCwdIssue {
43 session_file: Some(session_file.to_owned()),
44 session_cwd: session_cwd.to_owned(),
45 fallback_cwd: fallback_cwd.to_owned(),
46 })
47}
48
49#[must_use]
51pub fn format_missing_session_cwd_error(issue: &SessionCwdIssue) -> String {
52 let session_file = issue
53 .session_file
54 .as_ref()
55 .map_or(String::new(), |p| format!("\nSession file: {p}"));
56 format!(
57 "Stored session working directory does not exist: {}{}\nCurrent working directory: {}",
58 issue.session_cwd, session_file, issue.fallback_cwd
59 )
60}
61
62#[must_use]
64pub fn format_missing_session_cwd_prompt(issue: &SessionCwdIssue) -> String {
65 format!(
66 "cwd from session file does not exist\n{}\n\ncontinue in current cwd\n{}",
67 issue.session_cwd, issue.fallback_cwd
68 )
69}
70
71#[derive(Debug, Error)]
76#[error("{}", format_missing_session_cwd_error(&self.issue))]
77pub struct MissingSessionCwdError {
78 pub issue: SessionCwdIssue,
80}
81
82impl MissingSessionCwdError {
83 #[must_use]
85 pub fn new(issue: SessionCwdIssue) -> Self {
86 Self { issue }
87 }
88}
89
90pub fn assert_session_cwd_exists(
97 session_manager: &impl SessionCwdSource,
98 fallback_cwd: &str,
99) -> Result<(), MissingSessionCwdError> {
100 if let Some(issue) = get_missing_session_cwd_issue(session_manager, fallback_cwd) {
101 return Err(MissingSessionCwdError::new(issue));
102 }
103 Ok(())
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 struct FakeSource {
111 cwd: String,
112 file: Option<String>,
113 }
114
115 impl SessionCwdSource for FakeSource {
116 fn get_cwd(&self) -> &str {
117 &self.cwd
118 }
119 fn get_session_file(&self) -> Option<&str> {
120 self.file.as_deref()
121 }
122 }
123
124 #[test]
125 fn detects_missing_cwd() -> Result<(), &'static str> {
126 let src = FakeSource {
127 cwd: "/tmp/pi-oxidized-definitely-missing-cwd-xyz".to_owned(),
128 file: Some("/tmp/session.jsonl".to_owned()),
129 };
130 let issue = get_missing_session_cwd_issue(&src, "/tmp/fallback").ok_or("expected issue")?;
131 assert_eq!(issue.session_cwd, src.cwd);
132 assert_eq!(issue.fallback_cwd, "/tmp/fallback");
133 assert_eq!(issue.session_file.as_deref(), Some("/tmp/session.jsonl"));
134
135 let err = format_missing_session_cwd_error(&issue);
136 assert!(err.contains("Stored session working directory does not exist:"));
137 assert!(err.contains("Session file: /tmp/session.jsonl"));
138 assert!(err.contains("Current working directory: /tmp/fallback"));
139
140 let prompt = format_missing_session_cwd_prompt(&issue);
141 assert!(prompt.contains("cwd from session file does not exist"));
142 assert!(prompt.contains("continue in current cwd"));
143 Ok(())
144 }
145
146 #[test]
147 fn no_issue_when_no_session_file() {
148 let src = FakeSource {
149 cwd: "/tmp/missing".to_owned(),
150 file: None,
151 };
152 assert!(get_missing_session_cwd_issue(&src, "/tmp").is_none());
153 }
154
155 #[test]
156 fn no_issue_when_cwd_exists() {
157 let src = FakeSource {
158 cwd: std::env::temp_dir().to_string_lossy().into_owned(),
159 file: Some("/tmp/session.jsonl".to_owned()),
160 };
161 assert!(get_missing_session_cwd_issue(&src, "/tmp").is_none());
162 }
163
164 #[test]
165 fn assert_throws_missing_cwd_error() -> Result<(), &'static str> {
166 let src = FakeSource {
167 cwd: "/tmp/pi-oxidized-definitely-missing-cwd-xyz".to_owned(),
168 file: Some("/tmp/session.jsonl".to_owned()),
169 };
170 let Err(err) = assert_session_cwd_exists(&src, "/tmp/fallback") else {
171 return Err("expected MissingSessionCwdError");
172 };
173 assert_eq!(err.issue.session_cwd, src.cwd);
174 Ok(())
175 }
176}