1use std::io::Write as _;
4
5use bytes::Bytes;
6use flate2::{Compression, write::GzEncoder};
7use serde::{Serialize, de::DeserializeOwned};
8
9use crate::{CanopyHttpError, Error, Result, transport::CanopyTransport};
10
11const COMPRESS_FROM: usize = 1024;
14
15pub struct CanopyClient<T> {
26 transport: T,
27}
28
29impl<T> std::fmt::Debug for CanopyClient<T> {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 f.debug_struct("CanopyClient").finish_non_exhaustive()
32 }
33}
34
35impl<T: CanopyTransport> CanopyClient<T> {
36 pub fn new(transport: T) -> Self {
38 Self { transport }
39 }
40
41 pub fn transport(&self) -> &T {
44 &self.transport
45 }
46
47 pub async fn call_json<B: Serialize + ?Sized, R: DeserializeOwned>(
51 &self,
52 method: http::Method,
53 path: &str,
54 body: Option<&B>,
55 ) -> Result<R> {
56 let response = self.call(method, path, body).await?;
57 serde_json::from_slice(response.body()).map_err(|source| Error::Decode {
58 path: path.to_owned(),
59 source,
60 })
61 }
62
63 pub async fn call_empty<B: Serialize + ?Sized>(
68 &self,
69 method: http::Method,
70 path: &str,
71 body: Option<&B>,
72 ) -> Result<()> {
73 self.call(method, path, body).await.map(|_| ())
74 }
75
76 async fn call<B: Serialize + ?Sized>(
78 &self,
79 method: http::Method,
80 path: &str,
81 body: Option<&B>,
82 ) -> Result<http::Response<Bytes>> {
83 let mut request = http::Request::builder().method(method).uri(path);
84
85 let payload = match body {
86 None => Bytes::new(),
87 Some(body) => {
88 let json = serde_json::to_vec(body).map_err(|source| Error::Encode {
89 path: path.to_owned(),
90 source,
91 })?;
92 request = request.header(http::header::CONTENT_TYPE, "application/json");
93 if json.len() >= COMPRESS_FROM {
94 request = request.header(http::header::CONTENT_ENCODING, "gzip");
95 Bytes::from(gzip(&json).map_err(|source| Error::Compress {
96 path: path.to_owned(),
97 source,
98 })?)
99 } else {
100 Bytes::from(json)
101 }
102 }
103 };
104
105 let request = request.body(payload).map_err(|source| Error::Request {
106 path: path.to_owned(),
107 source,
108 })?;
109
110 let response = self.transport.call(request).await?;
111 if response.status().is_success() {
112 Ok(response)
113 } else {
114 Err(CanopyHttpError {
115 status: response.status(),
116 path: path.to_owned(),
117 body: response.into_body(),
118 }
119 .into())
120 }
121 }
122}
123
124fn gzip(bytes: &[u8]) -> std::io::Result<Vec<u8>> {
125 let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
126 encoder.write_all(bytes)?;
127 encoder.finish()
128}