adrscope/domain/
status.rs

1//! ADR status lifecycle states.
2//!
3//! Status represents the lifecycle state of an Architecture Decision Record,
4//! following the structured-madr specification.
5
6use serde::{Deserialize, Serialize};
7
8/// The status of an Architecture Decision Record in its lifecycle.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
10#[serde(rename_all = "lowercase")]
11pub enum Status {
12    /// The decision is under discussion and not yet finalized.
13    #[default]
14    Proposed,
15    /// The decision has been accepted and is in effect.
16    Accepted,
17    /// The decision has been deprecated and should not be used for new work.
18    Deprecated,
19    /// The decision has been replaced by a newer decision.
20    Superseded,
21}
22
23impl Status {
24    /// Returns the status as a lowercase string.
25    #[must_use]
26    pub const fn as_str(&self) -> &'static str {
27        match self {
28            Self::Proposed => "proposed",
29            Self::Accepted => "accepted",
30            Self::Deprecated => "deprecated",
31            Self::Superseded => "superseded",
32        }
33    }
34
35    /// Returns the CSS class name for styling this status.
36    #[must_use]
37    pub const fn css_class(&self) -> &'static str {
38        match self {
39            Self::Proposed => "status-proposed",
40            Self::Accepted => "status-accepted",
41            Self::Deprecated => "status-deprecated",
42            Self::Superseded => "status-superseded",
43        }
44    }
45
46    /// Returns the display color for this status in hex format.
47    #[must_use]
48    pub const fn color(&self) -> &'static str {
49        match self {
50            Self::Proposed => "#f59e0b",   // amber
51            Self::Accepted => "#10b981",   // green
52            Self::Deprecated => "#ef4444", // red
53            Self::Superseded => "#6b7280", // gray
54        }
55    }
56
57    /// Returns all possible status values.
58    #[must_use]
59    pub const fn all() -> &'static [Self] {
60        &[
61            Self::Proposed,
62            Self::Accepted,
63            Self::Deprecated,
64            Self::Superseded,
65        ]
66    }
67}
68
69impl std::fmt::Display for Status {
70    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71        write!(f, "{}", self.as_str())
72    }
73}
74
75impl std::str::FromStr for Status {
76    type Err = String;
77
78    fn from_str(s: &str) -> Result<Self, Self::Err> {
79        match s.to_lowercase().as_str() {
80            "proposed" => Ok(Self::Proposed),
81            "accepted" => Ok(Self::Accepted),
82            "deprecated" => Ok(Self::Deprecated),
83            "superseded" => Ok(Self::Superseded),
84            _ => Err(format!("invalid status: {s}")),
85        }
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn test_status_as_str() {
95        assert_eq!(Status::Proposed.as_str(), "proposed");
96        assert_eq!(Status::Accepted.as_str(), "accepted");
97        assert_eq!(Status::Deprecated.as_str(), "deprecated");
98        assert_eq!(Status::Superseded.as_str(), "superseded");
99    }
100
101    #[test]
102    fn test_status_default() {
103        assert_eq!(Status::default(), Status::Proposed);
104    }
105
106    #[test]
107    fn test_status_from_str() {
108        assert_eq!("proposed".parse::<Status>().ok(), Some(Status::Proposed));
109        assert_eq!("ACCEPTED".parse::<Status>().ok(), Some(Status::Accepted));
110        assert_eq!(
111            "Deprecated".parse::<Status>().ok(),
112            Some(Status::Deprecated)
113        );
114        assert!("invalid".parse::<Status>().is_err());
115    }
116
117    #[test]
118    fn test_status_display() {
119        assert_eq!(format!("{}", Status::Accepted), "accepted");
120    }
121
122    #[test]
123    fn test_status_css_class() {
124        assert_eq!(Status::Proposed.css_class(), "status-proposed");
125        assert_eq!(Status::Accepted.css_class(), "status-accepted");
126        assert_eq!(Status::Deprecated.css_class(), "status-deprecated");
127        assert_eq!(Status::Superseded.css_class(), "status-superseded");
128    }
129
130    #[test]
131    fn test_status_color() {
132        assert_eq!(Status::Proposed.color(), "#f59e0b");
133        assert_eq!(Status::Accepted.color(), "#10b981");
134        assert_eq!(Status::Deprecated.color(), "#ef4444");
135        assert_eq!(Status::Superseded.color(), "#6b7280");
136    }
137
138    #[test]
139    fn test_status_all() {
140        let all = Status::all();
141        assert_eq!(all.len(), 4);
142        assert!(all.contains(&Status::Proposed));
143        assert!(all.contains(&Status::Accepted));
144        assert!(all.contains(&Status::Deprecated));
145        assert!(all.contains(&Status::Superseded));
146    }
147
148    #[test]
149    fn test_status_serialization() {
150        let status = Status::Accepted;
151        let json = serde_json::to_string(&status).ok();
152        assert_eq!(json, Some("\"accepted\"".to_string()));
153    }
154
155    #[test]
156    fn test_status_deserialization() {
157        let status: Status = serde_json::from_str("\"proposed\"").expect("should parse");
158        assert_eq!(status, Status::Proposed);
159    }
160}