use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InvalidTaskStatus(String);
impl fmt::Display for InvalidTaskStatus {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"error invalid-status input={} choices={}",
self.0,
STATUSES.join(",")
)
}
}
impl std::error::Error for InvalidTaskStatus {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InvalidTaskPriority(String);
impl fmt::Display for InvalidTaskPriority {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"error invalid-priority input={} choices={}",
self.0,
PRIORITIES.join(",")
)
}
}
impl std::error::Error for InvalidTaskPriority {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TaskStatus {
Inbox,
Backlog,
Todo,
Active,
Done,
Canceled,
}
impl TaskStatus {
#[allow(dead_code)]
pub const OPEN: [Self; 4] = [Self::Inbox, Self::Backlog, Self::Todo, Self::Active];
#[allow(dead_code)]
pub const TERMINAL: [Self; 2] = [Self::Done, Self::Canceled];
pub const fn is_open(self) -> bool {
matches!(
self,
Self::Inbox | Self::Backlog | Self::Todo | Self::Active
)
}
#[allow(dead_code)]
pub const fn is_terminal(self) -> bool {
matches!(self, Self::Done | Self::Canceled)
}
pub const fn as_str(self) -> &'static str {
match self {
Self::Inbox => "inbox",
Self::Backlog => "backlog",
Self::Todo => "todo",
Self::Active => "active",
Self::Done => "done",
Self::Canceled => "canceled",
}
}
pub fn parse(value: &str) -> Result<Self, InvalidTaskStatus> {
match value {
"inbox" => Ok(Self::Inbox),
"backlog" => Ok(Self::Backlog),
"todo" => Ok(Self::Todo),
"active" => Ok(Self::Active),
"done" => Ok(Self::Done),
"canceled" => Ok(Self::Canceled),
_ => Err(InvalidTaskStatus(value.to_string())),
}
}
}
impl std::fmt::Display for TaskStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl TryFrom<&str> for TaskStatus {
type Error = InvalidTaskStatus;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::parse(value)
}
}
pub const STATUSES: &[&str] = &[
TaskStatus::Inbox.as_str(),
TaskStatus::Backlog.as_str(),
TaskStatus::Todo.as_str(),
TaskStatus::Active.as_str(),
TaskStatus::Done.as_str(),
TaskStatus::Canceled.as_str(),
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TaskPriority {
None,
Low,
Medium,
High,
Urgent,
}
impl TaskPriority {
pub const ALL: [Self; 5] = [
Self::None,
Self::Low,
Self::Medium,
Self::High,
Self::Urgent,
];
pub const fn as_str(self) -> &'static str {
match self {
Self::None => "none",
Self::Low => "low",
Self::Medium => "medium",
Self::High => "high",
Self::Urgent => "urgent",
}
}
pub fn parse(value: &str) -> Result<Self, InvalidTaskPriority> {
match value {
"none" => Ok(Self::None),
"low" => Ok(Self::Low),
"medium" => Ok(Self::Medium),
"high" => Ok(Self::High),
"urgent" => Ok(Self::Urgent),
_ => Err(InvalidTaskPriority(value.to_string())),
}
}
}
impl std::fmt::Display for TaskPriority {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl TryFrom<&str> for TaskPriority {
type Error = InvalidTaskPriority;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::parse(value)
}
}
pub const PRIORITIES: &[&str] = &[
TaskPriority::None.as_str(),
TaskPriority::Low.as_str(),
TaskPriority::Medium.as_str(),
TaskPriority::High.as_str(),
TaskPriority::Urgent.as_str(),
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn status_and_priority_parse_display_and_reject_invalid_values() {
assert_eq!(TaskStatus::parse("active").unwrap(), TaskStatus::Active);
assert_eq!(TaskStatus::Active.as_str(), "active");
assert_eq!(TaskStatus::Active.to_string(), "active");
assert_eq!(
TaskStatus::parse("blocked").unwrap_err().to_string(),
"error invalid-status input=blocked choices=inbox,backlog,todo,active,done,canceled"
);
assert_eq!(TaskPriority::parse("urgent").unwrap(), TaskPriority::Urgent);
assert_eq!(TaskPriority::Urgent.as_str(), "urgent");
assert_eq!(TaskPriority::Urgent.to_string(), "urgent");
assert_eq!(
TaskPriority::parse("soon").unwrap_err().to_string(),
"error invalid-priority input=soon choices=none,low,medium,high,urgent"
);
}
}