1use std::path::Path;
6
7#[derive(Debug, Clone)]
9pub struct SessionCwdIssue {
10 pub session_file: Option<String>,
12 pub session_cwd: String,
14 pub fallback_cwd: String,
16}
17
18pub trait SessionCwdSource {
20 fn get_cwd(&self) -> Option<String>;
22 fn get_session_file(&self) -> Option<String>;
24}
25
26pub fn get_missing_session_cwd_issue(
31 source: &dyn SessionCwdSource,
32 fallback_cwd: &str,
33) -> Option<SessionCwdIssue> {
34 let session_file = source.get_session_file()?;
35 let session_cwd = source.get_cwd()?;
36
37 if Path::new(&session_cwd).is_dir() {
39 return None;
40 }
41
42 Some(SessionCwdIssue {
43 session_file: Some(session_file),
44 session_cwd,
45 fallback_cwd: fallback_cwd.to_string(),
46 })
47}
48
49pub fn format_missing_session_cwd_error(issue: &SessionCwdIssue) -> String {
51 let session_file_line = issue
52 .session_file
53 .as_ref()
54 .map(|f| format!("\nSession file: {}", f))
55 .unwrap_or_default();
56 format!(
57 "Stored session working directory does not exist: {}{}\nCurrent working directory: {}",
58 issue.session_cwd, session_file_line, issue.fallback_cwd
59 )
60}
61
62pub fn format_missing_session_cwd_prompt(issue: &SessionCwdIssue) -> String {
64 format!(
65 "cwd from session file does not exist\n{}\n\ncontinue in current cwd\n{}",
66 issue.session_cwd, issue.fallback_cwd
67 )
68}
69
70#[derive(Debug)]
72pub struct MissingSessionCwdError {
73 pub issue: SessionCwdIssue,
75}
76
77impl std::fmt::Display for MissingSessionCwdError {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 write!(f, "{}", format_missing_session_cwd_error(&self.issue))
80 }
81}
82
83impl std::error::Error for MissingSessionCwdError {}
84
85pub fn assert_session_cwd_exists(
87 source: &dyn SessionCwdSource,
88 fallback_cwd: &str,
89) -> Result<(), MissingSessionCwdError> {
90 if let Some(issue) = get_missing_session_cwd_issue(source, fallback_cwd) {
91 return Err(MissingSessionCwdError { issue });
92 }
93 Ok(())
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 struct MockSource {
101 cwd: Option<String>,
102 file: Option<String>,
103 }
104
105 impl SessionCwdSource for MockSource {
106 fn get_cwd(&self) -> Option<String> {
107 self.cwd.clone()
108 }
109 fn get_session_file(&self) -> Option<String> {
110 self.file.clone()
111 }
112 }
113
114 #[test]
115 fn no_issue_when_no_session_file() {
116 let src = MockSource {
117 cwd: Some("/nonexistent".into()),
118 file: None,
119 };
120 assert!(get_missing_session_cwd_issue(&src, "/tmp").is_none());
121 }
122
123 #[test]
124 fn no_issue_when_cwd_exists() {
125 let src = MockSource {
126 cwd: Some("/tmp".into()),
127 file: Some("/tmp/session.json".into()),
128 };
129 assert!(get_missing_session_cwd_issue(&src, "/tmp").is_none());
130 }
131
132 #[test]
133 fn error_format() {
134 let issue = SessionCwdIssue {
135 session_file: Some("/tmp/s.json".into()),
136 session_cwd: "/gone".into(),
137 fallback_cwd: "/tmp".into(),
138 };
139 let msg = format_missing_session_cwd_error(&issue);
140 assert!(msg.contains("/gone"));
141 assert!(msg.contains("/tmp/s.json"));
142 }
143
144 #[test]
145 fn prompt_format() {
146 let issue = SessionCwdIssue {
147 session_file: None,
148 session_cwd: "/gone".into(),
149 fallback_cwd: "/here".into(),
150 };
151 let prompt = format_missing_session_cwd_prompt(&issue);
152 assert!(prompt.contains("/gone"));
153 assert!(prompt.contains("/here"));
154 }
155}