use std::path::Path;
use std::process::Command;
const DEFAULT_MIGRATIONS_DIR: &str = "migrations";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MigrateAction {
Run,
Status,
}
pub fn run(action: MigrateAction) {
eprintln!("\u{1F342} autumn migrate\n");
let database_url = resolve_database_url();
let migrations_dir = resolve_migrations_dir();
check_diesel_cli();
match action {
MigrateAction::Run => run_migrations(&database_url, &migrations_dir),
MigrateAction::Status => show_status(&database_url, &migrations_dir),
}
}
fn resolve_database_url() -> String {
resolve_database_url_with_env(|key| std::env::var(key))
}
fn resolve_database_url_with_env<F>(env_var: F) -> String
where
F: Fn(&str) -> Result<String, std::env::VarError>,
{
if let Ok(url) = env_var("AUTUMN_DATABASE__URL") {
if !url.is_empty() {
return url;
}
}
if let Ok(url) = env_var("DATABASE_URL") {
if !url.is_empty() {
return url;
}
}
let config_path = Path::new("autumn.toml");
if config_path.exists() {
if let Ok(contents) = std::fs::read_to_string(config_path) {
if let Ok(table) = toml::from_str::<toml::Table>(&contents) {
let value = toml::Value::Table(table);
if let Some(url) = value
.get("database")
.and_then(|db: &toml::Value| db.get("url"))
.and_then(|u: &toml::Value| u.as_str())
{
if !url.is_empty() {
return url.to_string();
}
}
}
}
}
eprintln!("\u{2717} No database URL found.");
eprintln!(" Set database.url in autumn.toml, or set AUTUMN_DATABASE__URL / DATABASE_URL.");
std::process::exit(1);
}
fn resolve_migrations_dir() -> String {
let dir = Path::new(DEFAULT_MIGRATIONS_DIR);
if !dir.exists() {
eprintln!("\u{2717} Migrations directory not found: {DEFAULT_MIGRATIONS_DIR}/");
eprintln!(" Create it with `diesel setup` or `diesel migration generate <name>`.");
std::process::exit(1);
}
DEFAULT_MIGRATIONS_DIR.to_string()
}
fn check_diesel_cli() {
match Command::new("diesel").arg("--version").output() {
Ok(output) if output.status.success() => {
let version = String::from_utf8_lossy(&output.stdout);
eprintln!(" Using {}", version.trim());
}
_ => {
eprintln!("\u{2717} diesel CLI not found on PATH.");
eprintln!(
" Install it with: cargo install diesel_cli --no-default-features --features postgres"
);
std::process::exit(1);
}
}
}
fn run_migrations(database_url: &str, migrations_dir: &str) {
eprintln!(" Running pending migrations...\n");
let status = Command::new("diesel")
.args(["migration", "run", "--migration-dir", migrations_dir])
.env("DATABASE_URL", database_url)
.status();
match status {
Ok(s) if s.success() => {
eprintln!("\n\u{2713} Migrations applied successfully.");
}
Ok(_) => {
eprintln!(
"\n\u{2717} Migration failed. Check the error output above for the failing SQL."
);
std::process::exit(1);
}
Err(e) => {
eprintln!("\u{2717} Failed to run diesel migration run: {e}");
std::process::exit(1);
}
}
}
fn show_status(database_url: &str, migrations_dir: &str) {
eprintln!(" Checking migration status...\n");
let status = Command::new("diesel")
.args(["migration", "list", "--migration-dir", migrations_dir])
.env("DATABASE_URL", database_url)
.status();
match status {
Ok(s) if s.success() => {}
Ok(_) => {
eprintln!("\n\u{2717} Failed to check migration status.");
std::process::exit(1);
}
Err(e) => {
eprintln!("\u{2717} Failed to run diesel migration list: {e}");
std::process::exit(1);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn migrate_action_eq() {
assert_eq!(MigrateAction::Run, MigrateAction::Run);
assert_eq!(MigrateAction::Status, MigrateAction::Status);
assert_ne!(MigrateAction::Run, MigrateAction::Status);
}
#[test]
fn default_migrations_dir_is_migrations() {
assert_eq!(DEFAULT_MIGRATIONS_DIR, "migrations");
}
#[test]
fn resolve_database_url_from_env() {
let env_var = |key: &str| -> Result<String, std::env::VarError> {
if key == "AUTUMN_DATABASE__URL" {
Ok("postgres://test:5432/mydb".to_string())
} else {
Err(std::env::VarError::NotPresent)
}
};
let url = resolve_database_url_with_env(env_var);
assert_eq!(url, "postgres://test:5432/mydb");
}
#[test]
fn resolve_database_url_from_database_url_env() {
let env_var = |key: &str| -> Result<String, std::env::VarError> {
if key == "DATABASE_URL" {
Ok("postgres://fallback:5432/db".to_string())
} else {
Err(std::env::VarError::NotPresent)
}
};
let url = resolve_database_url_with_env(env_var);
assert_eq!(url, "postgres://fallback:5432/db");
}
}