Skip to main content

tftio_lib/
types.rs

1//! Shared types for Workhelix CLI tools.
2
3/// Repository information for CLI tools.
4///
5/// This structure holds basic repository metadata for identification purposes.
6#[derive(Debug, Clone)]
7pub struct RepoInfo {
8    /// Repository owner (e.g., "workhelix")
9    pub owner: &'static str,
10    /// Repository name (e.g., "prompter")
11    pub name: &'static str,
12}
13
14impl RepoInfo {
15    /// Create a new `RepoInfo` instance.
16    #[must_use]
17    pub const fn new(owner: &'static str, name: &'static str) -> Self {
18        Self { owner, name }
19    }
20}
21
22/// Health check result for doctor command.
23#[derive(Debug, Clone)]
24pub struct DoctorCheck {
25    /// Name of the check
26    pub name: String,
27    /// Whether the check passed
28    pub passed: bool,
29    /// Optional message
30    pub message: Option<String>,
31}
32
33impl DoctorCheck {
34    /// Create a new passing check.
35    #[must_use]
36    pub fn pass(name: impl Into<String>) -> Self {
37        Self {
38            name: name.into(),
39            passed: true,
40            message: None,
41        }
42    }
43
44    /// Create a new failing check with a message.
45    #[must_use]
46    pub fn fail(name: impl Into<String>, message: impl Into<String>) -> Self {
47        Self {
48            name: name.into(),
49            passed: false,
50            message: Some(message.into()),
51        }
52    }
53
54    /// Create a file existence check.
55    ///
56    /// # Errors
57    /// Returns a failing check if the file doesn't exist.
58    pub fn file_exists(path: impl AsRef<std::path::Path>) -> Self {
59        let path_ref = path.as_ref();
60        if path_ref.exists() && path_ref.is_file() {
61            Self::pass(format!("File exists: {}", path_ref.display()))
62        } else {
63            Self::fail(
64                format!("File check: {}", path_ref.display()),
65                format!("File not found: {}", path_ref.display()),
66            )
67        }
68    }
69
70    /// Create a directory existence check.
71    ///
72    /// # Errors
73    /// Returns a failing check if the directory doesn't exist.
74    pub fn dir_exists(path: impl AsRef<std::path::Path>) -> Self {
75        let path_ref = path.as_ref();
76        if path_ref.exists() && path_ref.is_dir() {
77            Self::pass(format!("Directory exists: {}", path_ref.display()))
78        } else {
79            Self::fail(
80                format!("Directory check: {}", path_ref.display()),
81                format!("Directory not found: {}", path_ref.display()),
82            )
83        }
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn test_repo_info_creation() {
93        let repo = RepoInfo::new("workhelix", "prompter");
94        assert_eq!(repo.owner, "workhelix");
95        assert_eq!(repo.name, "prompter");
96    }
97
98    #[test]
99    fn test_doctor_check_pass() {
100        let check = DoctorCheck::pass("test check");
101        assert!(check.passed);
102        assert_eq!(check.name, "test check");
103        assert!(check.message.is_none());
104    }
105
106    #[test]
107    fn test_doctor_check_fail() {
108        let check = DoctorCheck::fail("test check", "error message");
109        assert!(!check.passed);
110        assert_eq!(check.name, "test check");
111        assert_eq!(check.message, Some("error message".to_string()));
112    }
113
114    fn unique_temp_path(label: &str) -> std::path::PathBuf {
115        use std::sync::atomic::{AtomicU32, Ordering};
116        static COUNTER: AtomicU32 = AtomicU32::new(0);
117        let n = COUNTER.fetch_add(1, Ordering::Relaxed);
118        std::env::temp_dir().join(format!(
119            "tftio-lib-types-{label}-{}-{n}",
120            std::process::id()
121        ))
122    }
123
124    #[test]
125    fn file_exists_passes_for_a_real_file() {
126        let path = unique_temp_path("file-pass");
127        std::fs::write(&path, b"data").unwrap();
128        let check = DoctorCheck::file_exists(&path);
129        assert!(check.passed);
130        assert!(check.name.starts_with("File exists:"));
131        assert!(check.message.is_none());
132        std::fs::remove_file(&path).unwrap();
133    }
134
135    #[test]
136    fn file_exists_fails_when_the_path_is_missing() {
137        let path = unique_temp_path("file-missing");
138        let check = DoctorCheck::file_exists(&path);
139        assert!(!check.passed);
140        assert_eq!(
141            check.message,
142            Some(format!("File not found: {}", path.display()))
143        );
144    }
145
146    #[test]
147    fn file_exists_fails_when_the_path_is_a_directory() {
148        let path = unique_temp_path("file-is-dir");
149        std::fs::create_dir(&path).unwrap();
150        let check = DoctorCheck::file_exists(&path);
151        assert!(!check.passed);
152        assert!(check.name.starts_with("File check:"));
153        std::fs::remove_dir(&path).unwrap();
154    }
155
156    #[test]
157    fn dir_exists_passes_for_a_real_directory() {
158        let path = unique_temp_path("dir-pass");
159        std::fs::create_dir(&path).unwrap();
160        let check = DoctorCheck::dir_exists(&path);
161        assert!(check.passed);
162        assert!(check.name.starts_with("Directory exists:"));
163        assert!(check.message.is_none());
164        std::fs::remove_dir(&path).unwrap();
165    }
166
167    #[test]
168    fn dir_exists_fails_when_the_path_is_missing() {
169        let path = unique_temp_path("dir-missing");
170        let check = DoctorCheck::dir_exists(&path);
171        assert!(!check.passed);
172        assert_eq!(
173            check.message,
174            Some(format!("Directory not found: {}", path.display()))
175        );
176    }
177
178    #[test]
179    fn dir_exists_fails_when_the_path_is_a_file() {
180        let path = unique_temp_path("dir-is-file");
181        std::fs::write(&path, b"data").unwrap();
182        let check = DoctorCheck::dir_exists(&path);
183        assert!(!check.passed);
184        assert!(check.name.starts_with("Directory check:"));
185        std::fs::remove_file(&path).unwrap();
186    }
187}