1use crate::http::error::{HttpError, HttpResult};
5use sms_types::http::{
6 HttpModemBatteryLevelResponse, HttpModemNetworkOperatorResponse,
7 HttpModemNetworkStatusResponse, HttpModemSignalStrengthResponse, HttpPaginationOptions,
8 HttpSmsDeviceInfoData, HttpSmsDeviceInfoResponse, HttpSmsSendResponse,
9 LatestNumberFriendlyNamePair,
10};
11use sms_types::sms::{SmsDeliveryReport, SmsOutgoingMessage};
12
13pub mod error;
14pub mod paginator;
15
16async fn read_http_response<T>(response: reqwest::Response) -> HttpResult<T>
19where
20 T: serde::de::DeserializeOwned,
21{
22 let is_json = response
23 .headers()
24 .get(reqwest::header::CONTENT_TYPE)
25 .and_then(|ct| ct.to_str().ok())
26 .is_some_and(|ct| ct.contains("application/json"));
27
28 if is_json {
29 let json: serde_json::Value = response.json().await?;
31 let success = json
32 .get("success")
33 .and_then(serde_json::Value::as_bool)
34 .unwrap_or(false);
35
36 if !success {
37 let message = json
38 .get("error")
39 .and_then(|v| v.as_str())
40 .unwrap_or("Unknown API error!")
41 .to_string();
42
43 return Err(HttpError::ApiError(message));
44 }
45
46 let response_value = json
48 .get("response")
49 .ok_or(HttpError::MissingResponseField)?;
50
51 return serde_json::from_value(response_value.clone()).map_err(HttpError::JsonError);
52 }
53
54 let status = response.status();
56 if !status.is_success() {
57 let error_text = response
58 .text()
59 .await
60 .unwrap_or_else(|_| "Unknown error!".to_string());
61
62 return Err(HttpError::HttpStatus {
63 status: status.as_u16(),
64 message: error_text,
65 });
66 }
67
68 Err(HttpError::MissingResponseField)
69}
70
71async fn read_modem_response<T>(expected: &str, response: reqwest::Response) -> HttpResult<T>
74where
75 T: serde::de::DeserializeOwned,
76{
77 let json_response: serde_json::Value = read_http_response(response).await?;
79 let actual = json_response
80 .get("type")
81 .and_then(|v| v.as_str())
82 .ok_or(HttpError::MissingTypeField)?;
83
84 if actual != expected {
85 return Err(HttpError::ResponseTypeMismatch {
86 expected: expected.to_string(),
87 actual: actual.to_string(),
88 });
89 }
90
91 let data = json_response
93 .get("data")
94 .ok_or(HttpError::MissingDataField)?;
95
96 serde_json::from_value(data.clone()).map_err(HttpError::JsonError)
97}
98
99fn client_builder(config: Option<&crate::config::TLSConfig>) -> HttpResult<reqwest::ClientBuilder> {
101 let builder = reqwest::Client::builder();
102 let Some(tls_config) = config.as_ref() else {
103 return Ok(builder);
104 };
105
106 #[cfg(not(any(feature = "http-tls-rustls", feature = "http-tls-native")))]
107 {
108 let _ = tls_config; Err(HttpError::TLSError(
110 "TLS configuration provided but no TLS features enabled. Enable either 'http-tls-rustls' or 'http-tls-native' feature".to_string()
111 ))
112 }
113
114 #[cfg(any(feature = "http-tls-rustls", feature = "http-tls-native"))]
115 {
116 let mut builder = builder;
117
118 #[cfg(feature = "http-tls-rustls")]
120 {
121 builder = builder.use_rustls_tls();
122 }
123
124 #[cfg(feature = "http-tls-native")]
125 {
126 builder = builder.use_native_tls();
127 }
128
129 let certificate = load_certificate(&tls_config.certificate)?;
131 Ok(builder.add_root_certificate(certificate))
132 }
133}
134
135#[cfg(any(feature = "http-tls-rustls", feature = "http-tls-native"))]
137fn load_certificate(cert_path: &std::path::Path) -> HttpResult<reqwest::tls::Certificate> {
138 let cert_data = std::fs::read(cert_path).map_err(HttpError::IOError)?;
139
140 if let Some(ext) = cert_path.extension().and_then(|s| s.to_str()) {
142 match ext {
143 "pem" => return Ok(reqwest::tls::Certificate::from_pem(&cert_data)?),
144 "der" => return Ok(reqwest::tls::Certificate::from_der(&cert_data)?),
145 "crt" => {
146 if cert_data.starts_with(b"-----BEGIN") {
147 return Ok(reqwest::tls::Certificate::from_pem(&cert_data)?);
148 } else {
149 return Ok(reqwest::tls::Certificate::from_der(&cert_data)?);
150 }
151 }
152 _ => {} }
154 }
155
156 reqwest::tls::Certificate::from_pem(&cert_data)
158 .or_else(|_| reqwest::tls::Certificate::from_der(&cert_data))
159 .map_err(Into::into)
160}
161
162#[derive(Debug)]
164pub struct HttpClient {
165 base_url: reqwest::Url,
166 authorization: Option<String>,
167 modem_timeout: Option<std::time::Duration>,
168 client: reqwest::Client,
169}
170impl HttpClient {
171 pub fn new(
173 config: crate::config::HttpConfig,
174 tls: Option<&crate::config::TLSConfig>,
175 ) -> HttpResult<Self> {
176 let client = client_builder(tls)?.timeout(config.base_timeout).build()?;
177
178 Ok(Self {
179 base_url: reqwest::Url::parse(config.url.as_str())?,
180 authorization: config.authorization,
181 modem_timeout: config.modem_timeout,
182 client,
183 })
184 }
185
186 pub async fn set_friendly_name(
188 &self,
189 phone_number: impl Into<String>,
190 friendly_name: Option<impl Into<String>>,
191 ) -> HttpResult<bool> {
192 let body = serde_json::json!({
193 "phone_number": phone_number.into(),
194 "friendly_name": friendly_name.map(Into::into)
195 });
196
197 let url = self.base_url.join("/db/friendly-names/set")?;
198 let response = self
199 .setup_request(false, self.client.post(url))
200 .json(&body)
201 .send()
202 .await?;
203
204 read_http_response(response).await
205 }
206
207 pub async fn get_friendly_name(
209 &self,
210 phone_number: impl Into<String>,
211 ) -> HttpResult<Option<String>> {
212 let body = serde_json::json!({
213 "phone_number": phone_number.into()
214 });
215
216 let url = self.base_url.join("/db/friendly-names/get")?;
217 let response = self
218 .setup_request(false, self.client.post(url))
219 .json(&body)
220 .send()
221 .await?;
222
223 read_http_response(response).await
224 }
225
226 pub async fn get_messages(
229 &self,
230 phone_number: impl Into<String>,
231 pagination: Option<HttpPaginationOptions>,
232 ) -> HttpResult<Vec<sms_types::sms::SmsMessage>> {
233 let mut body = serde_json::json!({
234 "phone_number": phone_number.into()
235 });
236 if let Some(pagination) = pagination {
237 pagination.add_to_body(&mut body);
238 }
239
240 let url = self.base_url.join("/db/sms")?;
241 let response = self
242 .setup_request(false, self.client.post(url))
243 .json(&body)
244 .send()
245 .await?;
246
247 read_http_response(response).await
248 }
249
250 pub async fn get_latest_numbers(
253 &self,
254 pagination: Option<HttpPaginationOptions>,
255 ) -> HttpResult<Vec<LatestNumberFriendlyNamePair>> {
256 let url = self.base_url.join("/db/latest-numbers")?;
257 let mut request = self.setup_request(false, self.client.post(url));
258
259 if let Some(pagination) = pagination {
261 request = request.json(&pagination);
262 }
263
264 let response = request.send().await?;
265 read_http_response(response).await
266 }
267
268 pub async fn get_delivery_reports(
271 &self,
272 message_id: i64,
273 pagination: Option<HttpPaginationOptions>,
274 ) -> HttpResult<Vec<SmsDeliveryReport>> {
275 let mut body = serde_json::json!({
276 "message_id": message_id
277 });
278 if let Some(pagination) = pagination {
279 pagination.add_to_body(&mut body);
280 }
281
282 let url = self.base_url.join("/db/delivery-reports")?;
283 let response = self
284 .setup_request(false, self.client.post(url))
285 .json(&body)
286 .send()
287 .await?;
288
289 read_http_response(response).await
290 }
291
292 pub async fn send_sms(&self, message: &SmsOutgoingMessage) -> HttpResult<HttpSmsSendResponse> {
296 let url = self.base_url.join("/sms/send")?;
297
298 let mut request = self.setup_request(true, self.client.post(url));
301 if let Some(timeout) = message.timeout {
302 request = request.timeout(std::time::Duration::from_secs(u64::from(timeout) + 5));
303 }
304
305 let response = request.json(message).send().await?;
306
307 read_http_response(response).await
308 }
309
310 pub async fn get_network_status(&self) -> HttpResult<HttpModemNetworkStatusResponse> {
312 self.modem_request("modem-status", "NetworkStatus").await
313 }
314
315 pub async fn get_signal_strength(&self) -> HttpResult<HttpModemSignalStrengthResponse> {
317 self.modem_request("signal-strength", "SignalStrength")
318 .await
319 }
320
321 pub async fn get_network_operator(&self) -> HttpResult<HttpModemNetworkOperatorResponse> {
324 self.modem_request("network-operator", "NetworkOperator")
325 .await
326 }
327
328 pub async fn get_service_provider(&self) -> HttpResult<String> {
331 self.modem_request("service-provider", "ServiceProvider")
332 .await
333 }
334
335 pub async fn get_battery_level(&self) -> HttpResult<HttpModemBatteryLevelResponse> {
337 self.modem_request("battery-level", "BatteryLevel").await
338 }
339
340 pub async fn get_device_info(&self) -> HttpResult<HttpSmsDeviceInfoData> {
342 let url = self.base_url.join("/sms/device-info")?;
343 let response = self
344 .setup_request(true, self.client.get(url))
345 .send()
346 .await?;
347
348 let response = read_http_response::<HttpSmsDeviceInfoResponse>(response).await?;
349 Ok(HttpSmsDeviceInfoData::from(response))
350 }
351
352 pub async fn get_phone_number(&self) -> HttpResult<Option<String>> {
355 let url = self.base_url.join("/sys/phone-number")?;
356 let response = self
357 .setup_request(false, self.client.get(url))
358 .send()
359 .await?;
360
361 read_http_response(response).await
362 }
363
364 pub async fn get_version(&self) -> HttpResult<String> {
367 let url = self.base_url.join("/sys/version")?;
368 let response = self
369 .setup_request(false, self.client.get(url))
370 .send()
371 .await?;
372
373 read_http_response(response).await
374 }
375
376 async fn modem_request<T>(&self, route: &str, expected: &str) -> HttpResult<T>
378 where
379 T: serde::de::DeserializeOwned,
380 {
381 let url = self.base_url.join(&format!("/sms/{route}"))?;
382 let response = self
383 .setup_request(true, self.client.get(url))
384 .send()
385 .await?;
386
387 read_modem_response::<T>(expected, response).await
388 }
389
390 fn setup_request(
393 &self,
394 is_modem: bool,
395 builder: reqwest::RequestBuilder,
396 ) -> reqwest::RequestBuilder {
397 let builder = if is_modem && let Some(timeout) = &self.modem_timeout {
398 builder.timeout(*timeout)
399 } else {
400 builder
401 };
402 if let Some(auth) = &self.authorization {
403 builder.header("authorization", auth)
404 } else {
405 builder
406 }
407 }
408}