1use std::{error::Error, fmt, time::Duration};
2
3use bytes::Bytes;
4use futures_util::stream::Stream;
5use log::debug;
6use reqwest::{
7 Client as HttpClient,
8 ClientBuilder as HttpClientBuilder,
9 Error as HttpError,
10 RequestBuilder as HttpRequestBuilder,
11};
12use serde::de::DeserializeOwned;
13use tokio::time::sleep;
14
15use super::payload::{Payload, PayloadError};
16use crate::types::{Response, ResponseError};
17
18#[cfg(test)]
19mod tests;
20
21const DEFAULT_HOST: &str = "https://api.telegram.org";
22const DEFAULT_MAX_RETRIES: u8 = 2;
23
24#[derive(Clone)]
26pub struct Client {
27 host: String,
28 http_client: HttpClient,
29 token: String,
30 max_retries: u8,
31}
32
33impl Client {
34 pub fn new<T>(token: T) -> Result<Self, ClientError>
40 where
41 T: Into<String>,
42 {
43 let client = HttpClientBuilder::new()
44 .use_rustls_tls()
45 .build()
46 .map_err(ClientError::BuildClient)?;
47 Ok(Self::with_http_client(client, token))
48 }
49
50 pub fn with_http_client<T>(http_client: HttpClient, token: T) -> Self
58 where
59 T: Into<String>,
60 {
61 Self {
62 http_client,
63 host: String::from(DEFAULT_HOST),
64 token: token.into(),
65 max_retries: DEFAULT_MAX_RETRIES,
66 }
67 }
68
69 pub fn with_host<T>(mut self, host: T) -> Self
75 where
76 T: Into<String>,
77 {
78 self.host = host.into();
79 self
80 }
81
82 pub fn with_max_retries(mut self, value: u8) -> Self {
88 self.max_retries = value;
89 self
90 }
91
92 pub async fn download_file<P>(
115 &self,
116 file_path: P,
117 ) -> Result<impl Stream<Item = Result<Bytes, HttpError>> + use<P>, DownloadFileError>
118 where
119 P: AsRef<str>,
120 {
121 let payload = Payload::empty(file_path.as_ref());
122 let url = payload.build_url(&format!("{}/file", &self.host), &self.token);
123 debug!("Downloading file from {url}");
124 let rep = self.http_client.get(&url).send().await?;
125 let status = rep.status();
126 if !status.is_success() {
127 Err(DownloadFileError::Response {
128 status: status.as_u16(),
129 text: rep.text().await?,
130 })
131 } else {
132 Ok(rep.bytes_stream())
133 }
134 }
135
136 pub async fn execute<M>(&self, method: M) -> Result<M::Response, ExecuteError>
147 where
148 M: Method,
149 M::Response: DeserializeOwned + Send + 'static,
150 {
151 let request = method
152 .into_payload()
153 .into_http_request_builder(&self.http_client, &self.host, &self.token)?;
154 let response = match send_request_retry(Box::new(request)).await? {
155 RetryResponse::Ok(response) => response,
156 RetryResponse::Retry {
157 mut request,
158 mut response,
159 mut retry_after,
160 } => {
161 for i in 0..self.max_retries {
162 debug!("Retry attempt {i}, sleeping for {retry_after} second(s)");
163 sleep(Duration::from_secs(retry_after)).await;
164 match send_request_retry(request).await? {
165 RetryResponse::Ok(new_response) => {
166 response = new_response;
167 break;
168 }
169 RetryResponse::Retry {
170 request: new_request,
171 response: new_response,
172 retry_after: new_retry_after,
173 } => {
174 request = new_request;
175 response = new_response;
176 retry_after = new_retry_after;
177 }
178 }
179 }
180 response
181 }
182 };
183 Ok(response.into_result()?)
184 }
185}
186
187enum RetryResponse<T> {
188 Ok(Response<T>),
189 Retry {
190 request: Box<HttpRequestBuilder>,
191 response: Response<T>,
192 retry_after: u64,
193 },
194}
195
196async fn send_request_retry<T>(request: Box<HttpRequestBuilder>) -> Result<RetryResponse<T>, ExecuteError>
197where
198 T: DeserializeOwned,
199{
200 Ok(match request.try_clone() {
201 Some(try_request) => {
202 let response = send_request(try_request).await?;
203 match response.retry_after() {
204 Some(retry_after) => RetryResponse::Retry {
205 request,
206 response,
207 retry_after,
208 },
209 None => RetryResponse::Ok(response),
210 }
211 }
212 None => {
213 debug!("Could not clone builder, sending request without retry");
214 RetryResponse::Ok(send_request(*request).await?)
215 }
216 })
217}
218
219async fn send_request<T>(request: HttpRequestBuilder) -> Result<Response<T>, ExecuteError>
220where
221 T: DeserializeOwned,
222{
223 let response = request.send().await?;
224 Ok(response.json::<Response<T>>().await?)
225}
226
227impl fmt::Debug for Client {
228 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
229 f.debug_struct("Client")
230 .field("http_client", &self.http_client)
231 .field("host", &self.host)
232 .field("token", &format_args!("..."))
233 .finish()
234 }
235}
236
237pub trait Method {
239 type Response;
241
242 fn into_payload(self) -> Payload;
244}
245
246#[derive(Debug)]
248pub enum ClientError {
249 BuildClient(HttpError),
251}
252
253impl Error for ClientError {
254 fn source(&self) -> Option<&(dyn Error + 'static)> {
255 Some(match self {
256 ClientError::BuildClient(err) => err,
257 })
258 }
259}
260
261impl fmt::Display for ClientError {
262 fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
263 match self {
264 ClientError::BuildClient(err) => write!(out, "can not build HTTP client: {err}"),
265 }
266 }
267}
268
269#[derive(Debug)]
272pub enum DownloadFileError {
273 Http(HttpError),
275 Response {
277 status: u16,
279 text: String,
281 },
282}
283
284impl From<HttpError> for DownloadFileError {
285 fn from(err: HttpError) -> Self {
286 Self::Http(err)
287 }
288}
289
290impl Error for DownloadFileError {
291 fn source(&self) -> Option<&(dyn Error + 'static)> {
292 match self {
293 DownloadFileError::Http(err) => Some(err),
294 _ => None,
295 }
296 }
297}
298
299impl fmt::Display for DownloadFileError {
300 fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
301 match self {
302 DownloadFileError::Http(err) => write!(out, "failed to download file: {err}"),
303 DownloadFileError::Response { status, text } => {
304 write!(out, "failed to download file: status={status} text={text}")
305 }
306 }
307 }
308}
309
310#[derive(Debug, derive_more::From)]
313pub enum ExecuteError {
314 Http(HttpError),
316 Payload(PayloadError),
318 Response(ResponseError),
320}
321
322impl Error for ExecuteError {
323 fn source(&self) -> Option<&(dyn Error + 'static)> {
324 use self::ExecuteError::*;
325 Some(match self {
326 Http(err) => err,
327 Payload(err) => err,
328 Response(err) => err,
329 })
330 }
331}
332
333impl fmt::Display for ExecuteError {
334 fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
335 use self::ExecuteError::*;
336 write!(
337 out,
338 "failed to execute method: {}",
339 match self {
340 Http(err) => err.to_string(),
341 Payload(err) => err.to_string(),
342 Response(err) => err.to_string(),
343 }
344 )
345 }
346}