1use sova_core::Error;
4use sea_orm::DatabaseConnection;
5use sea_orm_migration::MigratorTrait;
6
7const USAGE: &str = "usage: migrate [up|down|status] [N]";
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum MigrateCmd {
11 Up(Option<u32>),
13 Down(u32),
15 Status,
16}
17
18pub fn parse_migrate_args(args: &[String]) -> Result<MigrateCmd, String> {
20 match args {
21 [] => Ok(MigrateCmd::Up(None)),
22 [s] if s == "up" => Ok(MigrateCmd::Up(None)),
23 [s] if s == "down" => Ok(MigrateCmd::Down(1)),
24 [s] if s == "status" => Ok(MigrateCmd::Status),
25 [s, n] if s == "up" => Ok(MigrateCmd::Up(Some(parse_steps(n)?))),
26 [s, n] if s == "down" => Ok(MigrateCmd::Down(parse_steps(n)?)),
27 _ => Err(USAGE.into()),
28 }
29}
30
31fn parse_steps(raw: &str) -> Result<u32, String> {
32 let n: u32 = raw
33 .parse()
34 .map_err(|_| format!("invalid step count `{raw}`; {USAGE}"))?;
35 if n == 0 {
36 return Err(format!("step count must be >= 1; {USAGE}"));
37 }
38 Ok(n)
39}
40
41pub(crate) async fn run_migrate<M: MigratorTrait>(
42 conn: DatabaseConnection,
43 args: &[String],
44) -> Result<(), Error> {
45 let cmd = parse_migrate_args(args).map_err(Error::Internal)?;
46 match cmd {
47 MigrateCmd::Up(steps) => M::up(&conn, steps)
48 .await
49 .map_err(|e| Error::Internal(format!("migrate up: {e}")))?,
50 MigrateCmd::Down(n) => M::down(&conn, Some(n))
51 .await
52 .map_err(|e| Error::Internal(format!("migrate down: {e}")))?,
53 MigrateCmd::Status => print_status::<M>(&conn).await?,
54 }
55 Ok(())
56}
57
58async fn print_status<M: MigratorTrait>(conn: &DatabaseConnection) -> Result<(), Error> {
59 let rows = M::get_migration_with_status(conn)
60 .await
61 .map_err(|e| Error::Internal(format!("migrate status: {e}")))?;
62 let mut applied = 0usize;
63 let mut pending = 0usize;
64 for m in &rows {
65 let status = m.status();
66 match status {
67 sea_orm_migration::MigrationStatus::Applied => applied += 1,
68 sea_orm_migration::MigrationStatus::Pending => pending += 1,
69 }
70 println!("{status:<9} {}", m.name());
71 }
72 println!("---");
73 println!("{applied} applied, {pending} pending");
74 Ok(())
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 fn s(args: &[&str]) -> Vec<String> {
82 args.iter().map(|a| (*a).to_string()).collect()
83 }
84
85 #[test]
86 fn parse_defaults_and_steps() {
87 assert_eq!(parse_migrate_args(&s(&[])).unwrap(), MigrateCmd::Up(None));
88 assert_eq!(parse_migrate_args(&s(&["up"])).unwrap(), MigrateCmd::Up(None));
89 assert_eq!(
90 parse_migrate_args(&s(&["up", "3"])).unwrap(),
91 MigrateCmd::Up(Some(3))
92 );
93 assert_eq!(parse_migrate_args(&s(&["down"])).unwrap(), MigrateCmd::Down(1));
94 assert_eq!(
95 parse_migrate_args(&s(&["down", "2"])).unwrap(),
96 MigrateCmd::Down(2)
97 );
98 assert_eq!(
99 parse_migrate_args(&s(&["status"])).unwrap(),
100 MigrateCmd::Status
101 );
102 }
103
104 #[test]
105 fn parse_rejects_bad() {
106 assert!(parse_migrate_args(&s(&["up", "0"])).is_err());
107 assert!(parse_migrate_args(&s(&["up", "x"])).is_err());
108 assert!(parse_migrate_args(&s(&["fresh"])).is_err());
109 assert!(parse_migrate_args(&s(&["status", "1"])).is_err());
110 }
111}