Skip to main content

a3s_orm/migration/
runner.rs

1use std::collections::HashSet;
2
3use super::{Migration, MigrationBackend, MigrationError, MigrationRunError, PreparedMigration};
4
5#[derive(Clone, Debug, Default, PartialEq, Eq)]
6pub struct MigrationReport {
7    pub applied: Vec<String>,
8}
9
10impl MigrationReport {
11    pub fn is_up_to_date(&self) -> bool {
12        self.applied.is_empty()
13    }
14}
15
16pub struct Migrator<B> {
17    backend: B,
18}
19
20impl<B: MigrationBackend> Migrator<B> {
21    pub const fn new(backend: B) -> Self {
22        Self { backend }
23    }
24
25    pub fn backend(&self) -> &B {
26        &self.backend
27    }
28
29    pub async fn run(
30        &self,
31        migrations: impl IntoIterator<Item = Migration>,
32    ) -> Result<MigrationReport, MigrationRunError<B::Error>> {
33        let migrations = prepare(migrations)?;
34        self.backend
35            .apply(&migrations)
36            .await
37            .map_err(MigrationRunError::Backend)
38    }
39}
40
41fn prepare(
42    migrations: impl IntoIterator<Item = Migration>,
43) -> Result<Vec<PreparedMigration>, MigrationError> {
44    let mut migrations = migrations.into_iter().collect::<Vec<_>>();
45    for migration in &migrations {
46        validate(migration)?;
47    }
48    migrations.sort_by(|left, right| left.version().cmp(right.version()));
49    let mut versions = HashSet::with_capacity(migrations.len());
50    for migration in &migrations {
51        if !versions.insert(migration.version().to_owned()) {
52            return Err(MigrationError::DuplicateVersion(
53                migration.version().to_owned(),
54            ));
55        }
56    }
57    Ok(migrations
58        .into_iter()
59        .map(PreparedMigration::prepare)
60        .collect())
61}
62
63fn validate(migration: &Migration) -> Result<(), MigrationError> {
64    if migration.version().is_empty() {
65        return Err(MigrationError::EmptyVersion);
66    }
67    if !migration
68        .version()
69        .bytes()
70        .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-'))
71    {
72        return Err(MigrationError::InvalidVersion(
73            migration.version().to_owned(),
74        ));
75    }
76    if migration.name().trim().is_empty() {
77        return Err(MigrationError::EmptyName {
78            version: migration.version().to_owned(),
79        });
80    }
81    if migration.up_sql().trim().is_empty() {
82        return Err(MigrationError::EmptySql {
83            version: migration.version().to_owned(),
84        });
85    }
86    Ok(())
87}