swink_agent_memory/migrate.rs
1//! Schema migration support for session stores.
2//!
3//! [`SessionMigrator`] implementations transform session entries from one
4//! schema version to the next. The migration runner in
5//! [`crate::store::SessionStore::load`] applies applicable migrators in order.
6
7use std::io;
8
9use crate::entry::SessionEntry;
10use crate::meta::SessionMeta;
11
12/// Migrates session entries from one schema version to the next.
13///
14/// Implementations should transform entries from `source_version()` to
15/// `target_version()`. The migration runner calls [`migrate`](Self::migrate)
16/// only when the session's version matches `source_version()`.
17pub trait SessionMigrator: Send + Sync {
18 /// The schema version this migrator reads.
19 fn source_version(&self) -> u32;
20
21 /// The schema version this migrator produces.
22 fn target_version(&self) -> u32;
23
24 /// Transform session entries from `source_version` to `target_version`.
25 ///
26 /// The implementation may modify, add, or remove entries. It must NOT
27 /// modify `meta.version` — the runner handles that.
28 fn migrate(
29 &self,
30 meta: &SessionMeta,
31 entries: Vec<SessionEntry>,
32 ) -> io::Result<Vec<SessionEntry>>;
33}
34
35/// The current schema version for new sessions.
36pub const CURRENT_VERSION: u32 = 1;
37
38/// Run applicable migrators against a loaded session.
39///
40/// Migrators are applied in order of `source_version()`. If the session version
41/// is already >= `CURRENT_VERSION`, no migration runs. Returns an error if
42/// the session version exceeds `CURRENT_VERSION` (unsupported future version)
43/// or if no migrator covers a needed step.
44pub fn run_migrations(
45 meta: &mut SessionMeta,
46 entries: &mut Vec<SessionEntry>,
47 migrators: &[Box<dyn SessionMigrator>],
48) -> io::Result<()> {
49 if meta.version > CURRENT_VERSION {
50 return Err(io::Error::new(
51 io::ErrorKind::InvalidData,
52 format!(
53 "unsupported session version {} (current: {CURRENT_VERSION})",
54 meta.version
55 ),
56 ));
57 }
58
59 while meta.version < CURRENT_VERSION {
60 let migrator = migrators
61 .iter()
62 .find(|m| m.source_version() == meta.version)
63 .ok_or_else(|| {
64 io::Error::new(
65 io::ErrorKind::InvalidData,
66 format!(
67 "no migrator found for version {} -> {}",
68 meta.version,
69 meta.version + 1
70 ),
71 )
72 })?;
73
74 *entries = migrator.migrate(meta, std::mem::take(entries))?;
75 meta.version = migrator.target_version();
76 }
77
78 Ok(())
79}
80
81#[cfg(test)]
82#[path = "migrate_tests.rs"]
83mod tests;