dovecote_sqlx_sqlite/
migration.rs1pub const SCHEMA_VERSION: u32 = 1;
4
5#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
6pub struct CrateVersion {
7 major: u16,
8 minor: u16,
9 patch: u16,
10}
11
12impl CrateVersion {
13 pub const fn new(major: u16, minor: u16, patch: u16) -> Self {
14 Self {
15 major,
16 minor,
17 patch,
18 }
19 }
20 pub const fn major(self) -> u16 {
21 self.major
22 }
23 pub const fn minor(self) -> u16 {
24 self.minor
25 }
26 pub const fn patch(self) -> u16 {
27 self.patch
28 }
29 const fn is_less_than(self, other: Self) -> bool {
30 self.major < other.major
31 || (self.major == other.major
32 && (self.minor < other.minor
33 || (self.minor == other.minor && self.patch < other.patch)))
34 }
35}
36
37#[derive(Clone, Copy, Debug, Eq, PartialEq)]
38pub struct MigrationCompatibilityError;
39impl std::fmt::Display for MigrationCompatibilityError {
40 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 formatter.write_str("migration compatibility maximum precedes minimum")
42 }
43}
44impl std::error::Error for MigrationCompatibilityError {}
45
46#[derive(Clone, Copy, Debug, Eq, PartialEq)]
47pub struct MigrationCompatibility {
48 minimum: CrateVersion,
49 maximum: Option<CrateVersion>,
50}
51impl MigrationCompatibility {
52 const fn new(minimum: CrateVersion, maximum: Option<CrateVersion>) -> Self {
53 Self { minimum, maximum }
54 }
55 pub const fn try_new(
56 minimum: CrateVersion,
57 maximum: Option<CrateVersion>,
58 ) -> Result<Self, MigrationCompatibilityError> {
59 if let Some(maximum) = maximum
60 && maximum.is_less_than(minimum)
61 {
62 return Err(MigrationCompatibilityError);
63 }
64 Ok(Self::new(minimum, maximum))
65 }
66 pub const fn minimum(self) -> CrateVersion {
67 self.minimum
68 }
69 pub const fn maximum(self) -> Option<CrateVersion> {
70 self.maximum
71 }
72 pub const fn contains(self, version: CrateVersion) -> bool {
73 !version.is_less_than(self.minimum)
74 && match self.maximum {
75 Some(maximum) => !maximum.is_less_than(version),
76 None => true,
77 }
78 }
79}
80
81#[derive(Clone, Copy, Debug, Eq, PartialEq)]
82pub struct Migration {
83 version: u32,
84 sql: &'static str,
85 compatibility: MigrationCompatibility,
86 rolling_compatible: bool,
87}
88impl Migration {
89 const fn new(
90 version: u32,
91 sql: &'static str,
92 compatibility: MigrationCompatibility,
93 rolling_compatible: bool,
94 ) -> Self {
95 Self {
96 version,
97 sql,
98 compatibility,
99 rolling_compatible,
100 }
101 }
102 pub const fn version(self) -> u32 {
103 self.version
104 }
105 pub const fn sql(self) -> &'static str {
106 self.sql
107 }
108 pub const fn compatibility(self) -> MigrationCompatibility {
109 self.compatibility
110 }
111 pub const fn rolling_compatible(self) -> bool {
112 self.rolling_compatible
113 }
114}
115
116pub const MIGRATIONS: &[Migration] = &[Migration::new(
117 1,
118 include_str!("../migrations/0001_dovecote.sql"),
119 MigrationCompatibility::new(CrateVersion::new(0, 1, 0), None),
120 false,
121)];
122
123pub(crate) fn current_crate_version() -> CrateVersion {
124 CrateVersion::new(
125 env!("CARGO_PKG_VERSION_MAJOR")
126 .parse()
127 .expect("Cargo version is numeric"),
128 env!("CARGO_PKG_VERSION_MINOR")
129 .parse()
130 .expect("Cargo version is numeric"),
131 env!("CARGO_PKG_VERSION_PATCH")
132 .parse()
133 .expect("Cargo version is numeric"),
134 )
135}
136pub(crate) fn current_migration() -> Result<Migration, String> {
137 MIGRATIONS
138 .iter()
139 .find(|migration| migration.version() == SCHEMA_VERSION)
140 .copied()
141 .ok_or_else(|| format!("adapter does not ship schema version {SCHEMA_VERSION}"))
142}
143pub(crate) fn migration_is_usable(migration: Migration) -> Result<(), String> {
144 if !migration.compatibility().contains(current_crate_version()) {
145 return Err("current crate is outside migration compatibility range".to_owned());
146 }
147 Ok(())
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153 #[test]
154 fn migrations_are_ordered_and_typed() {
155 assert_eq!(MIGRATIONS[0].version(), SCHEMA_VERSION);
156 assert!(!MIGRATIONS[0].sql().is_empty());
157 assert_eq!(
158 MIGRATIONS[0].compatibility().minimum(),
159 CrateVersion::new(0, 1, 0)
160 );
161 assert!(!MIGRATIONS[0].sql().contains("dovecote_schema"));
162 assert!(!MIGRATIONS[0].rolling_compatible());
163 assert!(
164 MIGRATIONS[0]
165 .compatibility()
166 .contains(CrateVersion::new(0, 9, 0))
167 );
168 assert!(
169 MigrationCompatibility::try_new(
170 CrateVersion::new(1, 0, 0),
171 Some(CrateVersion::new(0, 9, 0))
172 )
173 .is_err()
174 );
175 }
176}