1#[derive(Clone, Debug)]
2pub struct SmsIrServiceBuilder {
3 api_key: String,
4 base_uri: String,
5 timeout: std::time::Duration,
6 proxy: Option<String>,
7 retry_count: usize,
8 retry_delay: std::time::Duration,
9 log_level: LogLevel,
10}
11
12impl SmsIrServiceBuilder {
13 pub fn new(api_key: impl Into<String>) -> Self {
14 Self {
15 api_key: api_key.into(),
16 base_uri: "https://api.sms.ir/v1/".to_string(),
17 timeout: std::time::Duration::from_secs(30),
18 proxy: None,
19 retry_count: 3,
20 retry_delay: std::time::Duration::from_millis(500),
21 log_level: LogLevel::Info,
22 }
23 }
24 pub fn base_uri(mut self, uri: &str) -> Self {
25 self.base_uri = uri.to_string(); self
26 }
27 pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
28 self.timeout = timeout; self
29 }
30 pub fn proxy(mut self, proxy: &str) -> Self {
31 self.proxy = Some(proxy.to_string()); self
32 }
33 pub fn retry_count(mut self, count: usize) -> Self {
34 self.retry_count = count; self
35 }
36 pub fn retry_delay(mut self, delay: std::time::Duration) -> Self {
37 self.retry_delay = delay; self
38 }
39 pub fn log_level(mut self, level: LogLevel) -> Self {
40 self.log_level = level; self
41 }
42 pub fn build(self) -> Result<SmsIrService, SmsIrError> {
43 let mut client_builder = reqwest::Client::builder();
44 if let Some(proxy_url) = &self.proxy {
45 client_builder = client_builder.proxy(reqwest::Proxy::all(proxy_url).expect("Invalid proxy"));
46 }
47 let client = client_builder.build().expect("Failed to build client");
48 Ok(SmsIrService {
49 api_key: self.api_key,
50 base_uri: self.base_uri,
51 client,
52 timeout: self.timeout,
53 retry_count: self.retry_count,
54 retry_delay: self.retry_delay,
55 log_level: self.log_level,
56 })
57 }
58}
59use crate::result::SmsIrResult;
60use crate::error::SmsIrError;
61use tracing::{info, error};
62use reqwest::{Client, Method};
63use serde_json::Value;
64
65
66#[derive(Clone)]
67pub struct SmsIrService {
68 api_key: String,
69 base_uri: String,
70 client: Client,
71 timeout: std::time::Duration,
72 retry_count: usize,
73 retry_delay: std::time::Duration,
74 log_level: LogLevel,
75}
76
77#[derive(Clone, Copy, Debug)]
78pub enum LogLevel {
79 Error,
80 Info,
81 Debug,
82 Off,
83}
84
85#[async_trait::async_trait]
86pub trait SmsService {
87 async fn get_credit(&self) -> Result<SmsIrResult, SmsIrError>;
88 async fn get_lines(&self) -> Result<SmsIrResult, SmsIrError>;
89 async fn get_latest_receives(&self, count: u32) -> Result<SmsIrResult, SmsIrError>;
90 async fn get_live_receives(&self, page_number: u32, page_size: u32, sort_by_newest: bool) -> Result<SmsIrResult, SmsIrError>;
91 async fn get_archived_receives(&self, page_number: u32, page_size: u32, from_date: Option<u64>, to_date: Option<u64>) -> Result<SmsIrResult, SmsIrError>;
92 async fn bulk_send(&self, line_number: &str, message_text: &str, mobiles: &[String], send_date_time: Option<u64>) -> Result<SmsIrResult, SmsIrError>;
93 async fn like_to_like_send(&self, line_number: &str, message_texts: &[String], mobiles: &[String], send_date_time: Option<u64>) -> Result<SmsIrResult, SmsIrError>;
94 async fn verify_send(&self, mobile: &str, template_id: u32, parameters: &[serde_json::Value]) -> Result<SmsIrResult, SmsIrError>;
95 async fn remove_scheduled_messages(&self, pack_id: &str) -> Result<SmsIrResult, SmsIrError>;
96 async fn get_report_by_message_id(&self, message_id: &str) -> Result<SmsIrResult, SmsIrError>;
97 async fn get_report_by_pack_id(&self, pack_id: &str) -> Result<SmsIrResult, SmsIrError>;
98 async fn get_live_report(&self, page_number: u32, page_size: u32, sort_by_newest: bool) -> Result<SmsIrResult, SmsIrError>;
99 async fn get_archived_report(&self, page_number: u32, page_size: u32, from_date: Option<u64>, to_date: Option<u64>, sort_by_newest: bool) -> Result<SmsIrResult, SmsIrError>;
100 async fn get_send_packs(&self, page_number: u32, page_size: u32) -> Result<SmsIrResult, SmsIrError>;
101}
102
103impl SmsIrService {
104 #[deprecated(note = "Use SmsIrServiceBuilder for easier configuration")]
117 pub fn new(
118 api_key: impl Into<String>,
119 base_uri: Option<&str>,
120 timeout: Option<std::time::Duration>,
121 proxy: Option<&str>,
122 retry_count: Option<usize>,
123 retry_delay: Option<std::time::Duration>,
124 log_level: Option<LogLevel>,
125 ) -> Result<Self, SmsIrError> {
126 let api_key_str = api_key.into();
127 if api_key_str.trim().is_empty() {
128 return Err(SmsIrError::InvalidApiKey("API key is empty".to_string()));
129 }
130 let mut client_builder = reqwest::Client::builder();
131 if let Some(proxy_url) = proxy {
132 client_builder = client_builder.proxy(reqwest::Proxy::all(proxy_url).expect("Invalid proxy"));
133 }
134 let client = client_builder.build().expect("Failed to build client");
135 Ok(Self {
136 api_key: api_key_str,
137 base_uri: base_uri.unwrap_or("https://api.sms.ir/v1/").to_string(),
138 client,
139 timeout: timeout.unwrap_or(std::time::Duration::from_secs(30)),
140 retry_count: retry_count.unwrap_or(3),
141 retry_delay: retry_delay.unwrap_or(std::time::Duration::from_millis(500)),
142 log_level: log_level.unwrap_or(LogLevel::Info),
143 })
144 }
145
146 pub fn with_client(
147 api_key: impl Into<String>,
148 base_uri: Option<&str>,
149 client: reqwest::Client,
150 timeout: std::time::Duration,
151 retry_count: Option<usize>,
152 retry_delay: Option<std::time::Duration>,
153 log_level: Option<LogLevel>,
154 ) -> Result<Self, SmsIrError> {
155 let api_key_str = api_key.into();
156 if api_key_str.trim().is_empty() {
157 return Err(SmsIrError::InvalidApiKey("API key is empty".to_string()));
158 }
159 Ok(Self {
160 api_key: api_key_str,
161 base_uri: base_uri.unwrap_or("https://api.sms.ir/v1/").to_string(),
162 client,
163 timeout,
164 retry_count: retry_count.unwrap_or(2),
165 retry_delay: retry_delay.unwrap_or(std::time::Duration::from_millis(500)),
166 log_level: log_level.unwrap_or(LogLevel::Info),
167 })
168 }
169
170 async fn send_request(&self, method: Method, uri: &str, query: Option<&[(&str, Value)]>, json: Option<&Value>) -> Result<SmsIrResult, SmsIrError> {
171 let url = format!("{}{}", self.base_uri, uri);
172 let mut last_err = None;
173 for attempt in 0..=self.retry_count {
174 self.log(format!("Sending request: {} {} (attempt {})", method, url, attempt), LogLevel::Info);
175 let mut req = self.client.request(method.clone(), &url)
176 .header("X-API-KEY", &self.api_key)
177 .header("Accept", "application/json");
178 if let Some(q) = query {
179 req = req.query(&q);
180 }
181 if let Some(j) = json {
182 req = req.json(j);
183 }
184 let resp = match tokio::time::timeout(self.timeout, req.send()).await {
185 Ok(Ok(r)) => r,
186 Ok(Err(e)) => {
187 self.log(format!("HTTP error: {}", e), LogLevel::Error);
188 last_err = Some(SmsIrError::Http(e.to_string()));
189 continue;
190 },
191 Err(_) => {
192 self.log("Request timed out".to_string(), LogLevel::Error);
193 last_err = Some(SmsIrError::Timeout);
194 continue;
195 },
196 };
197 let http_status = resp.status().as_u16();
198 let body: Value = match tokio::time::timeout(self.timeout, resp.json()).await {
199 Ok(Ok(j)) => j,
200 Ok(Err(e)) => {
201 self.log(format!("JSON error: {}", e), LogLevel::Error);
202 last_err = Some(SmsIrError::Json(e.to_string()));
203 continue;
204 },
205 Err(_) => {
206 self.log("Response JSON timed out".to_string(), LogLevel::Error);
207 last_err = Some(SmsIrError::Timeout);
208 continue;
209 },
210 };
211
212 let status_code = body.get("status")
213 .and_then(|v| if v.is_i64() { v.as_i64().map(|i| i as i32) } else { v.as_str().and_then(|s| s.parse::<i32>().ok()) })
214 .unwrap_or(-9999);
215 let status_enum = crate::result::SendStatus::from_i32(status_code);
216 let message = body.get("message").and_then(|v| v.as_str()).unwrap_or("").to_string();
217 let data = body.get("data").cloned();
218 match status_enum {
219 crate::result::SendStatus::Success => {
220 return Ok(SmsIrResult::new(status_code, message, data));
221 },
222 crate::result::SendStatus::SystemError | crate::result::SendStatus::TooManyRequests => {
224 self.log(format!("API status error: {:?} - {} (HTTP {}) - retrying", status_enum, message, http_status), LogLevel::Error);
225 last_err = Some(SmsIrError::StatusError(status_enum, message.clone()));
226 tokio::time::sleep(self.retry_delay).await;
227 continue;
228 },
229 crate::result::SendStatus::InvalidApiKey | crate::result::SendStatus::ApiKeyDisabled | crate::result::SendStatus::ApiKeyIpRestricted => {
231 self.log(format!("API key error: {:?} - {} (HTTP {})", status_enum, message, http_status), LogLevel::Error);
232 return Err(SmsIrError::StatusError(status_enum, message));
233 },
234 crate::result::SendStatus::UserDisabled | crate::result::SendStatus::UserSuspended => {
236 self.log(format!("User/account error: {:?} - {} (HTTP {})", status_enum, message, http_status), LogLevel::Error);
237 return Err(SmsIrError::StatusError(status_enum, message));
238 },
239 _ => {
241 self.log(format!("SmsIr status error: {:?} - {} (HTTP {})", status_enum, message, http_status), LogLevel::Error);
242 return Err(SmsIrError::StatusError(status_enum, message));
243 }
244 }
245 }
246 Err(last_err.unwrap_or(SmsIrError::Unexpected("Unknown error after retries".to_string(), 0)))
247 }
248
249 fn log(&self, msg: String, level: LogLevel) {
250 match self.log_level {
251 LogLevel::Off => {},
252 LogLevel::Error => if matches!(level, LogLevel::Error) { error!("{}", msg); },
253 LogLevel::Info => if matches!(level, LogLevel::Error | LogLevel::Info) { info!("{}", msg); },
254 LogLevel::Debug => info!("{}", msg),
255 }
256 }
257
258 pub async fn get_credit(&self) -> Result<SmsIrResult, SmsIrError> {
259 self.send_request(Method::GET, "credit", None, None).await
260 }
261
262 pub async fn get_lines(&self) -> Result<SmsIrResult, SmsIrError> {
263 self.send_request(Method::GET, "line", None, None).await
264 }
265
266 #[cfg(feature = "receive")]
267 pub async fn get_latest_receives(&self, count: u32) -> Result<SmsIrResult, SmsIrError> {
268 let query = vec![("count", Value::from(count))];
269 self.send_request(Method::GET, "receive/latest", Some(&query), None).await
270 }
271
272 #[cfg(feature = "receive")]
273 pub async fn get_live_receives(&self, page_number: u32, page_size: u32, sort_by_newest: bool) -> Result<SmsIrResult, SmsIrError> {
274 let query = vec![
275 ("pageNumber", Value::from(page_number)),
276 ("pageSize", Value::from(page_size)),
277 ("sortByNewest", Value::from(sort_by_newest)),
278 ];
279 self.send_request(Method::GET, "receive/live", Some(&query), None).await
280 }
281
282 #[cfg(feature = "receive")]
283 pub async fn get_archived_receives(&self, page_number: u32, page_size: u32, from_date: Option<u64>, to_date: Option<u64>) -> Result<SmsIrResult, SmsIrError> {
284 let mut query = vec![
285 ("pageNumber", Value::from(page_number)),
286 ("pageSize", Value::from(page_size)),
287 ];
288 if let Some(fd) = from_date {
289 query.push(("fromDate", Value::from(fd)));
290 }
291 if let Some(td) = to_date {
292 query.push(("toDate", Value::from(td)));
293 }
294 self.send_request(Method::GET, "receive/archive", Some(&query), None).await
295 }
296
297 #[cfg(feature = "bulk_send")]
298 pub async fn bulk_send(&self, line_number: &str, message_text: &str, mobiles: &[String], send_date_time: Option<u64>) -> Result<SmsIrResult, SmsIrError> {
299 let mut payload = serde_json::json!({
300 "lineNumber": line_number,
301 "messageText": message_text,
302 "mobiles": mobiles,
303 });
304 if let Some(dt) = send_date_time {
305 payload["sendDateTime"] = Value::from(dt);
306 }
307 self.send_request(Method::POST, "send/bulk", None, Some(&payload)).await
308 }
309
310 #[cfg(feature = "bulk_send")]
311 pub async fn like_to_like_send(&self, line_number: &str, message_texts: &[String], mobiles: &[String], send_date_time: Option<u64>) -> Result<SmsIrResult, SmsIrError> {
312 let mut payload = serde_json::json!({
313 "lineNumber": line_number,
314 "messageTexts": message_texts,
315 "mobiles": mobiles,
316 });
317 if let Some(dt) = send_date_time {
318 payload["sendDateTime"] = Value::from(dt);
319 }
320 self.send_request(Method::POST, "send/likeTolike", None, Some(&payload)).await
321 }
322
323 #[cfg(feature = "verify_send")]
324 pub async fn verify_send(&self, mobile: &str, template_id: u32, parameters: &[serde_json::Value]) -> Result<SmsIrResult, SmsIrError> {
325 let mut params = Vec::new();
326 for param in parameters {
327 if let Some(obj) = param.as_object() {
328 for (k, v) in obj {
329 params.push(serde_json::json!({
330 "name": k,
331 "value": v,
332 }));
333 }
334 }
335 }
336 let payload = serde_json::json!({
337 "mobile": mobile,
338 "templateId": template_id,
339 "parameters": params,
340 });
341 self.send_request(Method::POST, "send/verify", None, Some(&payload)).await
342 }
343
344 #[cfg(feature = "bulk_send")]
345 pub async fn remove_scheduled_messages(&self, pack_id: &str) -> Result<SmsIrResult, SmsIrError> {
346 let uri = format!("send/scheduled/{}", pack_id);
347 self.send_request(Method::DELETE, &uri, None, None).await
348 }
349
350 #[cfg(feature = "report")]
351 pub async fn get_report_by_message_id(&self, message_id: &str) -> Result<SmsIrResult, SmsIrError> {
352 let uri = format!("send/{}", message_id);
353 self.send_request(Method::GET, &uri, None, None).await
354 }
355
356 #[cfg(feature = "report")]
357 pub async fn get_report_by_pack_id(&self, pack_id: &str) -> Result<SmsIrResult, SmsIrError> {
358 let uri = format!("send/pack/{}", pack_id);
359 self.send_request(Method::GET, &uri, None, None).await
360 }
361
362 #[cfg(feature = "report")]
363 pub async fn get_live_report(&self, page_number: u32, page_size: u32, sort_by_newest: bool) -> Result<SmsIrResult, SmsIrError> {
364 let query = vec![
365 ("pageNumber", Value::from(page_number)),
366 ("pageSize", Value::from(page_size)),
367 ("sortByNewest", Value::from(sort_by_newest)),
368 ];
369 self.send_request(Method::GET, "send/live", Some(&query), None).await
370 }
371
372 #[cfg(feature = "report")]
373 pub async fn get_archived_report(&self, page_number: u32, page_size: u32, from_date: Option<u64>, to_date: Option<u64>, sort_by_newest: bool) -> Result<SmsIrResult, SmsIrError> {
374 let mut query = vec![
375 ("pageNumber", Value::from(page_number)),
376 ("pageSize", Value::from(page_size)),
377 ("sortByNewest", Value::from(sort_by_newest)),
378 ];
379 if let Some(fd) = from_date {
380 query.push(("fromDate", Value::from(fd)));
381 }
382 if let Some(td) = to_date {
383 query.push(("toDate", Value::from(td)));
384 }
385 self.send_request(Method::GET, "send/archive", Some(&query), None).await
386 }
387
388 #[cfg(feature = "report")]
389 pub async fn get_send_packs(&self, page_number: u32, page_size: u32) -> Result<SmsIrResult, SmsIrError> {
390 let query = vec![
391 ("pageNumber", Value::from(page_number)),
392 ("pageSize", Value::from(page_size)),
393 ];
394 self.send_request(Method::GET, "send/pack", Some(&query), None).await
395 }
396}