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