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