Skip to main content

couch_rs/
lib.rs

1//! # `CouchDB` library for Rust
2//!
3//! ## Description
4//!
5//! This crate is an interface to `CouchDB` HTTP REST API. Works with stable Rust.
6//!
7//! This library is a spin-off based on the excellent work done by Mathieu Amiot and others at Yellow Innovation on the
8//! Sofa library. The original project can be found at <https://github.com/YellowInnovation/sofa>
9//!
10//! The Sofa library lacked support for async I/O, and missed a few essential operations we needed in our projects. That's
11//! why I've decided to create a new project based on the original Sofa code.
12//!
13//! The rust-rs library has been updated to the Rust 2021 edition standards, uses async I/O, and compiles against the latest serde and
14//! reqwest libraries.
15//!
16//! **NOT 1.0 YET, so expect changes**
17//!
18//! **Supports `CouchDB` 2.3.0 and up. Used in production with various `CouchDB` versions, including 3.4.1**
19//!
20//! Be sure to check [CouchDB's Documentation](http://docs.couchdb.org/en/latest/index.html) in detail to see what's possible.
21//!
22//! ## Example code
23//!
24//! You can launch the included example with:
25//! ```shell script
26//! cargo run --example basic_operations
27//! ```
28//!
29//! ## Running tests
30//!
31//! Make sure that you have an instance of `CouchDB` 2.0+ running, either via the supplied `docker-compose.yml` file or by yourself. It must be listening on the default port.
32//! Since Couch 3.0 the "Admin Party" mode is no longer supported. This means you need to provide a username and password during launch.
33//! The tests and examples assume an "admin" `CouchDB` user with a "password" `CouchDB` password. Docker run command:
34//!
35//! ```shell script
36//! docker run --rm -p 5984:5984 -e COUCHDB_USER=admin -e COUCHDB_PW=password couchdb:3
37//! ```
38//!
39//! And then
40//! `cargo test -- --test-threads=1`
41//!
42//! Single-threading the tests is very important because we need to make sure that the basic features are working before actually testing features on dbs/documents.
43//!
44//! ## Usage
45//!
46//! A typical find operation looks like this.
47//!
48//! ```
49//! use couch_rs::types::find::FindQuery;
50//! use std::error::Error;
51//! use serde_json::Value;
52//! use couch_rs::document::DocumentCollection;
53//!
54//! const DB_HOST: &str = "http://localhost:5984";
55//! const TEST_DB: &str = "test_db";
56//!
57//! #[tokio::main]
58//! async fn main() -> Result<(), Box<dyn Error>> {
59//!     let client = couch_rs::Client::new(DB_HOST, "admin", "password")?;
60//!     let db = client.db(TEST_DB).await?;
61//!     let find_all = FindQuery::find_all();
62//!     let docs = db.find_raw(&find_all).await?;
63//!     Ok(())
64//! }
65//!```
66//!
67//! You can use a similar operation to get a typed Couch document.
68//!
69//! ```
70//! use couch_rs::CouchDocument;
71//! use couch_rs::types::find::FindQuery;
72//! use couch_rs::document::{DocumentCollection, TypedCouchDocument};
73//! use couch_rs::types::document::DocumentId;
74//! use std::error::Error;
75//! use serde_json::Value;
76//! use serde::{Deserialize, Serialize};
77//!
78//! const DB_HOST: &str = "http://localhost:5984";
79//! const TEST_DB: &str = "user_db";
80//!
81//! #[derive(Serialize, Deserialize, CouchDocument)]
82//! pub struct UserDetails {
83//!    #[serde(skip_serializing_if = "String::is_empty")]
84//!     pub _id: DocumentId,
85//!     #[serde(skip_serializing_if = "String::is_empty")]
86//!     pub _rev: String,
87//!     #[serde(rename = "firstName")]
88//!     pub first_name: Option<String>,
89//!     #[serde(rename = "lastName")]
90//!     pub last_name: String,
91//! }
92//!
93//! #[tokio::main]
94//! async fn main() -> Result<(), Box<dyn Error>> {
95//!     let client = couch_rs::Client::new(DB_HOST, "admin", "password")?;
96//!     let db = client.db(TEST_DB).await?;
97//!     let find_all = FindQuery::find_all();
98//!     let docs: DocumentCollection<UserDetails> = db.find(&find_all).await?;
99//!     Ok(())
100//! }
101//!```
102//!
103//! See the `database` module for additional usage examples. Or have a look at the `examples` in the
104//! GitHub repositiory.
105//!
106//! The `typed` module provides a typed wrapper around `Database` where all operations are performed on a specific generic type.
107//! This is useful when you want to work with a specific type of document for all operations on a database insteance as the compiler
108//! will flag any errors at compile time if different types are mixed using the same database instance.
109
110#![allow(clippy::used_underscore_binding)]
111#![allow(clippy::pub_underscore_fields)]
112
113// Re-export #[derive(CouchDocument)].
114#[cfg(feature = "couch_rs_derive")]
115#[allow(unused_imports)]
116#[macro_use]
117extern crate couch_rs_derive;
118
119#[cfg(feature = "couch_rs_derive")]
120#[doc(hidden)]
121pub use couch_rs_derive::*;
122// Re-export the http crate which is used in `CouchError`.
123pub use http;
124pub use std::borrow::Cow;
125
126/// Macros that the crate exports to facilitate most of the
127/// doc-to-json-to-string-related tasks
128#[allow(unused_macros)]
129#[macro_use]
130mod macros {
131    /// Shortcut to `mod $mod; pub use mod::*;`
132    macro_rules! mod_use {
133        ($module:ident) => {
134            mod $module;
135            pub use self::$module::*;
136        };
137    }
138
139    /// Extracts a JSON Value to a defined Struct; Returns the default value when the field can not be found
140    /// or converted
141    macro_rules! json_extr {
142        ($e:expr) => {
143            serde_json::from_value($e.to_owned()).unwrap_or_default()
144        };
145    }
146
147    /// Automatic call to `serde_json::to_string()` function, with prior
148    /// `Document::get_data()` call to get documents' inner data
149    macro_rules! dtj {
150        ($e:expr) => {
151            js!(&$e.get_data())
152        };
153    }
154
155    /// Automatic call to `serde_json::to_string()` function
156    macro_rules! js {
157        ($e:expr) => {
158            serde_json::to_string(&$e).unwrap()
159        };
160    }
161
162    /// String creation
163    macro_rules! s {
164        ($e:expr) => {
165            String::from($e)
166        };
167    }
168
169    /// Gets milliseconds from timespec
170    macro_rules! tspec_ms {
171        ($tspec:ident) => {{ $tspec.sec * 1000 + $tspec.nsec as i64 / 1000000 }};
172    }
173
174    /// Gets current UNIX time in milliseconds
175    macro_rules! msnow {
176        () => {{
177            let tm = time::now().to_timespec();
178            tspec_ms!(tm)
179        }};
180    }
181
182    /// Url encode path segments
183    macro_rules! url_encode {
184        ($id:ident) => {{ url::form_urlencoded::byte_serialize($id.as_bytes()).collect::<String>() }};
185    }
186}
187
188mod client;
189/// Database operations on a `CouchDB` Database.
190pub mod database;
191
192/// Typed Database operations on a `CouchDB` Database.
193pub mod typed;
194
195/// Document model to support `CouchDB` document operations.
196pub mod document;
197/// Error wrappers for the HTTP status codes returned by `CouchDB`.
198pub mod error;
199/// Data types to support `CouchDB` management operations
200pub mod management;
201/// Trait that provides methods that can be used to switch between abstract Document and
202/// concrete Model implementors (such as your custom data models)
203pub mod model;
204/// Data types to support `CouchDB` operations.
205pub mod types;
206
207mod changes;
208
209pub use client::Client;
210
211#[allow(unused_mut, unused_variables)]
212#[cfg(feature = "integration-tests")]
213#[cfg(test)]
214mod couch_rs_tests {
215    use crate as couch_rs;
216    use couch_rs::{CouchDocument, document::TypedCouchDocument, types::document::DocumentId};
217    use serde::{Deserialize, Serialize};
218    use std::borrow::Cow;
219
220    #[derive(Serialize, Deserialize, CouchDocument, Default, Debug)]
221    pub struct TestDoc {
222        #[serde(skip_serializing_if = "String::is_empty")]
223        pub _id: DocumentId,
224        #[serde(skip_serializing_if = "String::is_empty")]
225        pub _rev: String,
226        pub first_name: String,
227        pub last_name: String,
228    }
229
230    #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
231    struct TestDocImplementing {
232        my_id: String,
233        my_rev: String,
234        first_name: String,
235        last_name: String,
236    }
237    impl TypedCouchDocument for TestDocImplementing {
238        fn get_id(&self) -> Cow<'_, str> {
239            Cow::Borrowed(&self.my_id)
240        }
241
242        fn get_rev(&self) -> Cow<'_, str> {
243            Cow::Borrowed(&self.my_rev)
244        }
245
246        fn set_rev(&mut self, rev: &str) {
247            self.my_rev = rev.to_string();
248        }
249
250        fn set_id(&mut self, id: &str) {
251            self.my_id = id.to_string();
252        }
253
254        fn merge_ids(&mut self, other: &Self) {
255            self.my_id = other.my_id.clone();
256        }
257    }
258
259    mod client_tests {
260        use crate::{
261            client::Client,
262            couch_rs_tests::{TestDoc, TestDocImplementing},
263            document::TypedCouchDocument,
264            error::CouchError,
265        };
266        use reqwest::StatusCode;
267        use serde_json::json;
268
269        #[tokio::test]
270        async fn should_check_couchdbs_status() {
271            let client = Client::new_local_test().unwrap();
272            let status = client.check_status().await;
273            assert!(status.is_ok());
274            assert_eq!("The Apache Software Foundation", status.unwrap().vendor.name);
275        }
276
277        #[tokio::test]
278        async fn should_create_test_db() {
279            let client = Client::new_local_test().unwrap();
280            let dbw = client.db("should_create_test_db").await;
281            assert!(dbw.is_ok());
282
283            client
284                .destroy_db("should_create_test_db")
285                .await
286                .expect("can not destroy db");
287        }
288
289        #[tokio::test]
290        async fn should_create_test_db_with_a_complex_name() {
291            // https://docs.couchdb.org/en/stable/api/database/common.html#put--db
292            // Name must begin with a lowercase letter (a-z)
293            // Lowercase characters (a-z)
294            // Digits (0-9)
295            // Any of the characters _, $, (, ), +, -, and /.
296            let client = Client::new_local_test().unwrap();
297            let dbname = "abcdefghijklmnopqrstuvwxyz+0123456789_$()-/";
298            let dbw = client.db(dbname).await;
299            assert!(dbw.is_ok());
300            assert!(client.exists(dbname).await.is_ok());
301            let info = client.get_info(dbname).await.expect("can not get db info");
302            assert_eq!(info.db_name, dbname);
303            client.destroy_db(dbname).await.expect("can not destroy db");
304        }
305
306        #[tokio::test]
307        async fn should_get_information_on_test_db() {
308            let client = Client::new_local_test().unwrap();
309            let dbname = "should_get_information_on_test_db";
310            let dbw = client.db(dbname).await;
311            assert!(dbw.is_ok());
312            assert!(client.exists(dbname).await.is_ok());
313            let info = client.get_info(dbname).await.expect("can not get db info");
314            assert_eq!(info.db_name, dbname);
315            client.destroy_db(dbname).await.expect("can not destroy db");
316        }
317
318        #[tokio::test]
319        async fn should_not_exist() {
320            let client = Client::new_local_test().unwrap();
321            let dbname = "should_not_exist";
322            let dbw = client.exists(dbname).await;
323            assert!(!client.exists(dbname).await.unwrap());
324        }
325
326        #[tokio::test]
327        async fn should_create_a_document() {
328            let client = Client::new_local_test().unwrap();
329            let dbw = client.db("should_create_a_document").await;
330            assert!(dbw.is_ok());
331            let db = dbw.unwrap();
332
333            let mut doc = json!({
334                "thing": true
335            });
336            let ndoc_result = db.create(&mut doc).await;
337
338            assert!(ndoc_result.is_ok());
339
340            let details = ndoc_result.unwrap();
341            assert_eq!(details.rev, doc.get("_rev").unwrap().as_str().unwrap());
342
343            client
344                .destroy_db("should_create_a_document")
345                .await
346                .expect("can not destroy db");
347        }
348
349        #[tokio::test]
350        async fn should_create_a_typed_document() {
351            let client = Client::new_local_test().unwrap();
352            let dbw = client.db("should_create_a_typed_document").await;
353            assert!(dbw.is_ok());
354            let db = dbw.unwrap();
355            let mut my_doc = TestDoc {
356                _id: String::new(),
357                _rev: String::new(),
358                first_name: "John".to_string(),
359                last_name: "Doe".to_string(),
360            };
361
362            let ndoc_result = db.create(&mut my_doc).await;
363
364            assert!(ndoc_result.is_ok());
365
366            let details = ndoc_result.unwrap();
367            assert_eq!(details.rev, my_doc._rev);
368            assert!(!my_doc._id.is_empty());
369            assert!(my_doc._rev.starts_with("1-"));
370
371            client
372                .destroy_db("should_create_a_typed_document")
373                .await
374                .expect("can not destroy db");
375        }
376
377        #[tokio::test]
378        async fn should_keep_id_creating_a_typed_document_deriving() {
379            const UNIQUE_ID: &str = "unique_id";
380
381            let client = Client::new_local_test().unwrap();
382            let dbw = client.db("should_keep_id_creating_a_typed_document").await;
383            assert!(dbw.is_ok());
384            let db = dbw.unwrap();
385            let mut my_doc = TestDoc {
386                _id: UNIQUE_ID.to_string(),
387                _rev: String::new(),
388                first_name: "John".to_string(),
389                last_name: "Doe".to_string(),
390            };
391
392            let ndoc_result = db.create(&mut my_doc).await;
393
394            assert!(ndoc_result.is_ok());
395
396            let details = ndoc_result.unwrap();
397            assert_eq!(details.rev, my_doc._rev);
398            assert!(!my_doc._id.is_empty());
399            assert!(my_doc._rev.starts_with("1-"));
400
401            let document: TestDoc = db.get(UNIQUE_ID).await.expect("can not get doc");
402
403            client
404                .destroy_db("should_keep_id_creating_a_typed_document")
405                .await
406                .expect("can not destroy db");
407        }
408
409        #[tokio::test]
410        async fn should_keep_id_creating_a_typed_document_implementing() {
411            create_read_remove(Some("id".to_string()), None).await;
412        }
413
414        #[tokio::test]
415        async fn should_ignore_rev_creating_a_typed_document_implementing() {
416            create_read_remove(Some("id".to_string()), Some("something".to_string())).await;
417        }
418
419        #[tokio::test]
420        async fn should_update_id_creating_a_typed_document_implementing() {
421            create_read_remove(None, None).await;
422        }
423
424        async fn create_read_remove(id: Option<String>, rev: Option<String>) {
425            let client = Client::new_local_test().unwrap();
426            let dbw = client.db("create_read_remove_with_rev").await;
427            assert!(dbw.is_ok());
428            let db = dbw.unwrap();
429            let (id, autogenerated_id) = if let Some(id) = id {
430                (id, false)
431            } else {
432                (String::new(), true)
433            };
434            let rev = rev.unwrap_or_default();
435
436            let mut my_doc = TestDocImplementing {
437                my_id: id.clone(),
438                my_rev: rev.clone(),
439                first_name: "John".to_string(),
440                last_name: "Doe".to_string(),
441            };
442
443            let details = db
444                .create(&mut my_doc)
445                .await
446                .unwrap_or_else(|err| panic!("can not create doc with rev '{rev}': {err}"));
447
448            assert_eq!(details.rev, my_doc.my_rev);
449            if autogenerated_id {
450                assert!(!my_doc.get_id().is_empty(), "Found empty _id for document {my_doc:?}");
451                assert_ne!(
452                    my_doc.my_id, id,
453                    "generated id and original id (empty) should be different"
454                );
455            } else {
456                assert_eq!(my_doc.my_id, id);
457            }
458            assert!(!my_doc.get_rev().is_empty(), "Found empty _rev for document {my_doc:?}");
459
460            let document: TestDocImplementing = db.get(&my_doc.my_id).await.expect("can not get doc");
461            assert!(db.remove(&document).await.is_ok(), "can not remove doc {document:?}");
462
463            client
464                .destroy_db("create_read_remove_with_rev")
465                .await
466                .expect("can not destroy db");
467        }
468
469        #[tokio::test]
470        async fn should_keep_id_bulk_creating_a_typed_document_implementing() {
471            const UNIQUE_ID: &str = "unique_id";
472            let client = Client::new_local_test().unwrap();
473            let dbw = client
474                .db("should_keep_id_bulk_creating_a_typed_document_implementing")
475                .await;
476            assert!(dbw.is_ok());
477            let db = dbw.unwrap();
478            let mut my_doc = TestDocImplementing {
479                my_id: UNIQUE_ID.to_string(),
480                my_rev: String::default(),
481                first_name: "John".to_string(),
482                last_name: "Doe".to_string(),
483            };
484
485            let mut docs = vec![my_doc];
486            let results = db
487                .bulk_docs(&mut docs)
488                .await
489                .unwrap_or_else(|err| panic!("can not create doc: {err}"));
490            let my_doc = docs.into_iter().next().expect("no doc found");
491            let details = results
492                .into_iter()
493                .collect::<Result<Vec<_>, CouchError>>()
494                .expect("operation failed")
495                .into_iter()
496                .next()
497                .expect("no result found");
498            assert_eq!(details.rev, my_doc.my_rev);
499            assert_eq!(my_doc.my_id, UNIQUE_ID);
500            assert!(!my_doc.get_rev().is_empty(), "Found empty _rev for document {my_doc:?}");
501
502            let document: TestDocImplementing = db.get(UNIQUE_ID).await.expect("can not get doc");
503            assert!(db.remove(&document).await.is_ok(), "can not remove doc");
504
505            client
506                .destroy_db("should_keep_id_bulk_creating_a_typed_document_implementing")
507                .await
508                .expect("can not destroy db");
509        }
510
511        #[tokio::test]
512        async fn should_create_bulk_documents() {
513            let client = Client::new_local_test().unwrap();
514            let dbname = "should_create_bulk_documents";
515            let dbw = client.db(dbname).await;
516            assert!(dbw.is_ok());
517            let db = dbw.unwrap();
518
519            let mut docs = vec![
520                json!({
521                    "_id":"first",
522                    "thing": true
523                }),
524                json!({
525                    "_id":"first",
526                    "thing": false
527                }),
528            ];
529            let ndoc_result = db.bulk_docs(&mut docs).await;
530
531            assert!(ndoc_result.is_ok());
532
533            let mut ndoc_result = ndoc_result.unwrap().into_iter();
534            let first_result = ndoc_result.next().unwrap();
535            assert!(first_result.is_ok());
536            let mut docs = docs.into_iter();
537            let first_doc = docs.next().unwrap();
538            assert_eq!(
539                first_doc.as_object().unwrap().get("_rev").unwrap().as_str().unwrap(),
540                first_result.unwrap().rev.as_str()
541            );
542
543            let second_result = ndoc_result.next().unwrap();
544            assert!(second_result.is_err());
545            assert_eq!(second_result.err().unwrap().status(), Some(StatusCode::CONFLICT));
546
547            client.destroy_db(dbname).await.expect("can not destroy db");
548        }
549
550        #[tokio::test]
551        async fn should_destroy_the_db() {
552            let client = Client::new_local_test().unwrap();
553            client.db("should_destroy_the_db").await.expect("can not create db");
554
555            assert!(client.destroy_db("should_destroy_the_db").await.unwrap());
556        }
557    }
558
559    mod database_tests {
560        use crate::{
561            client::Client,
562            database::Database,
563            document::{DocumentCollection, TypedCouchDocument},
564            error::{CouchResult, CouchResultExt},
565            management::{ClusterSetup, EnsureDbsExist},
566            types,
567            types::{
568                find::FindQuery,
569                query::{QueriesParams, QueryParams},
570                view::{CouchFunc, CouchViews, ViewCollection},
571            },
572        };
573        use serde_json::{Value, json};
574        use tokio::sync::{
575            mpsc,
576            mpsc::{Receiver, Sender},
577        };
578
579        async fn setup(dbname: &str) -> (Client, Database, Value) {
580            let client = Client::new_local_test().unwrap();
581            let dbw = client.db(dbname).await;
582            assert!(dbw.is_ok());
583            let db = dbw.unwrap();
584
585            let mut doc = json!({
586                "thing": true
587            });
588            let ndoc_result = db.create(&mut doc).await;
589
590            assert!(ndoc_result.is_ok());
591
592            let details = ndoc_result.unwrap();
593            assert_eq!(details.rev, doc.get("_rev").unwrap().as_str().unwrap());
594            (client, db, doc)
595        }
596
597        async fn setup_multiple(dbname: &str, nr_of_docs: usize) -> (Client, Database, Vec<Value>) {
598            let client = Client::new_local_test().unwrap();
599            let dbw = client.db(dbname).await;
600            assert!(dbw.is_ok());
601            let db = dbw.unwrap();
602            let mut docs = vec![];
603
604            for _ in 0..nr_of_docs {
605                let mut doc = json!({
606                    "thing": true
607                });
608                let ndoc_result = db.create(&mut doc).await;
609
610                assert!(ndoc_result.is_ok());
611
612                let details = ndoc_result.unwrap();
613                assert_eq!(details.rev, doc.get("_rev").unwrap().as_str().unwrap());
614
615                docs.push(doc);
616            }
617
618            (client, db, docs)
619        }
620
621        async fn teardown(client: Client, dbname: &str) {
622            assert!(client.destroy_db(dbname).await.unwrap());
623        }
624
625        #[tokio::test]
626        async fn should_update_a_document() {
627            let (client, db, mut doc) = setup("should_update_a_document").await;
628
629            doc["thing"] = json!(false);
630
631            let save_result = db.save(&mut doc).await;
632            assert!(save_result.is_ok());
633            let details = save_result.unwrap();
634            assert_eq!(doc["_rev"], details.rev);
635
636            teardown(client, "should_update_a_document").await;
637        }
638
639        #[tokio::test]
640        async fn should_handle_a_document_plus() {
641            let dbname = "should_handle_a_document_plus";
642            let (client, db, mut doc) = setup(dbname).await;
643
644            assert!(db.remove(&doc).await.is_ok());
645            // make sure db is empty
646            assert_eq!(db.get_all_raw().await.unwrap().rows.len(), 0);
647
648            // create 1 doc with plus sign in the _id
649            let id = "1+2";
650            let mut created = json!({ "_id": id });
651            let details = db.create(&mut created).await.unwrap();
652            assert_eq!(details.id, id);
653
654            // update it
655            let save_result = db.save(&mut created).await;
656            assert!(save_result.is_ok());
657            // make sure db has only 1 doc
658            assert_eq!(db.get_all_raw().await.unwrap().rows.len(), 1);
659
660            // delete it
661            assert!(db.remove(&created).await.is_ok());
662            // make sure db has no docs
663            assert_eq!(db.get_all_raw().await.unwrap().rows.len(), 0);
664
665            teardown(client, dbname).await;
666        }
667
668        #[tokio::test]
669        async fn should_remove_a_document() {
670            let (client, db, doc) = setup("should_remove_a_document").await;
671            assert!(db.remove(&doc).await.is_ok());
672
673            teardown(client, "should_remove_a_document").await;
674        }
675
676        #[tokio::test]
677        async fn should_recognize_a_non_existent_document() {
678            let (client, db, doc) = setup("should_recognize_a_non_existent_document").await;
679            let result = db.get_raw("non_existent").await;
680            assert!(result.expect_err("should be a 404").is_not_found());
681            teardown(client, "should_recognize_a_non_existent_document").await;
682        }
683
684        #[tokio::test]
685        async fn should_turn_a_non_existent_document_into_an_option() {
686            let (client, db, doc) = setup("should_turn_a_non_existent_document_into_an_option").await;
687            let result = db.get_raw("non_existent").await;
688            let maybe_doc = result.into_option();
689            assert!(maybe_doc.expect("should not be an error").is_none());
690            teardown(client, "should_turn_a_non_existent_document_into_an_option").await;
691        }
692
693        #[tokio::test]
694        async fn should_get_a_single_document() {
695            let (client, ..) = setup("should_get_a_single_document").await;
696            teardown(client, "should_get_a_single_document").await;
697        }
698
699        #[tokio::test]
700        async fn should_get_a_document_with_a_space_in_id() {
701            let (client, db, _) = setup("should_get_a_document_with_a_space_in_id").await;
702            let space_doc_result = db
703                .create(&mut json!({
704                    "_id": "some crazy name"
705                }))
706                .await;
707            assert!(space_doc_result.is_ok());
708
709            let doc_result = db.get_raw("some crazy name").await;
710            assert!(doc_result.is_ok());
711
712            teardown(client, "should_get_a_document_with_a_space_in_id").await;
713        }
714
715        async fn setup_create_indexes(dbname: &str) -> (Client, Database, Value) {
716            let (client, db, doc) = setup(dbname).await;
717
718            let spec = types::index::IndexFields::new(vec![types::find::SortSpec::Simple(s!("thing"))]);
719
720            let res = db.insert_index("thing-index", spec, None, None).await;
721
722            assert!(res.is_ok());
723
724            (client, db, doc)
725        }
726
727        #[tokio::test]
728        async fn should_create_index_in_db() {
729            let (client, db, _) = setup_create_indexes("should_create_index_in_db").await;
730            teardown(client, "should_create_index_in_db").await;
731        }
732
733        #[tokio::test]
734        async fn should_list_indexes_in_db() {
735            let (client, db, _) = setup_create_indexes("should_list_indexes_in_db").await;
736
737            let index_list = db.read_indexes().await.unwrap();
738            assert!(index_list.indexes.len() > 1);
739            let findex = &index_list.indexes[1];
740
741            assert_eq!(findex.name.as_str(), "thing-index");
742            teardown(client, "should_list_indexes_in_db").await;
743        }
744
745        #[tokio::test]
746        async fn should_insert_index_in_db() {
747            let (client, db, _) = setup("should_insert_index_in_db").await;
748
749            let spec = types::index::IndexFields::new(vec![types::find::SortSpec::Simple(s!("thing"))]);
750
751            let res = db.insert_index("thing-index", spec, None, None).await;
752            assert!(res.is_ok());
753
754            teardown(client, "should_insert_index_in_db").await;
755        }
756
757        #[tokio::test]
758        async fn should_find_documents_in_db() {
759            let (client, db, doc) = setup_create_indexes("should_find_documents_in_db").await;
760            let query = FindQuery::new_from_value(json!({
761                "selector": {
762                    "thing": true
763                },
764                "limit": 1,
765                "sort": [{
766                    "thing": "desc"
767                }]
768            }));
769
770            let documents_res = db.find_raw(&query).await;
771
772            assert!(documents_res.is_ok());
773            let documents = documents_res.unwrap();
774            assert_eq!(documents.rows.len(), 1);
775
776            teardown(client, "should_find_documents_in_db").await;
777        }
778
779        #[tokio::test]
780        async fn should_bulk_get_a_document() {
781            let (client, db, doc) = setup("should_bulk_get_a_document").await;
782            let id = doc.get_id().into_owned();
783
784            let collection = db.get_bulk_raw(vec![id]).await.unwrap();
785            assert_eq!(collection.rows.len(), 1);
786            assert!(db.remove(&doc).await.is_ok());
787
788            teardown(client, "should_bulk_get_a_document").await;
789        }
790
791        #[tokio::test]
792        async fn should_bulk_get_invalid_documents() {
793            let (client, db, doc) = setup("should_bulk_get_invalid_documents").await;
794            let id = doc.get_id().into_owned();
795            let invalid_id = "does_not_exist".to_string();
796
797            let collection = db.get_bulk_raw(vec![id, invalid_id]).await.unwrap();
798            assert_eq!(collection.rows.len(), 1);
799            assert!(db.remove(&doc).await.is_ok());
800
801            teardown(client, "should_bulk_get_invalid_documents").await;
802        }
803
804        #[tokio::test]
805        async fn should_get_all_documents_with_keys() {
806            let (client, db, doc) = setup("should_get_all_documents_with_keys").await;
807            let id = doc.get_id().into_owned();
808
809            let params = QueryParams::from_keys(vec![id]);
810
811            let collection = db.get_all_params_raw(Some(params)).await.unwrap();
812            assert_eq!(collection.rows.len(), 1);
813            assert!(db.remove(&doc).await.is_ok());
814
815            teardown(client, "should_get_all_documents_with_keys").await;
816        }
817
818        #[tokio::test]
819        async fn should_query_documents_with_keys() {
820            let db_name = "should_query_documents_with_keys";
821            let (client, db, doc) = setup(db_name).await;
822            let id = doc.get_id().into_owned();
823            let view_name = "testViewAll";
824            db.create_view(
825                view_name,
826                CouchViews::new(
827                    view_name,
828                    CouchFunc {
829                        map: r"function(doc) {{
830                                    emit(doc._id, null);
831                            }}"
832                        .to_string(),
833                        reduce: None,
834                    },
835                ),
836            )
837            .await
838            .unwrap();
839            let mut second_doc = json!({
840                "thing": true
841            });
842            let details = db.create(&mut second_doc).await.unwrap();
843            let ndoc_id = details.id;
844            let single_view_name = "testViewSingle";
845            db.create_view(
846                single_view_name,
847                CouchViews::new(
848                    single_view_name,
849                    CouchFunc {
850                        map: format!(
851                            r#"function(doc) {{
852                                    if(doc._id === "{ndoc_id}") {{
853                                        emit(doc._id, null);
854                                    }}
855                            }}"#
856                        )
857                        .to_string(),
858                        reduce: None,
859                    },
860                ),
861            )
862            .await
863            .unwrap();
864
865            // executing 'all' view querying with keys containing 1 key should result in 1 and 0 entries, respectively
866            assert_eq!(
867                db.query_raw(
868                    view_name,
869                    view_name,
870                    Some(QueryParams::from_keys(vec![id.clone().into()]))
871                )
872                .await
873                .unwrap()
874                .rows
875                .len(),
876                1
877            );
878            assert_eq!(
879                db.query_raw(
880                    single_view_name,
881                    single_view_name,
882                    Some(QueryParams::from_keys(vec![id.into()])),
883                )
884                .await
885                .unwrap()
886                .rows
887                .len(),
888                0
889            );
890
891            assert!(db.remove(&second_doc).await.is_ok());
892            assert!(db.remove(&doc).await.is_ok());
893
894            teardown(client, db_name).await;
895        }
896
897        #[tokio::test]
898        async fn should_query_documents_with_key() {
899            let db_name = "should_query_documents_with_key";
900            let (client, db, doc) = setup(db_name).await;
901            let id = doc.get_id().into_owned();
902            let view_name = "testViewAll";
903            db.create_view(
904                view_name,
905                CouchViews::new(
906                    view_name,
907                    CouchFunc {
908                        map: r"function(doc) {{
909                                    emit(doc._id, null);
910                            }}"
911                        .to_string(),
912                        reduce: None,
913                    },
914                ),
915            )
916            .await
917            .unwrap();
918
919            // get the view's design information
920            let design_info = db.get_design_info(view_name).await.unwrap();
921            assert_eq!(design_info.name, view_name);
922
923            let mut ndoc = json!({
924                "thing": true
925            });
926            let details = db.create(&mut ndoc).await.unwrap();
927            let ndoc_id = ndoc.get_id().into_owned();
928            let single_view_name = "testViewSingle";
929            db.create_view(
930                single_view_name,
931                CouchViews::new(
932                    single_view_name,
933                    CouchFunc {
934                        map: format!(
935                            r#"function(doc) {{
936                                    if(doc._id === "{ndoc_id}") {{
937                                        emit(doc._id, null);
938                                    }}
939                            }}"#
940                        )
941                        .to_string(),
942                        reduce: None,
943                    },
944                ),
945            )
946            .await
947            .unwrap();
948
949            // executing 'all' view querying with a specific key should result in 1 and 0 entries, respectively
950            let one_key = QueryParams {
951                key: Some(doc.get_id().into()),
952                ..Default::default()
953            };
954
955            assert_eq!(
956                db.query_raw(view_name, view_name, Some(one_key.clone()))
957                    .await
958                    .unwrap()
959                    .rows
960                    .len(),
961                1
962            );
963            assert_eq!(
964                db.query_raw(single_view_name, single_view_name, Some(one_key))
965                    .await
966                    .unwrap()
967                    .rows
968                    .len(),
969                0
970            );
971
972            assert!(db.remove(&ndoc).await.is_ok());
973            assert!(db.remove(&doc).await.is_ok());
974
975            teardown(client, db_name).await;
976        }
977
978        #[tokio::test]
979        async fn should_query_documents_with_defaultparams() {
980            let dbname = "should_query_documents_with_defaultparams";
981            let (client, db, doc) = setup(dbname).await;
982            let id = doc.get_id().into_owned();
983            let view_name = "testViewAll";
984            db.create_view(
985                view_name,
986                CouchViews::new(
987                    view_name,
988                    CouchFunc {
989                        map: r"function(doc) {{
990                                    emit(doc._id, null);
991                            }}"
992                        .to_string(),
993                        reduce: None,
994                    },
995                ),
996            )
997            .await
998            .unwrap();
999            let mut ndoc = json!({
1000                "thing": true
1001            });
1002            let details = db.create(&mut ndoc).await.unwrap();
1003            let ndoc_id = ndoc.get_id().into_owned();
1004            let single_view_name = "testViewSingle";
1005            db.create_view(
1006                single_view_name,
1007                CouchViews::new(
1008                    single_view_name,
1009                    CouchFunc {
1010                        map: format!(
1011                            r#"function(doc) {{
1012                                    if(doc._id === "{ndoc_id}") {{
1013                                        emit(doc._id, null);
1014                                    }}
1015                            }}"#
1016                        )
1017                        .to_string(),
1018                        reduce: None,
1019                    },
1020                ),
1021            )
1022            .await
1023            .unwrap();
1024
1025            let query_result = db.query_raw(view_name, view_name, None).await;
1026
1027            // executing 'all' view without any params should result in 2 and 1 entries, respectively
1028            assert_eq!(query_result.unwrap().rows.len(), 2);
1029            assert_eq!(
1030                db.query_raw(single_view_name, single_view_name, None)
1031                    .await
1032                    .unwrap()
1033                    .rows
1034                    .len(),
1035                1
1036            );
1037            // executing 'all' view with default params should result in 2 and 1 entries, respectively
1038            assert_eq!(
1039                db.query_raw(view_name, view_name, Some(QueryParams::default()))
1040                    .await
1041                    .unwrap()
1042                    .rows
1043                    .len(),
1044                2
1045            );
1046            assert_eq!(
1047                db.query_raw(single_view_name, single_view_name, Some(QueryParams::default()))
1048                    .await
1049                    .unwrap()
1050                    .rows
1051                    .len(),
1052                1
1053            );
1054
1055            assert!(db.remove(&ndoc).await.is_ok());
1056            assert!(db.remove(&doc).await.is_ok());
1057
1058            teardown(client, dbname).await;
1059        }
1060
1061        #[tokio::test]
1062        async fn should_get_many_all_documents_with_keys() {
1063            let dbname = "should_get_many_all_documents_with_keys";
1064            let (client, db, docs) = setup_multiple(dbname, 4).await;
1065            let doc = docs.first().unwrap();
1066
1067            let params1 = QueryParams {
1068                key: Some(doc.get_id().into_owned()),
1069                ..Default::default()
1070            };
1071            let params2 = QueryParams {
1072                include_docs: Some(true),
1073                ..Default::default()
1074            };
1075            let mut params3 = QueryParams::default();
1076
1077            let params = vec![params1, params2, params3];
1078            let collections = db.query_many_all_docs(QueriesParams::new(params)).await.unwrap();
1079
1080            assert_eq!(collections.len(), 3);
1081            assert_eq!(collections.first().unwrap().rows.len(), 1);
1082            // first result has no docs and only 1 row
1083            assert!(collections.first().unwrap().rows.first().unwrap().doc.is_none());
1084            // second result has 4 rows with docs
1085            assert_eq!(collections.get(1).unwrap().rows.len(), 4);
1086            assert!(collections.get(1).unwrap().rows.first().unwrap().doc.is_some());
1087            // third result has 4 rows without docs
1088            assert_eq!(collections.get(2).unwrap().rows.len(), 4);
1089            assert!(collections.get(2).unwrap().rows.first().unwrap().doc.is_none());
1090
1091            for doc in docs {
1092                assert!(db.remove(&doc).await.is_ok());
1093            }
1094
1095            teardown(client, dbname).await;
1096        }
1097
1098        #[tokio::test]
1099        async fn should_handle_null_view_keys() {
1100            let dbname = "should_handle_null_view_keys";
1101            let (client, db, docs) = setup_multiple(dbname, 4).await;
1102            let doc = docs.first().unwrap();
1103            let count_by_id = r"function (doc) {
1104                                        emit(doc._id, null);
1105                                    }";
1106            let view_name = "should_handle_null_view_keys";
1107            /* a view/reduce like this will return something like the following:
1108
1109               {"rows":[
1110                   {"key":null,"value":14}
1111               ]}
1112
1113               this will fail to deserialize if ViewItem.key is a String. It needs to be a Value to cover for all json scenarios
1114            */
1115            assert!(
1116                db.create_view(
1117                    view_name,
1118                    CouchViews::new(view_name, CouchFunc::new(count_by_id, Some("_count"))),
1119                )
1120                .await
1121                .is_ok()
1122            );
1123
1124            assert!(db.query_raw(view_name, view_name, None).await.is_ok());
1125
1126            teardown(client, dbname).await;
1127        }
1128
1129        #[tokio::test]
1130        async fn should_handle_null_values() {
1131            let dbname = "should_handle_null_values";
1132            let nr_of_docs = 4;
1133            let (client, db, docs) = setup_multiple(dbname, nr_of_docs).await;
1134            let doc = docs.first().unwrap();
1135            // this view generates 'null' values
1136            let count_by_id = r"function (doc) {
1137                                        emit(doc._id, null);
1138                                    }";
1139            let view_name = "should_handle_null_values";
1140            /* a view/reduce like this will return something like the following:
1141
1142               {"rows":[
1143                   {"key":"aaa","value":null}
1144               ]}
1145            */
1146            assert!(
1147                db.create_view(view_name, CouchViews::new(view_name, CouchFunc::new(count_by_id, None)),)
1148                    .await
1149                    .is_ok(),
1150                "problems creating view"
1151            );
1152
1153            // executing a view against a non-existing key
1154            let options = QueryParams::from_keys(vec!["doesnotexist".to_string()]);
1155            // we expect the operation to work even if the type of key is String because there will be no results returned so deserialization will not fail
1156            let result: CouchResult<ViewCollection<String, String, Value>> =
1157                db.query(view_name, view_name, Some(options)).await;
1158
1159            match result {
1160                Ok(_) => {}
1161                Err(e) => {
1162                    panic!("problems executing query: {e}");
1163                }
1164            }
1165
1166            // getting all entries fails because value is null and we're deserializing to String
1167            let result: CouchResult<ViewCollection<String, String, Value>> = db.query(view_name, view_name, None).await;
1168
1169            match result {
1170                Ok(entries) => {
1171                    panic!("previous query should have failed, but succeeded");
1172                }
1173                Err(e) => {}
1174            }
1175
1176            // getting all entries now succeeds because value is null and we're deserializing to Value
1177            let result: CouchResult<ViewCollection<String, Value, Value>> = db.query(view_name, view_name, None).await;
1178
1179            match result {
1180                Ok(entries) => {
1181                    assert_eq!(nr_of_docs, entries.rows.len());
1182                }
1183                Err(e) => {
1184                    panic!("{}", e)
1185                }
1186            }
1187            teardown(client, dbname).await;
1188        }
1189
1190        #[tokio::test]
1191        async fn should_bulk_insert_and_get_many_docs() {
1192            let (client, db, _doc) = setup("should_bulk_insert_and_get_many_docs").await;
1193            let mut docs: Vec<Value> = (0..2000)
1194                .map(|idx| {
1195                    json!({
1196                        "_id": format!("bd_{}", idx),
1197                        "count": idx,
1198                    })
1199                })
1200                .collect();
1201
1202            db.bulk_docs(&mut docs).await.expect("should insert 2000 documents");
1203
1204            // Create a sender and receiver channel pair
1205            let (tx, mut rx): (Sender<DocumentCollection<Value>>, Receiver<DocumentCollection<Value>>) =
1206                mpsc::channel(1000);
1207
1208            // Spawn a separate thread to retrieve the batches from Couch
1209            let t = tokio::spawn(async move {
1210                db.get_all_batched(tx, 0, 0).await.expect("can not launch batch fetch");
1211            });
1212
1213            let mut retrieved = 0;
1214            while let Some(all_docs) = rx.recv().await {
1215                retrieved += all_docs.total_rows;
1216            }
1217
1218            // 2001 == 2000 we created with bulk_docs + 1 that is created by setup()
1219            assert_eq!(retrieved, 2001);
1220
1221            // Wait for the spawned task to finish (should be done by now).
1222            t.await.unwrap();
1223            teardown(client, "should_bulk_insert_and_get_many_docs").await;
1224        }
1225
1226        #[tokio::test]
1227        async fn should_bulk_upsert_docs() {
1228            let (client, db, _doc) = setup("should_bulk_upsert_docs").await;
1229            let count = 3;
1230            let mut docs: Vec<Value> = (0..count)
1231                .map(|idx| {
1232                    json!({
1233                        "_id": format!("bd_{}", idx),
1234                        "value": "hello",
1235                        "count": idx,
1236                    })
1237                })
1238                .collect();
1239
1240            db.bulk_docs(&mut docs).await.expect("should insert documents");
1241
1242            for doc in &mut docs {
1243                doc.as_object_mut()
1244                    .unwrap()
1245                    .insert("updated".to_string(), Value::Bool(true));
1246            }
1247
1248            let res = db.bulk_upsert(&mut docs).await.expect("should upsert documents");
1249
1250            for i in 0..count {
1251                assert_eq!(
1252                    res[i].as_ref().unwrap().rev,
1253                    docs[i].get_rev(),
1254                    "Received rev for item {}: '{}' does not match document rev: '{}'",
1255                    i,
1256                    res[i].as_ref().unwrap().rev,
1257                    docs[i].get_rev()
1258                );
1259            }
1260            let ids: Vec<String> = (0..count).map(|idx| format!("bd_{idx}")).collect();
1261            let docs = db.get_bulk::<Value>(ids).await.expect("should get documents");
1262
1263            for i in 0..count {
1264                assert!(docs[i].get_rev() == res[i].as_ref().unwrap().rev);
1265                assert!(
1266                    docs[i]
1267                        .as_object()
1268                        .expect("should be an object")
1269                        .get("updated")
1270                        .expect("should have updated key")
1271                        == true
1272                );
1273            }
1274            teardown(client, "should_bulk_upsert_docs").await;
1275        }
1276
1277        #[tokio::test]
1278        async fn should_retrieve_membership() {
1279            let client = Client::new_local_test().unwrap();
1280            let membership = client
1281                .membership()
1282                .await
1283                .expect("unable to retrieve cluster membership");
1284            dbg!(membership);
1285        }
1286
1287        #[tokio::test]
1288        async fn should_retrieve_cluster_setup_status() {
1289            let client = Client::new_local_test().unwrap();
1290            let cluster_setup = client
1291                .cluster_setup(EnsureDbsExist::default())
1292                .await
1293                .expect("unable to retrieve cluster setup status");
1294            assert_eq!(cluster_setup, ClusterSetup::ClusterEnabled);
1295        }
1296    }
1297}