use std::{
collections::HashMap,
path::PathBuf,
};
use crate::error::ValidationError;
#[cfg(feature = "with_surf")]
pub use surf_client::SurfClient;
#[cfg(feature = "with_hyper")]
pub use hyper_client::HyperClient;
#[cfg(feature = "with_isahc")]
pub use isahc_client::IsahcClient;
#[async_trait::async_trait]
pub trait HttpClient
where Self: Default + Clone + Sized,
{
type Error;
fn get(&mut self, url: impl AsRef<str>)
-> Result<&mut Self, ValidationError>;
fn head(&mut self, url: impl AsRef<str>)
-> Result<&mut Self, ValidationError>;
fn post(&mut self, url: impl AsRef<str>)
-> Result<&mut Self, ValidationError>;
fn with_header<S: AsRef<str>>(&mut self, name: S, value: S)
-> Result<&mut Self, ValidationError>;
fn with_body(&mut self, data: impl Into<Vec<u8>>) -> &mut Self;
fn with_body_json(&mut self, body: serde_json::Value) -> &mut Self;
fn read_body_from_file(&mut self, path: impl Into<PathBuf>) -> &mut Self;
fn user_agent(&mut self, user_agent_string: impl Into<String>)
-> Result<&mut Self, ValidationError>;
async fn send(&mut self) -> Result<Vec<u8>, Self::Error>;
async fn send_keep_headers(&mut self)
-> Result<(Vec<u8>, HeaderMap), Self::Error>;
}
pub type HeaderMap = HashMap<String, String>;
#[macro_export]
macro_rules! default_user_agent {
($client:literal) => {
format!("rust-b2-client/{}; {}",
option_env!("CARGO_PKG_VERSION").unwrap_or("unknown"),
$client
)
};
}
pub use default_user_agent;
#[cfg(feature = "with_surf")]
mod surf_client {
use std::path::PathBuf;
use super::*;
use crate::error::Error;
use surf::{
http::Method,
Request,
Url,
};
#[derive(Debug, Clone)]
pub struct SurfClient {
client: surf::Client,
req: Option<Request>,
body: Option<Body>,
user_agent: String,
}
impl Default for SurfClient {
fn default() -> Self {
Self {
client: surf::Client::new(),
req: None,
body: None,
user_agent: default_user_agent!("surf"),
}
}
}
#[derive(Debug, Clone)]
enum Body {
Json(serde_json::Value),
Bytes(Vec<u8>),
File(PathBuf),
}
impl SurfClient {
pub fn with_client(mut self, client: surf::Client) -> Self {
self.client = client;
self
}
async fn send_impl(&mut self, keep_headers: bool)
-> Result<(Vec<u8>, Option<HeaderMap>), <Self as HttpClient>::Error> {
if let Some(mut req) = self.req.to_owned() {
if let Some(body) = &self.body {
match body {
Body::Json(val) => req.body_json(val)?,
Body::Bytes(data) => req.body_bytes(data),
Body::File(path) =>
req.set_body(surf::Body::from_file(path).await?),
}
}
req.insert_header("User-Agent", &self.user_agent);
let mut res = self.client.send(req).await?;
let body = res.body_bytes().await?;
let headers = if keep_headers {
let headers: &surf::http::Headers = res.as_ref();
let mut ret = HeaderMap::new();
for (k, v) in headers.iter() {
ret.insert(k.to_string(), v.to_string());
}
Some(ret)
} else {
None
};
self.req = None;
Ok((body, headers))
} else {
Err(Error::NoRequest)
}
}
}
macro_rules! gen_method_func {
($func:ident, $method:ident) => {
fn $func(&mut self, url: impl AsRef<str>)
-> Result<&mut Self, ValidationError> {
let url = Url::parse(url.as_ref())?;
self.req = Some(Request::new(Method::$method, url));
Ok(self)
}
}
}
#[async_trait::async_trait]
impl HttpClient for SurfClient {
type Error = Error<surf::Error>;
gen_method_func!(get, Get);
gen_method_func!(head, Head);
gen_method_func!(post, Post);
fn with_header<S: AsRef<str>>(&mut self, name: S, value: S)
-> Result<&mut Self, ValidationError> {
use std::str::FromStr as _;
use http_types::headers::{HeaderName, HeaderValue};
if let Some(req) = &mut self.req {
let name = HeaderName::from_str(name.as_ref())?;
let value = HeaderValue::from_str(value.as_ref())?;
req.insert_header(name, value);
}
Ok(self)
}
fn with_body(&mut self, data: impl Into<Vec<u8>>) -> &mut Self
{
self.body = Some(Body::Bytes(data.into()));
self
}
fn with_body_json(&mut self, body: serde_json::Value) -> &mut Self {
self.body = Some(Body::Json(body));
self
}
fn read_body_from_file(&mut self, path: impl Into<PathBuf>)
-> &mut Self {
self.body = Some(Body::File(path.into()));
self
}
fn user_agent(&mut self, user_agent_string: impl Into<String>)
-> Result<&mut Self, ValidationError> {
let user_agent = user_agent_string.into();
if user_agent.is_empty() {
Err(ValidationError::MissingData(
"User-Agent is required".into()
))
} else {
self.user_agent = user_agent;
Ok(self)
}
}
async fn send(&mut self) -> Result<Vec<u8>, Self::Error> {
self.send_impl(false).await.map(|v| v.0)
}
async fn send_keep_headers(&mut self)
-> Result<(Vec<u8>, HeaderMap), Self::Error> {
self.send_impl(true).await.map(|(r, m)| (r, m.unwrap()))
}
}
}
#[cfg(feature = "with_hyper")]
mod hyper_client {
use std::path::PathBuf;
use super::*;
use crate::error::Error;
use hyper::{
client::connect::HttpConnector,
header::{HeaderName, HeaderValue},
Method
};
use hyper_tls::HttpsConnector;
use url::Url;
#[derive(Debug, Clone)]
pub struct HyperClient {
client: hyper::Client<HttpsConnector<HttpConnector>>,
method: Option<Method>,
url: String,
headers: Vec<(HeaderName, HeaderValue)>,
body: Option<Body>,
user_agent: String,
}
impl Default for HyperClient {
fn default() -> Self {
let https = HttpsConnector::new();
let client = hyper::Client::builder()
.build::<_, hyper::Body>(https);
Self {
client,
method: None,
url: String::default(),
headers: vec![],
body: None,
user_agent: default_user_agent!("hyper"),
}
}
}
#[derive(Debug, Clone)]
enum Body {
Json(serde_json::Value),
Bytes(hyper::body::Bytes),
File(PathBuf),
}
macro_rules! gen_method_func {
($func:ident, $method: ident) => {
fn $func(&mut self, url: impl AsRef<str>)
-> Result<&mut Self, ValidationError> {
let _url = Url::parse(url.as_ref())?;
self.method = Some(Method::$method);
self.url = String::from(url.as_ref());
Ok(self)
}
}
}
impl HyperClient {
pub fn with_client(
mut self,
client: hyper::Client<HttpsConnector<HttpConnector>>
) -> Self {
self.client = client;
self
}
pub fn with_body_bytes(&mut self, bytes: hyper::body::Bytes)
-> &mut Self {
self.body = Some(Body::Bytes(bytes));
self
}
async fn send_impl(&mut self, keep_headers: bool)
-> Result<(Vec<u8>, Option<HeaderMap>), <Self as HttpClient>::Error> {
if self.method.is_none() {
return Err(Error::NoRequest);
}
let mut req = hyper::Request::builder()
.method(self.method.as_ref().unwrap())
.uri(&self.url);
for (name, value) in &self.headers {
req = req.header(name, value);
}
req = req.header("User-Agent", &self.user_agent);
let body = match &self.body {
Some(body) => match body {
Body::Json(val) => hyper::Body::from(val.to_string()),
Body::Bytes(data) => hyper::Body::from(data.clone()),
Body::File(path) => {
use tokio::{
fs::File,
io::AsyncReadExt as _,
};
let mut file = File::open(path).await?;
let mut buf = vec![];
file.read_to_end(&mut buf).await?;
hyper::Body::from(buf)
},
},
None => hyper::Body::empty(),
};
let req = req.body(body).expect(concat!(
"Invalid request. Please file an issue on b2-client for ",
"improper validation"
));
let (mut parts, body) = self.client.request(req).await?
.into_parts();
let body = hyper::body::to_bytes(body).await?.to_vec();
let headers = if keep_headers {
let mut headers = HeaderMap::new();
headers.extend(
parts.headers.drain()
.filter(|(k, _)| k.is_some())
.map(|(k, v)|
(
k.unwrap().to_string(),
v.to_str().unwrap().to_owned()
)
)
);
Some(headers)
} else {
None
};
self.method = None;
self.url = String::default();
self.headers.clear();
self.body = None;
Ok((body, headers))
}
}
#[async_trait::async_trait]
impl HttpClient for HyperClient {
type Error = Error<hyper::Error>;
gen_method_func!(get, GET);
gen_method_func!(head, HEAD);
gen_method_func!(post, POST);
fn with_header<S: AsRef<str>>(&mut self, name: S, value: S)
-> Result<&mut Self, ValidationError> {
use std::str::FromStr as _;
use hyper::header::{HeaderName, HeaderValue};
let name = HeaderName::from_str(name.as_ref())?;
let value = HeaderValue::from_str(value.as_ref())?;
self.headers.push((name, value));
Ok(self)
}
fn with_body<'a>(&mut self, data: impl Into<Vec<u8>>) -> &mut Self {
self.body = Some(
Body::Bytes(hyper::body::Bytes::from(data.into()))
);
self
}
fn with_body_json(&mut self, body: serde_json::Value) -> &mut Self {
self.body = Some(Body::Json(body));
self
}
fn read_body_from_file(&mut self, path: impl Into<PathBuf>)
-> &mut Self {
self.body = Some(Body::File(path.into()));
self
}
fn user_agent(&mut self, user_agent_string: impl Into<String>)
-> Result<&mut Self, ValidationError> {
let user_agent = user_agent_string.into();
if user_agent.is_empty() {
Err(ValidationError::MissingData(
"User-Agent is required".into()
))
} else {
self.user_agent = user_agent;
Ok(self)
}
}
async fn send(&mut self) -> Result<Vec<u8>, Self::Error> {
self.send_impl(false).await.map(|v| v.0)
}
async fn send_keep_headers(&mut self)
-> Result<(Vec<u8>, HeaderMap), Self::Error> {
self.send_impl(true).await.map(|(r, m)| (r, m.unwrap()))
}
}
}
#[cfg(feature = "with_isahc")]
mod isahc_client {
use super::*;
use crate::error::Error;
use isahc::http::{
header::{HeaderName, HeaderValue},
method::Method,
request::Builder as RequestBuilder,
};
#[derive(Debug)]
enum Body {
Bytes(Vec<u8>),
Json(serde_json::Value),
File(PathBuf),
}
#[derive(Debug)]
pub struct IsahcClient {
client: isahc::HttpClient,
req: Option<RequestBuilder>,
user_agent: String,
body: Option<Body>,
headers: Vec<(HeaderName, HeaderValue)>,
}
impl Default for IsahcClient {
fn default() -> Self {
Self {
client: isahc::HttpClient::new().unwrap(),
req: None,
user_agent: default_user_agent!("isahc"),
body: None,
headers: Vec::new(),
}
}
}
impl Clone for IsahcClient {
fn clone(&self) -> Self {
Self {
client: self.client.clone(),
req: None,
user_agent: self.user_agent.clone(),
body: None,
headers: Vec::new(),
}
}
}
impl IsahcClient {
async fn send_impl(&mut self, keep_headers: bool)
-> Result<(
Vec<u8>, Option<HeaderMap>),
<Self as super::HttpClient>::Error
> {
use futures_lite::AsyncReadExt as _;
if let Some(mut req) = self.req.take() {
for (name, value) in &self.headers {
req = req.header(name, value);
}
req = req.header("User-Agent", &self.user_agent);
let body = if let Some(body) = self.body.take() {
match body {
Body::Bytes(bytes) => Some(bytes),
Body::Json(json) => Some(serde_json::to_vec(&json)?),
Body::File(path) => {
use std::{fs::File, io::Read as _};
let mut file = File::open(path)?;
let mut buf: Vec<u8> = vec![];
file.read_to_end(&mut buf)?;
Some(buf)
},
}
} else {
None
};
let (mut parts, body) = match body {
Some(body) => self.client.send_async(req.body(body)?)
.await?.into_parts(),
None => self.client.send_async(
req.body(isahc::AsyncBody::empty())?
).await?.into_parts(),
};
let headers = if keep_headers {
let mut headers = HeaderMap::new();
headers.extend(
parts.headers.drain()
.filter(|(k, _)| k.is_some())
.map(|(k, v)|
(
k.unwrap().to_string(),
v.to_str().unwrap().to_owned()
)
)
);
Some(headers)
} else {
None
};
let mut buf = Vec::new();
body.bytes().read_to_end(&mut buf).await?;
self.headers.clear();
Ok((buf, headers))
} else {
Err(Error::NoRequest)
}
}
}
macro_rules! gen_method_func {
($func:ident, $method:ident) => {
fn $func(&mut self, url: impl AsRef<str>)
-> Result<&mut Self, ValidationError> {
self.req = Some(
RequestBuilder::new()
.method(Method::$method)
.uri(url.as_ref())
);
Ok(self)
}
};
}
#[async_trait::async_trait]
impl HttpClient for IsahcClient
where Self: Clone + Sized,
{
type Error = Error<isahc::Error>;
gen_method_func!(get, GET);
gen_method_func!(head, HEAD);
gen_method_func!(post, POST);
fn with_header<S: AsRef<str>>(&mut self, name: S, value: S)
-> Result<&mut Self, ValidationError> {
use std::str::FromStr as _;
let name = HeaderName::from_str(name.as_ref())?;
let value = HeaderValue::from_str(value.as_ref())?;
self.headers.push((name, value));
Ok(self)
}
fn with_body(&mut self, data: impl Into<Vec<u8>>) -> &mut Self {
self.body = Some(Body::Bytes(data.into()));
self
}
fn with_body_json(&mut self, body: serde_json::Value) -> &mut Self {
self.body = Some(Body::Json(body));
self
}
fn read_body_from_file(&mut self, path: impl Into<PathBuf>)
-> &mut Self {
self.body = Some(Body::File(path.into()));
self
}
fn user_agent(&mut self, user_agent_string: impl Into<String>)
-> Result<&mut Self, ValidationError> {
let user_agent = user_agent_string.into();
if ! user_agent.is_empty() {
self.user_agent = user_agent;
Ok(self)
} else {
Err(ValidationError::MissingData(
"User-Agent is required".into()
))
}
}
async fn send(&mut self) -> Result<Vec<u8>, Self::Error> {
self.send_impl(false).await.map(|v| v.0)
}
async fn send_keep_headers(&mut self)
-> Result<(Vec<u8>, HeaderMap), Self::Error> {
self.send_impl(true).await.map(|v| (v.0, v.1.unwrap()))
}
}
}