1use crate::http::types::*;
5use crate::http::error::*;
6
7pub mod types;
8pub mod error;
9pub mod paginator;
10
11async fn read_http_response<T>(response: reqwest::Response) -> HttpResult<T>
14where
15 T: serde::de::DeserializeOwned
16{
17 let status = response.status();
19 if !status.is_success() {
20 let error_text = response.text().await.unwrap_or_else(|_| "Unknown error!".to_string());
21 return Err(HttpError::HttpStatus {
22 status: status.as_u16(),
23 message: error_text
24 })
25 }
26
27 let json: serde_json::Value = response.json().await?;
29 let success = json.get("success")
30 .and_then(|v| v.as_bool())
31 .unwrap_or(false);
32
33 if !success {
34 let message = json.get("error_message")
35 .and_then(|v| v.as_str())
36 .unwrap_or("Unknown API error!")
37 .to_string();
38 return Err(HttpError::ApiError { message })
39 }
40
41 let response_value = json.get("response")
43 .ok_or(HttpError::MissingResponseField)?;
44
45 serde_json::from_value(response_value.clone())
46 .map_err(HttpError::JsonError)
47}
48
49async fn read_modem_response<T>(expected: &str, response: reqwest::Response) -> HttpResult<T>
52where
53 T: serde::de::DeserializeOwned
54{
55
56 let json_response: serde_json::Value = read_http_response(response).await?;
58 let actual = json_response.get("type")
59 .and_then(|v| v.as_str())
60 .ok_or(HttpError::MissingTypeField)?;
61
62 if actual != expected {
63 return Err(HttpError::ResponseTypeMismatch {
64 expected: expected.to_string(),
65 actual: actual.to_string()
66 });
67 }
68
69 let data = json_response.get("data")
71 .ok_or(HttpError::MissingDataField)?;
72
73 serde_json::from_value(data.clone())
74 .map_err(HttpError::JsonError)
75}
76
77fn client_builder(config: &Option<crate::config::TLSConfig>) -> HttpResult<reqwest::ClientBuilder> {
79 let builder = reqwest::Client::builder();
80 let Some(tls_config) = config.as_ref() else {
81 return Ok(builder);
82 };
83
84 #[cfg(not(any(feature = "http-tls-rustls", feature = "http-tls-native")))]
85 {
86 let _ = tls_config; return Err(HttpError::TLSError(
88 "TLS configuration provided but no TLS features enabled. Enable either 'http-tls-rustls' or 'http-tls-native' feature".to_string()
89 ));
90 }
91
92 #[cfg(any(feature = "http-tls-rustls", feature = "http-tls-native"))]
93 {
94 let mut builder = builder;
95
96 #[cfg(feature = "http-tls-rustls")]
98 { builder = builder.use_rustls_tls(); }
99
100 #[cfg(feature = "http-tls-native")]
101 { builder = builder.use_native_tls(); }
102
103 let certificate = load_certificate(&tls_config.certificate)?;
105 builder = builder.add_root_certificate(certificate);
106
107 return Ok(builder);
108 }
109}
110
111#[cfg(any(feature = "http-tls-rustls", feature = "http-tls-native"))]
113fn load_certificate(cert_path: &std::path::Path) -> HttpResult<reqwest::tls::Certificate> {
114 let cert_data = std::fs::read(cert_path).map_err(HttpError::IOError)?;
115
116 if let Some(ext) = cert_path.extension().and_then(|s| s.to_str()) {
118 match ext {
119 "pem" => return Ok(reqwest::tls::Certificate::from_pem(&cert_data)?),
120 "der" => return Ok(reqwest::tls::Certificate::from_der(&cert_data)?),
121 "crt" => {
122 if cert_data.starts_with(b"-----BEGIN") {
123 return Ok(reqwest::tls::Certificate::from_pem(&cert_data)?);
124 } else {
125 return Ok(reqwest::tls::Certificate::from_der(&cert_data)?);
126 }
127 }
128 _ => {} }
130 }
131
132 reqwest::tls::Certificate::from_pem(&cert_data)
134 .or_else(|_| reqwest::tls::Certificate::from_der(&cert_data))
135 .map_err(Into::into)
136}
137
138#[derive(Debug)]
140pub struct HttpClient {
141 base_url: reqwest::Url,
142 authorization: Option<String>,
143 modem_timeout: Option<std::time::Duration>,
144 client: reqwest::Client
145}
146impl HttpClient {
147
148 pub fn new(config: crate::config::HttpConfig, tls: &Option<crate::config::TLSConfig>) -> HttpResult<Self> {
150 let client = client_builder(tls)?
151 .timeout(config.base_timeout)
152 .build()?;
153
154 Ok(Self {
155 base_url: reqwest::Url::parse(config.url.as_str())?,
156 authorization: config.authorization.map(|a| a.into()),
157 modem_timeout: config.modem_timeout,
158 client
159 })
160 }
161
162 pub async fn set_friendly_name(&self, phone_number: impl Into<String>, friendly_name: Option<impl Into<String>>) -> HttpResult<bool> {
164 let body = serde_json::json!({
165 "phone_number": phone_number.into(),
166 "friendly_name": friendly_name.map(Into::into)
167 });
168
169 let url = self.base_url.join("/db/friendly-names/set")?;
170 let response = self.setup_request(false, self.client.post(url))
171 .json(&body)
172 .send()
173 .await?;
174
175 read_http_response(response).await
176 }
177
178 pub async fn get_friendly_name(&self, phone_number: impl Into<String>) -> HttpResult<Option<String>> {
180 let body = serde_json::json!({
181 "phone_number": phone_number.into()
182 });
183
184 let url = self.base_url.join("/db/friendly-names/get")?;
185 let response = self.setup_request(false, self.client.post(url))
186 .json(&body)
187 .send()
188 .await?;
189
190 read_http_response(response).await
191 }
192
193 pub async fn get_messages(&self, phone_number: impl Into<String>, pagination: Option<HttpPaginationOptions>) -> HttpResult<Vec<crate::types::SmsStoredMessage>> {
196 let mut body = serde_json::json!({
197 "phone_number": phone_number.into()
198 });
199 if let Some(pagination) = pagination {
200 pagination.add_to_body(&mut body);
201 }
202
203 let url = self.base_url.join("/db/sms")?;
204 let response = self.setup_request(false, self.client.post(url))
205 .json(&body)
206 .send()
207 .await?;
208
209 read_http_response(response).await
210 }
211
212 pub async fn get_latest_numbers(&self, pagination: Option<HttpPaginationOptions>) -> HttpResult<Vec<LatestNumberFriendlyNamePair>> {
215 let url = self.base_url.join("/db/latest-numbers")?;
216 let mut request = self.setup_request(false, self.client.post(url));
217
218 if let Some(pagination) = pagination {
220 request = request.json(&pagination);
221 }
222
223 let response = request.send().await?;
224 read_http_response(response).await
225 }
226
227 pub async fn get_delivery_reports(&self, message_id: i64, pagination: Option<HttpPaginationOptions>) -> HttpResult<Vec<HttpSmsDeliveryReport>> {
230 let mut body = serde_json::json!({
231 "message_id": message_id
232 });
233 if let Some(pagination) = pagination {
234 pagination.add_to_body(&mut body);
235 }
236
237 let url = self.base_url.join("/db/delivery-reports")?;
238 let response = self.setup_request(false, self.client.post(url))
239 .json(&body)
240 .send()
241 .await?;
242
243 read_http_response(response).await
244 }
245
246 pub async fn send_sms(&self, message: &HttpOutgoingSmsMessage) -> HttpResult<HttpSmsSendResponse> {
250 let url = self.base_url.join("/sms/send")?;
251
252 let mut request = self.setup_request(true, self.client.post(url));
255 if let Some(timeout) = message.timeout {
256 request = request.timeout(std::time::Duration::from_secs(timeout as u64 + 5));
257 }
258
259 let response = request.json(message)
260 .send()
261 .await?;
262
263 read_http_response(response).await
264 }
265
266 pub async fn get_network_status(&self) -> HttpResult<HttpModemNetworkStatusResponse> {
268 self.modem_request("modem-status", "NetworkStatus").await
269 }
270
271 pub async fn get_signal_strength(&self) -> HttpResult<HttpModemSignalStrengthResponse> {
273 self.modem_request("signal-strength", "SignalStrength").await
274 }
275
276 pub async fn get_network_operator(&self) -> HttpResult<HttpModemNetworkOperatorResponse> {
279 self.modem_request("network-operator", "NetworkOperator").await
280 }
281
282 pub async fn get_service_provider(&self) -> HttpResult<String> {
285 self.modem_request("service-provider", "ServiceProvider").await
286 }
287
288 pub async fn get_battery_level(&self) -> HttpResult<HttpModemBatteryLevelResponse> {
290 self.modem_request("battery-level", "BatteryLevel").await
291 }
292
293 pub async fn get_device_info(&self) -> HttpResult<HttpSmsDeviceInfoData> {
295 let url = self.base_url.join("/sms/device-info")?;
296 let response = self.setup_request(true, self.client.get(url))
297 .send()
298 .await?;
299
300 let response = read_http_response::<HttpSmsDeviceInfoResponse>(response).await?;
301 Ok(HttpSmsDeviceInfoData::from(response))
302 }
303
304 pub async fn get_phone_number(&self) -> HttpResult<Option<String>> {
307 let url = self.base_url.join("/sys/phone-number")?;
308 let response = self.setup_request(false, self.client.get(url))
309 .send()
310 .await?;
311
312 read_http_response(response).await
313 }
314
315 pub async fn get_version(&self) -> HttpResult<String> {
318 let url = self.base_url.join("/sys/version")?;
319 let response = self.setup_request(false, self.client.get(url))
320 .send()
321 .await?;
322
323 read_http_response(response).await
324 }
325
326 async fn modem_request<T>(&self, route: &str, expected: &str) -> HttpResult<T>
328 where
329 T: serde::de::DeserializeOwned
330 {
331 let url = self.base_url.join(&format!("/sms/{route}"))?;
332 let response = self.setup_request(true, self.client.get(url))
333 .send()
334 .await?;
335
336 read_modem_response::<T>(expected, response).await
337 }
338
339 fn setup_request(&self, is_modem: bool, builder: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
342 let builder = if is_modem && let Some(timeout) = &self.modem_timeout {
343 builder.timeout(*timeout)
344 } else {
345 builder
346 };
347 if let Some(auth) = &self.authorization {
348 builder.header("authorization", auth)
349 } else {
350 builder
351 }
352 }
353}