a3s_orm/migration/
definition.rs1use sha2::{Digest, Sha256};
2
3use super::MigrationError;
4
5#[derive(Clone, Debug, PartialEq, Eq)]
6pub struct Migration {
7 version: String,
8 name: String,
9 up_sql: String,
10}
11
12impl Migration {
13 pub fn new(
14 version: impl Into<String>,
15 name: impl Into<String>,
16 up_sql: impl Into<String>,
17 ) -> Self {
18 Self {
19 version: version.into(),
20 name: name.into(),
21 up_sql: up_sql.into(),
22 }
23 }
24
25 pub fn version(&self) -> &str {
26 &self.version
27 }
28
29 pub fn name(&self) -> &str {
30 &self.name
31 }
32
33 pub fn up_sql(&self) -> &str {
34 &self.up_sql
35 }
36}
37
38#[derive(Clone, Debug, PartialEq, Eq)]
39pub struct PreparedMigration {
40 version: String,
41 name: String,
42 up_sql: String,
43 checksum: String,
44}
45
46#[derive(Clone, Debug, PartialEq, Eq)]
47pub struct AppliedMigration {
48 pub version: String,
49 pub checksum: String,
50}
51
52impl PreparedMigration {
53 pub(crate) fn prepare(migration: Migration) -> Self {
54 let mut digest = Sha256::new();
55 digest.update(migration.up_sql.as_bytes());
56 let checksum = hex_encode(&digest.finalize());
57 Self {
58 version: migration.version,
59 name: migration.name,
60 up_sql: migration.up_sql,
61 checksum,
62 }
63 }
64
65 pub fn version(&self) -> &str {
66 &self.version
67 }
68
69 pub fn name(&self) -> &str {
70 &self.name
71 }
72
73 pub fn up_sql(&self) -> &str {
74 &self.up_sql
75 }
76
77 pub fn checksum(&self) -> &str {
78 &self.checksum
79 }
80}
81
82fn hex_encode(bytes: &[u8]) -> String {
83 const HEX: &[u8; 16] = b"0123456789abcdef";
84 let mut encoded = String::with_capacity(bytes.len() * 2);
85 for byte in bytes {
86 encoded.push(HEX[(byte >> 4) as usize] as char);
87 encoded.push(HEX[(byte & 0x0f) as usize] as char);
88 }
89 encoded
90}
91
92pub fn pending_migrations<'a>(
93 applied: &[AppliedMigration],
94 source: &'a [PreparedMigration],
95) -> Result<Vec<&'a PreparedMigration>, MigrationError> {
96 for applied_migration in applied {
97 let Some(source_migration) = source
98 .iter()
99 .find(|migration| migration.version() == applied_migration.version)
100 else {
101 return Err(MigrationError::MissingSourceMigration(
102 applied_migration.version.clone(),
103 ));
104 };
105 if source_migration.checksum() != applied_migration.checksum {
106 return Err(MigrationError::ChecksumMismatch {
107 version: applied_migration.version.clone(),
108 applied_checksum: applied_migration.checksum.clone(),
109 source_checksum: source_migration.checksum().to_owned(),
110 });
111 }
112 }
113 Ok(source
114 .iter()
115 .filter(|migration| {
116 !applied
117 .iter()
118 .any(|applied| applied.version == migration.version())
119 })
120 .collect())
121}