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, Copy, PartialEq, Eq, Hash)]
36pub enum TaskStatus {
37 Inbox,
38 Backlog,
39 Todo,
40 Active,
41 Done,
42 Canceled,
43}
44
45impl TaskStatus {
46 #[allow(dead_code)]
47 pub const OPEN: [Self; 4] = [Self::Inbox, Self::Backlog, Self::Todo, Self::Active];
48 #[allow(dead_code)]
49 pub const TERMINAL: [Self; 2] = [Self::Done, Self::Canceled];
50
51 pub const fn is_open(self) -> bool {
52 matches!(
53 self,
54 Self::Inbox | Self::Backlog | Self::Todo | Self::Active
55 )
56 }
57
58 #[allow(dead_code)]
59 pub const fn is_terminal(self) -> bool {
60 matches!(self, Self::Done | Self::Canceled)
61 }
62
63 pub const fn as_str(self) -> &'static str {
64 match self {
65 Self::Inbox => "inbox",
66 Self::Backlog => "backlog",
67 Self::Todo => "todo",
68 Self::Active => "active",
69 Self::Done => "done",
70 Self::Canceled => "canceled",
71 }
72 }
73
74 pub fn parse(value: &str) -> Result<Self, InvalidTaskStatus> {
75 match value {
76 "inbox" => Ok(Self::Inbox),
77 "backlog" => Ok(Self::Backlog),
78 "todo" => Ok(Self::Todo),
79 "active" => Ok(Self::Active),
80 "done" => Ok(Self::Done),
81 "canceled" => Ok(Self::Canceled),
82 _ => Err(InvalidTaskStatus(value.to_string())),
83 }
84 }
85}
86
87impl std::fmt::Display for TaskStatus {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 f.write_str(self.as_str())
90 }
91}
92
93impl TryFrom<&str> for TaskStatus {
94 type Error = InvalidTaskStatus;
95
96 fn try_from(value: &str) -> Result<Self, Self::Error> {
97 Self::parse(value)
98 }
99}
100
101pub const STATUSES: &[&str] = &[
102 TaskStatus::Inbox.as_str(),
103 TaskStatus::Backlog.as_str(),
104 TaskStatus::Todo.as_str(),
105 TaskStatus::Active.as_str(),
106 TaskStatus::Done.as_str(),
107 TaskStatus::Canceled.as_str(),
108];
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
111pub enum TaskPriority {
112 None,
113 Low,
114 Medium,
115 High,
116 Urgent,
117}
118
119impl TaskPriority {
120 pub const ALL: [Self; 5] = [
121 Self::None,
122 Self::Low,
123 Self::Medium,
124 Self::High,
125 Self::Urgent,
126 ];
127
128 pub const fn as_str(self) -> &'static str {
129 match self {
130 Self::None => "none",
131 Self::Low => "low",
132 Self::Medium => "medium",
133 Self::High => "high",
134 Self::Urgent => "urgent",
135 }
136 }
137
138 pub fn parse(value: &str) -> Result<Self, InvalidTaskPriority> {
139 match value {
140 "none" => Ok(Self::None),
141 "low" => Ok(Self::Low),
142 "medium" => Ok(Self::Medium),
143 "high" => Ok(Self::High),
144 "urgent" => Ok(Self::Urgent),
145 _ => Err(InvalidTaskPriority(value.to_string())),
146 }
147 }
148}
149
150impl std::fmt::Display for TaskPriority {
151 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 f.write_str(self.as_str())
153 }
154}
155
156impl TryFrom<&str> for TaskPriority {
157 type Error = InvalidTaskPriority;
158
159 fn try_from(value: &str) -> Result<Self, Self::Error> {
160 Self::parse(value)
161 }
162}
163
164pub const PRIORITIES: &[&str] = &[
165 TaskPriority::None.as_str(),
166 TaskPriority::Low.as_str(),
167 TaskPriority::Medium.as_str(),
168 TaskPriority::High.as_str(),
169 TaskPriority::Urgent.as_str(),
170];
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn status_and_priority_parse_display_and_reject_invalid_values() {
178 assert_eq!(TaskStatus::parse("active").unwrap(), TaskStatus::Active);
179 assert_eq!(TaskStatus::Active.as_str(), "active");
180 assert_eq!(TaskStatus::Active.to_string(), "active");
181 assert_eq!(
182 TaskStatus::parse("blocked").unwrap_err().to_string(),
183 "error invalid-status input=blocked choices=inbox,backlog,todo,active,done,canceled"
184 );
185
186 assert_eq!(TaskPriority::parse("urgent").unwrap(), TaskPriority::Urgent);
187 assert_eq!(TaskPriority::Urgent.as_str(), "urgent");
188 assert_eq!(TaskPriority::Urgent.to_string(), "urgent");
189 assert_eq!(
190 TaskPriority::parse("soon").unwrap_err().to_string(),
191 "error invalid-priority input=soon choices=none,low,medium,high,urgent"
192 );
193 }
194}