use serde::Serialize;
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum TaskStatus {
Active,
Archived,
}
impl TaskStatus {
pub const ACTIVE: &'static str = "active";
pub const ARCHIVED: &'static str = "archived";
pub fn as_str(&self) -> &str {
match self {
TaskStatus::Active => Self::ACTIVE,
TaskStatus::Archived => Self::ARCHIVED,
}
}
pub fn can_transition_to(self, target: Self) -> bool {
matches!(
(self, target),
(TaskStatus::Active, TaskStatus::Active)
| (TaskStatus::Active, TaskStatus::Archived)
| (TaskStatus::Archived, TaskStatus::Archived)
)
}
}
impl FromStr for TaskStatus {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
Self::ACTIVE => Ok(TaskStatus::Active),
Self::ARCHIVED => Ok(TaskStatus::Archived),
_ => Err(format!("Invalid TaskStatus: {}", s)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum TodoStatus {
Pending,
Done,
Cancelled,
}
impl TodoStatus {
pub const PENDING: &'static str = "pending";
pub const DONE: &'static str = "done";
pub const CANCELLED: &'static str = "cancelled";
pub fn as_str(&self) -> &str {
match self {
TodoStatus::Pending => Self::PENDING,
TodoStatus::Done => Self::DONE,
TodoStatus::Cancelled => Self::CANCELLED,
}
}
pub fn can_transition_to(self, target: Self) -> bool {
if Self::is_reopen_attempt(self, target) {
return false;
}
matches!(
(self, target),
(TodoStatus::Pending, TodoStatus::Pending)
| (TodoStatus::Pending, TodoStatus::Done)
| (TodoStatus::Pending, TodoStatus::Cancelled)
| (TodoStatus::Done, TodoStatus::Done)
| (TodoStatus::Cancelled, TodoStatus::Cancelled)
)
}
pub fn is_reopen_attempt(from: Self, to: Self) -> bool {
from != Self::Pending && to == Self::Pending
}
pub fn is_terminal(self) -> bool {
matches!(self, Self::Done | Self::Cancelled)
}
}
impl FromStr for TodoStatus {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
Self::PENDING => Ok(TodoStatus::Pending),
Self::DONE => Ok(TodoStatus::Done),
Self::CANCELLED => Ok(TodoStatus::Cancelled),
_ => Err(format!("Invalid TodoStatus: {}", s)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_task_status_as_str() {
assert_eq!(TaskStatus::Active.as_str(), "active");
assert_eq!(TaskStatus::Archived.as_str(), "archived");
}
#[test]
fn test_task_status_from_str() {
assert!(matches!(
"active".parse::<TaskStatus>(),
Ok(TaskStatus::Active)
));
assert!(matches!(
"archived".parse::<TaskStatus>(),
Ok(TaskStatus::Archived)
));
assert!("invalid".parse::<TaskStatus>().is_err());
}
#[test]
fn test_task_status_transitions() {
assert!(TaskStatus::Active.can_transition_to(TaskStatus::Archived));
assert!(!TaskStatus::Archived.can_transition_to(TaskStatus::Active));
assert!(TaskStatus::Archived.can_transition_to(TaskStatus::Archived));
}
#[test]
fn test_todo_status_as_str() {
assert_eq!(TodoStatus::Pending.as_str(), "pending");
assert_eq!(TodoStatus::Done.as_str(), "done");
assert_eq!(TodoStatus::Cancelled.as_str(), "cancelled");
}
#[test]
fn test_todo_status_from_str() {
assert!(matches!(
"pending".parse::<TodoStatus>(),
Ok(TodoStatus::Pending)
));
assert!(matches!("done".parse::<TodoStatus>(), Ok(TodoStatus::Done)));
assert!(matches!(
"cancelled".parse::<TodoStatus>(),
Ok(TodoStatus::Cancelled)
));
assert!("invalid".parse::<TodoStatus>().is_err());
}
#[test]
fn test_todo_status_reopen_is_forbidden() {
assert!(TodoStatus::is_reopen_attempt(
TodoStatus::Done,
TodoStatus::Pending
));
assert!(TodoStatus::is_reopen_attempt(
TodoStatus::Cancelled,
TodoStatus::Pending
));
assert!(!TodoStatus::is_reopen_attempt(
TodoStatus::Pending,
TodoStatus::Pending
));
assert!(!TodoStatus::can_transition_to(
TodoStatus::Done,
TodoStatus::Pending
));
assert!(!TodoStatus::can_transition_to(
TodoStatus::Cancelled,
TodoStatus::Pending
));
assert!(TodoStatus::Done.is_terminal());
assert!(TodoStatus::Cancelled.is_terminal());
assert!(!TodoStatus::Pending.is_terminal());
}
}