use anyhow::{Context, Result};
use glob::glob;
use std::path::Path;
use crate::version::is_valid_version;
use crate::Migration;
pub fn discover_migrations(dir: &Path) -> Result<Vec<Migration>> {
let pattern = dir.join("[0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z]-*");
let pattern_str = pattern
.to_str()
.context("Invalid path for migration directory")?;
let mut migrations: Vec<Migration> = glob(pattern_str)
.context("Failed to read glob pattern")?
.filter_map(|entry| entry.ok())
.filter(|path| path.is_file())
.filter_map(|path| {
let filename = path.file_name()?.to_str()?;
let version = extract_version(filename)?;
let id = extract_id(filename);
Some(Migration {
id,
version,
file_path: path,
})
})
.collect();
migrations.sort_by(|a, b| a.version.cmp(&b.version));
Ok(migrations)
}
pub fn extract_version(filename: &str) -> Option<String> {
if filename.len() < 6 {
return None;
}
if filename.as_bytes().get(5) != Some(&b'-') {
return None;
}
let version = &filename[..5];
if is_valid_version(version) {
Some(version.to_string())
} else {
None
}
}
pub fn extract_id(filename: &str) -> String {
match filename.rfind('.') {
Some(pos) => filename[..pos].to_string(),
None => filename.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_version() {
assert_eq!(extract_version("1f72f-init.sh"), Some("1f72f".to_string()));
assert_eq!(
extract_version("00000-something.ts"),
Some("00000".to_string())
);
assert_eq!(extract_version("zzzzz-last.py"), Some("zzzzz".to_string()));
assert_eq!(extract_version("ab-invalid.sh"), None); assert_eq!(extract_version("1234-short.sh"), None); assert_eq!(extract_version("123456-toolong.sh"), None); }
#[test]
fn test_extract_id() {
assert_eq!(extract_id("1f72f-init.sh"), "1f72f-init");
assert_eq!(extract_id("00000-add-config.ts"), "00000-add-config");
assert_eq!(extract_id("zzzzz-no-extension"), "zzzzz-no-extension");
}
}