use std::collections::BTreeMap;
use reqwest::{header::HeaderMap, StatusCode};
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Clone, Debug)]
pub struct UniResponse<T> {
pub status: StatusCode,
pub code: String,
pub message: String,
pub data: Option<T>,
pub request_id: Option<String>,
pub headers: HeaderMap,
pub raw_body: Value,
}
impl<T> UniResponse<T> {
pub fn into_data(self) -> crate::Result<T> {
self.data.ok_or_else(|| crate::UniError::InvalidResponse {
status: self.status,
request_id: self.request_id,
message: "response did not contain data".to_owned(),
body: self.raw_body.to_string(),
})
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
#[serde(untagged)]
pub enum Recipients {
One(String),
Many(Vec<String>),
}
impl From<String> for Recipients {
fn from(value: String) -> Self {
Self::One(value)
}
}
impl From<&str> for Recipients {
fn from(value: &str) -> Self {
Self::One(value.to_owned())
}
}
impl From<Vec<String>> for Recipients {
fn from(value: Vec<String>) -> Self {
Self::Many(value)
}
}
impl<const N: usize> From<[&str; N]> for Recipients {
fn from(value: [&str; N]) -> Self {
Self::Many(value.into_iter().map(str::to_owned).collect())
}
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SendMessageRequest {
pub to: Recipients,
#[serde(skip_serializing_if = "Option::is_none")]
pub signature: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub template_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub template_data: Option<BTreeMap<String, Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
}
impl SendMessageRequest {
pub fn text(to: impl Into<Recipients>, text: impl Into<String>) -> Self {
Self {
to: to.into(),
signature: None,
template_id: None,
template_data: None,
content: None,
text: Some(text.into()),
}
}
pub fn template(
to: impl Into<Recipients>,
signature: impl Into<String>,
template_id: impl Into<String>,
) -> Self {
Self {
to: to.into(),
signature: Some(signature.into()),
template_id: Some(template_id.into()),
template_data: None,
content: None,
text: None,
}
}
pub fn content(
to: impl Into<Recipients>,
signature: impl Into<String>,
content: impl Into<String>,
) -> Self {
Self {
to: to.into(),
signature: Some(signature.into()),
template_id: None,
template_data: None,
content: Some(content.into()),
text: None,
}
}
pub fn with_template_data(mut self, data: BTreeMap<String, Value>) -> Self {
self.template_data = Some(data);
self
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UniMessage {
pub id: String,
pub to: String,
pub iso: String,
pub cc: String,
pub count: u32,
pub price: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SendMessageData {
pub recipients: u32,
pub message_count: u32,
pub currency: String,
pub total_amount: String,
pub messages: Vec<UniMessage>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum OtpChannel {
Auto,
Sms,
Call,
Voice,
Whatsapp,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SendOtpRequest {
pub to: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ttl: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub digits: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub intent: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub channel: Option<OtpChannel>,
#[serde(skip_serializing_if = "Option::is_none")]
pub signature: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub template_id: Option<String>,
}
impl SendOtpRequest {
pub fn new(to: impl Into<String>) -> Self {
Self {
to: to.into(),
code: None,
ttl: None,
digits: None,
intent: None,
channel: None,
signature: None,
template_id: None,
}
}
pub fn with_code(mut self, code: impl Into<String>) -> Self {
self.code = Some(code.into());
self
}
pub fn with_ttl(mut self, ttl: u32) -> Self {
self.ttl = Some(ttl);
self
}
pub fn with_digits(mut self, digits: u8) -> Self {
self.digits = Some(digits);
self
}
pub fn with_intent(mut self, intent: impl Into<String>) -> Self {
self.intent = Some(intent.into());
self
}
pub fn with_channel(mut self, channel: OtpChannel) -> Self {
self.channel = Some(channel);
self
}
pub fn with_template(
mut self,
signature: impl Into<String>,
template_id: impl Into<String>,
) -> Self {
self.signature = Some(signature.into());
self.template_id = Some(template_id.into());
self
}
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct VerifyOtpRequest {
pub to: String,
pub code: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub ttl: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub intent: Option<String>,
}
impl VerifyOtpRequest {
pub fn new(to: impl Into<String>, code: impl Into<String>) -> Self {
Self {
to: to.into(),
code: code.into(),
ttl: None,
intent: None,
}
}
pub fn with_ttl(mut self, ttl: u32) -> Self {
self.ttl = Some(ttl);
self
}
pub fn with_intent(mut self, intent: impl Into<String>) -> Self {
self.intent = Some(intent.into());
self
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct VerifyOtpData {
pub to: String,
pub valid: bool,
}