use futures::prelude::*;
use reqwest::{Client as HttpClient, Url, header};
use std::{
borrow::Borrow,
};
use crate::{error, serialization, Point, Points, Precision, data_model};
use serde_json::json;
#[derive(Debug, Clone)]
pub struct Client {
pub host: Url,
pub bucket: String,
pub org: String,
pub org_id: String,
pub authentication: Option<(String, String)>,
pub jwt_token: Option<String>,
pub client: HttpClient,
}
impl Client {
#[inline]
pub async fn new<T>(host: Url, bucket: T, org: T, jwt: T) -> Result<Self, error::Error>
where
T: Into<String>,
{
let token = jwt.into();
let authorized = format!("Token {}", token.clone());
let mut headers = header::HeaderMap::new();
headers.insert("Authorization", header::HeaderValue::from_str(&authorized).unwrap());
let httpclient = reqwest::Client::builder()
.default_headers(headers)
.build()?;
let mut client = Client {
host,
bucket: bucket.into(),
org: org.into(),
org_id: "".to_string(),
authentication: None,
jwt_token: Some(token.clone()),
client: httpclient,
};
client.org_id = client.get_ord_id().await?;
Ok(client)
}
#[inline]
pub async fn get_ord_id(&mut self) -> Result<String, error::Error> {
let param = vec![("org", self.org.as_str())];
let url = self.build_url("api/v2/orgs", Some(param));
let fut = self.client.get(url.await).send();
let res = fut.await?;
let status = res.status().as_u16();
match status {
200 => {
let contents = res.json::<data_model::org::Orgs>().await?;
let org_id = contents.orgs[0].id.clone();
Ok(org_id)
}
_ => {
let err = res.text().await?;
Err(error::Error::SyntaxError(serialization::conversion(&err)))
}
}
}
pub fn new_with_client<T>(host: Url, bucket: T, org: T, client: HttpClient) -> Self
where
T: Into<String>,
{
Client {
host,
bucket: bucket.into(),
org: org.into(),
org_id: "".to_string(),
authentication: None,
jwt_token: None,
client,
}
}
#[inline]
pub fn switch_database<T>(&mut self, database: T)
where
T: Into<String>,
{
self.bucket = database.into();
}
#[inline]
pub fn set_authentication<T>(mut self, user: T, passwd: T) -> Self
where
T: Into<String>,
{
self.authentication = Some((user.into(), passwd.into()));
self
}
#[inline]
pub fn set_jwt_token<T>(mut self, token: T) -> Self
where
T: Into<String>,
{
self.jwt_token = Some(token.into());
self
}
#[inline]
pub fn get_db(&self) -> &str {
self.bucket.as_str()
}
#[inline]
pub async fn ping(&self) -> impl Future<Output = Result<bool, error::Error>> {
let url = self.build_url("ping", None);
let resp_future = self.client.get(url.await).send().boxed();
async move {
let res = resp_future.await?;
let status = res.status().as_u16();
let err = res.text().await?;
match status{
204 => Ok(true),
_ => Err(error::Error::SyntaxError(serialization::conversion(&err))),
}
}
}
pub async fn get_version(&self) -> Result<String, error::Error>{
let url = self.build_url("ping", None);
let resp_future = self.client.get(url.await).send().boxed();
let res = resp_future.await?;
let status = res.status().as_u16();
let headers = res.headers().get("X-Influxdb-Version");
match status{
204 => {
match headers {
Some(header) => {
let version = header.to_owned().to_str().unwrap().to_string();
Ok(version)
}
None => {
Ok(String::from("Don't know"))
}
}
},
_ => {
let err = res.text().await?;
Err(error::Error::SyntaxError(serialization::conversion(&err)))
}
}
}
pub async fn write_point<'a>(
&self,
point: Point<'a>,
precision: Option<Precision>,
rp: Option<&str>,
) -> Result<(), error::Error>{
let points = Points::new(point);
self.write_points(points, precision, rp).await
}
pub async fn write_points<'a, T: IntoIterator<Item = impl Borrow<Point<'a>>>>(
&self,
points: T,
precision: Option<Precision>,
rp: Option<&str>,
) -> Result<(), error::Error> {
let line = serialization::line_serialization(points);
let mut param = vec![("bucket", self.bucket.as_str()), ("org", self.org.as_str())];
match precision {
Some(ref t) => param.push(("precision", t.to_str())),
None => param.push(("precision", "s")),
};
if let Some(t) = rp {
param.push(("rp", t))
}
let url = self.build_url("api/v2/write", Some(param));
let fut = self.client.post(url.await).body(line).send();
let res = fut.await?;
let status = res.status().as_u16();
let err = res.text().await?;
match status {
204 => Ok(()),
400 => Err(error::Error::SyntaxError(serialization::conversion(&err))),
401 | 403 => Err(error::Error::InvalidCredentials(
"Invalid authentication credentials.".to_string(),
)),
404 => Err(error::Error::DataBaseDoesNotExist(
serialization::conversion(&err),
)),
500 => Err(error::Error::RetentionPolicyDoesNotExist(err)),
status => Err(error::Error::Unknown(format!(
"Received status code {}",
status
))),
}
}
pub async fn drop_measurement(
&self,
measurement: &str,
start: &str,
stop: &str,
) -> Result<(), error::Error> {
let param = vec![("bucket", self.bucket.as_str()),("org", self.org.as_str())];
let url = self.build_url("api/v2/delete", Some(param));
let measure = format!("_measurement=\"{}\"", measurement.clone()).as_str().to_owned();
let body = data_model::query::DeleteQuery{
predicate: measure,
start: start.to_string(),
stop: stop.to_string()
};
let builder = self.client.post(url.await).body(json!(body).to_string());
let resp_future = builder.send();
let res = resp_future.await?;
match res.status().as_u16() {
204 => Ok(()),
400 => {
let err = res.text().await?;
Err(error::Error::SyntaxError(serialization::conversion(
&err,
)))
}
401 | 403 => Err(error::Error::InvalidCredentials(
"Invalid authentication credentials.".to_string(),
)),
_ => Err(error::Error::Unknown("There is something wrong".to_string())),
}
}
pub async fn create_database(&self, dbname: &str) -> Result<(), error::Error> {
let retention_rules = data_model::retention_rules::RetentionRules{
every_seconds: 0,
shard_group_duration_seconds: 0,
retention_type: "expire".to_string(),
};
let body = data_model::bucket::Bucket{
name: dbname.to_string(),
org_id: self.org_id.clone(),
retention_rules: vec![retention_rules],
description: "".to_string(),
rp: None,
schema_type: "Implicit".to_string(),
};
let post_body = json!(body).to_string();
let url = self.build_url("api/v2/buckets", None);
let fut = self.client.post(url.await).body(post_body).send();
let res = fut.await?;
let status = res.status().as_u16();
let err = res.text().await?;
match status {
201 => Ok(()),
422 => Err(error::Error::SyntaxError(serialization::conversion(&err))),
_ => Err(error::Error::SyntaxError(serialization::conversion(&err)))
}
}
pub async fn get_bucket_id(&self, bucket_name: &str) -> Result<String, error::Error> {
let param = vec![("name", bucket_name)];
let url = self.build_url("api/v2/buckets", Some(param));
let fut = self.client.get(url.await).send();
let res = fut.await?;
let status = res.status().as_u16();
match status {
200 => {
let contents = res.json::<data_model::bucket::Buckets>().await?;
let bucket_id = contents.buckets[0].id.clone();
Ok(bucket_id)
}
_ => {
let err = res.text().await?;
Err(error::Error::SyntaxError(serialization::conversion(&err)))
}
}
}
pub async fn drop_database(&self, dbname: &str) -> Result<(), error::Error> {
let client = self.clone();
let name = dbname.to_string();
let id = client.get_bucket_id(&name.clone()).await?;
let id = format!("api/v2/buckets/{}", id);
let url = client.build_url(&id, None);
let fut = client.client.delete(url.await).send();
let res = fut.await?;
let status = res.status().as_u16();
let err = res.text().await?;
match status {
204 => Ok(()),
404 => Err(error::Error::SyntaxError(serialization::conversion(&err))),
_ => Err(error::Error::SyntaxError(serialization::conversion(&err)))
}
}
#[inline]
pub async fn build_url(&self, key: &str, param: Option<Vec<(&str, &str)>>) -> Url {
let url = self.host.join(key).unwrap();
if let Some(param) = param {
Url::parse_with_params(url.as_str(), param).unwrap()
} else {
url
}
}
pub fn default() -> impl Future<Output = Result<Self, error::Error>> {
async {
Client::new(Url::parse("http://localhost:8086").unwrap(), "test", "test", "00000000").await
}
}
}