Skip to main content

aven_core/
choices.rs

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}