Skip to main content

oxi_store/
session_cwd.rs

1//! Session working directory tracking and validation.
2//!
3//! Session working directory tracking and validation.
4
5use std::path::Path;
6
7/// Information about a missing session working directory.
8#[derive(Debug, Clone)]
9pub struct SessionCwdIssue {
10    /// Path to the session file (if known).
11    pub session_file: Option<String>,
12    /// The stored session working directory that no longer exists.
13    pub session_cwd: String,
14    /// The fallback (current) working directory.
15    pub fallback_cwd: String,
16}
17
18/// Trait for sources that can provide session CWD info.
19pub trait SessionCwdSource {
20    /// Get the working directory stored in the session.
21    fn get_cwd(&self) -> Option<String>;
22    /// Get the session file path, if any.
23    fn get_session_file(&self) -> Option<String>;
24}
25
26/// Check whether the session's stored CWD is missing.
27///
28/// Returns `Some(SessionCwdIssue)` if the session has a CWD recorded but the
29/// directory no longer exists on disk.
30pub 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 the directory still exists, no issue
38    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
49/// Format an error message for a missing session CWD.
50pub 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
62/// Format a user-facing prompt for the missing CWD situation.
63pub 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/// Error when the session's working directory no longer exists.
71#[derive(Debug)]
72pub struct MissingSessionCwdError {
73    /// pub.
74    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
85/// Assert that the session CWD exists, returning an error if it doesn't.
86pub 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}