use crate::database::Database;
use crate::error::{CouchError, CouchResult};
use crate::types::system::{CouchResponse, CouchStatus, DbInfo};
use base64::write::EncoderWriter as Base64Encoder;
use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE, REFERER, USER_AGENT};
use reqwest::{self, Method, StatusCode, Url};
use reqwest::{header, RequestBuilder};
use std::collections::HashMap;
use std::io::Write;
use std::time::Duration;
fn construct_json_headers(uri: Option<&str>) -> HeaderMap {
let mut headers = HeaderMap::new();
headers.insert(USER_AGENT, HeaderValue::from_static("reqwest"));
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
if let Some(u) = uri {
headers.insert(REFERER, HeaderValue::from_str(u).unwrap());
}
headers
}
#[derive(Debug, Clone)]
pub struct Client {
_client: reqwest::Client,
dbs: Vec<&'static str>,
_gzip: bool,
_timeout: u64,
pub uri: String,
username: Option<String>,
password: Option<String>,
pub db_prefix: String,
}
const TEST_DB_HOST: &str = "http://localhost:5984";
const TEST_DB_USER: &str = "admin";
const TEST_DB_PW: &str = "password";
const DEFAULT_TIME_OUT: u64 = 10;
impl Client {
pub fn new(uri: &str, username: &str, password: &str) -> CouchResult<Client> {
Client::new_with_timeout(uri, Some(username), Some(password), DEFAULT_TIME_OUT)
}
pub fn new_no_auth(uri: &str) -> CouchResult<Client> {
Client::new_with_timeout(uri, None, None, DEFAULT_TIME_OUT)
}
pub fn new_local_test() -> CouchResult<Client> {
Client::new_with_timeout(TEST_DB_HOST, Some(TEST_DB_USER), Some(TEST_DB_PW), DEFAULT_TIME_OUT)
}
pub fn new_with_timeout(
uri: &str,
username: Option<&str>,
password: Option<&str>,
timeout: u64,
) -> CouchResult<Client> {
let mut headers = header::HeaderMap::new();
if let Some(username) = username {
let mut header_value = b"Basic ".to_vec();
{
let mut encoder = Base64Encoder::new(&mut header_value, base64::STANDARD);
write!(encoder, "{}:", username).unwrap();
if let Some(password) = password {
write!(encoder, "{}", password).unwrap();
}
}
let auth_header = header::HeaderValue::from_bytes(&header_value).expect("can not set AUTHORIZATION header");
headers.insert(header::AUTHORIZATION, auth_header);
}
let client = reqwest::Client::builder()
.default_headers(headers)
.gzip(true)
.timeout(Duration::new(timeout, 0))
.build()?;
Ok(Client {
_client: client,
uri: uri.to_string(),
_gzip: true,
_timeout: timeout,
dbs: Vec::new(),
db_prefix: String::new(),
username: username.map(|u| u.to_string()),
password: password.map(|p| p.to_string()),
})
}
pub fn get_self(&mut self) -> &mut Self {
self
}
pub fn set_uri(&mut self, uri: String) -> &Self {
self.uri = uri;
self
}
pub fn set_prefix(&mut self, prefix: String) -> &Self {
self.db_prefix = prefix;
self
}
pub async fn list_dbs(&self) -> CouchResult<Vec<String>> {
let response = self.get(String::from("/_all_dbs"), None)?.send().await?;
let data = response.json().await?;
Ok(data)
}
fn build_dbname(&self, dbname: &str) -> String {
self.db_prefix.clone() + dbname
}
pub async fn db(&self, dbname: &str) -> CouchResult<Database> {
let name = self.build_dbname(dbname);
let db = Database::new(name.clone(), self.clone());
let path = self.create_path(name, None)?;
let head_response = self
._client
.head(&path)
.headers(construct_json_headers(None))
.send()
.await?;
match head_response.status() {
StatusCode::OK => Ok(db),
_ => self.make_db(dbname).await,
}
}
pub async fn make_db(&self, dbname: &str) -> CouchResult<Database> {
let name = self.build_dbname(dbname);
let db = Database::new(name.clone(), self.clone());
let path = self.create_path(name, None)?;
let put_response = self
._client
.put(&path)
.headers(construct_json_headers(None))
.send()
.await?;
let status = put_response.status();
let s: CouchResponse = put_response.json().await?;
match s.ok {
Some(true) => Ok(db),
_ => {
let err = s.error.unwrap_or_else(|| s!("unspecified error"));
Err(CouchError::new(err, status))
}
}
}
pub async fn destroy_db(&self, dbname: &str) -> CouchResult<bool> {
let path = self.create_path(self.build_dbname(dbname), None)?;
let response = self
._client
.delete(&path)
.headers(construct_json_headers(None))
.send()
.await?;
let s: CouchResponse = response.json().await?;
Ok(s.ok.unwrap_or(false))
}
pub async fn exists(&self, dbname: &str) -> CouchResult<bool> {
let path = self.create_path(self.build_dbname(dbname), None)?;
let result = self.head(path, None)?.send().await;
Ok(result.is_ok())
}
pub async fn get_info(&self, dbname: &str) -> CouchResult<DbInfo> {
let path = self.create_path(self.build_dbname(dbname), None)?;
let response = self.get(path, None)?.send().await?.error_for_status()?;
let info = response.json().await?;
Ok(info)
}
pub async fn check_status(&self) -> CouchResult<CouchStatus> {
let response = self
._client
.get(&self.uri)
.headers(construct_json_headers(None))
.send()
.await?;
let status = response.json().await?;
Ok(status)
}
fn create_path(&self, path: String, args: Option<HashMap<String, String>>) -> CouchResult<String> {
let mut uri = Url::parse(&self.uri)?.join(&path)?;
if let Some(ref map) = args {
let mut qp = uri.query_pairs_mut();
for (k, v) in map {
qp.append_pair(k, v);
}
}
Ok(uri.into_string())
}
pub fn req(
&self,
method: Method,
path: String,
opts: Option<HashMap<String, String>>,
) -> CouchResult<RequestBuilder> {
let uri = self.create_path(path, opts)?;
let req = self
._client
.request(method, &uri)
.headers(construct_json_headers(Some(&uri)));
Ok(req)
}
pub(crate) fn get(&self, path: String, args: Option<HashMap<String, String>>) -> CouchResult<RequestBuilder> {
Ok(self.req(Method::GET, path, args)?)
}
pub(crate) fn post(&self, path: String, body: String) -> CouchResult<RequestBuilder> {
let req = self.req(Method::POST, path, None)?.body(body);
Ok(req)
}
pub(crate) fn put(&self, path: String, body: String) -> CouchResult<RequestBuilder> {
let req = self.req(Method::PUT, path, None)?.body(body);
Ok(req)
}
pub(crate) fn head(&self, path: String, args: Option<HashMap<String, String>>) -> CouchResult<RequestBuilder> {
Ok(self.req(Method::HEAD, path, args)?)
}
pub(crate) fn delete(&self, path: String, args: Option<HashMap<String, String>>) -> CouchResult<RequestBuilder> {
Ok(self.req(Method::DELETE, path, args)?)
}
}