#![recursion_limit = "1024"]
#![deny(rustdoc::broken_intra_doc_links, rust_2018_idioms)]
#![warn(
missing_copy_implementations,
missing_debug_implementations,
missing_docs,
clippy::explicit_iter_loop,
clippy::use_self,
clippy::clone_on_ref_ptr,
clippy::future_not_send
)]
use reqwest::{Method, Url};
use secrecy::{ExposeSecret, Secret};
use snafu::{ResultExt, Snafu};
#[derive(Debug, Snafu)]
pub enum RequestError {
#[snafu(display("Error while processing the HTTP request: {}", source))]
ReqwestProcessing {
source: reqwest::Error,
},
#[snafu(display("HTTP request returned an error: {}, `{}`", status, text))]
Http {
status: reqwest::StatusCode,
text: String,
},
#[snafu(display("Error while serializing to JSON: {}", source))]
Serializing {
source: serde_json::error::Error,
},
#[snafu(display("Error while parsing response: {}", text))]
Deserializing {
text: String,
},
}
#[cfg(feature = "gzip")]
#[derive(Debug, Clone)]
enum Compression {
None,
Gzip,
}
#[derive(Debug, Clone)]
pub struct Client {
pub base: Url,
pub org: String,
auth_header: Option<Secret<String>>,
reqwest: reqwest::Client,
#[cfg(feature = "gzip")]
compression: Compression,
}
impl Client {
pub fn new(
url: impl Into<String>,
org: impl Into<String>,
auth_token: impl Into<String>,
) -> Self {
ClientBuilder::new(url, org, auth_token).build().unwrap()
}
fn request(&self, method: Method, url: &str) -> reqwest::RequestBuilder {
let mut req = self.reqwest.request(method, url);
if let Some(auth) = &self.auth_header {
req = req.header("Authorization", auth.expose_secret());
}
req
}
fn url(&self, endpoint: &str) -> String {
let mut url = self.base.clone();
url.set_path(endpoint);
url.into()
}
}
#[derive(Debug, Snafu)]
pub enum BuildError {
#[snafu(display("Error while building the client: {}", source))]
ReqwestClientError {
source: reqwest::Error,
},
}
#[derive(Debug)]
pub struct ClientBuilder {
pub base: Url,
pub org: String,
auth_header: Option<Secret<String>>,
reqwest: reqwest::ClientBuilder,
#[cfg(feature = "gzip")]
compression: Compression,
}
impl ClientBuilder {
pub fn new(
url: impl Into<String>,
org: impl Into<String>,
auth_token: impl Into<String>,
) -> Self {
Self::with_builder(reqwest::ClientBuilder::new(), url, org, auth_token)
}
pub fn with_builder(
builder: reqwest::ClientBuilder,
url: impl Into<String>,
org: impl Into<String>,
auth_token: impl Into<String>,
) -> Self {
let token = auth_token.into();
let auth_header = if token.is_empty() {
None
} else {
Some(format!("Token {}", token).into())
};
let url: String = url.into();
let base =
Url::parse(&url).unwrap_or_else(|_| panic!("Invalid url was provided: {}", &url));
Self {
base,
org: org.into(),
auth_header,
reqwest: builder,
#[cfg(feature = "gzip")]
compression: Compression::None,
}
}
#[cfg(feature = "gzip")]
pub fn gzip(mut self, enable: bool) -> ClientBuilder {
self.reqwest = self.reqwest.gzip(enable);
self.compression = Compression::Gzip;
self
}
pub fn build(self) -> Result<Client, BuildError> {
Ok(Client {
base: self.base,
org: self.org,
auth_header: self.auth_header,
reqwest: self.reqwest.build().context(ReqwestClientError)?,
#[cfg(feature = "gzip")]
compression: self.compression,
})
}
}
pub mod common;
pub mod api;
pub mod models;
pub mod writable;
pub use influxdb2_derive::FromDataPoint;
pub use influxdb2_structmap::FromMap;
#[cfg(test)]
mod tests {
use crate::Client;
#[test]
fn url_invalid_panic() {
let result = std::panic::catch_unwind(|| Client::new("/3242/23", "some-org", "some-token"));
assert!(result.is_err());
}
#[test]
fn url_ignores_double_slashes() {
let base = "http://influxdb.com/";
let client = Client::new(base, "some-org", "some-token");
assert_eq!(format!("{}api/v2/write", base), client.url("/api/v2/write"));
assert_eq!(client.url("/api/v2/write"), client.url("api/v2/write"));
}
}