Skip to main content

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;