1#[allow(dead_code)]
4#[derive(Clone, PartialEq, Debug)]
5pub struct SemVer {
6 pub major: u32,
7 pub minor: u32,
8 pub patch: u32,
9}
10
11#[allow(dead_code)]
12#[derive(Clone)]
13pub struct MigrationStep {
14 pub from_version: SemVer,
15 pub to_version: SemVer,
16 pub description: String,
17 pub breaking: bool,
18}
19
20#[allow(dead_code)]
21pub struct MigrationPlan {
22 pub steps: Vec<MigrationStep>,
23 pub source: SemVer,
24 pub target: SemVer,
25}
26
27#[allow(dead_code)]
28pub struct MigrationRegistry {
29 pub steps: Vec<MigrationStep>,
30}
31
32#[allow(dead_code)]
33pub fn new_semver(major: u32, minor: u32, patch: u32) -> SemVer {
34 SemVer {
35 major,
36 minor,
37 patch,
38 }
39}
40
41#[allow(dead_code)]
42pub fn semver_parse(s: &str) -> Option<SemVer> {
43 let parts: Vec<&str> = s.splitn(3, '.').collect();
44 if parts.len() != 3 {
45 return None;
46 }
47 let major = parts[0].parse::<u32>().ok()?;
48 let minor = parts[1].parse::<u32>().ok()?;
49 let patch = parts[2].parse::<u32>().ok()?;
50 Some(SemVer {
51 major,
52 minor,
53 patch,
54 })
55}
56
57#[allow(dead_code)]
58pub fn semver_compare(a: &SemVer, b: &SemVer) -> std::cmp::Ordering {
59 a.major
60 .cmp(&b.major)
61 .then(a.minor.cmp(&b.minor))
62 .then(a.patch.cmp(&b.patch))
63}
64
65#[allow(dead_code)]
66pub fn semver_to_string(v: &SemVer) -> String {
67 format!("{}.{}.{}", v.major, v.minor, v.patch)
68}
69
70#[allow(dead_code)]
71pub fn is_breaking_change(from: &SemVer, to: &SemVer) -> bool {
72 to.major > from.major
73}
74
75#[allow(dead_code)]
76pub fn new_migration_registry() -> MigrationRegistry {
77 MigrationRegistry { steps: Vec::new() }
78}
79
80#[allow(dead_code)]
81pub fn register_migration(registry: &mut MigrationRegistry, step: MigrationStep) {
82 registry.steps.push(step);
83}
84
85#[allow(dead_code)]
87pub fn plan_migration(
88 registry: &MigrationRegistry,
89 from: &SemVer,
90 to: &SemVer,
91) -> Option<MigrationPlan> {
92 if from == to {
93 return Some(MigrationPlan {
94 steps: Vec::new(),
95 source: from.clone(),
96 target: to.clone(),
97 });
98 }
99
100 use std::collections::{HashMap, VecDeque};
102
103 let mut queue: VecDeque<SemVer> = VecDeque::new();
104 let mut prev: HashMap<String, (SemVer, usize)> = HashMap::new();
106
107 queue.push_back(from.clone());
108
109 while let Some(current) = queue.pop_front() {
110 for (idx, step) in registry.steps.iter().enumerate() {
111 if step.from_version == current {
112 let next = step.to_version.clone();
113 let next_key = semver_to_string(&next);
114 if !prev.contains_key(&next_key)
115 && semver_to_string(&next) != semver_to_string(from)
116 {
117 prev.insert(next_key.clone(), (current.clone(), idx));
118 if &next == to {
119 let mut path_steps: Vec<MigrationStep> = Vec::new();
121 let mut cur = next;
122 loop {
123 let cur_key = semver_to_string(&cur);
124 if let Some((pred, sidx)) = prev.get(&cur_key) {
125 path_steps.push(registry.steps[*sidx].clone());
126 if pred == from {
127 break;
128 }
129 cur = pred.clone();
130 } else {
131 break;
132 }
133 }
134 path_steps.reverse();
135 return Some(MigrationPlan {
136 steps: path_steps,
137 source: from.clone(),
138 target: to.clone(),
139 });
140 }
141 queue.push_back(next);
142 }
143 }
144 }
145 }
146 None
147}
148
149#[allow(dead_code)]
150pub fn has_migration_path(registry: &MigrationRegistry, from: &SemVer, to: &SemVer) -> bool {
151 plan_migration(registry, from, to).is_some()
152}
153
154#[allow(dead_code)]
155pub fn migration_step_count(plan: &MigrationPlan) -> usize {
156 plan.steps.len()
157}
158
159#[allow(dead_code)]
160pub fn plan_has_breaking(plan: &MigrationPlan) -> bool {
161 plan.steps.iter().any(|s| s.breaking)
162}
163
164#[allow(dead_code)]
165pub fn migration_description(plan: &MigrationPlan) -> Vec<&str> {
166 plan.steps.iter().map(|s| s.description.as_str()).collect()
167}
168
169#[allow(dead_code)]
170pub fn latest_version(registry: &MigrationRegistry) -> Option<SemVer> {
171 registry
172 .steps
173 .iter()
174 .map(|s| &s.to_version)
175 .max_by(|a, b| semver_compare(a, b))
176 .cloned()
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 fn v(major: u32, minor: u32, patch: u32) -> SemVer {
184 new_semver(major, minor, patch)
185 }
186
187 fn make_step(from: SemVer, to: SemVer, desc: &str, breaking: bool) -> MigrationStep {
188 MigrationStep {
189 from_version: from,
190 to_version: to,
191 description: desc.to_string(),
192 breaking,
193 }
194 }
195
196 #[test]
197 fn test_semver_parse_valid() {
198 let sv = semver_parse("1.2.3").expect("should succeed");
199 assert_eq!(sv.major, 1);
200 assert_eq!(sv.minor, 2);
201 assert_eq!(sv.patch, 3);
202 }
203
204 #[test]
205 fn test_semver_parse_invalid() {
206 assert!(semver_parse("1.2").is_none());
207 assert!(semver_parse("abc").is_none());
208 assert!(semver_parse("1.x.3").is_none());
209 }
210
211 #[test]
212 fn test_semver_compare_less() {
213 assert_eq!(
214 semver_compare(&v(1, 0, 0), &v(2, 0, 0)),
215 std::cmp::Ordering::Less
216 );
217 assert_eq!(
218 semver_compare(&v(1, 2, 3), &v(1, 2, 4)),
219 std::cmp::Ordering::Less
220 );
221 assert_eq!(
222 semver_compare(&v(1, 0, 0), &v(1, 1, 0)),
223 std::cmp::Ordering::Less
224 );
225 }
226
227 #[test]
228 fn test_semver_compare_equal() {
229 assert_eq!(
230 semver_compare(&v(1, 2, 3), &v(1, 2, 3)),
231 std::cmp::Ordering::Equal
232 );
233 }
234
235 #[test]
236 fn test_semver_compare_greater() {
237 assert_eq!(
238 semver_compare(&v(2, 0, 0), &v(1, 9, 9)),
239 std::cmp::Ordering::Greater
240 );
241 }
242
243 #[test]
244 fn test_semver_to_string() {
245 assert_eq!(semver_to_string(&v(3, 14, 0)), "3.14.0");
246 }
247
248 #[test]
249 fn test_is_breaking_change_true() {
250 assert!(is_breaking_change(&v(1, 5, 0), &v(2, 0, 0)));
251 }
252
253 #[test]
254 fn test_is_breaking_change_false_minor_bump() {
255 assert!(!is_breaking_change(&v(1, 0, 0), &v(1, 1, 0)));
256 }
257
258 #[test]
259 fn test_is_breaking_change_false_patch_bump() {
260 assert!(!is_breaking_change(&v(1, 0, 0), &v(1, 0, 1)));
261 }
262
263 #[test]
264 fn test_register_migration() {
265 let mut registry = new_migration_registry();
266 register_migration(
267 &mut registry,
268 make_step(v(1, 0, 0), v(1, 1, 0), "add field", false),
269 );
270 assert_eq!(registry.steps.len(), 1);
271 }
272
273 #[test]
274 fn test_plan_migration_single_step() {
275 let mut registry = new_migration_registry();
276 register_migration(
277 &mut registry,
278 make_step(v(1, 0, 0), v(1, 1, 0), "bump minor", false),
279 );
280 let plan = plan_migration(®istry, &v(1, 0, 0), &v(1, 1, 0)).expect("should succeed");
281 assert_eq!(migration_step_count(&plan), 1);
282 }
283
284 #[test]
285 fn test_plan_migration_multi_step() {
286 let mut registry = new_migration_registry();
287 register_migration(
288 &mut registry,
289 make_step(v(1, 0, 0), v(1, 1, 0), "step1", false),
290 );
291 register_migration(
292 &mut registry,
293 make_step(v(1, 1, 0), v(2, 0, 0), "step2", true),
294 );
295 let plan = plan_migration(®istry, &v(1, 0, 0), &v(2, 0, 0)).expect("should succeed");
296 assert_eq!(migration_step_count(&plan), 2);
297 }
298
299 #[test]
300 fn test_plan_migration_no_path() {
301 let mut registry = new_migration_registry();
302 register_migration(
303 &mut registry,
304 make_step(v(1, 0, 0), v(1, 1, 0), "step1", false),
305 );
306 let result = plan_migration(®istry, &v(1, 0, 0), &v(3, 0, 0));
307 assert!(result.is_none());
308 }
309
310 #[test]
311 fn test_has_migration_path_true() {
312 let mut registry = new_migration_registry();
313 register_migration(&mut registry, make_step(v(1, 0, 0), v(1, 1, 0), "s", false));
314 assert!(has_migration_path(®istry, &v(1, 0, 0), &v(1, 1, 0)));
315 }
316
317 #[test]
318 fn test_has_migration_path_false() {
319 let registry = new_migration_registry();
320 assert!(!has_migration_path(®istry, &v(1, 0, 0), &v(2, 0, 0)));
321 }
322
323 #[test]
324 fn test_migration_step_count() {
325 let mut registry = new_migration_registry();
326 register_migration(
327 &mut registry,
328 make_step(v(1, 0, 0), v(1, 1, 0), "s1", false),
329 );
330 register_migration(
331 &mut registry,
332 make_step(v(1, 1, 0), v(1, 2, 0), "s2", false),
333 );
334 let plan = plan_migration(®istry, &v(1, 0, 0), &v(1, 2, 0)).expect("should succeed");
335 assert_eq!(migration_step_count(&plan), 2);
336 }
337
338 #[test]
339 fn test_plan_has_breaking_true() {
340 let mut registry = new_migration_registry();
341 register_migration(
342 &mut registry,
343 make_step(v(1, 0, 0), v(2, 0, 0), "major", true),
344 );
345 let plan = plan_migration(®istry, &v(1, 0, 0), &v(2, 0, 0)).expect("should succeed");
346 assert!(plan_has_breaking(&plan));
347 }
348
349 #[test]
350 fn test_plan_has_breaking_false() {
351 let mut registry = new_migration_registry();
352 register_migration(
353 &mut registry,
354 make_step(v(1, 0, 0), v(1, 1, 0), "minor", false),
355 );
356 let plan = plan_migration(®istry, &v(1, 0, 0), &v(1, 1, 0)).expect("should succeed");
357 assert!(!plan_has_breaking(&plan));
358 }
359
360 #[test]
361 fn test_latest_version() {
362 let mut registry = new_migration_registry();
363 register_migration(
364 &mut registry,
365 make_step(v(1, 0, 0), v(1, 1, 0), "s1", false),
366 );
367 register_migration(&mut registry, make_step(v(1, 1, 0), v(2, 0, 0), "s2", true));
368 let latest = latest_version(®istry).expect("should succeed");
369 assert_eq!(latest, v(2, 0, 0));
370 }
371
372 #[test]
373 fn test_latest_version_none_on_empty() {
374 let registry = new_migration_registry();
375 assert!(latest_version(®istry).is_none());
376 }
377
378 #[test]
379 fn test_migration_description() {
380 let mut registry = new_migration_registry();
381 register_migration(
382 &mut registry,
383 make_step(v(1, 0, 0), v(1, 1, 0), "add feature", false),
384 );
385 let plan = plan_migration(®istry, &v(1, 0, 0), &v(1, 1, 0)).expect("should succeed");
386 let desc = migration_description(&plan);
387 assert_eq!(desc, vec!["add feature"]);
388 }
389
390 #[test]
391 fn test_new_semver() {
392 let sv = new_semver(2, 3, 4);
393 assert_eq!(sv.major, 2);
394 assert_eq!(sv.minor, 3);
395 assert_eq!(sv.patch, 4);
396 }
397}