use crate::client::couchdb::actions::db::all_docs::get_all_docs;
use crate::regexes::COUCHDB_DB_RULE;
use awc::Client;
use serde::{de::DeserializeOwned, Serialize};
use std::fmt::Debug;
pub mod errors;
use errors::DBMigrationError;
async fn post_request<T: Serialize + DeserializeOwned + Debug>(
client: &awc::Client,
uri: &str,
body: &Vec<T>,
) -> Result<bool, Box<dyn std::error::Error>> {
let json_body: serde_json::Value = serde_json::json!({ "docs": body });
match client.post(uri).send_json(&json_body).await {
Ok(mut response) => {
if response.status().as_u16() >= 400 {
let bytes: Vec<u8> = response.body().await?.iter().cloned().collect();
let error: serde_json::Value =
serde_json::from_str(std::str::from_utf8(bytes.as_slice()).unwrap()).unwrap();
Err(Box::new(DBMigrationError::new(&format!(
"Error with the request to {}: error: \n{}",
&uri, error
))))
} else {
Ok(true)
}
}
Err(error) => Err(Box::new(DBMigrationError::new(&format!(
"Error with the request to {}: error: \n{}",
uri, error
)))),
}
}
pub async fn migrate_db<
D: From<S> + Serialize + DeserializeOwned + Debug,
S: Clone + Serialize + DeserializeOwned + Debug,
>(
client: &Client,
db_name: &str,
host: &str,
) -> Result<bool, Box<dyn std::error::Error>> {
if !COUCHDB_DB_RULE.is_match(db_name) {
return Err(Box::new(DBMigrationError::new(&format!(
"{} string doesn't respect the regex rule {}",
db_name,
COUCHDB_DB_RULE.as_str()
))));
}
let mut skip: usize = 0;
let mut max_rows: usize = 1000;
let limit: usize = 100;
let uri: String = format!("{}/{}/_bulk_docs", host, db_name,);
while skip < max_rows {
println!("chunk {} migrated", skip / 100);
let all_docs = get_all_docs::<S>(client, db_name, host, skip, limit).await?;
max_rows = all_docs.total_rows;
let new_docs: Vec<D> = all_docs
.rows
.iter()
.map(|old| {
let value: S = old.doc.clone();
D::from(value)
})
.collect();
post_request(&client, &uri, &new_docs).await?;
skip += limit;
}
Ok(true)
}
#[cfg(test)]
mod tests {
extern crate actix_web;
extern crate couchdb_orm_meta;
extern crate wiremock;
use super::*;
use std::error::Error;
use std::fs;
use std::path::PathBuf;
use std::str::FromStr;
use wiremock::matchers::{method, path, path_regex};
use wiremock::{Mock, MockServer, ResponseTemplate};
use couchdb_orm_meta::schemas::tasks::schema_1661680133::Task as OldTask;
use couchdb_orm_meta::schemas::tasks::schema_1661681370::Task;
#[actix_rt::test]
async fn migrate_db_regex_error_test() {
let client: Client = Client::default();
let box_result: Box<dyn Error> = migrate_db::<Task, OldTask>(&client, "Test", "")
.await
.unwrap_err();
let result: &DBMigrationError = box_result.downcast_ref().unwrap();
assert_eq!(
format!("{}", result),
format!(
"DBMigrationError: {} string doesn't respect the regex rule {}",
"Test",
COUCHDB_DB_RULE.as_str()
)
);
let box_result: Box<dyn Error> = migrate_db::<Task, OldTask>(&client, "testT", "")
.await
.unwrap_err();
let result: &DBMigrationError = box_result.downcast_ref().unwrap();
assert_eq!(
format!("{}", result),
format!(
"DBMigrationError: {} string doesn't respect the regex rule {}",
"testT",
COUCHDB_DB_RULE.as_str()
)
)
}
#[actix_rt::test]
async fn migrate_db_http_error_test() {
let mock_server = MockServer::start().await;
let data = r#"
{
"error": "test error"
}"#;
let this_file: PathBuf = PathBuf::from_str(file!()).unwrap();
let file = fs::read_to_string(&format!(
"{}/__fixtures__/_all_docs.200.response.json",
this_file.parent().unwrap().to_str().unwrap()
))
.unwrap();
let all_docs_response: ResponseTemplate =
ResponseTemplate::new(200).set_body_raw(file.as_bytes(), "application/json");
let bulk_docs_response: ResponseTemplate =
ResponseTemplate::new(400).set_body_raw(data.as_bytes(), "application/json");
let all_docs_mock = Mock::given(method("POST"))
.and(path_regex(r"/test/\w+"))
.respond_with(all_docs_response);
let bulk_docs_mock = Mock::given(method("POST"))
.and(path("/test/_bulk_docs"))
.respond_with(bulk_docs_response);
mock_server.register(bulk_docs_mock).await;
mock_server.register(all_docs_mock).await;
let error: serde_json::Value = serde_json::from_str(data).unwrap();
let client: Client = Client::default();
let host: String = mock_server.uri();
let result = migrate_db::<Task, OldTask>(&client, "test", &host).await;
println!("{:?}", result);
let box_result: Box<dyn Error> = result.unwrap_err();
let result: &DBMigrationError = box_result.downcast_ref().unwrap();
assert_eq!(
format!("{}", result),
format!(
"DBMigrationError: Error with the request to {}: error: \n{}",
format!("{}/{}/_bulk_docs", host, "test"),
error
)
)
}
}