Skip to content

Commit

Permalink
Merge pull request #41 from horacimacias/master
Browse files Browse the repository at this point in the history
Add a typed wrapper for Database
  • Loading branch information
mibes authored Oct 17, 2024
2 parents ee2ab74 + cdca582 commit 7d0f02e
Show file tree
Hide file tree
Showing 9 changed files with 286 additions and 66 deletions.
2 changes: 1 addition & 1 deletion couch_rs/examples/typed_documents/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub struct TestDoc {
/// `CouchDB` stores its documents in a B+ tree. Each additional or updated document is stored as
/// a leaf node, and may require re-writing intermediary and parent nodes. You may be able to take
/// advantage of sequencing your own ids more effectively than the automatically generated ids if
/// you can arrange them to be sequential yourself. (https://docs.couchdb.org/en/stable/best-practices/documents.html)
/// you can arrange them to be sequential yourself. <https://docs.couchdb.org/en/stable/best-practices/documents.html>
#[serde(skip_serializing_if = "String::is_empty")]
pub _id: DocumentId,
/// Document Revision, provided by `CouchDB`, helps negotiating conflicts
Expand Down
18 changes: 9 additions & 9 deletions couch_rs/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ pub(crate) async fn is_ok(request: RequestBuilder) -> bool {
/// It is also responsible for the creation/access/destruction of databases.
#[derive(Debug, Clone)]
pub struct Client {
_client: reqwest::Client,
client: reqwest::Client,
_gzip: bool,
_timeout: Option<u64>,
uri: Url,
Expand All @@ -66,21 +66,21 @@ const DEFAULT_TIME_OUT: u64 = 10;
impl Client {
/// new creates a new Couch client with a default timeout of 10 seconds.
/// The timeout is applied from when the request starts connecting until the response body has finished.
/// The URI has to be in this format: http://hostname:5984, for example: http://192.168.64.5:5984
/// The URI has to be in this format: <http://hostname:5984>, for example: <http://192.168.64.5:5984>
pub fn new(uri: &str, username: &str, password: &str) -> CouchResult<Client> {
Client::new_with_timeout(uri, Some(username), Some(password), Some(DEFAULT_TIME_OUT))
}

/// `new_no_auth` creates a new Couch client with a default timeout of 10 seconds. *Without authentication*.
/// The timeout is applied from when the request starts connecting until the response body has finished.
/// The URI has to be in this format: http://hostname:5984, for example: http://192.168.64.5:5984
/// The URI has to be in this format: <http://hostname:5984>, for example: <http://192.168.64.5:5984>
pub fn new_no_auth(uri: &str) -> CouchResult<Client> {
Client::new_with_timeout(uri, None, None, Some(DEFAULT_TIME_OUT))
}

/// `new_local_test` creates a new Couch client *for testing purposes* with a default timeout of 10 seconds.
/// The timeout is applied from when the request starts connecting until the response body has finished.
/// The URI that will be used is: http://hostname:5984, with a username of "admin" and a password
/// The URI that will be used is: <http://hostname:5984>, with a username of "admin" and a password
/// of "password". Use this only for testing!!!
pub fn new_local_test() -> CouchResult<Client> {
Client::new_with_timeout(
Expand All @@ -91,7 +91,7 @@ impl Client {
)
}

/// `new_with_timeout` creates a new Couch client. The URI has to be in this format: http://hostname:5984,
/// `new_with_timeout` creates a new Couch client. The URI has to be in this format: <http://hostname:5984>,
/// The timeout is applied from when the request starts connecting until the response body has finished.
/// Timeout is in seconds.
///
Expand All @@ -103,7 +103,7 @@ impl Client {
password: Option<&str>,
timeout: Option<u64>,
) -> CouchResult<Client> {
let mut headers = header::HeaderMap::new();
let mut headers = HeaderMap::new();

if let Some(username) = username {
let mut header_value = b"Basic ".to_vec();
Expand All @@ -116,7 +116,7 @@ impl Client {
}
}

let auth_header = header::HeaderValue::from_bytes(&header_value).expect("can not set AUTHORIZATION header");
let auth_header = HeaderValue::from_bytes(&header_value).expect("can not set AUTHORIZATION header");
headers.insert(header::AUTHORIZATION, auth_header);
}

Expand All @@ -127,7 +127,7 @@ impl Client {
let client = client_builder.build()?;

Ok(Client {
_client: client,
client,
uri: parse_server(uri)?,
_gzip: true,
_timeout: timeout,
Expand Down Expand Up @@ -317,7 +317,7 @@ impl Client {
}
}

self._client
self.client
.request(method, uri.as_str())
.headers(construct_json_headers(Some(uri.as_str())))
}
Expand Down
73 changes: 33 additions & 40 deletions couch_rs/src/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ impl CouchJsonExt for reqwest::Response {
/// (sometimes called Collection in other `NoSQL` flavors such as `MongoDB`).
#[derive(Debug, Clone)]
pub struct Database {
_client: Client,
client: Client,
name: String,
}

impl Database {
#[must_use]
pub fn new(name: String, client: Client) -> Database {
Database { _client: client, name }
Database { client, name }
}

// convenience function to retrieve the name of the database
Expand Down Expand Up @@ -102,7 +102,7 @@ impl Database {
let mut path: String = self.name.clone();
path.push_str("/_compact");

let request = self._client.post(&path, String::new());
let request = self.client.post(&path, String::new());
is_accepted(request).await
}

Expand All @@ -111,13 +111,13 @@ impl Database {
let mut path: String = self.name.clone();
path.push_str("/_view_cleanup");

let request = self._client.post(&path, String::new());
let request = self.client.post(&path, String::new());
is_accepted(request).await
}

/// Starts the compaction of a given index
pub async fn compact_index(&self, index: &str) -> bool {
let request = self._client.post(&self.create_compact_path(index), String::new());
let request = self.client.post(&self.create_compact_path(index), String::new());
is_accepted(request).await
}

Expand All @@ -143,7 +143,7 @@ impl Database {
/// }
/// ```
pub async fn exists(&self, id: &str) -> bool {
let request = self._client.head(&self.create_document_path(id), None);
let request = self.client.head(&self.create_document_path(id), None);
is_ok(request).await
}

Expand Down Expand Up @@ -203,8 +203,8 @@ impl Database {
/// }
///```
pub async fn get<T: TypedCouchDocument>(&self, id: &str) -> CouchResult<T> {
let value: serde_json::Value = self
._client
let value: Value = self
.client
.get(&self.create_document_path(id), None)
.send()
.await?
Expand Down Expand Up @@ -272,7 +272,7 @@ impl Database {
.collect::<CouchResult<_>>()?;
let body = format!(r#"{{"docs":{} }}"#, to_string(&upsert_values)?);
let response = self
._client
.client
.post(&self.create_raw_path("_bulk_docs"), body)
.send()
.await?;
Expand Down Expand Up @@ -359,7 +359,7 @@ impl Database {
options.keys = ids;

let response = self
._client
.client
.post(&self.create_raw_path("_all_docs"), to_string(&options)?)
.send()
.await?
Expand Down Expand Up @@ -410,11 +410,11 @@ impl Database {
batch_size: u64,
max_results: u64,
) -> CouchResult<u64> {
let mut bookmark = Option::None;
let mut bookmark = None;
let limit = if batch_size > 0 { batch_size } else { 1000 };

let mut results: u64 = 0;
query.limit = Option::Some(limit);
query.limit = Some(limit);

let maybe_err = loop {
let mut segment_query = query.clone();
Expand Down Expand Up @@ -524,7 +524,7 @@ impl Database {
// we use POST here, because this allows for a larger set of keys to be provided, compared
// to a GET call. It provides the same functionality
let response = self
._client
.client
.post(view_path, js!(&queries))
.send()
.await?
Expand Down Expand Up @@ -554,7 +554,7 @@ impl Database {
// we use POST here, because this allows for a larger set of keys to be provided, compared
// to a GET call. It provides the same functionality
let response = self
._client
.client
.post(&self.create_raw_path("_all_docs"), js!(&options))
.send()
.await?
Expand Down Expand Up @@ -623,7 +623,7 @@ impl Database {
/// ```
pub async fn find<T: TypedCouchDocument>(&self, query: &FindQuery) -> CouchResult<DocumentCollection<T>> {
let path = self.create_raw_path("_find");
let response = self._client.post(&path, js!(query)).send().await?;
let response = self.client.post(&path, js!(query)).send().await?;
let status = response.status();
let data: FindResult<T> = response.couch_json().await?;

Expand All @@ -637,7 +637,7 @@ impl Database {
})
.collect();

let mut bookmark = Option::None;
let mut bookmark = None;
let returned_bookmark = data.bookmark.unwrap_or_default();

if returned_bookmark != "nil" && !returned_bookmark.is_empty() {
Expand Down Expand Up @@ -707,7 +707,7 @@ impl Database {
pub async fn save<T: TypedCouchDocument>(&self, doc: &mut T) -> DocumentCreatedResult {
let id = doc.get_id().to_string();
let body = to_string(&doc)?;
let response = self._client.put(&self.create_document_path(&id), body).send().await?;
let response = self.client.put(&self.create_document_path(&id), body).send().await?;
let status = response.status();
let data: DocumentCreatedResponse = response.json().await?;

Expand Down Expand Up @@ -750,7 +750,7 @@ impl Database {
/// ```
pub async fn create<T: TypedCouchDocument>(&self, doc: &mut T) -> DocumentCreatedResult {
let value = to_create_value(doc)?;
let response = self._client.post(&self.name, to_string(&value)?).send().await?;
let response = self.client.post(&self.name, to_string(&value)?).send().await?;

let status = response.status();
let data: DocumentCreatedResponse = response.json().await?;
Expand Down Expand Up @@ -828,10 +828,7 @@ impl Database {
///
/// This will first fetch the latest rev for each document that does not have a rev set. It
/// will then insert all documents into the database.
pub async fn bulk_upsert<T: TypedCouchDocument + Clone>(
&self,
docs: &mut [T],
) -> CouchResult<Vec<DocumentCreatedResult>> {
pub async fn bulk_upsert<T: TypedCouchDocument>(&self, docs: &mut [T]) -> CouchResult<Vec<DocumentCreatedResult>> {
// First collect all docs that do not have a rev set.
let mut docs_without_rev = vec![];
for (i, doc) in docs.iter().enumerate() {
Expand Down Expand Up @@ -891,14 +888,10 @@ impl Database {
/// Ok(())
/// }
/// ```
pub async fn create_view<T: Into<serde_json::Value>>(
&self,
design_name: &str,
views: T,
) -> CouchResult<DesignCreated> {
pub async fn create_view<T: Into<Value>>(&self, design_name: &str, views: T) -> CouchResult<DesignCreated> {
let doc: Value = views.into();
let response = self
._client
.client
.put(&self.create_design_path(design_name), to_string(&doc)?)
.send()
.await?;
Expand Down Expand Up @@ -972,7 +965,7 @@ impl Database {
/// }
/// ```
pub async fn query<
K: Serialize + DeserializeOwned + PartialEq + std::fmt::Debug + Clone,
K: Serialize + DeserializeOwned + PartialEq + Debug + Clone,
V: DeserializeOwned,
T: TypedCouchDocument,
>(
Expand All @@ -985,7 +978,7 @@ impl Database {
options = Some(QueryParams::default());
}

self._client
self.client
.post(&self.create_query_view_path(design_name, view_name), js!(&options))
.send()
.await?
Expand All @@ -1008,7 +1001,7 @@ impl Database {
None => String::default(),
};

self._client
self.client
.put(&self.create_execute_update_path(design_id, name, document_id), body)
.send()
.await?
Expand Down Expand Up @@ -1046,7 +1039,7 @@ impl Database {
let mut h = HashMap::new();
h.insert(s!("rev"), doc.get_rev().into_owned());

let request = self._client.delete(&self.create_document_path(&doc.get_id()), Some(&h));
let request = self.client.delete(&self.create_document_path(&doc.get_id()), Some(&h));
is_ok(request).await
}

Expand Down Expand Up @@ -1126,7 +1119,7 @@ impl Database {
}

let response = self
._client
.client
.post(&self.create_raw_path("_index"), js!(Value::Object(body.clone())))
.send()
.await?;
Expand All @@ -1143,7 +1136,7 @@ impl Database {

/// Reads the database's indexes and returns them
pub async fn read_indexes(&self) -> CouchResult<DatabaseIndexList> {
self._client
self.client
.get(&self.create_raw_path("_index"), None)
.send()
.await?
Expand All @@ -1157,7 +1150,7 @@ impl Database {
let uri = format!("_index/{ddoc}/json/{name}");

match self
._client
.client
.delete(&self.create_raw_path(&uri), None)
.send()
.await?
Expand All @@ -1180,7 +1173,7 @@ impl Database {
return Err(CouchError::new_with_id(
result.id,
"DesignCreated did not return 'result' field as expected".to_string(),
reqwest::StatusCode::INTERNAL_SERVER_ERROR,
StatusCode::INTERNAL_SERVER_ERROR,
));
};

Expand All @@ -1199,13 +1192,13 @@ impl Database {
/// It can return all changes from a `seq` string, and can optionally run in infinite (live)
/// mode.
#[must_use]
pub fn changes(&self, last_seq: Option<serde_json::Value>) -> ChangesStream {
ChangesStream::new(self._client.clone(), self.name.clone(), last_seq)
pub fn changes(&self, last_seq: Option<Value>) -> ChangesStream {
ChangesStream::new(self.client.clone(), self.name.clone(), last_seq)
}
}
fn get_mandatory_string_value(key: &str, value: &Value) -> CouchResult<String> {
let id = if let Some(serde_json::Value::String(id)) = value.get(key) {
let id = if let Some(Value::String(id)) = value.get(key) {
id.to_owned()
} else {
return Err(CouchError::new(
Expand All @@ -1232,7 +1225,7 @@ fn to_upsert_value(doc: &impl TypedCouchDocument) -> CouchResult<serde_json::Map

fn get_value_map(doc: &impl TypedCouchDocument) -> CouchResult<serde_json::Map<String, Value>> {
let value = serde_json::to_value(doc)?;
let serde_json::Value::Object(value) = value else {
let Value::Object(value) = value else {
return Err(CouchError::new(
s!("invalid document type, expected something that deserializes as json object"),
StatusCode::INTERNAL_SERVER_ERROR,
Expand Down
2 changes: 1 addition & 1 deletion couch_rs/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ impl<T: TypedCouchDocument> DocumentCollection<T> {
offset: doc.offset,
total_rows: u32::try_from(items.len()).expect("total_rows > u32::MAX is not supported"),
rows: items,
bookmark: Option::None,
bookmark: None,
}
}

Expand Down
Loading

0 comments on commit 7d0f02e

Please sign in to comment.