use super::{ErrorCode, Status};
use crate::{
models::{
CallbackUrl, MediaUrl, SendStyle,
},
traits::SendableMessage,
SendblueError,
};
use chrono::{DateTime, Utc};
#[cfg(feature = "schemars")]
use schemars::{schema::Schema, schema_for, JsonSchema};
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, skip_serializing_none};
use validator::Validate;
#[derive(Serialize, Deserialize, Validate, Debug)]
pub struct Message {
pub number: String,
#[validate(length(min = 1))]
#[serde(skip_serializing_if = "Option::is_none", default)]
pub content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub media_url: Option<MediaUrl>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub status_callback: Option<CallbackUrl>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub send_style: Option<SendStyle>,
}
impl SendableMessage for Message {
fn endpoint() -> &'static str {
"/send-message"
}
type ResponseType = MessageResponse;
}
#[serde_as]
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
pub struct MessageResponse {
#[serde(rename = "accountEmail")]
pub account_email: String,
pub content: String,
pub is_outbound: bool,
pub status: Status,
pub error_code: Option<String>,
pub error_message: Option<String>,
pub message_handle: String,
pub date_sent: DateTime<Utc>,
pub date_updated: DateTime<Utc>,
pub from_number: String,
pub number: String,
pub to_number: String,
pub was_downgraded: Option<bool>,
pub plan: Option<String>,
pub media_url: String,
pub message_type: Option<String>,
pub group_id: Option<String>,
pub participants: Option<Vec<String>>,
pub send_style: String,
pub opted_out: bool,
pub error_detail: Option<String>,
}
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MessageStatusCallback {
#[serde(rename = "accountEmail")]
pub account_email: String,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub content: Option<String>,
pub is_outbound: bool,
pub status: Status,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub error_code: Option<ErrorCode>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub error_message: Option<String>,
pub message_handle: String,
pub date_sent: DateTime<Utc>,
pub date_updated: DateTime<Utc>,
pub from_number: String,
pub number: String,
pub to_number: String,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub was_downgraded: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub plan: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub media_url: Option<MediaUrl>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub message_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub group_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub participants: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub send_style: Option<String>,
pub opted_out: bool,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub error_detail: Option<String>,
}
#[cfg(feature = "schemars")]
#[derive(Serialize, Deserialize, JsonSchema)]
pub struct MessageStatusCallbackSchema(pub MessageStatusCallback);
#[cfg(feature = "schemars")]
impl JsonSchema for MessageStatusCallback {
fn schema_name() -> String {
"MessageStatusCallback".to_string()
}
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> Schema {
schema_for!(MessageStatusCallbackSchema).schema.into()
}
}
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct GetMessagesParams {
pub cid: Option<String>,
pub number: Option<String>,
pub limit: Option<u32>,
pub offset: Option<u32>,
pub from_date: Option<String>, }
#[derive(Serialize, Deserialize, Debug)]
pub struct RetrievedMessage {
pub date: String,
#[serde(rename = "allowSMS")]
pub allow_sms: Option<bool>,
#[serde(rename = "sendStyle")]
pub send_style: Option<String>,
#[serde(rename = "type")]
pub message_type: String,
pub uuid: String,
pub media_url: Option<String>,
pub content: Option<String>,
pub number: Option<String>,
pub is_outbound: bool,
#[serde(rename = "accountEmail")]
pub account_email: String,
pub was_downgraded: Option<bool>,
#[serde(rename = "callbackURL")]
pub callback_url: Option<String>,
pub row_id: Option<String>,
pub status: Status,
pub error_message: Option<String>,
pub to_number: Option<String>,
pub date_sent: Option<DateTime<Utc>>,
pub date_updated: Option<DateTime<Utc>>,
pub error_detail: Option<String>,
#[serde(rename = "phoneID")]
pub phone_id: Option<String>,
pub group_id: Option<String>,
pub from_number: Option<String>,
pub error_code: Option<i32>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct GetMessagesResponse {
pub messages: Vec<RetrievedMessage>,
}
#[derive(Serialize, Deserialize, Validate, Debug)]
pub struct GroupMessage {
pub numbers: Option<Vec<String>>,
pub group_id: Option<String>,
#[validate(length(min = 1))]
pub content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub media_url: Option<MediaUrl>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub send_style: Option<SendStyle>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub status_callback: Option<CallbackUrl>,
}
impl SendableMessage for GroupMessage {
fn endpoint() -> &'static str {
"/send-group-message"
}
type ResponseType = GroupMessageResponse;
}
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug)]
pub struct GroupMessageResponse {
#[serde(rename = "accountEmail")]
pub account_email: String,
pub content: String,
pub is_outbound: bool,
pub status: Status,
pub error_code: Option<i32>,
pub error_message: Option<String>,
pub message_handle: String,
pub date_sent: DateTime<Utc>,
pub date_updated: DateTime<Utc>,
pub from_number: String,
pub number: Vec<String>,
pub to_number: Vec<String>,
pub was_downgraded: Option<bool>,
pub plan: String,
pub media_url: String,
pub message_type: String,
pub group_id: String,
}
pub struct MessageBuilder<T> {
message: Option<Message>,
group_message: Option<GroupMessage>,
_marker: std::marker::PhantomData<T>,
}
impl MessageBuilder<Message> {
pub fn new(number: String) -> Self {
Self {
message: Some(Message {
number,
content: None,
media_url: None,
status_callback: None,
send_style: None,
}),
group_message: None,
_marker: std::marker::PhantomData,
}
}
pub fn content(mut self, content: String) -> Self {
if let Some(ref mut msg) = self.message {
msg.content = Some(content);
}
self
}
pub fn media_url(mut self, media_url: MediaUrl) -> Self {
if let Some(ref mut msg) = self.message {
msg.media_url = Some(media_url);
}
self
}
pub fn status_callback(mut self, status_callback: CallbackUrl) -> Self {
if let Some(ref mut msg) = self.message {
msg.status_callback = Some(status_callback);
}
self
}
pub fn send_style(mut self, send_style: SendStyle) -> Self {
if let Some(ref mut msg) = self.message {
msg.send_style = Some(send_style);
}
self
}
pub fn build(self) -> Result<Message, SendblueError> {
if let Some(msg) = self.message {
msg.validate()
.map_err(|e| SendblueError::ValidationError(e.to_string()))?;
Ok(msg)
} else {
Err(SendblueError::ValidationError(
"Message not initialized".into(),
))
}
}
}
impl MessageBuilder<GroupMessage> {
pub fn new_group() -> Self {
Self {
message: None,
group_message: Some(GroupMessage {
numbers: None,
group_id: None,
content: None,
media_url: None,
send_style: None,
status_callback: None,
}),
_marker: std::marker::PhantomData,
}
}
pub fn numbers(mut self, numbers: Vec<String>) -> Self {
if let Some(ref mut grp_msg) = self.group_message {
grp_msg.numbers = Some(numbers);
}
self
}
pub fn group_id(mut self, group_id: String) -> Self {
if let Some(ref mut grp_msg) = self.group_message {
grp_msg.group_id = Some(group_id);
}
self
}
pub fn content(mut self, content: String) -> Self {
if let Some(ref mut grp_msg) = self.group_message {
grp_msg.content = Some(content);
}
self
}
pub fn media_url(mut self, media_url: MediaUrl) -> Self {
if let Some(ref mut grp_msg) = self.group_message {
grp_msg.media_url = Some(media_url);
}
self
}
pub fn status_callback(mut self, status_callback: CallbackUrl) -> Self {
if let Some(ref mut grp_msg) = self.group_message {
grp_msg.status_callback = Some(status_callback);
}
self
}
pub fn send_style(mut self, send_style: SendStyle) -> Self {
if let Some(ref mut grp_msg) = self.group_message {
grp_msg.send_style = Some(send_style);
}
self
}
pub fn build(self) -> Result<GroupMessage, SendblueError> {
if let Some(grp_msg) = self.group_message {
if grp_msg.numbers.as_ref().map_or(true, |ns| ns.is_empty())
&& grp_msg.group_id.is_none()
{
return Err(SendblueError::ValidationError(
"Either numbers or group_id must be provided".into(),
));
}
if grp_msg.content.is_none() && grp_msg.media_url.is_none() {
return Err(SendblueError::ValidationError(
"Either content or media_url must be provided".into(),
));
}
grp_msg
.validate()
.map_err(|e| SendblueError::ValidationError(e.to_string()))?;
Ok(grp_msg)
} else {
Err(SendblueError::ValidationError(
"GroupMessage not initialized".into(),
))
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct GetMessagesParamsBuilder {
cid: Option<String>,
number: Option<String>,
limit: Option<u32>,
offset: Option<u32>,
from_date: Option<String>,
}
impl GetMessagesParamsBuilder {
pub fn new() -> Self {
Self {
cid: None,
number: None,
limit: None,
offset: None,
from_date: None,
}
}
pub fn cid(mut self, cid: Option<String>) -> Self {
self.cid = cid;
self
}
pub fn number(mut self, number: Option<String>) -> Self {
self.number = number;
self
}
pub fn limit(mut self, limit: Option<u32>) -> Self {
self.limit = limit;
self
}
pub fn offset(mut self, offset: Option<u32>) -> Self {
self.offset = offset;
self
}
pub fn from_date(mut self, from_date: Option<String>) -> Self {
self.from_date = from_date;
self
}
pub fn build(self) -> GetMessagesParams {
GetMessagesParams {
cid: self.cid,
number: self.number,
limit: self.limit,
offset: self.offset,
from_date: self.from_date,
}
}
}
impl Default for GetMessagesParamsBuilder {
fn default() -> Self {
Self::new()
}
}