use std::collections::HashMap;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ProcessingState {
#[serde(rename = "Pending")]
Pending,
#[serde(rename = "Cancelling")]
Cancelling,
#[serde(rename = "Cancelled")]
Cancelled,
#[serde(rename = "Processed")]
Processed,
#[serde(rename = "Sent")]
Sent,
#[serde(rename = "Delivered")]
Delivered,
#[serde(rename = "Failed")]
Failed,
}
pub type MessagePriority = i8;
pub const PRIORITY_MINIMUM: MessagePriority = -128;
pub const PRIORITY_DEFAULT: MessagePriority = 0;
pub const PRIORITY_BYPASS_THRESHOLD: MessagePriority = 100;
pub const PRIORITY_MAXIMUM: MessagePriority = 127;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TextMessage {
pub text: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DataMessage {
pub data: String,
pub port: u16,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MmsAttachment {
pub content_type: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
pub data: String,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MmsMessage {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subject: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub attachments: Vec<MmsAttachment>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HashedMessage {
pub hash: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Message {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub device_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub text_message: Option<TextMessage>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub data_message: Option<DataMessage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mms_message: Option<MmsMessage>,
pub phone_numbers: Vec<String>,
#[serde(default)]
pub is_encrypted: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sim_number: Option<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub with_delivery_report: Option<bool>,
#[serde(default)]
pub priority: MessagePriority,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ttl: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub valid_until: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub schedule_at: Option<DateTime<Utc>>,
}
impl Message {
pub fn get_text_message(&self) -> Option<TextMessage> {
if let Some(ref tm) = self.text_message {
return Some(tm.clone());
}
self.message
.as_ref()
.filter(|m| !m.is_empty())
.map(|m| TextMessage { text: m.clone() })
}
pub fn get_data_message(&self) -> Option<&DataMessage> {
self.data_message.as_ref()
}
pub fn get_mms_message(&self) -> Option<&MmsMessage> {
self.mms_message.as_ref()
}
pub fn validate(&self) -> Result<(), crate::Error> {
let filled = self
.message
.as_ref()
.map(|s| !s.is_empty())
.unwrap_or(false) as u8
+ self.text_message.is_some() as u8
+ self.data_message.is_some() as u8
+ self.mms_message.is_some() as u8;
if filled == 0 {
return Err(crate::Error::Validation(
"must specify exactly one of: textMessage, dataMessage or mmsMessage".to_string(),
));
}
if filled > 1 {
return Err(crate::Error::ConflictFields(
"must specify exactly one of: textMessage, dataMessage or mmsMessage".to_string(),
));
}
if let Some(ref mms) = self.mms_message {
let has_text = mms.text.as_ref().map(|t| !t.is_empty()).unwrap_or(false);
let has_attachments = !mms.attachments.is_empty();
if !has_text && !has_attachments {
return Err(crate::Error::Validation(
"mmsMessage must specify either text or at least one attachment".to_string(),
));
}
}
if self.ttl.is_some() && self.valid_until.is_some() {
return Err(crate::Error::ConflictFields(
"ttl and validUntil".to_string(),
));
}
if let Some(ref schedule_at) = self.schedule_at {
if *schedule_at <= Utc::now() {
return Err(crate::Error::Validation(
"scheduleAt must be in the future".to_string(),
));
}
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RecipientState {
pub phone_number: String,
pub state: ProcessingState,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MessageState {
pub id: String,
pub device_id: String,
pub state: ProcessingState,
#[serde(default)]
pub is_hashed: bool,
#[serde(default)]
pub is_encrypted: bool,
#[serde(default)]
pub recipients: Vec<RecipientState>,
#[serde(default)]
pub states: HashMap<String, DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub text_message: Option<TextMessage>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub data_message: Option<DataMessage>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mms_message: Option<MmsMessage>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hashed_message: Option<HashedMessage>,
}
impl MessageState {
pub fn validate(&self) -> Result<(), crate::Error> {
for key in self.states.keys() {
match key.as_str() {
"Pending" | "Cancelling" | "Cancelled" | "Processed" | "Sent" | "Delivered"
| "Failed" => {}
_ => {
return Err(crate::Error::Validation(format!(
"invalid state value: {}",
key
)));
}
}
}
Ok(())
}
}