use crate::cli::args::actions::register::db::register_db;
use couchdb_orm::utils::{get_dir_entries, init_meta_dir, main_content};
use dialoguer::{theme::ColorfulTheme, MultiSelect};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
use std::str::FromStr;
use thiserror::Error;
#[derive(Debug, Error, Serialize, Deserialize, Clone)]
#[error("RegisterMigrationError: {0}")]
struct RegisterMigrationError(String);
fn write_main_file(rootdir: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
let main_file_name: PathBuf =
PathBuf::from_str(&format!("{}/_meta/src/main.rs", rootdir.to_str().unwrap())).unwrap();
let main_content: String = main_content(rootdir)?;
fs::write(main_file_name, main_content)?;
Ok(())
}
fn write_migration_db_mod(
db_path: &str,
filename: &PathBuf,
rootdir: &PathBuf,
) -> Result<(), Box<dyn std::error::Error>> {
let migrations_path: PathBuf = PathBuf::from_str(&db_path)?;
let mod_file_name: PathBuf = PathBuf::from_str(&format!("{}/mod.rs", db_path)).unwrap();
let available_migrations: Vec<PathBuf> = fs::read_dir(&migrations_path)?
.map(|res| res.map(|e| e.path()))
.collect::<Result<Vec<PathBuf>, std::io::Error>>()?;
let available_migrations: Vec<&PathBuf> = available_migrations
.iter()
.filter(|e| e.file_name().unwrap() != "mod.rs")
.collect();
let available_migrations_mod_filenames: String = available_migrations
.iter()
.map(|e| {
let mut s = e
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string()
.replace(".rs", ";");
s.push_str("\n");
format!("pub mod {}", s)
})
.collect();
let mut available_migrations_enum: Vec<String> = available_migrations
.iter()
.map(|e| {
e.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string()
.replace(".rs", "")
})
.collect();
let filename_str: String = format!(
r#"{}"#,
filename
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string()
.replace(".rs", "")
);
if !available_migrations_enum.contains(&filename_str) {
available_migrations_enum.push(filename_str.clone())
}
let available_migrations_enum: String = available_migrations_enum.join(",\n");
let mut available_migrations_list: Vec<String> = available_migrations
.iter()
.map(|e| {
let s = e
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string()
.replace(".rs", "");
let s = format!(r#""{}""#, s);
s
})
.collect();
let filename_str: String = format!(r#""{}""#, &filename_str);
if !available_migrations_list.contains(&filename_str) {
available_migrations_list.push(filename_str.clone());
}
let available_migrations_list: String = available_migrations_list.join(",\n");
let available_migration_content: String = format!(
include_str!("../../../../../../../static/templates/available_migrations.tpl"),
available_migrations_mod_filenames, available_migrations_enum, available_migrations_list
);
fs::write(mod_file_name, available_migration_content)?;
write_main_file(rootdir)?;
Ok(())
}
fn write_migration_file(
db_path: &str,
db_name: &str,
filenames: (String, String),
override_version: bool,
rootdir: &PathBuf,
) -> Result<(), Box<dyn std::error::Error>> {
let src_name: String = filenames.0.replace(".rs", "");
let dest_name: String = filenames.1.replace(".rs", "");
let file_content: String = format!(
include_str!("../../../../../../../static/templates/migration_file.tpl"),
db_name, src_name, db_name, dest_name, db_name
);
let file_name: PathBuf = PathBuf::from_str(&format!(
"{}/from_{}_to_{}.rs",
db_path, src_name, dest_name
))
.unwrap();
if !file_name.exists() || override_version {
fs::write(&file_name, &file_content)?;
}
write_migration_db_mod(db_path, &file_name, rootdir)?;
Ok(())
}
pub fn register_migration(
db_name: String,
override_version: bool,
rootdir: &PathBuf,
) -> Result<(), Box<dyn std::error::Error>> {
init_meta_dir(&rootdir)?;
let db_path: PathBuf = PathBuf::from_str(&format!(
"{}/_meta/src/migrations/{}",
rootdir.to_str().unwrap(),
db_name
))?;
let schemas_path: PathBuf = PathBuf::from_str(&format!(
"{}/_meta/src/schemas/{}",
rootdir.to_str().unwrap(),
db_name
))?;
register_db(&db_name, rootdir)?;
let schemas_available: Vec<PathBuf> = get_dir_entries(&schemas_path)?;
let schemas_available: Vec<&str> = schemas_available
.iter()
.filter(|res| !res.to_str().unwrap().contains(&"mod.rs"))
.map(|s| s.to_str().unwrap())
.collect();
if schemas_available.len() > 0 {
let selection: Vec<usize> = MultiSelect::with_theme(&ColorfulTheme::default())
.with_prompt("Select 2 schemas to create a migration: ")
.items(&schemas_available)
.interact()?;
if selection.len() != 2 {
Err(Box::new(RegisterMigrationError(String::from(
"2 schemas must be selected to register a migration",
))))
} else {
let selection: Vec<PathBuf> = selection
.iter()
.cloned()
.map(|i| PathBuf::from_str(schemas_available[i]).unwrap())
.collect();
let filenames: Vec<String> = selection
.iter()
.map(|p| p.file_name().unwrap().to_os_string().into_string().unwrap())
.collect();
write_migration_file(
&db_path.to_str().unwrap(),
&db_name,
(filenames[0].clone(), filenames[1].clone()),
override_version,
rootdir,
)
}
} else {
Err(Box::new(RegisterMigrationError(format!(
"No schemas registered in {}",
schemas_path.to_str().unwrap()
))))
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::cli::args::actions::register::schema::register_schema;
use couchdb_orm::tests::utils::*;
#[test]
fn test_register_migration() {
create_tests_folder();
let root_path = create_test_dir("register_migration");
let content: &str = "this is my content";
let content2: &str = "this is my content2";
let filepath: PathBuf = root_path.join("test_model");
let filepath2: PathBuf = root_path.join("test_model_2");
fs::write(&filepath, &content).unwrap();
fs::write(&filepath2, &content2).unwrap();
let db_name: &str = "tasks";
register_schema(filepath.clone(), db_name.to_string(), false, &root_path).unwrap();
let mut child = std::process::Command::new("sleep")
.arg("1")
.spawn()
.unwrap();
let _result = child.wait().unwrap();
register_schema(filepath2.clone(), db_name.to_string(), false, &root_path).unwrap();
assert!(true)
}
}