a3s_orm/migration/
runner.rs1use 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}