use crate::{
core::{service_response::APIErrorBody, PubNubError, ScalarValue},
dx::subscribe::{
types::Message,
AppContext, File, MessageAction, Presence, {SubscribeMessageType, SubscriptionCursor},
},
lib::{
alloc::{
boxed::Box,
string::{String, ToString},
vec,
vec::Vec,
},
collections::HashMap,
core::fmt::Debug,
},
};
#[derive(Debug)]
pub struct SubscribeResult {
pub cursor: SubscriptionCursor,
pub messages: Vec<Update>,
}
#[derive(Debug, Clone)]
pub enum Update {
Presence(Presence),
AppContext(AppContext),
MessageAction(MessageAction),
File(File),
Message(Message),
Signal(Message),
}
#[cfg_attr(feature = "serde", derive(serde::Deserialize), serde(untagged))]
pub enum SubscribeResponseBody {
SuccessResponse(APISuccessBody),
ErrorResponse(APIErrorBody),
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
pub struct APISuccessBody {
#[cfg_attr(feature = "serde", serde(rename = "t"))]
pub cursor: SubscriptionCursor,
#[cfg_attr(feature = "serde", serde(rename = "m"))]
pub messages: Vec<Envelope>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
pub struct Envelope {
#[cfg_attr(feature = "serde", serde(rename = "a"))]
pub shard: String,
#[cfg_attr(feature = "serde", serde(rename = "f"))]
pub debug_flags: u32,
#[cfg_attr(
feature = "serde",
serde(rename = "e"),
serde(default = "Envelope::default_message_type")
)]
pub message_type: SubscribeMessageType,
#[cfg_attr(feature = "serde", serde(rename = "i"), serde(default))]
pub sender: Option<String>,
#[cfg_attr(feature = "serde", serde(rename = "s"), serde(default))]
pub sequence_number: Option<u32>,
#[cfg_attr(feature = "serde", serde(rename = "p"))]
pub published: SubscriptionCursor,
#[cfg_attr(feature = "serde", serde(rename = "c"))]
pub channel: String,
#[cfg_attr(feature = "serde", serde(rename = "d"))]
pub payload: EnvelopePayload,
#[cfg_attr(feature = "serde", serde(rename = "b"), serde(default))]
pub subscription: Option<String>,
#[cfg_attr(feature = "serde", serde(rename = "cmt"), serde(default))]
pub r#type: Option<String>,
#[cfg_attr(feature = "serde", serde(rename = "si"), serde(default))]
pub space_id: Option<String>,
#[cfg(feature = "serde")]
#[cfg_attr(feature = "serde", serde(rename = "u"))]
pub user_metadata: Option<serde_json::Value>,
#[cfg(not(feature = "serde"))]
#[cfg_attr(feature = "serde", serde(rename = "u"))]
pub user_metadata: Option<Vec<u8>>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize), serde(untagged))]
pub enum EnvelopePayload {
PresenceStateChange {
action: String,
timestamp: usize,
occupancy: usize,
uuid: String,
#[cfg(feature = "serde")]
data: serde_json::Value,
#[cfg(not(feature = "serde"))]
data: Vec<u8>,
},
PresenceAnnounce {
action: String,
timestamp: usize,
uuid: String,
occupancy: usize,
#[cfg(feature = "serde")]
data: Option<serde_json::Value>,
#[cfg(not(feature = "serde"))]
data: Option<Vec<u8>>,
},
PresenceInterval {
timestamp: usize,
occupancy: usize,
join: Option<Vec<String>>,
leave: Option<Vec<String>>,
timeout: Option<Vec<String>>,
here_now_refresh: Option<bool>,
},
Object {
event: String,
r#type: String,
data: ObjectDataBody,
source: String,
version: String,
},
MessageAction {
event: String,
data: MessageActionDataBody,
source: String,
version: String,
},
File {
message: String,
file: FileDataBody,
},
#[cfg(feature = "serde")]
Message(serde_json::Value),
#[cfg(not(feature = "serde"))]
Message(Vec<u8>),
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize), serde(untagged))]
pub enum ObjectDataBody {
Channel {
name: Option<String>,
description: Option<String>,
r#type: Option<String>,
status: Option<String>,
id: String,
custom: Option<HashMap<String, ScalarValue>>,
updated: String,
#[cfg_attr(feature = "serde", serde(rename(deserialize = "eTag")))]
tag: String,
},
Uuid {
name: Option<String>,
email: Option<String>,
#[cfg_attr(feature = "serde", serde(rename(deserialize = "externalId")))]
external_id: Option<String>,
#[cfg_attr(feature = "serde", serde(rename(deserialize = "profileUrl")))]
profile_url: Option<String>,
r#type: Option<String>,
status: Option<String>,
id: String,
custom: Option<HashMap<String, ScalarValue>>,
updated: String,
#[cfg_attr(feature = "serde", serde(rename(deserialize = "eTag")))]
tag: String,
},
Membership {
channel: Box<ObjectDataBody>,
custom: Option<HashMap<String, ScalarValue>>,
uuid: String,
status: Option<String>,
updated: String,
#[cfg_attr(feature = "serde", serde(rename(deserialize = "eTag")))]
tag: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
pub struct MessageActionDataBody {
#[cfg_attr(feature = "serde", serde(rename(deserialize = "messageTimetoken")))]
pub message_timetoken: String,
#[cfg_attr(feature = "serde", serde(rename(deserialize = "actionTimetoken")))]
pub action_timetoken: String,
pub r#type: String,
pub value: String,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
pub struct FileDataBody {
pub id: String,
pub name: String,
}
impl TryFrom<SubscribeResponseBody> for SubscribeResult {
type Error = PubNubError;
fn try_from(value: SubscribeResponseBody) -> Result<Self, Self::Error> {
match value {
SubscribeResponseBody::SuccessResponse(resp) => {
let mut messages = Vec::new();
for message in resp.messages {
messages.push(message.try_into()?)
}
Ok(SubscribeResult {
cursor: resp.cursor,
messages,
})
}
SubscribeResponseBody::ErrorResponse(resp) => Err(resp.into()),
}
}
}
#[cfg(feature = "serde")]
impl Envelope {
fn default_message_type() -> SubscribeMessageType {
SubscribeMessageType::Message
}
}
#[cfg(feature = "std")]
impl Update {
pub(crate) fn subscription(&self) -> String {
match self {
Self::Presence(presence) => presence.subscription(),
Self::AppContext(object) => object.subscription(),
Self::MessageAction(reaction) => reaction.subscription.clone(),
Self::File(file) => file.subscription.clone(),
Self::Message(message) | Self::Signal(message) => message.subscription.clone(),
}
}
pub(crate) fn event_timestamp(&self) -> usize {
match self {
Self::Presence(presence) => presence.event_timestamp(),
Self::AppContext(object) => object.event_timestamp(),
Self::MessageAction(reaction) => reaction.timestamp,
Self::File(file) => file.timestamp,
Self::Message(message) | Self::Signal(message) => message.timestamp,
}
}
}
impl TryFrom<Envelope> for Update {
type Error = PubNubError;
fn try_from(value: Envelope) -> Result<Self, Self::Error> {
match value.payload {
EnvelopePayload::PresenceAnnounce { .. }
| EnvelopePayload::PresenceInterval { .. }
| EnvelopePayload::PresenceStateChange { .. } => {
Ok(Update::Presence(value.try_into()?))
}
EnvelopePayload::Object { .. }
if matches!(value.message_type, SubscribeMessageType::Object) =>
{
Ok(Update::AppContext(value.try_into()?))
}
EnvelopePayload::MessageAction { .. }
if matches!(value.message_type, SubscribeMessageType::MessageAction) =>
{
Ok(Update::MessageAction(value.try_into()?))
}
EnvelopePayload::File { .. }
if matches!(value.message_type, SubscribeMessageType::File) =>
{
Ok(Update::File(value.try_into()?))
}
EnvelopePayload::Message(_) => {
if matches!(value.message_type, SubscribeMessageType::Message) {
Ok(Update::Message(value.try_into()?))
} else {
Ok(Update::Signal(value.try_into()?))
}
}
_ => Err(PubNubError::Deserialization {
details: "Unable deserialize unknown payload".to_string(),
}),
}
}
}
impl From<EnvelopePayload> for Vec<u8> {
#[cfg(feature = "serde")]
fn from(value: EnvelopePayload) -> Self {
if let EnvelopePayload::Message(payload) = value {
return serde_json::to_vec(&payload).unwrap_or_default();
}
vec![]
}
#[cfg(not(feature = "serde"))]
fn from(value: EnvelopePayload) -> Self {
if let EnvelopePayload::Message(payload) = value {
return payload;
}
vec![]
}
}