1use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, strum::Display)]
18#[serde(rename_all = "lowercase")]
19#[strum(serialize_all = "lowercase")]
20pub enum DocumentState {
21 Draft,
27
28 Review,
34
35 Frozen,
42
43 Published,
50}
51
52impl DocumentState {
53 #[must_use]
55 pub const fn is_editable(&self) -> bool {
56 matches!(self, Self::Draft | Self::Review)
57 }
58
59 #[must_use]
61 pub const fn is_immutable(&self) -> bool {
62 matches!(self, Self::Frozen | Self::Published)
63 }
64
65 #[must_use]
67 pub const fn requires_signature(&self) -> bool {
68 matches!(self, Self::Frozen | Self::Published)
69 }
70
71 #[must_use]
78 pub const fn requires_lineage(&self) -> bool {
79 matches!(self, Self::Frozen | Self::Published)
80 }
81
82 #[must_use]
84 pub const fn requires_computed_id(&self) -> bool {
85 matches!(self, Self::Review | Self::Frozen | Self::Published)
86 }
87
88 #[must_use]
93 pub const fn requires_precise_layout(&self) -> bool {
94 matches!(self, Self::Frozen | Self::Published)
95 }
96
97 #[must_use]
99 pub const fn can_transition_to(&self, target: Self) -> bool {
100 use DocumentState::{Draft, Frozen, Published, Review};
101 matches!(
102 (self, target),
103 (Draft, Review) | (Review, Frozen | Draft) | (Frozen, Published)
104 )
105 }
106
107 #[must_use]
109 pub const fn valid_transitions(&self) -> &'static [DocumentState] {
110 use DocumentState::{Draft, Frozen, Published, Review};
111 match self {
112 Draft => &[Review],
113 Review => &[Draft, Frozen],
114 Frozen => &[Published],
115 Published => &[],
116 }
117 }
118}
119
120impl std::str::FromStr for DocumentState {
121 type Err = crate::Error;
122
123 fn from_str(s: &str) -> Result<Self, Self::Err> {
124 match s.to_lowercase().as_str() {
125 "draft" => Ok(Self::Draft),
126 "review" => Ok(Self::Review),
127 "frozen" => Ok(Self::Frozen),
128 "published" => Ok(Self::Published),
129 _ => Err(crate::Error::InvalidManifest {
130 reason: format!("invalid state: {s}"),
131 }),
132 }
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn test_editability() {
142 assert!(DocumentState::Draft.is_editable());
143 assert!(DocumentState::Review.is_editable());
144 assert!(!DocumentState::Frozen.is_editable());
145 assert!(!DocumentState::Published.is_editable());
146 }
147
148 #[test]
149 fn test_immutability() {
150 assert!(!DocumentState::Draft.is_immutable());
151 assert!(!DocumentState::Review.is_immutable());
152 assert!(DocumentState::Frozen.is_immutable());
153 assert!(DocumentState::Published.is_immutable());
154 }
155
156 #[test]
157 fn test_valid_transitions() {
158 use DocumentState::{Draft, Frozen, Published, Review};
159
160 assert!(Draft.can_transition_to(Review));
162 assert!(!Draft.can_transition_to(Frozen));
163 assert!(!Draft.can_transition_to(Published));
164
165 assert!(Review.can_transition_to(Draft));
167 assert!(Review.can_transition_to(Frozen));
168 assert!(!Review.can_transition_to(Published));
169
170 assert!(!Frozen.can_transition_to(Draft));
172 assert!(!Frozen.can_transition_to(Review));
173 assert!(Frozen.can_transition_to(Published));
174
175 assert!(!Published.can_transition_to(Draft));
177 assert!(!Published.can_transition_to(Review));
178 assert!(!Published.can_transition_to(Frozen));
179 }
180
181 #[test]
182 fn test_serialization() {
183 let state = DocumentState::Frozen;
184 let json = serde_json::to_string(&state).unwrap();
185 assert_eq!(json, "\"frozen\"");
186
187 let parsed: DocumentState = serde_json::from_str("\"review\"").unwrap();
188 assert_eq!(parsed, DocumentState::Review);
189 }
190
191 #[test]
192 fn test_display() {
193 assert_eq!(DocumentState::Draft.to_string(), "draft");
194 assert_eq!(DocumentState::Published.to_string(), "published");
195 }
196
197 #[test]
198 fn test_from_str() {
199 assert_eq!(
200 "draft".parse::<DocumentState>().unwrap(),
201 DocumentState::Draft
202 );
203 assert_eq!(
204 "FROZEN".parse::<DocumentState>().unwrap(),
205 DocumentState::Frozen
206 );
207 assert!("invalid".parse::<DocumentState>().is_err());
208 }
209
210 #[test]
211 fn test_precise_layout_requirement() {
212 assert!(!DocumentState::Draft.requires_precise_layout());
213 assert!(!DocumentState::Review.requires_precise_layout());
214 assert!(DocumentState::Frozen.requires_precise_layout());
215 assert!(DocumentState::Published.requires_precise_layout());
216 }
217}