Skip to main content

toolu_orm_cli/
status.rs

1//! Migration status reporting (applied vs pending).
2
3use std::path::Path;
4
5use toolu_orm_connection::DbConnection;
6use toolu_orm_core::dialect::Dialect;
7
8use crate::migrate::{ensure_migrations_table, get_applied_migrations, MigrateError};
9
10pub struct MigrationStatus {
11  pub applied: Vec<String>,
12  pub pending: Vec<String>,
13}
14
15/// Returns the current migration status.
16///
17/// # Errors
18///
19/// Returns `MigrateError` on database or file I/O failures.
20pub async fn get_status(
21  conn: &impl DbConnection,
22  migrations_dir: &str,
23  dialect: Dialect,
24) -> Result<MigrationStatus, MigrateError> {
25  ensure_migrations_table(conn, dialect).await?;
26  let applied = get_applied_migrations(conn).await?;
27  let all_files = collect_all_sql_files(migrations_dir)?;
28
29  let pending: Vec<String> = all_files
30    .into_iter()
31    .filter(|f| !applied.contains(f))
32    .collect();
33
34  Ok(MigrationStatus { applied, pending })
35}
36
37fn collect_all_sql_files(migrations_dir: &str) -> Result<Vec<String>, MigrateError> {
38  let path = Path::new(migrations_dir);
39  if !path.exists() {
40    return Ok(vec![]);
41  }
42
43  let mut all_files: Vec<String> = Vec::new();
44  let entries =
45    std::fs::read_dir(path).map_err(|e| MigrateError::ReadDir(format!("{migrations_dir}: {e}")))?;
46
47  for entry in entries {
48    let entry = entry.map_err(|e| MigrateError::ReadDir(format!("{migrations_dir}: {e}")))?;
49    let name = entry.file_name().to_string_lossy().into_owned();
50    if name.ends_with(".sql") {
51      all_files.push(name);
52    }
53  }
54
55  all_files.sort();
56  Ok(all_files)
57}