1use sea_orm::DatabaseConnection;
4use sea_orm_migration::MigratorTrait;
5use sova_core::Error;
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!(
89 parse_migrate_args(&s(&["up"])).unwrap(),
90 MigrateCmd::Up(None)
91 );
92 assert_eq!(
93 parse_migrate_args(&s(&["up", "3"])).unwrap(),
94 MigrateCmd::Up(Some(3))
95 );
96 assert_eq!(
97 parse_migrate_args(&s(&["down"])).unwrap(),
98 MigrateCmd::Down(1)
99 );
100 assert_eq!(
101 parse_migrate_args(&s(&["down", "2"])).unwrap(),
102 MigrateCmd::Down(2)
103 );
104 assert_eq!(
105 parse_migrate_args(&s(&["status"])).unwrap(),
106 MigrateCmd::Status
107 );
108 }
109
110 #[test]
111 fn parse_rejects_bad() {
112 assert!(parse_migrate_args(&s(&["up", "0"])).is_err());
113 assert!(parse_migrate_args(&s(&["up", "x"])).is_err());
114 assert!(parse_migrate_args(&s(&["fresh"])).is_err());
115 assert!(parse_migrate_args(&s(&["status", "1"])).is_err());
116 }
117}