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