telexide_fork/api/
api_client.rs

1use super::{api::API, endpoints::APIEndpoint, response::Response};
2use crate::utils::{
3    encode_multipart_form_data, result::Result, AsFormData, FormDataFile, BOUNDARY,
4};
5use async_trait::async_trait;
6use hyper::{body::HttpBody, client::HttpConnector, Body, Client, Request};
7use std::io::Write;
8
9static TELEGRAM_API: &str = "https://api.telegram.org/bot";
10
11/// A default implementation of the [`API`] trait.
12///
13/// It requires your bot token in order to interact with the telegram API and
14/// also allows you to configure your own [`hyper::Client`] for it to use.
15///
16/// Using the default `APIClient` is as easy as:
17/// ```no_run
18/// use telexide_fork::api::{APIClient, API, types::SendMessage};
19///
20/// # #[tokio::main]
21/// # async fn main() {
22///     let token = String::from("test token");
23///     # let chat_id = 3;
24///     let message = SendMessage::new(chat_id, "hi!");
25///
26///     let client = APIClient::new_default(&token);
27///     client.send_message(message).await;
28/// # }
29/// ```
30///
31/// In most cases you would want to get updates though and the [`Client`] is
32/// best suited for that, as it allows for easier handling of those updates
33///
34/// [`Client`]: ../client/struct.Client.html
35pub struct APIClient {
36    hyper_client: Client<hyper_tls::HttpsConnector<HttpConnector>>,
37    token: String,
38}
39
40impl APIClient {
41    /// Creates a new `APIClient` with the provided token and hyper client (if
42    /// it is Some).
43    pub fn new<T: ToString>(
44        hyper_client: Option<Client<hyper_tls::HttpsConnector<HttpConnector>>>,
45        token: &T,
46    ) -> Self {
47        hyper_client.map_or_else(
48            || Self {
49                hyper_client: hyper::Client::builder().build(hyper_tls::HttpsConnector::new()),
50                token: token.to_string(),
51            },
52            |c| Self {
53                hyper_client: c,
54                token: token.to_string(),
55            },
56        )
57    }
58
59    /// Creates a new `APIClient` with the provided token and the default hyper
60    /// client.
61    pub fn new_default<T: ToString>(token: &T) -> Self {
62        Self {
63            hyper_client: hyper::Client::builder().build(hyper_tls::HttpsConnector::new()),
64            token: token.to_string(),
65        }
66    }
67
68    fn parse_endpoint(&self, endpoint: &APIEndpoint) -> String {
69        format!("{}{}/{}", TELEGRAM_API, self.token, endpoint)
70    }
71
72    /// Sends a request to the provided `APIEndpoint` with the data provided
73    /// (does not support files)
74    pub async fn request<D>(&self, endpoint: APIEndpoint, data: Option<&D>) -> Result<Response>
75    where
76        D: ?Sized + serde::Serialize,
77    {
78        let data: Option<serde_json::Value> = if let Some(d) = data {
79            Some(serde_json::to_value(d)?)
80        } else {
81            None
82        };
83
84        match endpoint {
85            e if e.as_str().starts_with("get") => self.get(e, data).await,
86            e => self.post(e, data).await,
87        }
88    }
89
90    /// gets a reference to the underlying hyper client, for example so you can
91    /// make custom api requests
92    pub fn get_hyper(&self) -> &Client<hyper_tls::HttpsConnector<HttpConnector>> {
93        &self.hyper_client
94    }
95}
96
97#[async_trait]
98impl API for APIClient {
99    async fn get(
100        &self,
101        endpoint: APIEndpoint,
102        data: Option<serde_json::Value>,
103    ) -> Result<Response> {
104        let req_builder = Request::get(self.parse_endpoint(&endpoint))
105            .header("content-type", "application/json")
106            .header("accept", "application/json");
107
108        let request = if let Some(d) = data {
109            req_builder.body(Body::from(serde_json::to_string(&d)?))?
110        } else {
111            req_builder.body(Body::empty())?
112        };
113
114        log::debug!("GET request to {}", &endpoint);
115        let mut response = self.hyper_client.request(request).await?;
116
117        let mut res: Vec<u8> = Vec::new();
118        while let Some(chunk) = response.body_mut().data().await {
119            res.write_all(&chunk?)?;
120        }
121
122        Ok(serde_json::from_slice(&res)?)
123    }
124
125    async fn post(
126        &self,
127        endpoint: APIEndpoint,
128        data: Option<serde_json::Value>,
129    ) -> Result<Response> {
130        let req_builder = Request::post(self.parse_endpoint(&endpoint))
131            .header("content-type", "application/json")
132            .header("accept", "application/json");
133
134        let request = if let Some(d) = data {
135            req_builder.body(Body::from(serde_json::to_string(&d)?))?
136        } else {
137            req_builder.body(Body::empty())?
138        };
139
140        log::debug!("POST request to {}", &endpoint);
141        let mut response = self.hyper_client.request(request).await?;
142
143        let mut res: Vec<u8> = Vec::new();
144        while let Some(chunk) = response.body_mut().data().await {
145            res.write_all(&chunk?)?;
146        }
147
148        Ok(serde_json::from_slice(&res)?)
149    }
150
151    async fn post_file(
152        &self,
153        endpoint: APIEndpoint,
154        data: Option<serde_json::Value>,
155        files: Option<Vec<FormDataFile>>,
156    ) -> Result<Response> {
157        if files.is_none() {
158            return self.post(endpoint, data).await;
159        }
160
161        let mut files = files.expect("no files");
162        if files.is_empty() {
163            return self.post(endpoint, data).await;
164        }
165
166        let req_builder = Request::post(self.parse_endpoint(&endpoint))
167            .header(
168                "content-type",
169                format!("multipart/form-data; boundary={BOUNDARY}"),
170            )
171            .header("accept", "application/json");
172
173        if data.is_some() {
174            files.append(&mut data.expect("no data").as_form_data()?);
175        }
176
177        let bytes = encode_multipart_form_data(&files)?;
178        let request = req_builder.body(Body::from(bytes))?;
179
180        log::debug!("POST request with files to {}", &endpoint);
181        let mut response = self.hyper_client.request(request).await?;
182
183        let mut res: Vec<u8> = Vec::new();
184        while let Some(chunk) = response.body_mut().data().await {
185            res.write_all(&chunk?)?;
186        }
187
188        Ok(serde_json::from_slice(&res)?)
189    }
190}