lib_migrations_core/
lib.rs

1mod error;
2mod phase;
3mod runner;
4mod store;
5
6pub use error::{Error, Result};
7pub use phase::Phase;
8pub use runner::{DryRunPlan, DryRunResult, MigrationRunner, MigrationStatus};
9pub use store::{MemoryStore, MigrationRecord, MigrationStore};
10
11/// A migration that can be applied or rolled back.
12///
13/// Migrations are versioned changes to your system. Each migration has:
14/// - A unique version number (must be sequential starting from 1)
15/// - A human-readable name
16/// - A deployment phase (pre-deploy or post-deploy)
17/// - An apply action
18/// - An optional rollback action
19///
20/// The generic type `Ctx` is the context passed to apply/rollback functions.
21/// This could be a database connection, file system handle, API client, etc.
22pub trait Migration<Ctx>: Send + Sync {
23    /// Unique version number (must be sequential: 1, 2, 3, ...)
24    fn version(&self) -> u64;
25
26    /// Human-readable name for this migration
27    fn name(&self) -> &str;
28
29    /// Deployment phase (pre-deploy or post-deploy)
30    fn phase(&self) -> Phase {
31        Phase::PreDeploy
32    }
33
34    /// Apply the migration
35    fn apply(&self, ctx: &mut Ctx) -> Result<()>;
36
37    /// Rollback the migration (optional)
38    fn rollback(&self, ctx: &mut Ctx) -> Result<()> {
39        let _ = ctx;
40        Err(Error::RollbackNotSupported(self.version()))
41    }
42
43    /// Whether this migration supports rollback
44    fn can_rollback(&self) -> bool {
45        false
46    }
47}
48
49use std::marker::PhantomData;
50
51/// Builder for creating simple migrations with closures
52pub struct FnMigration<Ctx, F, R>
53where
54    F: Fn(&mut Ctx) -> Result<()> + Send + Sync,
55    R: Fn(&mut Ctx) -> Result<()> + Send + Sync,
56{
57    version: u64,
58    name: String,
59    phase: Phase,
60    apply_fn: F,
61    rollback_fn: Option<R>,
62    _phantom: PhantomData<fn(&mut Ctx)>, // Use fn pointer for Send+Sync invariance
63}
64
65impl<Ctx, F> FnMigration<Ctx, F, fn(&mut Ctx) -> Result<()>>
66where
67    F: Fn(&mut Ctx) -> Result<()> + Send + Sync,
68{
69    pub fn new(version: u64, name: impl Into<String>, apply_fn: F) -> Self {
70        Self {
71            version,
72            name: name.into(),
73            phase: Phase::PreDeploy,
74            apply_fn,
75            rollback_fn: None,
76            _phantom: PhantomData,
77        }
78    }
79}
80
81impl<Ctx, F, R> FnMigration<Ctx, F, R>
82where
83    F: Fn(&mut Ctx) -> Result<()> + Send + Sync,
84    R: Fn(&mut Ctx) -> Result<()> + Send + Sync,
85{
86    /// Set the deployment phase
87    pub fn phase(mut self, phase: Phase) -> Self {
88        self.phase = phase;
89        self
90    }
91
92    pub fn with_rollback<R2>(self, rollback_fn: R2) -> FnMigration<Ctx, F, R2>
93    where
94        R2: Fn(&mut Ctx) -> Result<()> + Send + Sync,
95    {
96        FnMigration {
97            version: self.version,
98            name: self.name,
99            phase: self.phase,
100            apply_fn: self.apply_fn,
101            rollback_fn: Some(rollback_fn),
102            _phantom: PhantomData,
103        }
104    }
105}
106
107impl<Ctx, F, R> Migration<Ctx> for FnMigration<Ctx, F, R>
108where
109    F: Fn(&mut Ctx) -> Result<()> + Send + Sync,
110    R: Fn(&mut Ctx) -> Result<()> + Send + Sync,
111{
112    fn version(&self) -> u64 {
113        self.version
114    }
115
116    fn name(&self) -> &str {
117        &self.name
118    }
119
120    fn phase(&self) -> Phase {
121        self.phase
122    }
123
124    fn apply(&self, ctx: &mut Ctx) -> Result<()> {
125        (self.apply_fn)(ctx)
126    }
127
128    fn rollback(&self, ctx: &mut Ctx) -> Result<()> {
129        match &self.rollback_fn {
130            Some(f) => f(ctx),
131            None => Err(Error::RollbackNotSupported(self.version)),
132        }
133    }
134
135    fn can_rollback(&self) -> bool {
136        self.rollback_fn.is_some()
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn test_fn_migration() {
146        let migration = FnMigration::new(1, "test", |ctx: &mut Vec<i32>| {
147            ctx.push(1);
148            Ok(())
149        });
150
151        assert_eq!(migration.version(), 1);
152        assert_eq!(migration.name(), "test");
153        assert!(!migration.can_rollback());
154
155        let mut ctx = vec![];
156        migration.apply(&mut ctx).unwrap();
157        assert_eq!(ctx, vec![1]);
158    }
159
160    #[test]
161    fn test_fn_migration_with_rollback() {
162        let migration = FnMigration::new(1, "test", |ctx: &mut Vec<i32>| {
163            ctx.push(1);
164            Ok(())
165        })
166        .with_rollback(|ctx: &mut Vec<i32>| {
167            ctx.pop();
168            Ok(())
169        });
170
171        assert!(migration.can_rollback());
172
173        let mut ctx = vec![];
174        migration.apply(&mut ctx).unwrap();
175        assert_eq!(ctx, vec![1]);
176
177        migration.rollback(&mut ctx).unwrap();
178        assert!(ctx.is_empty());
179    }
180}