use crate::cli::args::actions::register::db::register_db;
use chrono::Utc;
use couchdb_orm::utils::{append_to_file, get_dir_entries, init_meta_dir};
use serde::{Deserialize, Serialize};
use std::fs;
use std::io::BufRead;
use std::path::PathBuf;
use std::str::FromStr;
use thiserror::Error;
#[derive(Debug, Error, Serialize, Deserialize, Clone)]
#[error("RegisterSchemaError: {0}")]
struct RegisterSchemaError(String);
fn get_previous_version(db_path: &PathBuf) -> Result<Option<PathBuf>, Box<dyn std::error::Error>> {
if db_path.exists() {
let entries: Vec<PathBuf> = get_dir_entries(&db_path)?;
let entries: Vec<&PathBuf> = entries
.iter()
.filter(|res| !res.to_str().unwrap().contains(&"mod.rs"))
.collect();
if entries.len() > 0 {
let mut entries: Vec<i64> = entries
.iter()
.map(|p| {
p.file_name()
.expect(&format!("could not get filename from {:?}", p))
.to_os_string()
.into_string()
.expect(&format!("could not convert {:?} to string", p))
.replace("schema_", "")
.replace(".rs", "")
.parse::<i64>()
.expect(&format!("could not convert {:?} to i64", p))
})
.collect();
entries.sort();
let last_version_name: PathBuf = entries
.pop()
.map(|o| {
PathBuf::from_str(&format!("schema_{}.rs", o))
.expect("could not convert to path")
})
.unwrap();
let mut n_db_path: PathBuf = db_path.clone();
n_db_path.push(last_version_name);
Ok(Some(n_db_path))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
fn write_file(db_path: &str, content: &str) -> Result<PathBuf, Box<dyn std::error::Error>> {
let timestamp: i64 = Utc::now().timestamp();
let file_name: String = format!("{}/schema_{}.rs", db_path, ×tamp);
let mod_file_name: PathBuf = PathBuf::from_str(&format!("{}/mod.rs", db_path)).unwrap();
let new_line: String = format!("pub mod schema_{};", ×tamp);
fs::write(&file_name, &content)?;
append_to_file(&mod_file_name, &new_line)?;
Ok(PathBuf::from(file_name))
}
pub fn register_schema(
path: PathBuf,
db_name: String,
override_version: bool,
rootdir: &PathBuf,
) -> Result<PathBuf, Box<dyn std::error::Error>> {
init_meta_dir(&rootdir)?;
let file_content: String = fs::read_to_string(&path).map_err(|e| {
RegisterSchemaError(format!(
"register_schema > read schema content error: {} -> {}",
e,
path.display()
))
})?;
let db_path: String = format!(
"{}/_meta/src/schemas/{}",
rootdir.to_str().unwrap(),
db_name
);
let previous_version: Option<PathBuf> = get_previous_version(&PathBuf::from_str(&db_path)?)?;
register_db(&db_name, rootdir)?;
match previous_version {
Some(ref p) => {
if file_content == fs::read_to_string(p)? && !override_version {
Err(Box::new(RegisterSchemaError(
"schema is same as last version.\naborting.".to_string(),
)))
} else if override_version {
fs::remove_file(p).map_err(|e| {
RegisterSchemaError(format!(
"register_schema > override_version > remove_file error: {}",
e
))
})?;
let mod_file_name: PathBuf =
PathBuf::from_str(&format!("{}/mod.rs", db_path)).unwrap();
let file = std::fs::File::open(&mod_file_name)?;
let reader = std::io::BufReader::new(file);
let mut mod_file_content: Vec<String> =
reader.lines().map(|l| l.unwrap()).collect();
mod_file_content.pop().unwrap();
let mod_file_content: String = mod_file_content.join("\n");
fs::write(&mod_file_name, &mod_file_content).map_err(|e| {
RegisterSchemaError(format!(
"register_schema > override_version > write_file error: {} -> {}",
e,
mod_file_name.display()
))
})?;
write_file(&db_path, &file_content)
} else {
write_file(&db_path, &file_content)
}
}
None => write_file(&db_path, &file_content),
}
}
#[cfg(test)]
mod test {
use super::*;
use couchdb_orm::tests::utils::*;
#[test]
#[should_panic]
fn test_get_previous_version_error() {
create_tests_folder();
let root_path = create_test_dir("get_previous_version_error");
let files_to_create: Vec<PathBuf> = vec![
root_path.join(PathBuf::from("not_a_version")),
root_path.join(PathBuf::from("not_a_version_again")),
root_path.join(PathBuf::from("not_a_version_again_again")),
];
files_to_create.iter().for_each(|f| {
fs::write(&f, "test").unwrap();
});
get_previous_version(&root_path).unwrap();
}
#[test]
fn test_get_previous_version_no_dir() {
let not_a_dir_path: PathBuf = PathBuf::from("not_a_dir");
assert_eq!(get_previous_version(¬_a_dir_path).unwrap(), None);
}
#[test]
fn test_get_previous_version_no_entries() {
create_tests_folder();
let root_path = create_test_dir("get_previous_version_no_entries");
assert_eq!(get_previous_version(&root_path).unwrap(), None);
}
#[test]
fn test_get_previous_version() {
create_tests_folder();
let root_path = create_test_dir("get_previous_version");
let files_to_create: Vec<PathBuf> = vec![
root_path.join(PathBuf::from("schema_0.rs")),
root_path.join(PathBuf::from("schema_1.rs")),
root_path.join(PathBuf::from("schema_2.rs")),
];
files_to_create.iter().for_each(|f| {
fs::write(&f, "test").unwrap();
});
assert_eq!(
get_previous_version(&root_path).unwrap(),
Some(root_path.join("schema_2.rs"))
);
}
#[test]
fn test_schema_write_file() {
create_tests_folder();
let root_path = create_test_dir("schema_write_file");
let content: &str = "this is my content";
let filepath: PathBuf = write_file(&root_path.to_str().unwrap(), &content).unwrap();
let modpath: PathBuf = root_path.join("mod.rs");
let file_content: String = fs::read_to_string(&filepath).unwrap();
let mod_content: String = fs::read_to_string(&modpath).unwrap();
let first_mod_content: String = format!(
"pub mod {};",
filepath
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string()
.replace(".rs", "")
);
assert!(filepath.exists());
assert_eq!(&file_content, &content);
assert_eq!(&mod_content, &first_mod_content);
let mut child = std::process::Command::new("sleep")
.arg("1")
.spawn()
.unwrap();
let _result = child.wait().unwrap();
let filepath: PathBuf = write_file(&root_path.to_str().unwrap(), &content).unwrap();
let file_content: String = fs::read_to_string(&filepath).unwrap();
let mod_content: String = fs::read_to_string(&modpath).unwrap();
assert!(filepath.exists());
assert_eq!(&file_content, &content);
assert_eq!(
&mod_content,
&format!(
"{}\npub mod {};",
first_mod_content,
filepath
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string()
.replace(".rs", "")
)
);
}
#[test]
fn test_register_schema_same_version() {
create_tests_folder();
let root_path = create_test_dir("schema_same_version");
let content: &str = "this is my content";
let filepath: PathBuf = root_path.join("test_model");
fs::write(&filepath, &content).unwrap();
let db_name: &str = "tasks";
register_schema(filepath.clone(), db_name.to_string(), false, &root_path).unwrap();
let error =
register_schema(filepath.clone(), db_name.to_string(), false, &root_path).unwrap_err();
let error: &RegisterSchemaError = error.downcast_ref().unwrap();
assert_eq!(
format!("{}", error),
format!(
"{}",
RegisterSchemaError("schema is same as last version.\naborting.".to_string())
)
);
}
#[test]
fn test_register_schema_override_last_version() {
create_tests_folder();
let root_path = create_test_dir("schema_override_last_version");
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");
let mod_path: PathBuf = root_path.join("_meta/src/schemas/tasks/mod.rs");
fs::write(&filepath, &content).unwrap();
fs::write(&filepath2, &content2).unwrap();
let db_name: &str = "tasks";
let schema_path_1: PathBuf =
register_schema(filepath.clone(), db_name.to_string(), false, &root_path).unwrap();
let schemas_content: String = fs::read_to_string(&schema_path_1).unwrap();
let mod_content: String = fs::read_to_string(&mod_path).unwrap();
assert!(schema_path_1.exists());
assert_eq!(&schemas_content, &content);
assert_eq!(
&mod_content,
&format!(
"pub mod {};",
schema_path_1
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string()
.replace(".rs", "")
)
);
let mut child = std::process::Command::new("sleep")
.arg("1")
.spawn()
.unwrap();
let _result = child.wait().unwrap();
let schema_path_2: PathBuf =
register_schema(filepath2.clone(), db_name.to_string(), true, &root_path).unwrap();
let mod_content: String = fs::read_to_string(&mod_path).unwrap();
assert_ne!(&schema_path_1, &schema_path_2);
assert_eq!(schema_path_1.exists(), false);
assert_eq!(
&mod_content,
&format!(
"pub mod {};",
schema_path_2
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string()
.replace(".rs", "")
)
);
}
#[test]
fn test_register_schema() {
create_tests_folder();
let root_path = create_test_dir("register_schema");
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";
let schemas_path =
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();
let schemas_path2 =
register_schema(filepath2.clone(), db_name.to_string(), false, &root_path).unwrap();
let schemas_content: String = fs::read_to_string(&schemas_path).unwrap();
let schemas_content2: String = fs::read_to_string(&schemas_path2).unwrap();
assert!(schemas_path.exists());
assert!(schemas_path2.exists());
assert_eq!(&schemas_content, &content);
assert_eq!(&schemas_content2, &content2);
}
}