Skip to main content

uni_sdk/
models.rs

1use std::collections::BTreeMap;
2
3use reqwest::{header::HeaderMap, StatusCode};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7/// A successful Unimatrix API response.
8#[derive(Clone, Debug)]
9pub struct UniResponse<T> {
10    /// HTTP status returned by the API.
11    pub status: StatusCode,
12    /// Unimatrix business response code. Successful responses use `"0"`.
13    pub code: String,
14    /// Human-readable response message.
15    pub message: String,
16    /// Typed response data, when supplied by the API.
17    pub data: Option<T>,
18    /// Request ID used for support and diagnostics.
19    pub request_id: Option<String>,
20    /// HTTP response headers.
21    pub headers: HeaderMap,
22    /// Complete parsed JSON response body.
23    pub raw_body: Value,
24}
25
26impl<T> UniResponse<T> {
27    /// Returns the response data or an error when the successful response did
28    /// not contain a `data` field.
29    pub fn into_data(self) -> crate::Result<T> {
30        self.data.ok_or_else(|| crate::UniError::InvalidResponse {
31            status: self.status,
32            request_id: self.request_id,
33            message: "response did not contain data".to_owned(),
34            body: self.raw_body.to_string(),
35        })
36    }
37}
38
39/// One phone number or a list of phone numbers.
40#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
41#[serde(untagged)]
42pub enum Recipients {
43    /// A single E.164 phone number.
44    One(String),
45    /// Multiple E.164 phone numbers.
46    Many(Vec<String>),
47}
48
49impl From<String> for Recipients {
50    fn from(value: String) -> Self {
51        Self::One(value)
52    }
53}
54
55impl From<&str> for Recipients {
56    fn from(value: &str) -> Self {
57        Self::One(value.to_owned())
58    }
59}
60
61impl From<Vec<String>> for Recipients {
62    fn from(value: Vec<String>) -> Self {
63        Self::Many(value)
64    }
65}
66
67impl<const N: usize> From<[&str; N]> for Recipients {
68    fn from(value: [&str; N]) -> Self {
69        Self::Many(value.into_iter().map(str::to_owned).collect())
70    }
71}
72
73/// Parameters accepted by `sms.message.send`.
74#[derive(Clone, Debug, Serialize)]
75#[serde(rename_all = "camelCase")]
76pub struct SendMessageRequest {
77    /// Destination phone number or phone numbers in E.164 format.
78    pub to: Recipients,
79    /// Message signature placed before or after content, depending on locale.
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub signature: Option<String>,
82    /// Unimatrix message template ID.
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub template_id: Option<String>,
85    /// Values substituted into the selected template.
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub template_data: Option<BTreeMap<String, Value>>,
88    /// Message content without a signature.
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub content: Option<String>,
91    /// Complete message text, including any required signature.
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub text: Option<String>,
94}
95
96impl SendMessageRequest {
97    /// Creates a request whose text already includes the message signature.
98    pub fn text(to: impl Into<Recipients>, text: impl Into<String>) -> Self {
99        Self {
100            to: to.into(),
101            signature: None,
102            template_id: None,
103            template_data: None,
104            content: None,
105            text: Some(text.into()),
106        }
107    }
108
109    /// Creates a request using a message template.
110    pub fn template(
111        to: impl Into<Recipients>,
112        signature: impl Into<String>,
113        template_id: impl Into<String>,
114    ) -> Self {
115        Self {
116            to: to.into(),
117            signature: Some(signature.into()),
118            template_id: Some(template_id.into()),
119            template_data: None,
120            content: None,
121            text: None,
122        }
123    }
124
125    /// Creates a request using raw content and a separate signature.
126    pub fn content(
127        to: impl Into<Recipients>,
128        signature: impl Into<String>,
129        content: impl Into<String>,
130    ) -> Self {
131        Self {
132            to: to.into(),
133            signature: Some(signature.into()),
134            template_id: None,
135            template_data: None,
136            content: Some(content.into()),
137            text: None,
138        }
139    }
140
141    /// Adds data used by a message template.
142    pub fn with_template_data(mut self, data: BTreeMap<String, Value>) -> Self {
143        self.template_data = Some(data);
144        self
145    }
146}
147
148/// Per-recipient message details returned by the API.
149#[derive(Clone, Debug, Deserialize, Serialize)]
150#[serde(rename_all = "camelCase")]
151pub struct UniMessage {
152    /// Unimatrix message ID.
153    pub id: String,
154    /// Recipient phone number.
155    pub to: String,
156    /// Recipient country ISO code.
157    pub iso: String,
158    /// Recipient calling code.
159    pub cc: String,
160    /// Number of billable message segments.
161    pub count: u32,
162    /// Price charged for the message.
163    pub price: String,
164}
165
166/// Data returned after sending one or more messages.
167#[derive(Clone, Debug, Deserialize, Serialize)]
168#[serde(rename_all = "camelCase")]
169pub struct SendMessageData {
170    /// Number of recipients accepted by the API.
171    pub recipients: u32,
172    /// Total number of billable message segments.
173    pub message_count: u32,
174    /// ISO 4217 currency code used for amounts.
175    pub currency: String,
176    /// Total amount charged for the request.
177    pub total_amount: String,
178    /// Per-recipient message details.
179    pub messages: Vec<UniMessage>,
180}
181
182/// Channel used to deliver a one-time passcode.
183#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
184#[serde(rename_all = "lowercase")]
185pub enum OtpChannel {
186    /// Lets Unimatrix select the delivery channel.
187    Auto,
188    /// Delivers the code by SMS.
189    Sms,
190    /// Delivers the code using a phone call.
191    Call,
192    /// Delivers the code using a voice message.
193    Voice,
194    /// Delivers the code using WhatsApp.
195    Whatsapp,
196}
197
198/// Parameters accepted by `otp.send`.
199#[derive(Clone, Debug, Serialize)]
200#[serde(rename_all = "camelCase")]
201pub struct SendOtpRequest {
202    /// Destination phone number in E.164 format.
203    pub to: String,
204    /// Caller-provided code. When omitted, Unimatrix generates one.
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub code: Option<String>,
207    /// Code lifetime in seconds.
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub ttl: Option<u32>,
210    /// Number of digits in an automatically generated code.
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub digits: Option<u8>,
213    /// Application-defined verification intent.
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub intent: Option<String>,
216    /// Preferred delivery channel.
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub channel: Option<OtpChannel>,
219    /// Message signature used by an SMS template.
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub signature: Option<String>,
222    /// Message template ID used to deliver the code.
223    #[serde(skip_serializing_if = "Option::is_none")]
224    pub template_id: Option<String>,
225}
226
227impl SendOtpRequest {
228    /// Creates a request that lets Unimatrix generate and deliver the code.
229    pub fn new(to: impl Into<String>) -> Self {
230        Self {
231            to: to.into(),
232            code: None,
233            ttl: None,
234            digits: None,
235            intent: None,
236            channel: None,
237            signature: None,
238            template_id: None,
239        }
240    }
241
242    /// Uses a caller-provided verification code.
243    pub fn with_code(mut self, code: impl Into<String>) -> Self {
244        self.code = Some(code.into());
245        self
246    }
247
248    /// Sets the code lifetime in seconds.
249    pub fn with_ttl(mut self, ttl: u32) -> Self {
250        self.ttl = Some(ttl);
251        self
252    }
253
254    /// Sets the number of digits in an automatically generated code.
255    pub fn with_digits(mut self, digits: u8) -> Self {
256        self.digits = Some(digits);
257        self
258    }
259
260    /// Associates an application-defined intent with the verification.
261    pub fn with_intent(mut self, intent: impl Into<String>) -> Self {
262        self.intent = Some(intent.into());
263        self
264    }
265
266    /// Selects the preferred delivery channel.
267    pub fn with_channel(mut self, channel: OtpChannel) -> Self {
268        self.channel = Some(channel);
269        self
270    }
271
272    /// Selects an SMS template and its signature.
273    pub fn with_template(
274        mut self,
275        signature: impl Into<String>,
276        template_id: impl Into<String>,
277    ) -> Self {
278        self.signature = Some(signature.into());
279        self.template_id = Some(template_id.into());
280        self
281    }
282}
283
284/// Parameters accepted by `otp.verify`.
285#[derive(Clone, Debug, Serialize)]
286#[serde(rename_all = "camelCase")]
287pub struct VerifyOtpRequest {
288    /// Phone number used when sending the code.
289    pub to: String,
290    /// Code supplied by the user.
291    pub code: String,
292    /// Maximum code age in seconds.
293    #[serde(skip_serializing_if = "Option::is_none")]
294    pub ttl: Option<u32>,
295    /// Application-defined intent that must match the send request.
296    #[serde(skip_serializing_if = "Option::is_none")]
297    pub intent: Option<String>,
298}
299
300impl VerifyOtpRequest {
301    /// Creates a verification request for a phone number and code.
302    pub fn new(to: impl Into<String>, code: impl Into<String>) -> Self {
303        Self {
304            to: to.into(),
305            code: code.into(),
306            ttl: None,
307            intent: None,
308        }
309    }
310
311    /// Sets the maximum accepted code age in seconds.
312    pub fn with_ttl(mut self, ttl: u32) -> Self {
313        self.ttl = Some(ttl);
314        self
315    }
316
317    /// Requires the verification intent to match this value.
318    pub fn with_intent(mut self, intent: impl Into<String>) -> Self {
319        self.intent = Some(intent.into());
320        self
321    }
322}
323
324/// Data returned after verifying an OTP.
325#[derive(Clone, Debug, Deserialize, Serialize)]
326pub struct VerifyOtpData {
327    /// Phone number checked by the API.
328    pub to: String,
329    /// Whether the supplied code is valid.
330    pub valid: bool,
331}