use super::migration::RawMigration;
pub trait MigrationSource: Send + Sync {
fn migrations(&self) -> Vec<Box<dyn RawMigration>>;
}
#[derive(Clone, Copy)]
pub struct EmbeddedMigration {
pub name: &'static str,
pub up: &'static str,
pub down: &'static str,
}
impl RawMigration for EmbeddedMigration {
fn name(&self) -> &str {
self.name
}
fn up(&self) -> String {
self.up.to_string()
}
fn down(&self) -> String {
self.down.to_string()
}
}
pub struct EmbeddedMigrations {
inner: &'static [EmbeddedMigration],
}
impl EmbeddedMigrations {
pub const fn new(inner: &'static [EmbeddedMigration]) -> Self {
Self { inner }
}
}
impl MigrationSource for EmbeddedMigrations {
fn migrations(&self) -> Vec<Box<dyn RawMigration>> {
self.inner
.iter()
.map(|m| Box::new(*m) as Box<dyn RawMigration>)
.collect()
}
}
pub struct FileSource {
dir: std::path::PathBuf,
}
impl FileSource {
pub fn new(dir: impl Into<std::path::PathBuf>) -> Self {
Self { dir: dir.into() }
}
}
struct FileMigration {
name: String,
up: String,
down: String,
}
impl RawMigration for FileMigration {
fn name(&self) -> &str {
&self.name
}
fn up(&self) -> String {
self.up.clone()
}
fn down(&self) -> String {
self.down.clone()
}
}
fn parse_up_down(content: &str) -> (String, String) {
if let Some(up_pos) = content.find("-- up\n") {
let after_up = &content[up_pos + 6..];
if let Some(down_pos) = after_up.find("-- down\n") {
return (
after_up[..down_pos].trim().to_string(),
after_up[down_pos + 8..].trim().to_string(),
);
}
return (after_up.trim().to_string(), String::new());
}
(content.trim().to_string(), String::new())
}
impl MigrationSource for FileSource {
fn migrations(&self) -> Vec<Box<dyn RawMigration>> {
let mut entries = match std::fs::read_dir(&self.dir) {
Ok(rd) => rd
.filter_map(|e| e.ok())
.filter(|e| {
let p = e.path();
p.extension().and_then(|s| s.to_str()) == Some("sql")
&& !p
.file_name()
.and_then(|s| s.to_str())
.map(|s| s.contains(".down."))
.unwrap_or(false)
})
.collect::<Vec<_>>(),
Err(_) => return Vec::new(),
};
entries.sort_by_key(|e| e.file_name());
entries
.into_iter()
.filter_map(|e| {
let path = e.path();
let name = path.file_stem()?.to_str()?.to_string();
let content = std::fs::read_to_string(&path).ok()?;
let (up, down) = parse_up_down(&content);
Some(Box::new(FileMigration { name, up, down }) as Box<dyn RawMigration>)
})
.collect()
}
}