1use std::fs::File;
4use std::io::Read;
5use std::{fmt::Debug, time::Duration};
6
7use log::error;
8use reqwest::multipart::{self, Part};
9use reqwest::Response;
10use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
11use reqwest_retry::policies::ExponentialBackoff;
12use reqwest_retry::RetryTransientMiddleware;
13use retry_policies::Jitter;
14
15use crate::api::models::objects::api_error::SquareApiError;
16use crate::http::client::config::{RetryConfig, SquareHttpClientConfig};
17
18#[derive(Clone, Debug)]
20pub struct SquareHttpClient {
21 pub client: reqwest::Client,
23 pub retry_client: ClientWithMiddleware,
24}
25
26impl SquareHttpClient {
28 pub fn try_new(config: &SquareHttpClientConfig) -> Result<Self, SquareApiError> {
30 let mut client_builder = reqwest::ClientBuilder::new();
31 client_builder = client_builder.timeout(Duration::from_secs(config.timeout.into()));
32 client_builder = client_builder.user_agent(&config.user_agent);
33 client_builder = client_builder.default_headers((&config.default_headers).try_into()?);
34 let client = client_builder.build().map_err(|e| {
35 let msg = format!("Failed to build client: {}", e);
36 error!("{}", msg);
37 SquareApiError::new(&msg)
38 })?;
39 let retry_policy = create_retry_policy(&config.retry_configuration);
40 let retry_client = ClientBuilder::new(client.clone())
41 .with(RetryTransientMiddleware::new_with_policy(retry_policy))
42 .build();
43 Ok(Self { client, retry_client })
44 }
45
46 pub async fn get(&self, url: &str) -> Result<Response, SquareApiError> {
48 let response = self.retry_client.get(url).send().await.map_err(|e| {
49 let msg = format!("Error getting {}: {}", url, e);
50 error!("{}", msg);
51 SquareApiError::new(&msg)
52 })?;
53 Ok(response)
54 }
55
56 pub async fn post(&self, url: &str, body: &str) -> Result<Response, SquareApiError> {
58 let body_string = body.to_string();
59 let response = self
60 .retry_client
61 .post(url)
62 .body(body_string)
63 .send()
64 .await
65 .map_err(|e| {
66 let msg = format!("Error posting to {}: {}", url, e);
67 error!("{}", msg);
68 SquareApiError::new(&msg)
69 })?;
70 Ok(response)
71 }
72
73 pub async fn post_multipart(&self, url: &str, body: &str, filepath: &str) -> Result<Response, SquareApiError> {
75 let request = serde_json::to_string(body).map_err(|e| {
76 let msg = format!("Error serializing request body - url: {}, body: {:?}: {}", url, body, e);
77 error!("{}", msg);
78 SquareApiError::new(&msg)
79 })?;
80
81 let mut file = File::open(filepath).map_err(|e| {
82 let msg = format!("Error opening file {}: {}", filepath, e);
83 error!("{}", msg);
84 SquareApiError::new(&msg)
85 })?;
86 let mut vec = Vec::new();
87 let _reader = file.read_to_end(&mut vec);
88 let mime = get_mime_type(filepath)?;
89 let part = Part::stream(vec).mime_str(mime).map_err(|e| {
90 let msg = format!(
91 "Error applying content type {} to form part for file {}: {}",
92 mime, filepath, e
93 );
94 error!("{}", msg);
95 SquareApiError::new(&msg)
96 })?;
97
98 let form = multipart::Form::new().text("request", request).part("file", part);
99
100 let response = self.client.post(url).multipart(form).send().await.map_err(|e| {
101 let msg = format!("Error posting to {}: {}", url, e);
102 error!("{}", msg);
103 SquareApiError::new(&msg)
104 })?;
105 Ok(response)
106 }
107
108 pub async fn empty_post(&self, url: &str) -> Result<Response, SquareApiError> {
110 let response = self.client.post(url).send().await.map_err(|e| {
111 let msg = format!("Error posting to {}: {}", url, e);
112 error!("{}", msg);
113 SquareApiError::new(&msg)
114 })?;
115 Ok(response)
116 }
117
118 pub async fn put(&self, url: &str, body: &str) -> Result<Response, SquareApiError> {
120 let body_string = body.to_string();
121 let response = self.retry_client.put(url).body(body_string).send().await.map_err(|e| {
122 let msg = format!("Error putting to {}: {}", url, e);
123 error!("{}", msg);
124 SquareApiError::new(&msg)
125 })?;
126 Ok(response)
127 }
128
129 pub async fn put_multipart(&self, url: &str, body: &str, filepath: &str) -> Result<Response, SquareApiError> {
131 let request = serde_json::to_string(body).map_err(|e| {
132 let msg = format!("Error serializing request body - url: {}, body: {:?}: {}", url, body, e);
133 error!("{}", msg);
134 SquareApiError::new(&msg)
135 })?;
136
137 let mut file = File::open(filepath).map_err(|e| {
138 let msg = format!("Error opening file {}: {}", filepath, e);
139 error!("{}", msg);
140 SquareApiError::new(&msg)
141 })?;
142 let mut vec = Vec::new();
143 let _reader = file.read_to_end(&mut vec);
144 let mime = get_mime_type(filepath)?;
145 let part = Part::stream(vec).mime_str(mime).map_err(|e| {
146 let msg = format!(
147 "Error applying content type {} to form part for file {}: {}",
148 mime, filepath, e
149 );
150 error!("{}", msg);
151 SquareApiError::new(&msg)
152 })?;
153
154 let form = multipart::Form::new().text("request", request).part("file", part);
155
156 let response = self.client.put(url).multipart(form).send().await.map_err(|e| {
157 let msg = format!("Error putting to {}: {}", url, e);
158 error!("{}", msg);
159 SquareApiError::new(&msg)
160 })?;
161 Ok(response)
162 }
163
164 pub async fn delete(&self, url: &str) -> Result<Response, SquareApiError> {
166 let response = self.retry_client.delete(url).send().await.map_err(|e| {
167 let msg = format!("Error putting to {}: {}", url, e);
168 error!("{}", msg);
169 SquareApiError::new(&msg)
170 })?;
171 Ok(response)
172 }
173}
174
175fn create_retry_policy(retry_configuration: &RetryConfig) -> ExponentialBackoff {
177 ExponentialBackoff::builder()
178 .retry_bounds(
179 retry_configuration.min_retry_interval,
180 retry_configuration.max_retry_interval,
181 )
182 .jitter(Jitter::Bounded)
183 .build_with_max_retries(retry_configuration.retries_count)
184}
185
186fn get_mime_type(filepath: &str) -> Result<&str, SquareApiError> {
188 let kind = infer::get_from_path(filepath).map_err(|e| {
189 let msg = format!("Error reading file {}: {}", filepath, e);
190 error!("{}", msg);
191 SquareApiError::new(&msg)
192 })?;
193
194 match kind {
195 Some(kind) => Ok(kind.mime_type()),
196 None => {
197 let msg = format!("Error determining mime type for file {}", filepath);
198 error!("{}", msg);
199 Err(SquareApiError::new(&msg))
200 }
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use crate::http::client::config::SquareHttpClientConfig;
207 use crate::http::client::http_client::SquareHttpClient;
208
209 #[test]
210 fn try_new_ok() {
211 let client = SquareHttpClient::try_new(&SquareHttpClientConfig::default());
212 assert!(client.is_ok());
213 }
214}