use std::{borrow::Cow, fmt, marker::PhantomData};
use serde::{
de::{self, SeqAccess, Unexpected, Visitor},
Deserialize, Deserializer,
};
use crate::websocket::Channel;
use crate::RoomName;
use self::messages::{ConversationUpdate, MessageUpdate};
use self::room::RoomUpdate;
use self::room_map_view::RoomMapViewUpdate;
use self::user_console::UserConsoleUpdate;
use self::user_cpu::UserCpuUpdate;
pub mod messages;
pub mod room;
pub mod room_map_view;
pub mod user_console;
pub mod user_cpu;
#[derive(Clone, Debug)]
pub enum ChannelUpdate<'a> {
RoomMapView {
room_name: RoomName,
shard_name: Option<String>,
update: RoomMapViewUpdate,
},
RoomDetail {
room_name: RoomName,
shard_name: Option<String>,
update: RoomUpdate,
},
NoRoomDetail {
room_name: RoomName,
shard_name: Option<String>,
},
UserCpu {
user_id: Cow<'a, str>,
update: UserCpuUpdate,
},
UserConsole {
user_id: Cow<'a, str>,
update: UserConsoleUpdate,
},
UserCredits {
user_id: Cow<'a, str>,
update: f64,
},
UserMessage {
user_id: Cow<'a, str>,
update: MessageUpdate,
},
UserConversation {
user_id: Cow<'a, str>,
target_user_id: Cow<'a, str>,
update: ConversationUpdate,
},
Other {
channel: Cow<'a, str>,
update: serde_json::Value,
},
}
impl<'a> ChannelUpdate<'a> {
pub fn shard_name(&self) -> Option<&str> {
match *self {
ChannelUpdate::RoomMapView { ref shard_name, .. }
| ChannelUpdate::RoomDetail { ref shard_name, .. }
| ChannelUpdate::NoRoomDetail { ref shard_name, .. } => {
shard_name.as_ref().map(String::as_str)
}
_ => None,
}
}
pub fn room_name(&self) -> Option<&RoomName> {
match *self {
ChannelUpdate::RoomMapView { ref room_name, .. }
| ChannelUpdate::RoomDetail { ref room_name, .. }
| ChannelUpdate::NoRoomDetail { ref room_name, .. } => Some(room_name),
_ => None,
}
}
pub fn user_id(&self) -> Option<&str> {
match *self {
ChannelUpdate::UserCpu { ref user_id, .. }
| ChannelUpdate::UserConsole { ref user_id, .. }
| ChannelUpdate::UserMessage { ref user_id, .. }
| ChannelUpdate::UserConversation { ref user_id, .. }
| ChannelUpdate::UserCredits { ref user_id, .. } => Some(user_id.as_ref()),
_ => None,
}
}
pub fn channel(&self) -> Channel {
match *self {
ChannelUpdate::RoomMapView {
room_name,
ref shard_name,
..
} => Channel::room_map_view(room_name, shard_name.as_ref().map(String::as_str)),
ChannelUpdate::RoomDetail {
room_name,
ref shard_name,
..
}
| ChannelUpdate::NoRoomDetail {
room_name,
ref shard_name,
..
} => Channel::room_detail(room_name, shard_name.as_ref().map(String::as_str)),
ChannelUpdate::UserCpu { ref user_id, .. } => Channel::user_cpu(user_id.as_ref()),
ChannelUpdate::UserConsole { ref user_id, .. } => {
Channel::user_console(user_id.as_ref())
}
ChannelUpdate::UserCredits { ref user_id, .. } => {
Channel::user_credits(user_id.as_ref())
}
ChannelUpdate::UserMessage { ref user_id, .. } => {
Channel::user_messages(user_id.as_ref())
}
ChannelUpdate::UserConversation {
ref user_id,
ref target_user_id,
..
} => Channel::user_conversation(user_id.as_ref(), target_user_id.as_ref()),
ChannelUpdate::Other { ref channel, .. } => Channel::other(channel.as_ref()),
}
}
}
struct ChannelUpdateVisitor<'a> {
marker: PhantomData<ChannelUpdate<'a>>,
}
impl<'a> ChannelUpdateVisitor<'a> {
fn new() -> Self {
ChannelUpdateVisitor {
marker: PhantomData,
}
}
}
impl<'de> Visitor<'de> for ChannelUpdateVisitor<'de> {
type Value = ChannelUpdate<'static>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a sequence")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
const ROOM_MAP_VIEW_PREFIX: &str = "roomMap2:";
const ROOM_PREFIX: &str = "room:";
const ROOM_ERR_PREFIX: &str = "err@room:";
const USER_PREFIX: &str = "user:";
const USER_CPU: &str = "cpu";
const USER_CONSOLE: &str = "console";
const USER_CREDITS: &str = "money";
const USER_MESSAGE: &str = "newMessage";
const USER_CONVERSATION_PREFIX: &str = "message:";
let channel: &str = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(2, &self))?;
macro_rules! finish_other {
() => {{
return Ok(ChannelUpdate::Other {
channel: channel.to_owned().into(),
update: seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(2, &self))?,
});
}};
}
if channel.starts_with(ROOM_MAP_VIEW_PREFIX) {
let room_name_and_shard = &channel[ROOM_MAP_VIEW_PREFIX.len()..];
let (shard_name, room_name) = {
let mut split = room_name_and_shard.splitn(2, "/");
match (split.next(), split.next()) {
(Some(shard), Some(room)) => (Some(shard), room),
(Some(room), None) => (None, room),
_ => finish_other!(),
}
};
let room_name = RoomName::new(room_name).map_err(|_| {
de::Error::invalid_value(
Unexpected::Str(room_name),
&"room name formatted `(E|W)[0-9]+(N|S)[0-9]+`",
)
})?;
return Ok(ChannelUpdate::RoomMapView {
room_name: room_name,
shard_name: shard_name.map(ToOwned::to_owned),
update: seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(2, &self))?,
});
} else if channel.starts_with(ROOM_PREFIX) {
let room_name_and_shard = &channel[ROOM_PREFIX.len()..];
let (shard_name, room_name) = {
let mut split = room_name_and_shard.splitn(2, '/');
match (split.next(), split.next()) {
(Some(shard), Some(room)) => (Some(shard), room),
(Some(room), None) => (None, room),
_ => finish_other!(),
}
};
let room_name = RoomName::new(room_name).map_err(|_| {
de::Error::invalid_value(
Unexpected::Str(room_name),
&"room name formatted `(E|W)[0-9]+(N|S)[0-9]+`",
)
})?;
return Ok(ChannelUpdate::RoomDetail {
room_name: room_name,
shard_name: shard_name.map(ToOwned::to_owned),
update: seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(2, &self))?,
});
} else if channel.starts_with(ROOM_ERR_PREFIX) {
let room_name_and_shard = &channel[ROOM_ERR_PREFIX.len()..];
let (shard_name, room_name) = {
let mut split = room_name_and_shard.splitn(2, '/');
match (split.next(), split.next()) {
(Some(shard), Some(room)) => (Some(shard), room),
(Some(room), None) => (None, room),
_ => finish_other!(),
}
};
let room_name = RoomName::new(room_name).map_err(|_| {
de::Error::invalid_value(
Unexpected::Str(room_name),
&"room name formatted `(E|W)[0-9]+(N|S)[0-9]+`",
)
})?;
let err_message = seq
.next_element::<&str>()?
.ok_or_else(|| de::Error::invalid_length(2, &self))?;
if err_message == "subscribe limit reached" {
return Ok(ChannelUpdate::NoRoomDetail {
room_name: room_name,
shard_name: shard_name.map(ToOwned::to_owned),
});
}
} else if channel.starts_with(USER_PREFIX) {
let user_id_and_part = &channel[USER_PREFIX.len()..];
let (user_id, sub_channel) = {
let mut split = user_id_and_part.splitn(2, '/');
match (split.next(), split.next()) {
(Some(v1), Some(v2)) => (v1, v2),
_ => finish_other!(),
}
};
match sub_channel {
USER_CPU => {
return Ok(ChannelUpdate::UserCpu {
user_id: user_id.to_owned().into(),
update: seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(2, &self))?,
});
}
USER_CONSOLE => {
return Ok(ChannelUpdate::UserConsole {
user_id: user_id.to_owned().into(),
update: seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(2, &self))?,
});
}
USER_CREDITS => {
return Ok(ChannelUpdate::UserCredits {
user_id: user_id.to_owned().into(),
update: seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(2, &self))?,
})
}
USER_MESSAGE => {
return Ok(ChannelUpdate::UserMessage {
user_id: user_id.to_owned().into(),
update: seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(2, &self))?,
})
}
sub_channel => {
if sub_channel.starts_with(USER_CONVERSATION_PREFIX) {
let target_user_id = &sub_channel[USER_CONVERSATION_PREFIX.len()..];
return Ok(ChannelUpdate::UserConversation {
user_id: user_id.to_owned().into(),
target_user_id: target_user_id.to_owned().into(),
update: seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(2, &self))?,
});
} else {
finish_other!()
}
}
}
}
finish_other!();
}
}
impl<'de> Deserialize<'de> for ChannelUpdate<'static> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_seq(ChannelUpdateVisitor::new())
}
}