> For the complete documentation index, see [llms.txt](https://davidsietz.gitbook.io/workspace/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://davidsietz.gitbook.io/workspace/module-03/03-section-02/03-section-02-3.md).

# Section II - module

> [couchdb.rs](https://github.com/dsietz/rust-daas/blob/master/src/couchdb.rs)

To create the module, create a new file named ***couchdb.rs*** in the **/src** directory.

> Now is a good time to rerun the `cargo test` command to ensure all your tests still pass.

Add the `use` declarations at the top of the file.

```
use std::str;
use base64::{encode, decode};
use roadrunner::RestClient;
use roadrunner::RestClientMethods;
use hyper::{StatusCode};
use serde_json::value::{Value};
use super::daas::{DaaSDoc};
```

## Tests

> NOTE: These tests require preexisting documents in CouchDB in the `test` database. You will need to create the `test` database and then create the following documents in the `test` database: 1. [doc-12345](https://github.com/dsietz/rust-daas/blob/master/tests/database/doc-12345.json) 2. [doc-upsert\_existing\_doc](https://github.com/dsietz/rust-daas/blob/master/tests/database/doc-upsert_existing_doc.json)

First create the tests for this module as a nested module at the bottom of the file.

```
#[cfg(test)]
mod tests {
    use super::*;
    use std::time::{SystemTime, UNIX_EPOCH};
    use rand::Rng;

    #[test]
    fn test_couchdb_new() {
        let couch = CouchDB::new("foo".to_string(), "bar".to_string());
        assert_eq!(couch.get_auth(), "Zm9vOmJhcg==");
        assert_eq!(couch.get_host(), "localhost");
        assert_eq!(couch.get_port(), 5984);
        assert_eq!(couch.get_protocol(), "http");
        assert_eq!(couch.get_base_url(), "http://localhost:5984".to_string())
    }

    #[test]
    fn test_cluster_setup() {
        let couch = CouchDB::new("admin".to_string(), "password".to_string());
        assert_eq!(couch.cluster_setup().unwrap(), StatusCode::Created);
    }    

    #[ignore]
    #[test]
    fn test_create_db_201() {
        let couch = CouchDB::new("admin".to_string(), "password".to_string());

        match couch.create_db("test".to_string()).unwrap() {
            StatusCode::Created => assert!(true),
            StatusCode::PreconditionFailed => assert!(true),
            _ => assert!(false)
        }
    }

    #[test]
    fn test_create_db_401() {
        let couch = CouchDB::new("foo".to_string(), "bar".to_string());
        assert_eq!(couch.create_db("test2".to_string()).unwrap(), StatusCode::Unauthorized);
    }

    #[test]
    fn test_create_doc_200() {
        let couch = CouchDB::new("admin".to_string(), "password".to_string());
        let src = "iStore".to_string();
        let mut rng = rand::thread_rng();
        let uid: usize = rng.gen_range(0, 1000000);
        let cat = "order".to_string();
        let sub = "clothing".to_string();
        let auth = "istore_app".to_string();
        let data = json!({
            "status": "new"
        });
        let doc = DaaSDoc::new(src, uid, cat, sub, auth, data);
        let rslt = couch.create_doc("test".to_string(),doc);

        match rslt {
            Ok(_v) => {
                assert!(true);
            },
            Err(e) => {
                println!("Failed: {}",e);
                assert!(false);
            }
        }
    }    

    #[test]
    fn test_get_doc_by_id_200() {
        let couch = CouchDB::new("admin".to_string(), "password".to_string());

        match couch.get_doc_by_id("test".to_string(),"12345".to_string()) {
            Ok(doc) => {
                println!("DOC ID: {}", doc._id);
                assert_eq!(doc._id, "12345".to_string())
            },
            Err(_e) => assert!(false)
        }
    }

    #[test]
    fn test_get_doc_by_id_404() {
        let couch = CouchDB::new("admin".to_string(), "password".to_string());

        match couch.get_doc_by_id("test".to_string(),"bad-id".to_string()) {
            Ok(_v) => assert!(false),
            Err(_e) => assert!(true)
        }
    }    

    #[test]
    fn test_get_password() {
        let couch = CouchDB::new("foo".to_string(), "bar".to_string());
        assert_eq!(couch.get_password(), "bar".to_string());
    }

    #[test]
    fn test_get_username() {
        let couch = CouchDB::new("foo".to_string(), "bar".to_string());
        assert_eq!(couch.get_username(), "foo".to_string());
    }    

    #[test]
    fn test_set_host() {
        let mut couch = CouchDB::new("foo".to_string(), "bar".to_string());
        assert!(couch.set_host("127.0.0.1".to_string()));
        assert_eq!(couch.get_host(), "127.0.0.1".to_string());
    }

    #[test]
    fn test_set_port() {
        let mut couch = CouchDB::new("foo".to_string(), "bar".to_string());
        assert!(couch.set_port(8080));
        assert_eq!(couch.get_port(), 8080);
    }

    #[test]
    fn test_set_protocol() {
        let mut couch = CouchDB::new("foo".to_string(), "bar".to_string());
        assert!(couch.set_protocol("https".to_string()));
        assert_eq!(couch.get_protocol(), "https".to_string());
    }

    #[test]
    fn test_update_doc_200() {
        let couch = CouchDB::new("admin".to_string(), "password".to_string());
        let mut before = couch.get_doc_by_id("test".to_string(),"12345".to_string()).unwrap();
        let old_uid = before.source_uid.clone();
        let _rev = before._rev.clone().unwrap();
        let new_src_uid: usize = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as usize;
        before.source_uid = new_src_uid;

        match couch.update_doc("test".to_string(), before) {
            Ok(_doc) => {
                match couch.get_doc_by_id("test".to_string(),"12345".to_string()) {
                    Ok(after) => assert_ne!(old_uid, after.source_uid),
                    _ => assert!(false)
                }
            },
            Err(_e) => assert!(false)
        }
    }   

    #[test]
    fn test_upsert_new_doc_200() {
        // create a new document
        let couch = CouchDB::new("admin".to_string(), "password".to_string());
        let src = "iStore".to_string();
        let mut rng = rand::thread_rng();
        let uid: usize = rng.gen_range(0, 1000000);
        let cat = "order".to_string();
        let sub = "clothing".to_string();
        let auth = "istore_app".to_string();
        let data = json!({
            "status": "new"
        });
        let doc = DaaSDoc::new(src, uid, cat, sub, auth, data);
        let rslt = couch.upsert_doc("test".to_string(),doc);

        match rslt {
            Ok(_v) => {
                assert!(true);
            },
            Err(e) => {
                println!("Failed: {}",e);
                assert!(false);
            }
        }
    }    

    #[test]
    fn test_upsert_existing_doc_200() {
        let couch = CouchDB::new("admin".to_string(), "password".to_string());
        let mut before = couch.get_doc_by_id("test".to_string(),"upsert_existing_doc".to_string()).unwrap();
        let old_uid = before.source_uid.clone();
        let new_src_uid: usize = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as usize;
        before.source_uid = new_src_uid;

        match couch.upsert_doc("test".to_string(), before) {
            Ok(_doc) => {
                match couch.get_doc_by_id("test".to_string(),"upsert_existing_doc".to_string()) {
                    Ok(after) => assert_ne!(old_uid, after.source_uid),
                    _ => assert!(false)
                }
            },
            Err(_e) => assert!(false)
        }
    }      

    #[test]
    fn test_upsert_existing_doc_without_rev_200() {
        // create a new document
        let couch = CouchDB::new("admin".to_string(), "password".to_string());
        let db = "test".to_string();
        let src = "iStore".to_string();
        let mut rng = rand::thread_rng();
        let uid: usize = rng.gen_range(0, 1000000);
        let cat = "order".to_string();
        let sub = "clothing".to_string();
        let auth = "istore_app".to_string();
        let data = json!({
            "status": "new"
        });
        let doc = DaaSDoc::new(src.clone(), uid.clone(), cat.clone(), sub.clone(), auth.clone(), data.clone());

        match couch.create_doc(db.clone(), doc) {
            Ok(_rslt) => {
                let updt_doc = DaaSDoc::new(src, 6000, cat, sub, auth, data);
                let doc_id = updt_doc._id.clone();

                match couch.upsert_doc(db.clone(), updt_doc) {
                    Ok(_v) => {
                        let after = couch.get_doc_by_id(db.clone(), doc_id).unwrap();
                        assert_eq!(6000, after.source_uid);
                    },
                    Err(e) => {
                        println!("Failed: {}",e);
                        assert!(false);
                    }
                }
            },
            Err(e) => {
                println!("Failed: {}",e);
                assert!(false);
            }
        }
    }             
}
```

## Code

Just like in the daas module, we will follow an OOD and first create a structure for the CouchDB object.

```
pub struct CouchDB{
    auth_basic: String,
    host: String,
    port: u32,
    proto: String,
}
```

And then use `impl` to provide the CouchDB object with funcitonality. Notice the `new()` constructor function.

```
impl CouchDB {
    pub fn new(user: String, password: String) -> CouchDB {
        CouchDB{
            auth_basic: encode(&format!("{}:{}", user, password)).to_string(),
            host: "localhost".to_string(),
            port: 5984,
            proto: "http".to_string(),
        }
    }

    //this function doesn't work and setup needs to be done manually
    pub fn cluster_setup(&self) -> Result<StatusCode, String> {
        let mut core = tokio_core::reactor::Core::new().unwrap();
        let data = format!(r#"{{"action":"enable_single_node", "bind_address":"0.0.0.0", "password":"{}", "port":"5984", "singlenode":"true", "username":"{}"}}"#,self.get_password(),self.get_username());
        let response = RestClient::post(&format!("{}/{}",self.get_base_url(), "_cluster_setup"))
            .authorization_basic(self.get_username(), self.get_password())
            .json_body_str(data)
            .execute_on(&mut core)
            .unwrap();

        Ok(*response.status())
    }    

    pub fn create_db(&self, db: String) -> Result<StatusCode, String>{
        let mut core = tokio_core::reactor::Core::new().unwrap();
        let response = RestClient::put(&format!("{}/{}",self.get_base_url(), db))
            .authorization_basic(self.get_username(), self.get_password())
            .execute_on(&mut core)
            .unwrap();

        Ok(*response.status())
    } 

    pub fn create_doc(&self, db: String, mut doc: DaaSDoc) -> Result<Value, String>{
        let json_doc = doc.serialize_without_rev();
        let mut core = tokio_core::reactor::Core::new().unwrap();
        let response = RestClient::post(&format!("{}/{}",self.get_base_url(), db))
            .authorization_basic(self.get_username(), self.get_password())
            .json_body_str(json_doc)
            .execute_on(&mut core)
            .unwrap();

        match response.status() {
            StatusCode::Created => {
                Ok(response.content().as_value().unwrap())
            },
            _ => {
                println!("{}",response.content().as_ref_string());
                Err(format!("Wrong status code. Status {} was returned.",response.status()))
            },
        }
    }     

    pub fn get_auth(&self) -> &str {
        &self.auth_basic
    }

    pub fn get_base_url(&self) -> String {
        let base_url = format!("{}://{}:{}", self.get_protocol(), self.get_host(), self.get_port());
        base_url.to_owned()
    }

    fn get_base64_part(&self, idx: usize) -> Option<String> {
        match decode(&self.auth_basic) {
            Ok(decoded) => {
                let decoded = str::from_utf8(&decoded).unwrap().to_string();
                let parts: Vec<&str> = decoded.split(':').collect();

                Some(parts[idx].to_string())
            },
            Err(_err) => {
                //panic!("Warning: Bad encoded Authorization Header value: {}", encoded)
                 None
            }
        }
    }

    pub fn get_doc_by_id(&self, db: String, doc_id: String) -> Result<DaaSDoc, String>{
        let mut core = tokio_core::reactor::Core::new().unwrap();
        let response = RestClient::get(&format!("{}/{}/{}",self.get_base_url(), db, doc_id))
            .authorization_basic(self.get_username(), self.get_password())
            .execute_on(&mut core)
            .unwrap();

        match response.status() {
            StatusCode::Ok => {
                Ok(DaaSDoc::from_serialized(response.content().as_ref_string()))
            },
            _ => Err(format!("Wrong status code. Status {} was returned.",response.status())),
        }
    } 

    pub fn get_host(&self) -> &str {
        &self.host
    }

    pub fn get_password(&self) -> String {
        let password = self.get_base64_part(1).unwrap();
        password.to_owned()
    }

    pub fn get_port(&self) -> u32 {
        self.port
    }    

    pub fn get_protocol(&self) -> &str {
        &self.proto
    }

    pub fn get_username(&self) -> String {
        let username = self.get_base64_part(0).unwrap();
        username.to_owned()
    }    

    pub fn query_view(&self, db: String, resource_path: String) -> Result<String, String>{
        let mut core = tokio_core::reactor::Core::new().unwrap();
        let response = RestClient::get(&format!("{}/{}/{}",self.get_base_url(), db, resource_path))
            .authorization_basic(self.get_username(), self.get_password())
            .execute_on(&mut core)
            .unwrap();

        match response.status() {
            StatusCode::Ok => {
                Ok(response.content().as_ref_string().to_string())
            },
            _ => Err(format!("Wrong status code. Status {} was returned.",response.status())),
        }
    } 

    pub fn set_host(&mut self, host: String) -> bool {
        self.host = host;
        true
    }

    pub fn set_port(&mut self, port: u32) -> bool {
        self.port = port;
        true
    }

    pub fn set_protocol(&mut self, proto: String) -> bool {
        self.proto = proto;
        true
    }

    pub fn update_doc(&self, db: String, mut doc: DaaSDoc) -> Result<Value, String>{
        let json_doc = doc.serialize();
        let mut core = tokio_core::reactor::Core::new().unwrap();
        let response = RestClient::put(&format!("{}/{}/{}",self.get_base_url(), db, doc._id))
            .authorization_basic(self.get_username(), self.get_password())
            .json_body_str(json_doc)
            .execute_on(&mut core)
            .unwrap();

        match response.status() {
            StatusCode::Created => {
                Ok(response.content().as_value().unwrap())
            },
            _ => {
                println!("{}",response.content().as_ref_string());
                Err(format!("Wrong status code. Status {} was returned.",response.status()))
            },
        }
    } 

    pub fn upsert_doc(&self, db: String, mut doc: DaaSDoc) -> Result<Value, String>{
        match self.get_doc_by_id(db.clone(), doc._id.clone()) {
            // exsisting document
            Ok(found) => {
                // add (or overwrite) the _rev to the doc
                doc._rev = found._rev;
                self.update_doc(db, doc)
            },
            // new document
            Err(_e) => {
                self.create_doc(db, doc)
            },
        }
    }     
}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://davidsietz.gitbook.io/workspace/module-03/03-section-02/03-section-02-3.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
