1use std::time::Duration;
13
14use reqwest::{
15 Method,
16 header::{CONTENT_TYPE, HeaderMap, HeaderValue},
17};
18use serde::{Serialize, de::DeserializeOwned};
19
20use crate::Error;
21
22#[derive(Debug, Clone)]
23pub struct HttpClientBuilder {
24 timeout: Duration,
25 headers: HeaderMap<HeaderValue>,
26}
27
28#[derive(Debug, Default, Clone)]
29pub struct HttpClient {
30 method: Method,
31 timeout: Duration,
32 url: String,
33 headers: HeaderMap<HeaderValue>,
34 body: Option<String>,
35}
36
37impl Default for HttpClientBuilder {
38 fn default() -> Self {
39 let mut headers = HeaderMap::new();
40 headers.append(CONTENT_TYPE, HeaderValue::from_static("application/json"));
41
42 Self {
43 timeout: Duration::from_secs(30),
44 headers,
45 }
46 }
47}
48
49impl HttpClientBuilder {
50 pub fn build(&self, method: Method, url: impl Into<String>) -> HttpClient {
51 HttpClient {
52 method,
53 url: url.into(),
54 headers: self.headers.clone(),
55 body: None,
56 timeout: self.timeout,
57 }
58 }
59
60 pub fn get(&self, url: impl Into<String>) -> HttpClient {
61 self.build(Method::GET, url)
62 }
63
64 pub fn post(&self, url: impl Into<String>) -> HttpClient {
65 self.build(Method::POST, url)
66 }
67
68 pub fn put(&self, url: impl Into<String>) -> HttpClient {
69 self.build(Method::PUT, url)
70 }
71
72 pub fn delete(&self, url: impl Into<String>) -> HttpClient {
73 self.build(Method::DELETE, url)
74 }
75
76 pub fn patch(&self, url: impl Into<String>) -> HttpClient {
77 self.build(Method::PATCH, url)
78 }
79
80 pub fn with_header(mut self, name: &'static str, value: impl AsRef<str>) -> Self {
81 if let Ok(value) = HeaderValue::from_str(value.as_ref()) {
82 self.headers.append(name, value);
83 }
84 self
85 }
86
87 pub fn with_timeout(mut self, timeout: Option<Duration>) -> Self {
88 if let Some(timeout) = timeout {
89 self.timeout = timeout;
90 }
91 self
92 }
93}
94
95impl HttpClient {
96 pub fn with_header(mut self, name: &'static str, value: impl AsRef<str>) -> Self {
97 if let Ok(value) = HeaderValue::from_str(value.as_ref()) {
98 self.headers.append(name, value);
99 }
100 self
101 }
102
103 pub fn with_body<B: Serialize>(mut self, body: B) -> crate::Result<Self> {
104 match serde_json::to_string(&body) {
105 Ok(body) => {
106 self.body = Some(body);
107 Ok(self)
108 }
109 Err(err) => Err(Error::Serialize(format!(
110 "Failed to serialize request: {err}"
111 ))),
112 }
113 }
114
115 pub fn with_raw_body(mut self, body: String) -> Self {
116 self.body = Some(body);
117 self
118 }
119
120 pub async fn send<T>(self) -> crate::Result<T>
121 where
122 T: DeserializeOwned,
123 {
124 let response = self.send_raw().await?;
125 serde_json::from_slice::<T>(response.as_bytes())
126 .map_err(|err| Error::Serialize(format!("Failed to deserialize response: {err}")))
127 }
128
129 pub async fn send_raw(self) -> crate::Result<String> {
130 let mut request = reqwest::Client::builder()
131 .timeout(self.timeout)
132 .build()
133 .unwrap_or_default()
134 .request(self.method, &self.url)
135 .headers(self.headers);
136
137 if let Some(body) = self.body {
138 request = request.body(body);
139 }
140
141 let response = request
142 .send()
143 .await
144 .map_err(|err| Error::Api(format!("Failed to send request to {}: {err}", self.url)))?;
145
146 match response.status().as_u16() {
147 204 => serde_json::from_str("{}")
148 .map_err(|err| Error::Serialize(format!("Failed to create empty response: {err}"))),
149 200..=299 => response.text().await.map_err(|err| {
150 Error::Api(format!("Failed to read response from {}: {err}", self.url))
151 }),
152 400 => {
153 let text = response.text().await.map_err(|err| {
154 Error::Api(format!("Failed to read response from {}: {err}", self.url))
155 })?;
156 Err(Error::Api(format!("BadRequest {}", text)))
157 }
158 401 => Err(Error::Unauthorized),
159 404 => Err(Error::NotFound),
160 code => Err(Error::Api(format!(
161 "Invalid HTTP response code {code}: {:?}",
162 response.error_for_status()
163 ))),
164 }
165 }
166
167 pub async fn send_with_retry<T>(self, max_retries: u32) -> crate::Result<T>
168 where
169 T: DeserializeOwned,
170 {
171 let mut attempts = 0;
172 let body = self.body;
173 loop {
174 let mut request = reqwest::Client::builder()
175 .timeout(self.timeout)
176 .build()
177 .unwrap_or_default()
178 .request(self.method.clone(), &self.url)
179 .headers(self.headers.clone());
180
181 if let Some(body) = body.as_ref() {
182 request = request.body(body.clone());
183 }
184
185 let response = request.send().await.map_err(|err| {
186 Error::Api(format!("Failed to send request to {}: {err}", self.url))
187 })?;
188
189 return match response.status().as_u16() {
190 204 => serde_json::from_str("{}").map_err(|err| {
191 Error::Serialize(format!("Failed to create empty response: {err}"))
192 }),
193 200..=299 => {
194 let text = response.text().await.map_err(|err| {
195 Error::Api(format!("Failed to read response from {}: {err}", self.url))
196 })?;
197 serde_json::from_str(&text).map_err(|err| {
198 Error::Serialize(format!("Failed to deserialize response: {err}"))
199 })
200 }
201 429 if attempts < max_retries => {
202 if let Some(retry_after) = response.headers().get("retry-after") {
203 if let Ok(seconds) = retry_after.to_str().unwrap_or("0").parse::<u64>() {
204 tokio::time::sleep(Duration::from_secs(seconds)).await;
205 attempts += 1;
206 continue;
207 }
208 }
209 Err(Error::Api("Rate limit exceeded".to_string()))
210 }
211 400 => {
212 let text = response.text().await.map_err(|err| {
213 Error::Api(format!("Failed to read response from {}: {err}", self.url))
214 })?;
215 Err(Error::Api(format!("BadRequest {}", text)))
216 }
217 401 => Err(Error::Unauthorized),
218 404 => Err(Error::NotFound),
219 code => Err(Error::Api(format!(
220 "Invalid HTTP response code {code}: {:?}",
221 response.error_for_status()
222 ))),
223 };
224 }
225 }
226}