couchdb_orm/client/couchdb/actions/db/migrate/
mod.rs1use crate::client::couchdb::actions::db::all_docs::get_all_docs;
18use crate::regexes::COUCHDB_DB_RULE;
19use awc::Client;
20use serde::{de::DeserializeOwned, Serialize};
21use std::fmt::Debug;
22
23pub mod errors;
24
25use errors::DBMigrationError;
26
27async fn post_request<T: Serialize + DeserializeOwned + Debug>(
28 client: &awc::Client,
29 uri: &str,
30 body: &Vec<T>,
31) -> Result<bool, Box<dyn std::error::Error>> {
32 let json_body: serde_json::Value = serde_json::json!({ "docs": body });
33 match client.post(uri).send_json(&json_body).await {
34 Ok(mut response) => {
35 if response.status().as_u16() >= 400 {
37 let bytes: Vec<u8> = response.body().await?.iter().cloned().collect();
38 let error: serde_json::Value =
39 serde_json::from_str(std::str::from_utf8(bytes.as_slice()).unwrap()).unwrap();
40 Err(Box::new(DBMigrationError::new(&format!(
41 "Error with the request to {}: error: \n{}",
42 &uri, error
43 ))))
44 } else {
45 Ok(true)
46 }
47 }
48 Err(error) => Err(Box::new(DBMigrationError::new(&format!(
49 "Error with the request to {}: error: \n{}",
50 uri, error
51 )))),
52 }
53}
54
55pub async fn migrate_db<
56 D: From<S> + Serialize + DeserializeOwned + Debug,
57 S: Clone + Serialize + DeserializeOwned + Debug,
58>(
59 client: &Client,
60 db_name: &str,
61 host: &str,
62) -> Result<bool, Box<dyn std::error::Error>> {
63 if !COUCHDB_DB_RULE.is_match(db_name) {
64 return Err(Box::new(DBMigrationError::new(&format!(
65 "{} string doesn't respect the regex rule {}",
66 db_name,
67 COUCHDB_DB_RULE.as_str()
68 ))));
69 }
70
71 let mut skip: usize = 0;
72 let mut max_rows: usize = 1000;
73 let limit: usize = 100;
74 let uri: String = format!("{}/{}/_bulk_docs", host, db_name,);
75 while skip < max_rows {
78 println!("chunk {} migrated", skip / 100);
79 let all_docs = get_all_docs::<S>(client, db_name, host, skip, limit).await?;
83 max_rows = all_docs.total_rows;
84 let new_docs: Vec<D> = all_docs
85 .rows
86 .iter()
87 .map(|old| {
88 let value: S = old.doc.clone();
89 D::from(value)
90 })
91 .collect();
92 post_request(&client, &uri, &new_docs).await?;
93 skip += limit;
96 }
97 Ok(true)
98}
99
100#[cfg(test)]
101mod tests {
102 extern crate actix_web;
103 extern crate couchdb_orm_meta;
104 extern crate wiremock;
105
106 use super::*;
107 use std::error::Error;
108 use std::fs;
109 use std::path::PathBuf;
110 use std::str::FromStr;
111
112 use wiremock::matchers::{method, path, path_regex};
113 use wiremock::{Mock, MockServer, ResponseTemplate};
114
115 use couchdb_orm_meta::schemas::tasks::schema_1661680133::Task as OldTask;
116 use couchdb_orm_meta::schemas::tasks::schema_1661681370::Task;
117
118 #[actix_rt::test]
119 async fn migrate_db_regex_error_test() {
120 let client: Client = Client::default();
121 let box_result: Box<dyn Error> = migrate_db::<Task, OldTask>(&client, "Test", "")
122 .await
123 .unwrap_err();
124 let result: &DBMigrationError = box_result.downcast_ref().unwrap();
125 assert_eq!(
126 format!("{}", result),
127 format!(
128 "DBMigrationError: {} string doesn't respect the regex rule {}",
129 "Test",
130 COUCHDB_DB_RULE.as_str()
131 )
132 );
133
134 let box_result: Box<dyn Error> = migrate_db::<Task, OldTask>(&client, "testT", "")
135 .await
136 .unwrap_err();
137 let result: &DBMigrationError = box_result.downcast_ref().unwrap();
138 assert_eq!(
139 format!("{}", result),
140 format!(
141 "DBMigrationError: {} string doesn't respect the regex rule {}",
142 "testT",
143 COUCHDB_DB_RULE.as_str()
144 )
145 )
146 }
147
148 #[actix_rt::test]
149 async fn migrate_db_http_error_test() {
150 let mock_server = MockServer::start().await;
151 let data = r#"
153 {
154 "error": "test error"
155 }"#;
156 let this_file: PathBuf = PathBuf::from_str(file!()).unwrap();
158 let file = fs::read_to_string(&format!(
159 "{}/__fixtures__/_all_docs.200.response.json",
160 this_file.parent().unwrap().to_str().unwrap()
161 ))
162 .unwrap();
163
164 let all_docs_response: ResponseTemplate =
165 ResponseTemplate::new(200).set_body_raw(file.as_bytes(), "application/json");
166 let bulk_docs_response: ResponseTemplate =
167 ResponseTemplate::new(400).set_body_raw(data.as_bytes(), "application/json");
168 let all_docs_mock = Mock::given(method("POST"))
169 .and(path_regex(r"/test/\w+"))
170 .respond_with(all_docs_response);
171 let bulk_docs_mock = Mock::given(method("POST"))
172 .and(path("/test/_bulk_docs"))
173 .respond_with(bulk_docs_response);
174
175 mock_server.register(bulk_docs_mock).await;
176 mock_server.register(all_docs_mock).await;
177
178 let error: serde_json::Value = serde_json::from_str(data).unwrap();
179
180 let client: Client = Client::default();
181 let host: String = mock_server.uri();
182
183 let result = migrate_db::<Task, OldTask>(&client, "test", &host).await;
184 println!("{:?}", result);
185
186 let box_result: Box<dyn Error> = result.unwrap_err();
187 let result: &DBMigrationError = box_result.downcast_ref().unwrap();
188 assert_eq!(
189 format!("{}", result),
190 format!(
191 "DBMigrationError: Error with the request to {}: error: \n{}",
192 format!("{}/{}/_bulk_docs", host, "test"),
193 error
194 )
195 )
196 }
197
198 }