1use alloc::boxed::Box;
2use alloc::string::{String, ToString};
3use alloc::vec::Vec;
4use core::fmt;
5
6pub trait MigrationStep: Send + Sync {
12 fn source_version(&self) -> u32;
14 fn target_version(&self) -> u32;
16 fn migrate(&self, data: &[u8]) -> Result<Vec<u8>, MigrationError>;
18}
19
20#[derive(Debug, Clone, PartialEq)]
22pub enum MigrationError {
23 NoPath { from: u32, to: u32 },
25 StepFailed { from: u32, to: u32, reason: String },
27 GapInChain { missing: u32 },
29 FutureVersion { found: u32, current: u32 },
31 Deserialization(String),
33 Serialization(String),
35}
36
37impl fmt::Display for MigrationError {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 match self {
40 Self::NoPath { from, to } => {
41 write!(f, "no migration path from v{from} to v{to}")
42 }
43 Self::StepFailed { from, to, reason } => {
44 write!(f, "migration v{from}→v{to} failed: {reason}")
45 }
46 Self::GapInChain { missing } => {
47 write!(f, "missing migration step for v{missing}")
48 }
49 Self::FutureVersion { found, current } => {
50 write!(f, "data version v{found} is newer than current v{current}")
51 }
52 Self::Deserialization(msg) => write!(f, "deserialization error: {msg}"),
53 Self::Serialization(msg) => write!(f, "serialization error: {msg}"),
54 }
55 }
56}
57
58#[derive(Debug, Clone)]
60pub struct MigrationConfig {
61 pub write_back_on_read: bool,
64 pub eager_migration: bool,
66}
67
68impl Default for MigrationConfig {
69 fn default() -> Self {
70 Self {
71 write_back_on_read: true,
72 eager_migration: false,
73 }
74 }
75}
76
77pub struct MigrationEngine {
109 current_version: u32,
110 steps: Vec<Box<dyn MigrationStep>>,
111}
112
113impl MigrationEngine {
114 pub fn new(current_version: u32) -> Self {
116 Self {
117 current_version,
118 steps: Vec::new(),
119 }
120 }
121
122 pub fn register(&mut self, step: Box<dyn MigrationStep>) {
124 self.steps.push(step);
125 self.steps.sort_by_key(|s| s.source_version());
127 }
128
129 pub fn current_version(&self) -> u32 {
131 self.current_version
132 }
133
134 pub fn needs_migration(&self, data_version: u32) -> bool {
136 data_version != self.current_version
137 }
138
139 pub fn migrate_to_current(
144 &self,
145 data: &[u8],
146 from_version: u32,
147 ) -> Result<Vec<u8>, MigrationError> {
148 if from_version == self.current_version {
149 return Ok(data.to_vec());
150 }
151
152 if from_version > self.current_version {
153 return Err(MigrationError::FutureVersion {
154 found: from_version,
155 current: self.current_version,
156 });
157 }
158
159 let mut current_data = data.to_vec();
160 let mut version = from_version;
161
162 while version < self.current_version {
163 let step = self
164 .steps
165 .iter()
166 .find(|s| s.source_version() == version)
167 .ok_or(MigrationError::GapInChain { missing: version })?;
168
169 current_data = step
170 .migrate(¤t_data)
171 .map_err(|e| MigrationError::StepFailed {
172 from: version,
173 to: step.target_version(),
174 reason: e.to_string(),
175 })?;
176
177 version = step.target_version();
178 }
179
180 Ok(current_data)
181 }
182
183 pub fn validate_chain(&self, min_version: u32) -> Result<(), MigrationError> {
185 let mut version = min_version;
186 while version < self.current_version {
187 if !self.steps.iter().any(|s| s.source_version() == version) {
188 return Err(MigrationError::GapInChain { missing: version });
189 }
190 version += 1;
191 }
192 Ok(())
193 }
194
195 pub fn registered_steps(&self) -> Vec<(u32, u32)> {
197 self.steps
198 .iter()
199 .map(|s| (s.source_version(), s.target_version()))
200 .collect()
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 struct SimpleStep {
209 from: u32,
210 to: u32,
211 suffix: &'static [u8],
212 }
213
214 impl MigrationStep for SimpleStep {
215 fn source_version(&self) -> u32 {
216 self.from
217 }
218 fn target_version(&self) -> u32 {
219 self.to
220 }
221 fn migrate(&self, data: &[u8]) -> Result<Vec<u8>, MigrationError> {
222 let mut result = data.to_vec();
223 result.extend_from_slice(self.suffix);
224 Ok(result)
225 }
226 }
227
228 #[test]
229 fn no_migration_needed() {
230 let engine = MigrationEngine::new(1);
231 let data = b"hello";
232 let result = engine.migrate_to_current(data, 1).unwrap();
233 assert_eq!(result, b"hello");
234 }
235
236 #[test]
237 fn single_step_migration() {
238 let mut engine = MigrationEngine::new(2);
239 engine.register(Box::new(SimpleStep {
240 from: 1,
241 to: 2,
242 suffix: b"+v2",
243 }));
244
245 let result = engine.migrate_to_current(b"data", 1).unwrap();
246 assert_eq!(result, b"data+v2");
247 }
248
249 #[test]
250 fn multi_step_chain() {
251 let mut engine = MigrationEngine::new(4);
252 engine.register(Box::new(SimpleStep {
253 from: 1,
254 to: 2,
255 suffix: b"+v2",
256 }));
257 engine.register(Box::new(SimpleStep {
258 from: 2,
259 to: 3,
260 suffix: b"+v3",
261 }));
262 engine.register(Box::new(SimpleStep {
263 from: 3,
264 to: 4,
265 suffix: b"+v4",
266 }));
267
268 let result = engine.migrate_to_current(b"v1", 1).unwrap();
270 assert_eq!(result, b"v1+v2+v3+v4");
271
272 let result = engine.migrate_to_current(b"v2", 2).unwrap();
274 assert_eq!(result, b"v2+v3+v4");
275
276 let result = engine.migrate_to_current(b"v3", 3).unwrap();
278 assert_eq!(result, b"v3+v4");
279 }
280
281 #[test]
282 fn future_version_error() {
283 let engine = MigrationEngine::new(2);
284 let err = engine.migrate_to_current(b"data", 5).unwrap_err();
285 assert_eq!(
286 err,
287 MigrationError::FutureVersion {
288 found: 5,
289 current: 2
290 }
291 );
292 }
293
294 #[test]
295 fn gap_in_chain_error() {
296 let mut engine = MigrationEngine::new(3);
297 engine.register(Box::new(SimpleStep {
298 from: 1,
299 to: 2,
300 suffix: b"+v2",
301 }));
302 let err = engine.migrate_to_current(b"data", 1).unwrap_err();
305 assert_eq!(err, MigrationError::GapInChain { missing: 2 });
306 }
307
308 #[test]
309 fn validate_chain_ok() {
310 let mut engine = MigrationEngine::new(3);
311 engine.register(Box::new(SimpleStep {
312 from: 1,
313 to: 2,
314 suffix: b"",
315 }));
316 engine.register(Box::new(SimpleStep {
317 from: 2,
318 to: 3,
319 suffix: b"",
320 }));
321
322 assert!(engine.validate_chain(1).is_ok());
323 }
324
325 #[test]
326 fn validate_chain_gap() {
327 let mut engine = MigrationEngine::new(3);
328 engine.register(Box::new(SimpleStep {
329 from: 1,
330 to: 2,
331 suffix: b"",
332 }));
333 let err = engine.validate_chain(1).unwrap_err();
336 assert_eq!(err, MigrationError::GapInChain { missing: 2 });
337 }
338
339 #[test]
340 fn needs_migration() {
341 let engine = MigrationEngine::new(3);
342 assert!(engine.needs_migration(1));
343 assert!(engine.needs_migration(2));
344 assert!(!engine.needs_migration(3));
345 }
346
347 #[test]
348 fn registered_steps_list() {
349 let mut engine = MigrationEngine::new(3);
350 engine.register(Box::new(SimpleStep {
351 from: 2,
352 to: 3,
353 suffix: b"",
354 }));
355 engine.register(Box::new(SimpleStep {
356 from: 1,
357 to: 2,
358 suffix: b"",
359 }));
360
361 let steps = engine.registered_steps();
362 assert_eq!(steps, vec![(1, 2), (2, 3)]); }
364}