entrenar/storage/registry/
stage.rs1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
7pub enum ModelStage {
8 None,
10 Development,
12 Staging,
14 Production,
16 Archived,
18}
19
20impl ModelStage {
21 pub fn can_transition_to(&self, target: ModelStage) -> bool {
23 match (self, target) {
24 (
26 ModelStage::None
27 | ModelStage::Development
28 | ModelStage::Staging
29 | ModelStage::Production
30 | ModelStage::Archived,
31 ModelStage::Archived,
32 ) => true,
33 (ModelStage::None, ModelStage::Development) => true,
35 (ModelStage::Development, ModelStage::Staging) => true,
37 (ModelStage::Staging, ModelStage::Production) => true,
39 (ModelStage::Production, ModelStage::Staging) => true,
41 (ModelStage::Staging, ModelStage::Development) => true,
43 (ModelStage::Archived, ModelStage::Development) => true,
45 (ModelStage::None, ModelStage::None)
47 | (ModelStage::Development, ModelStage::Development)
48 | (ModelStage::Staging, ModelStage::Staging)
49 | (ModelStage::Production, ModelStage::Production) => true,
50 (
52 ModelStage::Development
53 | ModelStage::Staging
54 | ModelStage::Production
55 | ModelStage::Archived,
56 ModelStage::None,
57 )
58 | (ModelStage::Production, ModelStage::Development)
59 | (ModelStage::None | ModelStage::Archived, ModelStage::Staging)
60 | (
61 ModelStage::None | ModelStage::Development | ModelStage::Archived,
62 ModelStage::Production,
63 ) => false,
64 }
65 }
66
67 pub fn as_str(&self) -> &'static str {
69 match self {
70 ModelStage::None => "None",
71 ModelStage::Development => "Development",
72 ModelStage::Staging => "Staging",
73 ModelStage::Production => "Production",
74 ModelStage::Archived => "Archived",
75 }
76 }
77}
78
79impl std::fmt::Display for ModelStage {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 write!(f, "{}", self.as_str())
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn test_stage_none_to_development() {
91 assert!(ModelStage::None.can_transition_to(ModelStage::Development));
92 }
93
94 #[test]
95 fn test_stage_development_to_staging() {
96 assert!(ModelStage::Development.can_transition_to(ModelStage::Staging));
97 }
98
99 #[test]
100 fn test_stage_staging_to_production() {
101 assert!(ModelStage::Staging.can_transition_to(ModelStage::Production));
102 }
103
104 #[test]
105 fn test_stage_production_rollback_to_staging() {
106 assert!(ModelStage::Production.can_transition_to(ModelStage::Staging));
107 }
108
109 #[test]
110 fn test_stage_any_to_archived() {
111 assert!(ModelStage::None.can_transition_to(ModelStage::Archived));
112 assert!(ModelStage::Development.can_transition_to(ModelStage::Archived));
113 assert!(ModelStage::Staging.can_transition_to(ModelStage::Archived));
114 assert!(ModelStage::Production.can_transition_to(ModelStage::Archived));
115 }
116
117 #[test]
118 fn test_stage_invalid_transitions() {
119 assert!(!ModelStage::None.can_transition_to(ModelStage::Production));
120 assert!(!ModelStage::Development.can_transition_to(ModelStage::Production));
121 }
122
123 #[test]
124 fn test_stage_display() {
125 assert_eq!(ModelStage::Production.to_string(), "Production");
126 assert_eq!(ModelStage::Development.as_str(), "Development");
127 }
128
129 #[test]
130 fn test_stage_staging_to_development_rejected() {
131 assert!(ModelStage::Staging.can_transition_to(ModelStage::Development));
132 }
133
134 #[test]
135 fn test_stage_archived_to_development_restore() {
136 assert!(ModelStage::Archived.can_transition_to(ModelStage::Development));
137 }
138
139 #[test]
140 fn test_stage_same_stage_noop() {
141 assert!(ModelStage::Development.can_transition_to(ModelStage::Development));
142 assert!(ModelStage::Staging.can_transition_to(ModelStage::Staging));
143 assert!(ModelStage::Production.can_transition_to(ModelStage::Production));
144 assert!(ModelStage::Archived.can_transition_to(ModelStage::Archived));
145 assert!(ModelStage::None.can_transition_to(ModelStage::None));
146 }
147
148 #[test]
149 fn test_stage_invalid_none_to_staging() {
150 assert!(!ModelStage::None.can_transition_to(ModelStage::Staging));
151 }
152
153 #[test]
154 fn test_stage_invalid_archived_to_staging() {
155 assert!(!ModelStage::Archived.can_transition_to(ModelStage::Staging));
156 }
157
158 #[test]
159 fn test_stage_invalid_archived_to_production() {
160 assert!(!ModelStage::Archived.can_transition_to(ModelStage::Production));
161 }
162
163 #[test]
164 fn test_as_str_all_stages() {
165 assert_eq!(ModelStage::None.as_str(), "None");
166 assert_eq!(ModelStage::Staging.as_str(), "Staging");
167 assert_eq!(ModelStage::Archived.as_str(), "Archived");
168 }
169
170 #[test]
171 fn test_display_all_stages() {
172 assert_eq!(format!("{}", ModelStage::None), "None");
173 assert_eq!(format!("{}", ModelStage::Development), "Development");
174 assert_eq!(format!("{}", ModelStage::Staging), "Staging");
175 assert_eq!(format!("{}", ModelStage::Archived), "Archived");
176 }
177
178 #[test]
179 fn test_stage_serialization() {
180 let stage = ModelStage::Production;
181 let json = serde_json::to_string(&stage).expect("JSON serialization should succeed");
182 assert!(json.contains("Production"));
183 }
184
185 #[test]
186 fn test_stage_deserialization() {
187 let json = "\"Staging\"";
188 let stage: ModelStage =
189 serde_json::from_str(json).expect("JSON deserialization should succeed");
190 assert_eq!(stage, ModelStage::Staging);
191 }
192
193 #[test]
194 fn test_stage_roundtrip() {
195 let stages = [
196 ModelStage::None,
197 ModelStage::Development,
198 ModelStage::Staging,
199 ModelStage::Production,
200 ModelStage::Archived,
201 ];
202 for stage in stages {
203 let json = serde_json::to_string(&stage).expect("JSON serialization should succeed");
204 let deserialized: ModelStage =
205 serde_json::from_str(&json).expect("JSON deserialization should succeed");
206 assert_eq!(stage, deserialized);
207 }
208 }
209
210 #[test]
211 fn test_stage_clone() {
212 let stage = ModelStage::Development;
213 let cloned = stage;
214 assert_eq!(stage, cloned);
215 }
216
217 #[test]
218 fn test_stage_copy() {
219 let stage = ModelStage::Production;
220 let copied = stage;
221 assert_eq!(stage, copied);
222 }
223
224 #[test]
225 fn test_stage_debug() {
226 let stage = ModelStage::Staging;
227 let debug = format!("{stage:?}");
228 assert!(debug.contains("Staging"));
229 }
230
231 #[test]
232 fn test_stage_hash() {
233 use std::collections::HashSet;
234 let mut set = HashSet::new();
235 set.insert(ModelStage::Development);
236 set.insert(ModelStage::Production);
237 assert_eq!(set.len(), 2);
238 }
239}
240
241#[cfg(test)]
242mod property_tests {
243 use super::*;
244 use proptest::prelude::*;
245
246 proptest! {
247 #![proptest_config(ProptestConfig::with_cases(200))]
248
249 #[test]
250 fn prop_stage_self_transition(stage in any::<u8>().prop_map(|n| match n % 5 {
251 0 => ModelStage::None,
252 1 => ModelStage::Development,
253 2 => ModelStage::Staging,
254 3 => ModelStage::Production,
255 _ => ModelStage::Archived,
256 })) {
257 prop_assert!(stage.can_transition_to(stage));
259 }
260
261 #[test]
262 fn prop_all_stages_can_archive(stage in any::<u8>().prop_map(|n| match n % 5 {
263 0 => ModelStage::None,
264 1 => ModelStage::Development,
265 2 => ModelStage::Staging,
266 3 => ModelStage::Production,
267 _ => ModelStage::Archived,
268 })) {
269 prop_assert!(stage.can_transition_to(ModelStage::Archived));
271 }
272 }
273}