use std::io::Write as _;
use bytes::Bytes;
use flate2::{Compression, write::GzEncoder};
use serde::{Serialize, de::DeserializeOwned};
use crate::{CanopyHttpError, Error, Result, transport::CanopyTransport};
const COMPRESS_FROM: usize = 1024;
pub struct CanopyClient<T> {
transport: T,
}
impl<T> std::fmt::Debug for CanopyClient<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CanopyClient").finish_non_exhaustive()
}
}
impl<T: CanopyTransport> CanopyClient<T> {
pub fn new(transport: T) -> Self {
Self { transport }
}
pub fn transport(&self) -> &T {
&self.transport
}
pub async fn call_json<B: Serialize + ?Sized, R: DeserializeOwned>(
&self,
method: http::Method,
path: &str,
body: Option<&B>,
) -> Result<R> {
let response = self.call(method, path, body).await?;
serde_json::from_slice(response.body()).map_err(|source| Error::Decode {
path: path.to_owned(),
source,
})
}
pub async fn call_empty<B: Serialize + ?Sized>(
&self,
method: http::Method,
path: &str,
body: Option<&B>,
) -> Result<()> {
self.call(method, path, body).await.map(|_| ())
}
async fn call<B: Serialize + ?Sized>(
&self,
method: http::Method,
path: &str,
body: Option<&B>,
) -> Result<http::Response<Bytes>> {
let mut request = http::Request::builder().method(method).uri(path);
let payload = match body {
None => Bytes::new(),
Some(body) => {
let json = serde_json::to_vec(body).map_err(|source| Error::Encode {
path: path.to_owned(),
source,
})?;
request = request.header(http::header::CONTENT_TYPE, "application/json");
if json.len() >= COMPRESS_FROM {
request = request.header(http::header::CONTENT_ENCODING, "gzip");
Bytes::from(gzip(&json).map_err(|source| Error::Compress {
path: path.to_owned(),
source,
})?)
} else {
Bytes::from(json)
}
}
};
let request = request.body(payload).map_err(|source| Error::Request {
path: path.to_owned(),
source,
})?;
let response = self.transport.call(request).await?;
if response.status().is_success() {
Ok(response)
} else {
Err(CanopyHttpError {
status: response.status(),
path: path.to_owned(),
body: response.into_body(),
}
.into())
}
}
}
fn gzip(bytes: &[u8]) -> std::io::Result<Vec<u8>> {
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(bytes)?;
encoder.finish()
}