1use std::fmt;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub struct InvalidTaskStatus(String);
5
6impl fmt::Display for InvalidTaskStatus {
7 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
8 write!(
9 formatter,
10 "error invalid-status input={} choices={}",
11 self.0,
12 STATUSES.join(",")
13 )
14 }
15}
16
17impl std::error::Error for InvalidTaskStatus {}
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct InvalidTaskPriority(String);
21
22impl fmt::Display for InvalidTaskPriority {
23 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
24 write!(
25 formatter,
26 "error invalid-priority input={} choices={}",
27 self.0,
28 PRIORITIES.join(",")
29 )
30 }
31}
32
33impl std::error::Error for InvalidTaskPriority {}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct InvalidTaskSource(String);
37
38impl fmt::Display for InvalidTaskSource {
39 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
40 write!(
41 formatter,
42 "error invalid-task-source input={} choices={}",
43 self.0,
44 TASK_SOURCES.join(",")
45 )
46 }
47}
48
49impl std::error::Error for InvalidTaskSource {}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
52pub enum TaskSource {
53 Cli,
54 Tui,
55 Api,
56 #[default]
57 Unknown,
58}
59
60impl TaskSource {
61 pub const fn as_str(self) -> &'static str {
62 match self {
63 Self::Cli => "cli",
64 Self::Tui => "tui",
65 Self::Api => "api",
66 Self::Unknown => "unknown",
67 }
68 }
69
70 pub fn parse(value: &str) -> Result<Self, InvalidTaskSource> {
71 match value {
72 "cli" => Ok(Self::Cli),
73 "tui" => Ok(Self::Tui),
74 "api" => Ok(Self::Api),
75 "unknown" => Ok(Self::Unknown),
76 _ => Err(InvalidTaskSource(value.to_string())),
77 }
78 }
79}
80
81impl fmt::Display for TaskSource {
82 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
83 formatter.write_str(self.as_str())
84 }
85}
86
87impl TryFrom<&str> for TaskSource {
88 type Error = InvalidTaskSource;
89
90 fn try_from(value: &str) -> Result<Self, Self::Error> {
91 Self::parse(value)
92 }
93}
94
95pub const TASK_SOURCES: &[&str] = &[
96 TaskSource::Cli.as_str(),
97 TaskSource::Tui.as_str(),
98 TaskSource::Api.as_str(),
99 TaskSource::Unknown.as_str(),
100];
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
103pub enum TaskStatus {
104 Inbox,
105 Backlog,
106 Todo,
107 Active,
108 Done,
109 Canceled,
110}
111
112impl TaskStatus {
113 #[allow(dead_code)]
114 pub const OPEN: [Self; 4] = [Self::Inbox, Self::Backlog, Self::Todo, Self::Active];
115 #[allow(dead_code)]
116 pub const TERMINAL: [Self; 2] = [Self::Done, Self::Canceled];
117
118 pub const fn is_open(self) -> bool {
119 matches!(
120 self,
121 Self::Inbox | Self::Backlog | Self::Todo | Self::Active
122 )
123 }
124
125 #[allow(dead_code)]
126 pub const fn is_terminal(self) -> bool {
127 matches!(self, Self::Done | Self::Canceled)
128 }
129
130 pub const fn as_str(self) -> &'static str {
131 match self {
132 Self::Inbox => "inbox",
133 Self::Backlog => "backlog",
134 Self::Todo => "todo",
135 Self::Active => "active",
136 Self::Done => "done",
137 Self::Canceled => "canceled",
138 }
139 }
140
141 pub fn parse(value: &str) -> Result<Self, InvalidTaskStatus> {
142 match value {
143 "inbox" => Ok(Self::Inbox),
144 "backlog" => Ok(Self::Backlog),
145 "todo" => Ok(Self::Todo),
146 "active" => Ok(Self::Active),
147 "done" => Ok(Self::Done),
148 "canceled" => Ok(Self::Canceled),
149 _ => Err(InvalidTaskStatus(value.to_string())),
150 }
151 }
152}
153
154impl std::fmt::Display for TaskStatus {
155 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156 f.write_str(self.as_str())
157 }
158}
159
160impl TryFrom<&str> for TaskStatus {
161 type Error = InvalidTaskStatus;
162
163 fn try_from(value: &str) -> Result<Self, Self::Error> {
164 Self::parse(value)
165 }
166}
167
168pub const STATUSES: &[&str] = &[
169 TaskStatus::Inbox.as_str(),
170 TaskStatus::Backlog.as_str(),
171 TaskStatus::Todo.as_str(),
172 TaskStatus::Active.as_str(),
173 TaskStatus::Done.as_str(),
174 TaskStatus::Canceled.as_str(),
175];
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
178pub enum TaskPriority {
179 None,
180 Low,
181 Medium,
182 High,
183 Urgent,
184}
185
186impl TaskPriority {
187 pub const ALL: [Self; 5] = [
188 Self::None,
189 Self::Low,
190 Self::Medium,
191 Self::High,
192 Self::Urgent,
193 ];
194
195 pub const fn as_str(self) -> &'static str {
196 match self {
197 Self::None => "none",
198 Self::Low => "low",
199 Self::Medium => "medium",
200 Self::High => "high",
201 Self::Urgent => "urgent",
202 }
203 }
204
205 pub fn parse(value: &str) -> Result<Self, InvalidTaskPriority> {
206 match value {
207 "none" => Ok(Self::None),
208 "low" => Ok(Self::Low),
209 "medium" => Ok(Self::Medium),
210 "high" => Ok(Self::High),
211 "urgent" => Ok(Self::Urgent),
212 _ => Err(InvalidTaskPriority(value.to_string())),
213 }
214 }
215}
216
217impl std::fmt::Display for TaskPriority {
218 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
219 f.write_str(self.as_str())
220 }
221}
222
223impl TryFrom<&str> for TaskPriority {
224 type Error = InvalidTaskPriority;
225
226 fn try_from(value: &str) -> Result<Self, Self::Error> {
227 Self::parse(value)
228 }
229}
230
231pub const PRIORITIES: &[&str] = &[
232 TaskPriority::None.as_str(),
233 TaskPriority::Low.as_str(),
234 TaskPriority::Medium.as_str(),
235 TaskPriority::High.as_str(),
236 TaskPriority::Urgent.as_str(),
237];
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242
243 #[test]
244 fn status_and_priority_parse_display_and_reject_invalid_values() {
245 assert_eq!(TaskSource::parse("tui").unwrap(), TaskSource::Tui);
246 assert_eq!(TaskSource::Api.as_str(), "api");
247 assert_eq!(TaskSource::Unknown.to_string(), "unknown");
248 assert_eq!(
249 TaskSource::parse("agent").unwrap_err().to_string(),
250 "error invalid-task-source input=agent choices=cli,tui,api,unknown"
251 );
252
253 assert_eq!(TaskStatus::parse("active").unwrap(), TaskStatus::Active);
254 assert_eq!(TaskStatus::Active.as_str(), "active");
255 assert_eq!(TaskStatus::Active.to_string(), "active");
256 assert_eq!(
257 TaskStatus::parse("blocked").unwrap_err().to_string(),
258 "error invalid-status input=blocked choices=inbox,backlog,todo,active,done,canceled"
259 );
260
261 assert_eq!(TaskPriority::parse("urgent").unwrap(), TaskPriority::Urgent);
262 assert_eq!(TaskPriority::Urgent.as_str(), "urgent");
263 assert_eq!(TaskPriority::Urgent.to_string(), "urgent");
264 assert_eq!(
265 TaskPriority::parse("soon").unwrap_err().to_string(),
266 "error invalid-priority input=soon choices=none,low,medium,high,urgent"
267 );
268 }
269}