telexide/api/
api_client.rs1use super::{api::API, endpoints::APIEndpoint, response::Response};
2use crate::utils::{
3 encode_multipart_form_data,
4 result::Result,
5 AsFormData,
6 FormDataFile,
7 BOUNDARY,
8};
9use async_trait::async_trait;
10use hyper::{body::HttpBody, client::HttpConnector, Body, Client, Request};
11use std::io::Write;
12
13static TELEGRAM_API: &str = "https://api.telegram.org/bot";
14
15#[cfg(feature = "native-tls")]
16pub type TlsClient = Client<hyper_tls::HttpsConnector<HttpConnector>>;
17#[cfg(all(feature = "rustls", not(feature = "native-tls")))]
18pub type TlsClient = Client<hyper_rustls::HttpsConnector<HttpConnector>>;
19
20pub struct APIClient {
45 hyper_client: TlsClient,
46 token: String,
47}
48
49impl APIClient {
50 #[allow(clippy::needless_pass_by_value)]
53 pub fn new(hyper_client: Option<TlsClient>, token: impl ToString) -> Self {
54 hyper_client.map_or_else(
55 || Self {
56 hyper_client: Self::make_default_client(),
57 token: token.to_string(),
58 },
59 |c| Self {
60 hyper_client: c,
61 token: token.to_string(),
62 },
63 )
64 }
65
66 #[cfg(feature = "native-tls")]
67 fn make_default_client() -> TlsClient {
68 hyper::Client::builder().build(hyper_tls::HttpsConnector::new())
69 }
70
71 #[cfg(all(feature = "rustls", not(feature = "native-tls")))]
72 fn make_default_client() -> TlsClient {
73 hyper::Client::builder().build(
74 hyper_rustls::HttpsConnectorBuilder::new()
75 .with_native_roots()
76 .https_or_http()
77 .enable_http1()
78 .build(),
79 )
80 }
81
82 #[allow(clippy::needless_pass_by_value)]
85 pub fn new_default(token: impl ToString) -> Self {
86 Self::new(None, token)
87 }
88
89 fn parse_endpoint(&self, endpoint: &APIEndpoint) -> String {
90 format!("{}{}/{}", TELEGRAM_API, self.token, endpoint)
91 }
92
93 pub async fn request<D>(&self, endpoint: APIEndpoint, data: Option<&D>) -> Result<Response>
96 where
97 D: ?Sized + serde::Serialize,
98 {
99 let data: Option<serde_json::Value> = if let Some(d) = data {
100 Some(serde_json::to_value(d)?)
101 } else {
102 None
103 };
104
105 match endpoint {
106 e if e.as_str().starts_with("get") => self.get(e, data).await,
107 e => self.post(e, data).await,
108 }
109 }
110
111 pub fn get_hyper(&self) -> &TlsClient {
114 &self.hyper_client
115 }
116}
117
118#[async_trait]
119impl API for APIClient {
120 async fn get(
121 &self,
122 endpoint: APIEndpoint,
123 data: Option<serde_json::Value>,
124 ) -> Result<Response> {
125 let req_builder = Request::get(self.parse_endpoint(&endpoint))
126 .header("content-type", "application/json")
127 .header("accept", "application/json");
128
129 let request = if let Some(d) = data {
130 req_builder.body(Body::from(serde_json::to_string(&d)?))?
131 } else {
132 req_builder.body(Body::empty())?
133 };
134
135 log::debug!("GET request to {}", &endpoint);
136 let mut response = self.hyper_client.request(request).await?;
137
138 let mut res: Vec<u8> = Vec::new();
139 while let Some(chunk) = response.body_mut().data().await {
140 res.write_all(&chunk?)?;
141 }
142
143 Ok(serde_json::from_slice(&res)?)
144 }
145
146 async fn post(
147 &self,
148 endpoint: APIEndpoint,
149 data: Option<serde_json::Value>,
150 ) -> Result<Response> {
151 let req_builder = Request::post(self.parse_endpoint(&endpoint))
152 .header("content-type", "application/json")
153 .header("accept", "application/json");
154
155 let request = if let Some(d) = data {
156 req_builder.body(Body::from(serde_json::to_string(&d)?))?
157 } else {
158 req_builder.body(Body::empty())?
159 };
160
161 log::debug!("POST request to {}", &endpoint);
162 let mut response = self.hyper_client.request(request).await?;
163
164 let mut res: Vec<u8> = Vec::new();
165 while let Some(chunk) = response.body_mut().data().await {
166 res.write_all(&chunk?)?;
167 }
168
169 Ok(serde_json::from_slice(&res)?)
170 }
171
172 async fn post_file(
173 &self,
174 endpoint: APIEndpoint,
175 data: Option<serde_json::Value>,
176 files: Option<Vec<FormDataFile>>,
177 ) -> Result<Response> {
178 if files.is_none() {
179 return self.post(endpoint, data).await;
180 }
181
182 let mut files = files.expect("no files");
183 if files.is_empty() {
184 return self.post(endpoint, data).await;
185 }
186
187 let req_builder = Request::post(self.parse_endpoint(&endpoint))
188 .header(
189 "content-type",
190 format!("multipart/form-data; boundary={BOUNDARY}"),
191 )
192 .header("accept", "application/json");
193
194 if data.is_some() {
195 files.append(&mut data.expect("no data").as_form_data()?);
196 }
197
198 let bytes = encode_multipart_form_data(&files)?;
199 let request = req_builder.body(Body::from(bytes))?;
200
201 log::debug!("POST request with files to {}", &endpoint);
202 let mut response = self.hyper_client.request(request).await?;
203
204 let mut res: Vec<u8> = Vec::new();
205 while let Some(chunk) = response.body_mut().data().await {
206 res.write_all(&chunk?)?;
207 }
208
209 Ok(serde_json::from_slice(&res)?)
210 }
211}