1use crate::error::{PachaError, Result};
4use serde::{Deserialize, Serialize};
5use std::fmt;
6use std::str::FromStr;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
10#[serde(rename_all = "lowercase")]
11pub enum ModelStage {
12 #[default]
14 Development,
15 Staging,
17 Production,
19 Archived,
21}
22
23impl ModelStage {
24 #[must_use]
26 pub fn all() -> &'static [ModelStage] {
27 &[
28 ModelStage::Development,
29 ModelStage::Staging,
30 ModelStage::Production,
31 ModelStage::Archived,
32 ]
33 }
34
35 #[must_use]
43 pub fn can_transition_to(&self, target: ModelStage) -> bool {
44 if *self == target {
45 return true; }
47
48 match self {
49 Self::Development => matches!(target, Self::Staging | Self::Archived),
50 Self::Staging => {
51 matches!(target, Self::Development | Self::Production | Self::Archived)
52 }
53 Self::Production => matches!(target, Self::Staging | Self::Archived),
54 Self::Archived => matches!(target, Self::Development),
55 }
56 }
57
58 pub fn transition_to(&self, target: ModelStage) -> Result<ModelStage> {
64 if self.can_transition_to(target) {
65 Ok(target)
66 } else {
67 Err(PachaError::InvalidStageTransition {
68 from: self.to_string(),
69 to: target.to_string(),
70 })
71 }
72 }
73
74 #[must_use]
76 pub fn is_mutable(&self) -> bool {
77 matches!(self, Self::Development)
78 }
79
80 #[must_use]
82 pub fn is_active(&self) -> bool {
83 !matches!(self, Self::Archived)
84 }
85}
86
87impl fmt::Display for ModelStage {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 let s = match self {
90 Self::Development => "development",
91 Self::Staging => "staging",
92 Self::Production => "production",
93 Self::Archived => "archived",
94 };
95 write!(f, "{s}")
96 }
97}
98
99impl FromStr for ModelStage {
100 type Err = PachaError;
101
102 fn from_str(s: &str) -> Result<Self> {
103 match s.to_lowercase().as_str() {
104 "development" | "dev" => Ok(Self::Development),
105 "staging" | "stage" => Ok(Self::Staging),
106 "production" | "prod" => Ok(Self::Production),
107 "archived" | "archive" => Ok(Self::Archived),
108 _ => Err(PachaError::Validation(format!("unknown stage: {s}"))),
109 }
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn test_stage_display() {
119 assert_eq!(ModelStage::Development.to_string(), "development");
120 assert_eq!(ModelStage::Staging.to_string(), "staging");
121 assert_eq!(ModelStage::Production.to_string(), "production");
122 assert_eq!(ModelStage::Archived.to_string(), "archived");
123 }
124
125 #[test]
126 fn test_stage_parse() {
127 assert_eq!("development".parse::<ModelStage>().unwrap(), ModelStage::Development);
128 assert_eq!("dev".parse::<ModelStage>().unwrap(), ModelStage::Development);
129 assert_eq!("staging".parse::<ModelStage>().unwrap(), ModelStage::Staging);
130 assert_eq!("stage".parse::<ModelStage>().unwrap(), ModelStage::Staging);
131 assert_eq!("production".parse::<ModelStage>().unwrap(), ModelStage::Production);
132 assert_eq!("prod".parse::<ModelStage>().unwrap(), ModelStage::Production);
133 assert_eq!("archived".parse::<ModelStage>().unwrap(), ModelStage::Archived);
134 }
135
136 #[test]
137 fn test_stage_parse_error() {
138 assert!("invalid".parse::<ModelStage>().is_err());
139 assert!("".parse::<ModelStage>().is_err());
140 }
141
142 #[test]
143 fn test_valid_transitions_from_development() {
144 let dev = ModelStage::Development;
145 assert!(dev.can_transition_to(ModelStage::Development));
146 assert!(dev.can_transition_to(ModelStage::Staging));
147 assert!(!dev.can_transition_to(ModelStage::Production)); assert!(dev.can_transition_to(ModelStage::Archived));
149 }
150
151 #[test]
152 fn test_valid_transitions_from_staging() {
153 let staging = ModelStage::Staging;
154 assert!(staging.can_transition_to(ModelStage::Development)); assert!(staging.can_transition_to(ModelStage::Staging));
156 assert!(staging.can_transition_to(ModelStage::Production));
157 assert!(staging.can_transition_to(ModelStage::Archived));
158 }
159
160 #[test]
161 fn test_valid_transitions_from_production() {
162 let prod = ModelStage::Production;
163 assert!(!prod.can_transition_to(ModelStage::Development)); assert!(prod.can_transition_to(ModelStage::Staging)); assert!(prod.can_transition_to(ModelStage::Production));
166 assert!(prod.can_transition_to(ModelStage::Archived));
167 }
168
169 #[test]
170 fn test_valid_transitions_from_archived() {
171 let archived = ModelStage::Archived;
172 assert!(archived.can_transition_to(ModelStage::Development)); assert!(!archived.can_transition_to(ModelStage::Staging));
174 assert!(!archived.can_transition_to(ModelStage::Production));
175 assert!(archived.can_transition_to(ModelStage::Archived));
176 }
177
178 #[test]
179 fn test_transition_to_success() {
180 let dev = ModelStage::Development;
181 let result = dev.transition_to(ModelStage::Staging);
182 assert_eq!(result.unwrap(), ModelStage::Staging);
183 }
184
185 #[test]
186 fn test_transition_to_error() {
187 let dev = ModelStage::Development;
188 let result = dev.transition_to(ModelStage::Production);
189 assert!(matches!(result, Err(PachaError::InvalidStageTransition { .. })));
190 }
191
192 #[test]
193 fn test_is_mutable() {
194 assert!(ModelStage::Development.is_mutable());
195 assert!(!ModelStage::Staging.is_mutable());
196 assert!(!ModelStage::Production.is_mutable());
197 assert!(!ModelStage::Archived.is_mutable());
198 }
199
200 #[test]
201 fn test_is_active() {
202 assert!(ModelStage::Development.is_active());
203 assert!(ModelStage::Staging.is_active());
204 assert!(ModelStage::Production.is_active());
205 assert!(!ModelStage::Archived.is_active());
206 }
207
208 #[test]
209 fn test_serialization() {
210 let stage = ModelStage::Production;
211 let json = serde_json::to_string(&stage).unwrap();
212 assert_eq!(json, "\"production\"");
213
214 let deserialized: ModelStage = serde_json::from_str(&json).unwrap();
215 assert_eq!(deserialized, ModelStage::Production);
216 }
217
218 #[test]
219 fn test_all_stages() {
220 let all = ModelStage::all();
221 assert_eq!(all.len(), 4);
222 assert!(all.contains(&ModelStage::Development));
223 assert!(all.contains(&ModelStage::Staging));
224 assert!(all.contains(&ModelStage::Production));
225 assert!(all.contains(&ModelStage::Archived));
226 }
227}