1use crate::server::{ServerError, ServerResult};
7use serde_json::Value;
8use std::collections::{HashMap, HashSet, VecDeque};
9
10pub struct MigrationContext {
12 doc: Value,
13}
14
15impl MigrationContext {
16 pub fn new(doc: Value) -> Self {
18 Self { doc }
19 }
20
21 pub fn get(&self, key: &str) -> Option<&Value> {
23 self.doc.get(key)
24 }
25
26 pub fn set(&mut self, key: &str, value: Value) {
28 if let Value::Object(map) = &mut self.doc {
29 map.insert(key.to_owned(), value);
30 }
31 }
32
33 pub fn remove(&mut self, key: &str) -> Option<Value> {
35 if let Value::Object(map) = &mut self.doc {
36 map.remove(key)
37 } else {
38 None
39 }
40 }
41
42 pub fn into_doc(self) -> Value {
44 self.doc
45 }
46
47 pub fn doc(&self) -> &Value {
49 &self.doc
50 }
51}
52
53pub trait Migration: Send + Sync {
55 #[allow(clippy::wrong_self_convention)]
57 fn from_version(&self) -> (u64, u64, u64);
58
59 fn to_version(&self) -> (u64, u64, u64);
61
62 fn description(&self) -> &str;
64
65 fn migrate(&self, ctx: &mut MigrationContext) -> ServerResult<()>;
67}
68
69pub struct MigrationPlan<'a> {
74 migrations: &'a [Box<dyn Migration>],
75 indices: Vec<usize>,
76}
77
78impl<'a> std::fmt::Debug for MigrationPlan<'a> {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 f.debug_struct("MigrationPlan")
81 .field("steps", &self.indices.len())
82 .finish()
83 }
84}
85
86impl<'a> MigrationPlan<'a> {
87 fn new(migrations: &'a [Box<dyn Migration>], indices: Vec<usize>) -> Self {
88 Self {
89 migrations,
90 indices,
91 }
92 }
93
94 pub fn is_empty(&self) -> bool {
96 self.indices.is_empty()
97 }
98
99 pub fn len(&self) -> usize {
101 self.indices.len()
102 }
103
104 pub fn apply(&self, ctx: &mut MigrationContext) -> ServerResult<()> {
108 for &idx in &self.indices {
109 self.migrations[idx].migrate(ctx)?;
110 }
111 Ok(())
112 }
113
114 pub fn step_descriptions(&self) -> Vec<&str> {
116 self.indices
117 .iter()
118 .map(|&idx| self.migrations[idx].description())
119 .collect()
120 }
121}
122
123pub struct MigrationRegistry {
128 migrations: Vec<Box<dyn Migration>>,
129}
130
131impl MigrationRegistry {
132 pub fn new() -> Self {
134 Self {
135 migrations: Vec::new(),
136 }
137 }
138
139 pub fn register(&mut self, m: impl Migration + 'static) -> &mut Self {
141 self.migrations.push(Box::new(m));
142 self
143 }
144
145 pub fn plan(
153 &self,
154 from: (u64, u64, u64),
155 to: (u64, u64, u64),
156 ) -> ServerResult<MigrationPlan<'_>> {
157 if from == to {
158 return Ok(MigrationPlan::new(&self.migrations, vec![]));
159 }
160
161 let mut adj: HashMap<(u64, u64, u64), Vec<usize>> = HashMap::new();
163 for (idx, m) in self.migrations.iter().enumerate() {
164 adj.entry(m.from_version()).or_default().push(idx);
165 }
166
167 let mut queue: VecDeque<((u64, u64, u64), Vec<usize>)> = VecDeque::new();
169 let mut visited: HashSet<(u64, u64, u64)> = HashSet::new();
170 queue.push_back((from, vec![]));
171 visited.insert(from);
172
173 while let Some((cur, path)) = queue.pop_front() {
174 if let Some(neighbors) = adj.get(&cur) {
175 for &idx in neighbors {
176 let next = self.migrations[idx].to_version();
177 let mut new_path = path.clone();
178 new_path.push(idx);
179
180 if next == to {
181 return Ok(MigrationPlan::new(&self.migrations, new_path));
182 }
183
184 if !visited.contains(&next) {
185 visited.insert(next);
186 queue.push_back((next, new_path));
187 }
188 }
189 }
190 }
191
192 Err(ServerError::Migration(format!(
193 "No migration path from {from:?} to {to:?}"
194 )))
195 }
196}
197
198impl Default for MigrationRegistry {
199 fn default() -> Self {
200 Self::new()
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207 use serde_json::json;
208
209 struct SetFieldMigration {
211 from: (u64, u64, u64),
212 to: (u64, u64, u64),
213 desc: String,
214 field: String,
215 value: Value,
216 }
217
218 impl SetFieldMigration {
219 fn new(from: (u64, u64, u64), to: (u64, u64, u64), field: &str, value: Value) -> Self {
220 let desc = format!("{from:?} -> {to:?}: set {field}");
221 Self {
222 from,
223 to,
224 desc,
225 field: field.to_owned(),
226 value,
227 }
228 }
229 }
230
231 impl Migration for SetFieldMigration {
232 fn from_version(&self) -> (u64, u64, u64) {
233 self.from
234 }
235
236 fn to_version(&self) -> (u64, u64, u64) {
237 self.to
238 }
239
240 fn description(&self) -> &str {
241 &self.desc
242 }
243
244 fn migrate(&self, ctx: &mut MigrationContext) -> ServerResult<()> {
245 ctx.set(&self.field, self.value.clone());
246 Ok(())
247 }
248 }
249
250 #[test]
251 fn test_linear_path() {
252 let mut registry = MigrationRegistry::new();
253 registry
254 .register(SetFieldMigration::new(
255 (0, 1, 0),
256 (0, 2, 0),
257 "step1",
258 json!("applied"),
259 ))
260 .register(SetFieldMigration::new(
261 (0, 2, 0),
262 (0, 3, 0),
263 "step2",
264 json!("applied"),
265 ));
266
267 let plan = registry
268 .plan((0, 1, 0), (0, 3, 0))
269 .expect("plan should exist");
270 assert_eq!(plan.len(), 2);
271
272 let mut ctx = MigrationContext::new(json!({}));
273 plan.apply(&mut ctx).expect("apply should succeed");
274
275 let doc = ctx.into_doc();
276 assert_eq!(doc.get("step1"), Some(&json!("applied")));
277 assert_eq!(doc.get("step2"), Some(&json!("applied")));
278 }
279
280 #[test]
281 fn test_already_at_target_returns_empty_plan() {
282 let registry = MigrationRegistry::new();
283 let plan = registry
284 .plan((0, 2, 0), (0, 2, 0))
285 .expect("same-version plan");
286 assert!(plan.is_empty());
287 assert_eq!(plan.len(), 0);
288 }
289
290 #[test]
291 fn test_no_path_returns_migration_error() {
292 let registry = MigrationRegistry::new(); let result = registry.plan((0, 1, 0), (0, 9, 0));
294 assert!(
295 matches!(result, Err(ServerError::Migration(_))),
296 "expected Migration error, got: {result:?}"
297 );
298 }
299
300 #[test]
301 fn test_branch_picks_shortest_path() {
302 let mut registry = MigrationRegistry::new();
307 registry
308 .register(SetFieldMigration::new(
309 (0, 1, 0),
310 (0, 2, 0),
311 "intermediate",
312 json!(true),
313 ))
314 .register(SetFieldMigration::new(
315 (0, 2, 0),
316 (0, 3, 0),
317 "via_intermediate",
318 json!(true),
319 ))
320 .register(SetFieldMigration::new(
321 (0, 1, 0),
322 (0, 3, 0),
323 "direct",
324 json!(true),
325 ));
326
327 let plan = registry
328 .plan((0, 1, 0), (0, 3, 0))
329 .expect("plan should exist");
330 assert_eq!(plan.len(), 1, "BFS should pick the direct 1-hop path");
331
332 let mut ctx = MigrationContext::new(json!({}));
333 plan.apply(&mut ctx).expect("apply should succeed");
334 let doc = ctx.into_doc();
335 assert_eq!(doc.get("direct"), Some(&json!(true)));
336 assert_eq!(
337 doc.get("intermediate"),
338 None,
339 "2-hop path must not be taken"
340 );
341 }
342
343 #[test]
344 fn test_cycle_terminates() {
345 let mut registry = MigrationRegistry::new();
348 registry
349 .register(SetFieldMigration::new(
350 (0, 1, 0),
351 (0, 2, 0),
352 "fwd",
353 json!(1),
354 ))
355 .register(SetFieldMigration::new(
356 (0, 2, 0),
357 (0, 1, 0),
358 "back",
359 json!(2),
360 ));
361
362 let result = registry.plan((0, 1, 0), (0, 3, 0));
363 assert!(
364 matches!(result, Err(ServerError::Migration(_))),
365 "expected Migration error for unreachable target, got: {result:?}"
366 );
367 }
368
369 #[test]
370 fn test_step_descriptions() {
371 let mut registry = MigrationRegistry::new();
372 registry
373 .register(SetFieldMigration::new(
374 (0, 1, 0),
375 (0, 2, 0),
376 "f",
377 json!(null),
378 ))
379 .register(SetFieldMigration::new(
380 (0, 2, 0),
381 (0, 3, 0),
382 "g",
383 json!(null),
384 ));
385
386 let plan = registry.plan((0, 1, 0), (0, 3, 0)).expect("plan");
387 let descs = plan.step_descriptions();
388 assert_eq!(descs.len(), 2);
389 for d in descs {
391 assert!(!d.is_empty());
392 }
393 }
394
395 #[test]
396 fn test_migration_context_set_get_remove() {
397 let mut ctx = MigrationContext::new(json!({"x": 1}));
398 assert_eq!(ctx.get("x"), Some(&json!(1)));
399 ctx.set("y", json!(2));
400 assert_eq!(ctx.get("y"), Some(&json!(2)));
401 ctx.remove("x");
402 assert_eq!(ctx.get("x"), None);
403 let doc = ctx.into_doc();
404 assert_eq!(doc.get("y"), Some(&json!(2)));
405 }
406
407 #[test]
408 fn test_registry_default() {
409 let registry = MigrationRegistry::default();
410 assert!(registry.migrations.is_empty());
411 }
412}