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";
22
23#[derive(Clone)]
25pub struct Client {
26 host: String,
27 http_client: HttpClient,
28 token: String,
29}
30
31impl Client {
32 pub fn new<T>(token: T) -> Result<Self, ClientError>
38 where
39 T: Into<String>,
40 {
41 let client = HttpClientBuilder::new()
42 .use_rustls_tls()
43 .build()
44 .map_err(ClientError::BuildClient)?;
45 Ok(Self::with_http_client(client, token))
46 }
47
48 pub fn with_http_client<T>(http_client: HttpClient, token: T) -> Self
56 where
57 T: Into<String>,
58 {
59 Self {
60 http_client,
61 host: String::from(DEFAULT_HOST),
62 token: token.into(),
63 }
64 }
65
66 pub fn with_host<T>(mut self, host: T) -> Self
72 where
73 T: Into<String>,
74 {
75 self.host = host.into();
76 self
77 }
78
79 pub async fn download_file<P>(
102 &self,
103 file_path: P,
104 ) -> Result<impl Stream<Item = Result<Bytes, HttpError>> + use<P>, DownloadFileError>
105 where
106 P: AsRef<str>,
107 {
108 let payload = Payload::empty(file_path.as_ref());
109 let url = payload.build_url(&format!("{}/file", &self.host), &self.token);
110 debug!("Downloading file from {}", url);
111 let rep = self.http_client.get(&url).send().await?;
112 let status = rep.status();
113 if !status.is_success() {
114 Err(DownloadFileError::Response {
115 status: status.as_u16(),
116 text: rep.text().await?,
117 })
118 } else {
119 Ok(rep.bytes_stream())
120 }
121 }
122
123 pub async fn execute<M>(&self, method: M) -> Result<M::Response, ExecuteError>
134 where
135 M: Method,
136 M::Response: DeserializeOwned + Send + 'static,
137 {
138 let builder = method
139 .into_payload()
140 .into_http_request_builder(&self.http_client, &self.host, &self.token)?;
141 for i in 0..2 {
142 if i != 0 {
143 debug!("Retrying request after timeout error");
144 }
145 match builder.try_clone() {
146 Some(builder) => {
147 let response = self.send_request(builder).await?;
148 match response.retry_after() {
149 Some(retry_after) => {
150 debug!("Got a timeout error (retry_after={retry_after})");
151 sleep(Duration::from_secs(retry_after)).await
152 }
153 None => return Ok(response.into_result()?),
154 }
155 }
156 None => {
157 debug!("Could not clone builder, sending request without retry");
158 return Ok(self.send_request(builder).await?.into_result()?);
159 }
160 }
161 }
162 Err(ExecuteError::TooManyRequests)
163 }
164
165 async fn send_request<T>(&self, http_request: HttpRequestBuilder) -> Result<Response<T>, ExecuteError>
166 where
167 T: DeserializeOwned,
168 {
169 let response = http_request.send().await?;
170 Ok(response.json::<Response<T>>().await?)
171 }
172}
173
174impl fmt::Debug for Client {
175 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
176 f.debug_struct("Client")
177 .field("http_client", &self.http_client)
178 .field("host", &self.host)
179 .field("token", &format_args!("..."))
180 .finish()
181 }
182}
183
184pub trait Method {
186 type Response;
188
189 fn into_payload(self) -> Payload;
191}
192
193#[derive(Debug)]
195pub enum ClientError {
196 BuildClient(HttpError),
198}
199
200impl Error for ClientError {
201 fn source(&self) -> Option<&(dyn Error + 'static)> {
202 Some(match self {
203 ClientError::BuildClient(err) => err,
204 })
205 }
206}
207
208impl fmt::Display for ClientError {
209 fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
210 match self {
211 ClientError::BuildClient(err) => write!(out, "can not build HTTP client: {}", err),
212 }
213 }
214}
215
216#[derive(Debug)]
219pub enum DownloadFileError {
220 Http(HttpError),
222 Response {
224 status: u16,
226 text: String,
228 },
229}
230
231impl From<HttpError> for DownloadFileError {
232 fn from(err: HttpError) -> Self {
233 Self::Http(err)
234 }
235}
236
237impl Error for DownloadFileError {
238 fn source(&self) -> Option<&(dyn Error + 'static)> {
239 match self {
240 DownloadFileError::Http(err) => Some(err),
241 _ => None,
242 }
243 }
244}
245
246impl fmt::Display for DownloadFileError {
247 fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
248 match self {
249 DownloadFileError::Http(err) => write!(out, "failed to download file: {}", err),
250 DownloadFileError::Response { status, text } => {
251 write!(out, "failed to download file: status={} text={}", status, text)
252 }
253 }
254 }
255}
256
257#[derive(Debug, derive_more::From)]
260pub enum ExecuteError {
261 Http(HttpError),
263 Payload(PayloadError),
265 Response(ResponseError),
267 TooManyRequests,
269}
270
271impl Error for ExecuteError {
272 fn source(&self) -> Option<&(dyn Error + 'static)> {
273 use self::ExecuteError::*;
274 Some(match self {
275 Http(err) => err,
276 Payload(err) => err,
277 Response(err) => err,
278 TooManyRequests => return None,
279 })
280 }
281}
282
283impl fmt::Display for ExecuteError {
284 fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
285 use self::ExecuteError::*;
286 write!(
287 out,
288 "failed to execute method: {}",
289 match self {
290 Http(err) => err.to_string(),
291 Payload(err) => err.to_string(),
292 Response(err) => err.to_string(),
293 TooManyRequests => "too many requests".to_string(),
294 }
295 )
296 }
297}