use std::future::Future;
use clap::Parser;
use dotenvy::dotenv;
use std::{error::Error, fmt::Display, process::exit};
use tracing_subscriber::{EnvFilter, prelude::*};
use sea_orm::{ConnectOptions, Database, DbConn, DbErr};
use sea_orm_cli::{MigrateSubcommands, run_migrate_generate, run_migrate_init};
use super::MigratorTraitSelf;
const MIGRATION_DIR: &str = "./";
pub async fn run_cli<M>(migrator: M)
where
M: MigratorTraitSelf,
{
run_cli_with_connection(migrator, Database::connect).await;
}
pub async fn run_cli_with_connection<M, F, Fut>(migrator: M, make_connection: F)
where
M: MigratorTraitSelf,
F: FnOnce(ConnectOptions) -> Fut,
Fut: Future<Output = Result<DbConn, DbErr>>,
{
dotenv().ok();
let cli = Cli::parse();
run_cli_with_connection_inner(migrator, cli, async move |cli| {
let url = cli
.database_url
.clone()
.expect("Environment variable 'DATABASE_URL' not set");
let schema = cli
.database_schema
.clone()
.unwrap_or_else(|| "public".to_owned());
let connect_options = ConnectOptions::new(url)
.set_schema_search_path(schema)
.to_owned();
make_connection(connect_options).await
})
.await
.unwrap_or_else(handle_error);
}
pub async fn run_cli_with_custom_connection(
migrator: impl MigratorTraitSelf,
make_connection: impl AsyncFnOnce() -> Result<DbConn, DbErr>,
) {
dotenv().ok();
let cli = Cli::parse();
run_cli_with_connection_inner(migrator, cli, async |_| make_connection().await)
.await
.unwrap_or_else(handle_error);
}
pub async fn run_migrate<M>(
migrator: M,
db: &DbConn,
command: Option<MigrateSubcommands>,
verbose: bool,
) -> Result<(), Box<dyn Error>>
where
M: MigratorTraitSelf,
{
setup_tracing(verbose);
run_migrate_inner(migrator, db, command).await
}
async fn run_cli_with_connection_inner(
migrator: impl MigratorTraitSelf,
cli: Cli,
make_connection: impl AsyncFnOnce(&Cli) -> Result<DbConn, DbErr>,
) -> Result<(), Box<dyn Error>> {
setup_tracing(cli.verbose);
if run_non_db_command(cli.command.as_ref())? {
return Ok(());
}
let db = make_connection(&cli)
.await
.expect("Fail to acquire database connection");
let command = cli.command;
run_migrate_inner(migrator, &db, command).await?;
Ok(())
}
async fn run_migrate_inner<M>(
migrator: M,
db: &DbConn,
command: Option<MigrateSubcommands>,
) -> Result<(), Box<dyn Error>>
where
M: MigratorTraitSelf,
{
if run_non_db_command(command.as_ref())? {
return Ok(());
}
match command {
Some(MigrateSubcommands::Fresh) => migrator.fresh(db).await?,
Some(MigrateSubcommands::Refresh) => migrator.refresh(db).await?,
Some(MigrateSubcommands::Reset) => migrator.reset(db).await?,
Some(MigrateSubcommands::Status) => migrator.status(db).await?,
Some(MigrateSubcommands::Up { num }) => migrator.up(db, num).await?,
Some(MigrateSubcommands::Down { num }) => migrator.down(db, Some(num)).await?,
_ => migrator.up(db, None).await?,
};
Ok(())
}
fn run_non_db_command(command: Option<&MigrateSubcommands>) -> Result<bool, Box<dyn Error>> {
match command {
Some(MigrateSubcommands::Init) => {
run_migrate_init(MIGRATION_DIR)?;
Ok(true)
}
Some(MigrateSubcommands::Generate {
migration_name,
universal_time: _,
local_time,
}) => {
run_migrate_generate(MIGRATION_DIR, migration_name, !*local_time)?;
Ok(true)
}
_ => Ok(false),
}
}
fn setup_tracing(verbose: bool) {
let filter = match verbose {
true => "debug",
false => "sea_orm_migration=info",
};
let filter_layer = EnvFilter::try_new(filter).unwrap();
if verbose {
let fmt_layer = tracing_subscriber::fmt::layer();
tracing_subscriber::registry()
.with(filter_layer)
.with(fmt_layer)
.init()
} else {
let fmt_layer = tracing_subscriber::fmt::layer()
.with_target(false)
.with_level(false)
.without_time();
tracing_subscriber::registry()
.with(filter_layer)
.with(fmt_layer)
.init()
};
}
#[derive(Parser)]
#[command(version)]
pub struct Cli {
#[arg(short = 'v', long, global = true, help = "Show debug messages")]
verbose: bool,
#[arg(
global = true,
short = 's',
long,
env = "DATABASE_SCHEMA",
long_help = "Database schema\n \
- For MySQL and SQLite, this argument is ignored.\n \
- For PostgreSQL, this argument is optional with default value 'public'.\n"
)]
database_schema: Option<String>,
#[arg(
global = true,
short = 'u',
long,
env = "DATABASE_URL",
help = "Database URL"
)]
database_url: Option<String>,
#[command(subcommand)]
command: Option<MigrateSubcommands>,
}
fn handle_error<E>(error: E)
where
E: Display,
{
eprintln!("{error}");
exit(1);
}