1use std::future::Future;
2
3use clap::Parser;
4use dotenvy::dotenv;
5use std::{error::Error, fmt::Display, process::exit};
6use tracing_subscriber::{EnvFilter, prelude::*};
7
8use sea_orm::{ConnectOptions, Database, DbConn, DbErr};
9use sea_orm_cli::{MigrateSubcommands, run_migrate_generate, run_migrate_init};
10
11use super::MigratorTraitSelf;
12
13const MIGRATION_DIR: &str = "./";
14
15pub async fn run_cli<M>(migrator: M)
16where
17 M: MigratorTraitSelf,
18{
19 run_cli_with_connection(migrator, Database::connect).await;
20}
21
22pub async fn run_cli_with_connection<M, F, Fut>(migrator: M, make_connection: F)
28where
29 M: MigratorTraitSelf,
30 F: FnOnce(ConnectOptions) -> Fut,
31 Fut: Future<Output = Result<DbConn, DbErr>>,
32{
33 dotenv().ok();
34 let cli = Cli::parse();
35
36 run_cli_with_connection_inner(migrator, cli, async move |cli| {
37 let url = cli
38 .database_url
39 .clone()
40 .expect("Environment variable 'DATABASE_URL' not set");
41 let schema = cli
42 .database_schema
43 .clone()
44 .unwrap_or_else(|| "public".to_owned());
45
46 let connect_options = ConnectOptions::new(url)
47 .set_schema_search_path(schema)
48 .to_owned();
49
50 make_connection(connect_options).await
51 })
52 .await
53 .unwrap_or_else(handle_error);
54}
55
56pub async fn run_cli_with_custom_connection(
62 migrator: impl MigratorTraitSelf,
63 make_connection: impl AsyncFnOnce() -> Result<DbConn, DbErr>,
64) {
65 dotenv().ok();
66 let cli = Cli::parse();
67
68 run_cli_with_connection_inner(migrator, cli, async |_| make_connection().await)
69 .await
70 .unwrap_or_else(handle_error);
71}
72
73pub async fn run_migrate<M>(
74 migrator: M,
75 db: &DbConn,
76 command: Option<MigrateSubcommands>,
77 verbose: bool,
78) -> Result<(), Box<dyn Error>>
79where
80 M: MigratorTraitSelf,
81{
82 setup_tracing(verbose);
83 run_migrate_inner(migrator, db, command).await
84}
85
86async fn run_cli_with_connection_inner(
87 migrator: impl MigratorTraitSelf,
88 cli: Cli,
89 make_connection: impl AsyncFnOnce(&Cli) -> Result<DbConn, DbErr>,
90) -> Result<(), Box<dyn Error>> {
91 setup_tracing(cli.verbose);
92
93 if run_non_db_command(cli.command.as_ref())? {
94 return Ok(());
95 }
96
97 let db = make_connection(&cli)
98 .await
99 .expect("Fail to acquire database connection");
100 let command = cli.command;
101 run_migrate_inner(migrator, &db, command).await?;
102
103 Ok(())
104}
105
106async fn run_migrate_inner<M>(
107 migrator: M,
108 db: &DbConn,
109 command: Option<MigrateSubcommands>,
110) -> Result<(), Box<dyn Error>>
111where
112 M: MigratorTraitSelf,
113{
114 if run_non_db_command(command.as_ref())? {
115 return Ok(());
116 }
117
118 match command {
119 Some(MigrateSubcommands::Fresh) => migrator.fresh(db).await?,
120 Some(MigrateSubcommands::Refresh) => migrator.refresh(db).await?,
121 Some(MigrateSubcommands::Reset) => migrator.reset(db).await?,
122 Some(MigrateSubcommands::Status) => migrator.status(db).await?,
123 Some(MigrateSubcommands::Up { num }) => migrator.up(db, num).await?,
124 Some(MigrateSubcommands::Down { num }) => migrator.down(db, Some(num)).await?,
125 _ => migrator.up(db, None).await?,
126 };
127
128 Ok(())
129}
130
131fn run_non_db_command(command: Option<&MigrateSubcommands>) -> Result<bool, Box<dyn Error>> {
132 match command {
133 Some(MigrateSubcommands::Init) => {
134 run_migrate_init(MIGRATION_DIR)?;
135 Ok(true)
136 }
137 Some(MigrateSubcommands::Generate {
138 migration_name,
139 universal_time: _,
140 local_time,
141 }) => {
142 run_migrate_generate(MIGRATION_DIR, migration_name, !*local_time)?;
143 Ok(true)
144 }
145 _ => Ok(false),
146 }
147}
148
149fn setup_tracing(verbose: bool) {
150 let filter = match verbose {
151 true => "debug",
152 false => "sea_orm_migration=info",
153 };
154
155 let filter_layer = EnvFilter::try_new(filter).unwrap();
156
157 if verbose {
158 let fmt_layer = tracing_subscriber::fmt::layer();
159 tracing_subscriber::registry()
160 .with(filter_layer)
161 .with(fmt_layer)
162 .init()
163 } else {
164 let fmt_layer = tracing_subscriber::fmt::layer()
165 .with_target(false)
166 .with_level(false)
167 .without_time();
168 tracing_subscriber::registry()
169 .with(filter_layer)
170 .with(fmt_layer)
171 .init()
172 };
173}
174
175#[derive(Parser)]
176#[command(version)]
177pub struct Cli {
178 #[arg(short = 'v', long, global = true, help = "Show debug messages")]
179 verbose: bool,
180
181 #[arg(
182 global = true,
183 short = 's',
184 long,
185 env = "DATABASE_SCHEMA",
186 long_help = "Database schema\n \
187 - For MySQL and SQLite, this argument is ignored.\n \
188 - For PostgreSQL, this argument is optional with default value 'public'.\n"
189 )]
190 database_schema: Option<String>,
191
192 #[arg(
193 global = true,
194 short = 'u',
195 long,
196 env = "DATABASE_URL",
197 help = "Database URL"
198 )]
199 database_url: Option<String>,
200
201 #[command(subcommand)]
202 command: Option<MigrateSubcommands>,
203}
204
205fn handle_error<E>(error: E)
206where
207 E: Display,
208{
209 eprintln!("{error}");
210 exit(1);
211}