#[cfg(feature = "couch_rs_derive")]
#[allow(unused_imports)]
#[macro_use]
extern crate couch_rs_derive;
#[cfg(feature = "couch_rs_derive")]
#[doc(hidden)]
pub use couch_rs_derive::*;
pub use std::borrow::Cow;
#[allow(unused_macros)]
#[macro_use]
mod macros {
macro_rules! mod_use {
($module:ident) => {
mod $module;
pub use self::$module::*;
};
}
macro_rules! json_extr {
($e:expr) => {
serde_json::from_value($e.to_owned()).unwrap_or_default()
};
}
macro_rules! dtj {
($e:expr) => {
js!(&$e.get_data())
};
}
macro_rules! js {
($e:expr) => {
serde_json::to_string(&$e).unwrap()
};
}
macro_rules! s {
($e:expr) => {
String::from($e)
};
}
macro_rules! tspec_ms {
($tspec:ident) => {{
$tspec.sec * 1000 + $tspec.nsec as i64 / 1000000
}};
}
macro_rules! msnow {
() => {{
let tm = time::now().to_timespec();
tspec_ms!(tm)
}};
}
macro_rules! url_encode {
($id:ident) => {{
url::form_urlencoded::byte_serialize($id.as_bytes()).collect::<String>()
}};
}
}
mod client;
pub mod database;
pub mod document;
pub mod error;
pub mod model;
pub mod types;
pub use client::Client;
#[allow(unused_mut, unused_variables)]
#[cfg(test)]
mod couch_rs_tests {
use crate as couch_rs;
use couch_rs::document::TypedCouchDocument;
use couch_rs::types::document::DocumentId;
use couch_rs::CouchDocument;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, CouchDocument, Default, Debug)]
pub struct TestDoc {
#[serde(skip_serializing_if = "String::is_empty")]
pub _id: DocumentId,
#[serde(skip_serializing_if = "String::is_empty")]
pub _rev: String,
pub first_name: String,
pub last_name: String,
}
mod client_tests {
use crate::client::Client;
use crate::couch_rs_tests::TestDoc;
use reqwest::StatusCode;
use serde_json::json;
#[tokio::test]
async fn should_check_couchdbs_status() {
let client = Client::new_local_test().unwrap();
let status = client.check_status().await;
assert!(status.is_ok());
assert_eq!("The Apache Software Foundation", status.unwrap().vendor.name);
}
#[tokio::test]
async fn should_create_test_db() {
let client = Client::new_local_test().unwrap();
let dbw = client.db("should_create_test_db").await;
assert!(dbw.is_ok());
let _ = client.destroy_db("should_create_test_db");
}
#[tokio::test]
async fn should_create_test_db_with_a_complex_name() {
let client = Client::new_local_test().unwrap();
let dbname = "some+database";
let dbw = client.db(dbname).await;
assert!(dbw.is_err())
}
#[tokio::test]
async fn should_get_information_on_test_db() {
let client = Client::new_local_test().unwrap();
let dbname = "should_get_information_on_test_db";
let dbw = client.db(dbname).await;
assert!(dbw.is_ok());
assert!(client.exists(dbname).await.is_ok());
let info = client.get_info(dbname).await.expect("can not get db info");
assert_eq!(info.db_name, dbname);
let _ = client.destroy_db(dbname);
}
#[tokio::test]
async fn should_create_a_document() {
let client = Client::new_local_test().unwrap();
let dbw = client.db("should_create_a_document").await;
assert!(dbw.is_ok());
let db = dbw.unwrap();
let ndoc_result = db
.create(json!({
"thing": true
}))
.await;
assert!(ndoc_result.is_ok());
let mut doc = ndoc_result.unwrap();
assert_eq!(doc["thing"], json!(true));
let _ = client.destroy_db("should_create_a_document");
}
#[tokio::test]
async fn should_create_a_typed_document() {
let client = Client::new_local_test().unwrap();
let dbw = client.db("should_create_a_typed_document").await;
assert!(dbw.is_ok());
let db = dbw.unwrap();
let my_doc = TestDoc {
_id: "".to_string(),
_rev: "".to_string(),
first_name: "John".to_string(),
last_name: "Doe".to_string(),
};
let ndoc_result = db.create(my_doc).await;
assert!(ndoc_result.is_ok());
let mut doc = ndoc_result.unwrap();
assert_eq!(doc.first_name, "John");
assert!(!doc._id.is_empty());
assert!(doc._rev.starts_with("1-"));
let _ = client.destroy_db("should_create_a_typed_document");
}
#[tokio::test]
async fn should_create_bulk_documents() {
let client = Client::new_local_test().unwrap();
let dbname = "should_create_bulk_documents";
let dbw = client.db(dbname).await;
assert!(dbw.is_ok());
let db = dbw.unwrap();
let ndoc_result = db
.bulk_docs(vec![
json!({
"_id":"first",
"thing": true
}),
json!({
"_id":"first",
"thing": false
}),
])
.await;
assert!(ndoc_result.is_ok());
let mut docs = ndoc_result.unwrap();
let mut docs = docs.into_iter();
let first_result = docs.next().unwrap();
assert!(first_result.is_ok());
assert!(first_result.unwrap().rev.is_some());
let second_result = docs.next().unwrap();
assert!(second_result.is_err());
assert_eq!(second_result.err().unwrap().status, StatusCode::CONFLICT);
let _ = client.destroy_db(dbname);
}
#[tokio::test]
async fn should_destroy_the_db() {
let client = Client::new_local_test().unwrap();
let _ = client.db("should_destroy_the_db").await;
assert!(client.destroy_db("should_destroy_the_db").await.unwrap());
}
}
mod database_tests {
use crate::client::Client;
use crate::database::Database;
use crate::document::{DocumentCollection, TypedCouchDocument};
use crate::types;
use crate::types::find::FindQuery;
use crate::types::query::{QueriesParams, QueryParams};
use crate::types::view::{CouchFunc, CouchViews};
use serde_json::{json, Value};
use tokio::sync::mpsc;
use tokio::sync::mpsc::{Receiver, Sender};
async fn setup(dbname: &str) -> (Client, Database, Value) {
let client = Client::new_local_test().unwrap();
let dbw = client.db(dbname).await;
assert!(dbw.is_ok());
let db = dbw.unwrap();
let ndoc_result = db
.create(json!({
"thing": true
}))
.await;
assert!(ndoc_result.is_ok());
let mut doc = ndoc_result.unwrap();
assert_eq!(doc["thing"], json!(true));
(client, db, doc)
}
async fn setup_multiple(dbname: &str, nr_of_docs: usize) -> (Client, Database, Vec<Value>) {
let client = Client::new_local_test().unwrap();
let dbw = client.db(dbname).await;
assert!(dbw.is_ok());
let db = dbw.unwrap();
let mut docs = vec![];
for _ in 0..nr_of_docs {
let ndoc_result = db
.create(json!({
"thing": true
}))
.await;
assert!(ndoc_result.is_ok());
let mut doc = ndoc_result.unwrap();
assert_eq!(doc["thing"], json!(true));
docs.push(doc)
}
(client, db, docs)
}
async fn teardown(client: Client, dbname: &str) {
assert!(client.destroy_db(dbname).await.unwrap())
}
#[tokio::test]
async fn should_update_a_document() {
let (client, db, mut doc) = setup("should_update_a_document").await;
doc["thing"] = json!(false);
let save_result = db.save(doc).await;
assert!(save_result.is_ok());
let new_doc = save_result.unwrap();
assert_eq!(new_doc["thing"], json!(false));
teardown(client, "should_update_a_document").await;
}
#[tokio::test]
async fn should_handle_a_document_plus() {
let dbname = "should_handle_a_document_plus";
let (client, db, mut doc) = setup(dbname).await;
assert!(db.remove(doc).await);
assert_eq!(db.get_all_raw().await.unwrap().rows.len(), 0);
let id = "1+2";
let created = db.create(json!({ "_id": id })).await.unwrap();
assert_eq!(created.get_id(), id);
let save_result = db.save(created.clone()).await;
assert!(save_result.is_ok());
assert_eq!(db.get_all_raw().await.unwrap().rows.len(), 1);
assert!(db.remove(save_result.unwrap()).await);
assert_eq!(db.get_all_raw().await.unwrap().rows.len(), 0);
teardown(client, dbname).await;
}
#[tokio::test]
async fn should_remove_a_document() {
let (client, db, doc) = setup("should_remove_a_document").await;
assert!(db.remove(doc).await);
teardown(client, "should_remove_a_document").await;
}
#[tokio::test]
async fn should_get_a_single_document() {
let (client, ..) = setup("should_get_a_single_document").await;
teardown(client, "should_get_a_single_document").await;
}
async fn setup_create_indexes(dbname: &str) -> (Client, Database, Value) {
let (client, db, doc) = setup(dbname).await;
let spec = types::index::IndexFields::new(vec![types::find::SortSpec::Simple(s!("thing"))]);
let res = db.insert_index("thing-index", spec).await;
assert!(res.is_ok());
(client, db, doc)
}
#[tokio::test]
async fn should_create_index_in_db() {
let (client, db, _) = setup_create_indexes("should_create_index_in_db").await;
teardown(client, "should_create_index_in_db").await;
}
#[tokio::test]
async fn should_list_indexes_in_db() {
let (client, db, _) = setup_create_indexes("should_list_indexes_in_db").await;
let index_list = db.read_indexes().await.unwrap();
assert!(index_list.indexes.len() > 1);
let findex = &index_list.indexes[1];
assert_eq!(findex.name.as_str(), "thing-index");
teardown(client, "should_list_indexes_in_db").await;
}
#[tokio::test]
async fn should_ensure_index_in_db() {
let (client, db, _) = setup("should_ensure_index_in_db").await;
let spec = types::index::IndexFields::new(vec![types::find::SortSpec::Simple(s!("thing"))]);
let res = db.ensure_index("thing-index", spec).await;
assert!(res.is_ok());
teardown(client, "should_ensure_index_in_db").await;
}
#[tokio::test]
async fn should_find_documents_in_db() {
let (client, db, doc) = setup_create_indexes("should_find_documents_in_db").await;
let query = FindQuery::new_from_value(json!({
"selector": {
"thing": true
},
"limit": 1,
"sort": [{
"thing": "desc"
}]
}));
let documents_res = db.find_raw(&query).await;
assert!(documents_res.is_ok());
let documents = documents_res.unwrap();
assert_eq!(documents.rows.len(), 1);
teardown(client, "should_find_documents_in_db").await;
}
#[tokio::test]
async fn should_bulk_get_a_document() {
let (client, db, doc) = setup("should_bulk_get_a_document").await;
let id = doc.get_id().into_owned();
let collection = db.get_bulk_raw(vec![id]).await.unwrap();
assert_eq!(collection.rows.len(), 1);
assert!(db.remove(doc).await);
teardown(client, "should_bulk_get_a_document").await;
}
#[tokio::test]
async fn should_bulk_get_invalid_documents() {
let (client, db, doc) = setup("should_bulk_get_invalid_documents").await;
let id = doc.get_id().into_owned();
let invalid_id = "does_not_exist".to_string();
let collection = db.get_bulk_raw(vec![id, invalid_id]).await.unwrap();
assert_eq!(collection.rows.len(), 1);
assert!(db.remove(doc).await);
teardown(client, "should_bulk_get_invalid_documents").await;
}
#[tokio::test]
async fn should_get_all_documents_with_keys() {
let (client, db, doc) = setup("should_get_all_documents_with_keys").await;
let id = doc.get_id().into_owned();
let params = QueryParams::from_keys(vec![id]);
let collection = db.get_all_params_raw(Some(params)).await.unwrap();
assert_eq!(collection.rows.len(), 1);
assert!(db.remove(doc).await);
teardown(client, "should_get_all_documents_with_keys").await;
}
#[tokio::test]
async fn should_query_documents_with_keys() {
let db_name = "should_query_documents_with_keys";
let (client, db, doc) = setup(db_name).await;
let id = doc.get_id().into_owned();
let view_name = "testViewAll";
db.create_view(
view_name,
CouchViews::new(
view_name,
CouchFunc {
map: r#"function(doc) {{
emit(doc._id, null);
}}"#
.to_string(),
reduce: None,
},
),
)
.await
.unwrap();
let ndoc = db
.create(json!({
"thing": true
}))
.await
.unwrap();
let ndoc_id = ndoc.get_id().into_owned();
let single_view_name = "testViewSingle";
db.create_view(
single_view_name,
CouchViews::new(
single_view_name,
CouchFunc {
map: format!(
r#"function(doc) {{
if(doc._id === "{}") {{
emit(doc._id, null);
}}
}}"#,
ndoc_id
)
.to_string(),
reduce: None,
},
),
)
.await
.unwrap();
assert_eq!(
db.query_raw(view_name, view_name, Some(QueryParams::from_keys(vec![id.clone()])))
.await
.unwrap()
.rows
.len(),
1
);
assert_eq!(
db.query_raw(
single_view_name,
single_view_name,
Some(QueryParams::from_keys(vec![id])),
)
.await
.unwrap()
.rows
.len(),
0
);
assert!(db.remove(ndoc).await);
assert!(db.remove(doc).await);
teardown(client, db_name).await;
}
#[tokio::test]
async fn should_query_documents_with_key() {
let db_name = "should_query_documents_with_key";
let (client, db, doc) = setup(db_name).await;
let id = doc.get_id().into_owned();
let view_name = "testViewAll";
db.create_view(
view_name,
CouchViews::new(
view_name,
CouchFunc {
map: r#"function(doc) {{
emit(doc._id, null);
}}"#
.to_string(),
reduce: None,
},
),
)
.await
.unwrap();
let ndoc = db
.create(json!({
"thing": true
}))
.await
.unwrap();
let ndoc_id = ndoc.get_id().into_owned();
let single_view_name = "testViewSingle";
db.create_view(
single_view_name,
CouchViews::new(
single_view_name,
CouchFunc {
map: format!(
r#"function(doc) {{
if(doc._id === "{}") {{
emit(doc._id, null);
}}
}}"#,
ndoc_id
)
.to_string(),
reduce: None,
},
),
)
.await
.unwrap();
let mut one_key = QueryParams::default();
one_key.key = Some(doc.get_id().into_owned());
assert_eq!(
db.query_raw(view_name, view_name, Some(one_key.clone()))
.await
.unwrap()
.rows
.len(),
1
);
assert_eq!(
db.query_raw(single_view_name, single_view_name, Some(one_key))
.await
.unwrap()
.rows
.len(),
0
);
assert!(db.remove(ndoc).await);
assert!(db.remove(doc).await);
teardown(client, db_name).await;
}
#[tokio::test]
async fn should_query_documents_with_defaultparams() {
let dbname = "should_query_documents_with_defaultparams";
let (client, db, doc) = setup(dbname).await;
let id = doc.get_id().into_owned();
let view_name = "testViewAll";
db.create_view(
view_name,
CouchViews::new(
view_name,
CouchFunc {
map: r#"function(doc) {{
emit(doc._id, null);
}}"#
.to_string(),
reduce: None,
},
),
)
.await
.unwrap();
let ndoc = db
.create(json!({
"thing": true
}))
.await
.unwrap();
let ndoc_id = ndoc.get_id().into_owned();
let single_view_name = "testViewSingle";
db.create_view(
single_view_name,
CouchViews::new(
single_view_name,
CouchFunc {
map: format!(
r#"function(doc) {{
if(doc._id === "{}") {{
emit(doc._id, null);
}}
}}"#,
ndoc_id
)
.to_string(),
reduce: None,
},
),
)
.await
.unwrap();
let query_result = db.query_raw(view_name, view_name, None).await;
assert_eq!(query_result.unwrap().rows.len(), 2);
assert_eq!(
db.query_raw(single_view_name, single_view_name, None)
.await
.unwrap()
.rows
.len(),
1
);
assert_eq!(
db.query_raw(view_name, view_name, Some(QueryParams::default()))
.await
.unwrap()
.rows
.len(),
2
);
assert_eq!(
db.query_raw(single_view_name, single_view_name, Some(QueryParams::default()))
.await
.unwrap()
.rows
.len(),
1
);
assert!(db.remove(ndoc).await);
assert!(db.remove(doc).await);
teardown(client, dbname).await;
}
#[tokio::test]
async fn should_get_many_all_documents_with_keys() {
let dbname = "should_get_many_all_documents_with_keys";
let (client, db, docs) = setup_multiple(dbname, 4).await;
let doc = docs.get(0).unwrap();
let mut params1 = QueryParams::default();
params1.key = Some(doc.get_id().into_owned());
let mut params2 = QueryParams::default();
params2.include_docs = Some(true);
let mut params3 = QueryParams::default();
let params = vec![params1, params2, params3];
let collections = db.query_many_all_docs(QueriesParams::new(params)).await.unwrap();
assert_eq!(collections.len(), 3);
assert_eq!(collections.get(0).unwrap().rows.len(), 1);
assert!(collections.get(0).unwrap().rows.get(0).unwrap().doc.is_none());
assert_eq!(collections.get(1).unwrap().rows.len(), 4);
assert!(collections.get(1).unwrap().rows.get(0).unwrap().doc.is_some());
assert_eq!(collections.get(2).unwrap().rows.len(), 4);
assert!(collections.get(2).unwrap().rows.get(0).unwrap().doc.is_none());
for doc in docs.into_iter() {
assert!(db.remove(doc).await);
}
teardown(client, dbname).await;
}
#[tokio::test]
async fn should_handle_null_view_keys() {
let dbname = "should_handle_null_view_keys";
let (client, db, docs) = setup_multiple(dbname, 4).await;
let doc = docs.get(0).unwrap();
let count_by_id = r#"function (doc) {
emit(doc._id, null);
}"#;
let view_name = "should_handle_null_view_keys";
assert!(db
.create_view(
view_name,
CouchViews::new(view_name, CouchFunc::new(count_by_id, Some("_count"))),
)
.await
.is_ok());
assert!(db.query_raw(view_name, view_name, None).await.is_ok());
teardown(client, dbname).await;
}
#[tokio::test]
async fn should_bulk_insert_and_get_many_docs() {
let (client, db, _doc) = setup("should_bulk_insert_and_get_many_docs").await;
let docs: Vec<Value> = (0..2000)
.map(|idx| {
json!({
"_id": format!("bd_{}", idx),
"count": idx,
})
})
.collect();
db.bulk_docs(docs).await.expect("should insert 2000 documents");
let (tx, mut rx): (Sender<DocumentCollection<Value>>, Receiver<DocumentCollection<Value>>) =
mpsc::channel(1000);
let t = tokio::spawn(async move {
db.get_all_batched(tx, 0, 0).await.expect("can not launch batch fetch");
});
let mut retrieved = 0;
while let Some(all_docs) = rx.recv().await {
retrieved += all_docs.total_rows;
}
assert_eq!(retrieved, 2001);
t.await.unwrap();
teardown(client, "should_bulk_insert_and_get_many_docs").await;
}
}
}