diff --git a/cove-tui/src/euph/api.rs b/cove-tui/src/euph/api.rs index d4729dd..95f76c7 100644 --- a/cove-tui/src/euph/api.rs +++ b/cove-tui/src/euph/api.rs @@ -1,13 +1,13 @@ //! Models the euphoria API at . mod events; -mod packet; +pub mod packet; mod room_cmds; mod session_cmds; mod types; pub use events::*; -pub use packet::*; +pub use packet::Data; pub use room_cmds::*; pub use session_cmds::*; pub use types::*; diff --git a/cove-tui/src/euph/api/events.rs b/cove-tui/src/euph/api/events.rs index 0fa7afd..1d79b03 100644 --- a/cove-tui/src/euph/api/events.rs +++ b/cove-tui/src/euph/api/events.rs @@ -2,10 +2,7 @@ use serde::{Deserialize, Serialize}; -use super::{ - has_packet_type, AuthOption, HasPacketType, Message, PacketType, PersonalAccountView, - SessionView, Snowflake, Time, UserId, -}; +use super::{AuthOption, Message, PersonalAccountView, SessionView, Snowflake, Time, UserId}; /// Indicates that access to a room is denied. #[derive(Debug, Clone, Serialize, Deserialize)] @@ -20,8 +17,6 @@ pub struct BounceEvent { pub ip: Option, } -has_packet_type!(BounceEvent); - /// Indicates that the session is being closed. The client will subsequently be /// disconnected. /// @@ -33,8 +28,6 @@ pub struct DisconnectEvent { pub reason: String, } -has_packet_type!(DisconnectEvent); - /// Sent by the server to the client when a session is started. /// /// It includes information about the client's authentication and associated @@ -58,14 +51,10 @@ pub struct HelloEvent { pub version: String, } -has_packet_type!(HelloEvent); - /// Indicates a session just joined the room. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct JoinEvent(pub SessionView); -has_packet_type!(JoinEvent); - /// Sent to all sessions of an agent when that agent is logged in (except for /// the session that issued the login command). #[derive(Debug, Clone, Serialize, Deserialize)] @@ -73,15 +62,11 @@ pub struct LoginEvent { pub account_id: Snowflake, } -has_packet_type!(LoginEvent); - /// Sent to all sessions of an agent when that agent is logged out (except for /// the session that issued the logout command). #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LogoutEvent; -has_packet_type!(LogoutEvent); - /// Indicates some server-side event that impacts the presence of sessions in a /// room. /// @@ -97,8 +82,6 @@ pub struct NetworkEvent { pub server_era: String, } -has_packet_type!(NetworkEvent); - /// Announces a nick change by another session in the room. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NickEvent { @@ -112,8 +95,6 @@ pub struct NickEvent { pub to: String, } -has_packet_type!(NickEvent); - /// Indicates that a message in the room has been modified or deleted. /// /// If the client offers a user interface and the indicated message is currently @@ -129,14 +110,10 @@ pub struct EditMessageEvent { pub message: Message, } -has_packet_type!(EditMessageEvent); - /// Indicates a session just disconnected from the room. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PartEvent(pub SessionView); -has_packet_type!(PartEvent); - /// Represents a server-to-client ping. /// /// The client should send back a ping-reply with the same value for the time @@ -150,8 +127,6 @@ pub struct PingEvent { pub next: Time, } -has_packet_type!(PingEvent); - /// Informs the client that another user wants to chat with them privately. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PmInitiateEvent { @@ -165,14 +140,10 @@ pub struct PmInitiateEvent { pub pm_id: Snowflake, } -has_packet_type!(PmInitiateEvent); - /// Indicates a message received by the room from another session. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SendEvent(pub Message); -has_packet_type!(SendEvent); - /// Indicates that a session has successfully joined a room. /// /// It also offers a snapshot of the room’s state and recent history. @@ -197,5 +168,3 @@ pub struct SnapshotEvent { /// If given, this room is for private chat with the given user. pub pm_with_user_id: Option, } - -has_packet_type!(SnapshotEvent); diff --git a/cove-tui/src/euph/api/packet.rs b/cove-tui/src/euph/api/packet.rs index 60f4f17..c269806 100644 --- a/cove-tui/src/euph/api/packet.rs +++ b/cove-tui/src/euph/api/packet.rs @@ -1,196 +1,7 @@ -use std::fmt; - -use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use serde_json::Value; -/// The type of a packet. -/// -/// Not all of these types have their corresponding data modeled as a struct. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub enum PacketType { - // Asynchronous events - /// See [`BounceEvent`](super::BounceEvent). - BounceEvent, - /// See [`DisconnectEvent`](super::DisconnectEvent). - DisconnectEvent, - /// See [`HelloEvent`](super::HelloEvent). - HelloEvent, - /// See [`JoinEvent`](super::JoinEvent). - JoinEvent, - /// See [`LoginEvent`](super::LoginEvent). - LoginEvent, - /// See [`LogoutEvent`](super::LogoutEvent). - LogoutEvent, - /// See [`NetworkEvent`](super::NetworkEvent). - NetworkEvent, - /// See [`NickEvent`](super::NickEvent). - NickEvent, - /// See [`EditMessageEvent`](super::EditMessageEvent). - EditMessageEvent, - /// See [`PartEvent`](super::PartEvent). - PartEvent, - /// See [`PingEvent`](super::PingEvent). - PingEvent, - /// See [`PmInitiateEvent`](super::PmInitiateEvent). - PmInitiateEvent, - /// See [`SendEvent`](super::SendEvent). - SendEvent, - /// See [`SnapshotEvent`](super::SnapshotEvent). - SnapshotEvent, - - // Session commands - /// See [`Auth`](super::Auth). - Auth, - /// See [`AuthReply`](super::AuthReply). - AuthReply, - /// See [`Ping`](super::Ping). - Ping, - /// See [`PingReply`](super::PingReply). - PingReply, - - // Chat room commands - /// See [`GetMessage`](super::GetMessage). - GetMessage, - /// See [`GetMessageReply`](super::GetMessageReply). - GetMessageReply, - /// See [`Log`](super::Log). - Log, - /// See [`LogReply`](super::LogReply). - LogReply, - /// See [`Nick`](super::Nick). - Nick, - /// See [`NickReply`](super::NickReply). - NickReply, - /// See [`PmInitiate`](super::PmInitiate). - PmInitiate, - /// See [`PmInitiateReply`](super::PmInitiateReply). - PmInitiateReply, - /// See [`Send`](super::Send). - Send, - /// See [`SendReply`](super::SendReply). - SendReply, - /// See [`Who`](super::Who). - Who, - /// See [`WhoReply`](super::WhoReply). - WhoReply, - - // Account commands - /// Not implemented. - ChangeEmail, - /// Not implemented. - ChangeEmailReply, - /// Not implemented. - ChangeName, - /// Not implemented. - ChangeNameReply, - /// Not implemented. - ChangePassword, - /// Not implemented. - ChangePasswordReply, - /// Not implemented. - Login, - /// Not implemented. - LoginReply, - /// Not implemented. - Logout, - /// Not implemented. - LogoutReply, - /// Not implemented. - RegisterAccount, - /// Not implemented. - RegisterAccountReply, - /// Not implemented. - ResendVerificationEmail, - /// Not implemented. - ResendVerificationEmailReply, - /// Not implemented. - ResetPassword, - /// Not implemented. - ResetPasswordReply, - - // Room host commands - /// Not implemented. - Ban, - /// Not implemented. - BanReply, - /// Not implemented. - EditMessage, - /// Not implemented. - EditMessageReply, - /// Not implemented. - GrantAccess, - /// Not implemented. - GrantAccessReply, - /// Not implemented. - GrantManager, - /// Not implemented. - GrantManagerReply, - /// Not implemented. - RevokeAccess, - /// Not implemented. - RevokeAccessReply, - /// Not implemented. - RevokeManager, - /// Not implemented. - RevokeManagerReply, - /// Not implemented. - Unban, - /// Not implemented. - UnbanReply, - - // Staff commands - /// Not implemented. - StaffCreateRoom, - /// Not implemented. - StaffCreateRoomReply, - /// Not implemented. - StaffEnrollOtp, - /// Not implemented. - StaffEnrollOtpReply, - /// Not implemented. - StaffGrantManager, - /// Not implemented. - StaffGrantManagerReply, - /// Not implemented. - StaffInvade, - /// Not implemented. - StaffInvadeReply, - /// Not implemented. - StaffLockRoom, - /// Not implemented. - StaffLockRoomReply, - /// Not implemented. - StaffRevokeAccess, - /// Not implemented. - StaffRevokeAccessReply, - /// Not implemented. - StaffValidateOtp, - /// Not implemented. - StaffValidateOtpReply, - /// Not implemented. - UnlockStaffCapability, - /// Not implemented. - UnlockStaffCapabilityReply, -} - -impl fmt::Display for PacketType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match serde_json::to_value(self) { - Ok(Value::String(s)) => write!(f, "{}", s), - _ => Err(fmt::Error), - } - } -} - -#[derive(Debug, thiserror::Error)] -pub enum PacketError { - #[error("throttled: {0}")] - Throttled(String), - #[error("error: {0}")] - Error(String), -} +use super::PacketType; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Packet { @@ -203,80 +14,206 @@ pub struct Packet { pub throttled_reason: Option, } -impl Packet { - pub fn data(self) -> Result { - if self.throttled { - let reason = self - .throttled_reason - .unwrap_or_else(|| "no reason given".to_string()); - Err(PacketError::Throttled(reason)) - } else if let Some(error) = self.error { - Err(PacketError::Error(error)) - } else { - Ok(self.data.unwrap_or_default()) +pub trait Command { + type Reply; +} + +macro_rules! packets { + ( $( $name:ident, )*) => { + #[derive(Debug, Clone)] + pub enum Data { + $( $name(super::$name), )* + Unimplemented, } - } + + impl Data { + pub fn from_value(ptype: PacketType, value: Value) -> serde_json::Result { + Ok(match ptype { + $( PacketType::$name => Self::$name(serde_json::from_value(value)?), )* + _ => Self::Unimplemented, + }) + } + + pub fn to_value(self) -> serde_json::Result { + Ok(match self{ + $( Self::$name(p) => serde_json::to_value(p)?, )* + Self::Unimplemented => panic!("using unimplemented data"), + }) + } + + pub fn packet_type(&self) -> PacketType { + match self { + $( Self::$name(p) => PacketType::$name, )* + Self::Unimplemented => panic!("using unimplemented data"), + } + } + } + + $( + impl From for Data { + fn from(p: super::$name) -> Self { + Self::$name(p) + } + } + + impl TryFrom for super::$name{ + type Error = (); + + fn try_from(value: Data) -> Result { + match value { + Data::$name(p) => Ok(p), + _ => Err(()) + } + } + } + )* + }; } -pub trait HasPacketType { - fn packet_type() -> PacketType; -} - -macro_rules! has_packet_type { - ($name:ident) => { - impl HasPacketType for $name { - fn packet_type() -> PacketType { - PacketType::$name +macro_rules! events { + ( $( $name:ident, )* ) => { + impl Data { + pub fn is_event(&self) -> bool { + match self { + $( Self::$name(_) => true, )* + _ => false, + } } } }; } -pub(crate) use has_packet_type; -pub trait ToPacket { - fn to_packet(self, id: Option) -> Packet; +macro_rules! commands { + ( $( $cmd:ident => $rpl:ident, )* ) => { + $( + impl Command for super::$cmd { + type Reply = super::$rpl; + } + )* + }; } -impl ToPacket for T { - fn to_packet(self, id: Option) -> Packet { - Packet { - id, - r#type: Self::packet_type(), - data: Some(serde_json::to_value(self).expect("malformed packet")), - error: None, - throttled: false, - throttled_reason: None, - } - } +packets! { + BounceEvent, + DisconnectEvent, + HelloEvent, + JoinEvent, + LoginEvent, + LogoutEvent, + NetworkEvent, + NickEvent, + EditMessageEvent, + PartEvent, + PingEvent, + PmInitiateEvent, + SendEvent, + SnapshotEvent, + Auth, + AuthReply, + Ping, + PingReply, + GetMessage, + GetMessageReply, + Log, + LogReply, + Nick, + NickReply, + PmInitiate, + PmInitiateReply, + Send, + SendReply, + Who, + WhoReply, } -#[derive(Debug, thiserror::Error)] -pub enum DecodeError { - #[error("incorrect packet type: expected {expected}, got {actual}")] - IncorrectType { - expected: PacketType, - actual: PacketType, - }, - #[error("{0}")] - SerdeJson(#[from] serde_json::Error), - #[error("{0}")] - Packet(#[from] PacketError), +events! { + BounceEvent, + DisconnectEvent, + HelloEvent, + JoinEvent, + LoginEvent, + LogoutEvent, + NetworkEvent, + NickEvent, + EditMessageEvent, + PartEvent, + PingEvent, + PmInitiateEvent, + SendEvent, + SnapshotEvent, } -pub trait FromPacket: Sized { - fn from_packet(packet: Packet) -> Result; +commands! { + Auth => AuthReply, + Ping => PingReply, + GetMessage => GetMessageReply, + Log => LogReply, + Nick => NickReply, + PmInitiate => PmInitiateReply, + Send => SendReply, + Who => WhoReply, } -impl FromPacket for T { - fn from_packet(packet: Packet) -> Result { - if packet.r#type != Self::packet_type() { - Err(DecodeError::IncorrectType { - expected: Self::packet_type(), - actual: packet.r#type, - }) +#[derive(Debug, Clone)] +pub struct ParsedPacket { + pub id: Option, + pub r#type: PacketType, + pub content: Result, + pub throttled: Option, +} + +impl ParsedPacket { + pub fn from_packet(packet: Packet) -> serde_json::Result { + let id = packet.id; + let r#type = packet.r#type; + + let content = if let Some(error) = packet.error { + Err(error) } else { - let data = packet.data()?; - Ok(serde_json::from_value(data)?) - } + let data = packet.data.unwrap_or_default(); + Ok(Data::from_value(r#type, data)?) + }; + + let throttled = if packet.throttled { + let reason = packet + .throttled_reason + .unwrap_or_else(|| "no reason given".to_string()); + Some(reason) + } else { + None + }; + + Ok(Self { + id, + r#type, + content, + throttled, + }) + } + + pub fn to_packet(self) -> serde_json::Result { + let id = self.id; + let r#type = self.r#type; + let throttled = self.throttled.is_some(); + let throttled_reason = self.throttled; + + Ok(match self.content { + Ok(data) => Packet { + id, + r#type, + data: Some(data.to_value()?), + error: None, + throttled, + throttled_reason, + }, + Err(error) => Packet { + id, + r#type, + data: None, + error: Some(error), + throttled, + throttled_reason, + }, + }) } } diff --git a/cove-tui/src/euph/api/room_cmds.rs b/cove-tui/src/euph/api/room_cmds.rs index 4469478..02dddd4 100644 --- a/cove-tui/src/euph/api/room_cmds.rs +++ b/cove-tui/src/euph/api/room_cmds.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; -use super::{has_packet_type, HasPacketType, Message, PacketType, SessionView, Snowflake, UserId}; +use super::{Message, SessionView, Snowflake, UserId}; /// Retrieve the full content of a single message in the room. #[derive(Debug, Clone, Serialize, Deserialize)] @@ -11,14 +11,10 @@ pub struct GetMessage { pub id: Snowflake, } -has_packet_type!(GetMessage); - /// The message retrieved by [`GetMessage`]. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GetMessageReply(pub Message); -has_packet_type!(GetMessageReply); - /// Request messages from the room's message log. /// /// This can be used to supplement the log provided by snapshot-event (for @@ -31,8 +27,6 @@ pub struct Log { pub before: Option, } -has_packet_type!(Log); - /// List of messages from the room's message log. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LogReply { @@ -42,8 +36,6 @@ pub struct LogReply { pub before: Option, } -has_packet_type!(LogReply); - /// Set the name you present to the room. /// /// This name applies to all messages sent during this session, until the nick @@ -54,8 +46,6 @@ pub struct Nick { pub name: String, } -has_packet_type!(Nick); - /// Confirms the [`Nick`] command. /// /// Returns the session's former and new names (the server may modify the @@ -72,8 +62,6 @@ pub struct NickReply { pub to: String, } -has_packet_type!(NickReply); - /// Constructs a virtual room for private messaging between the client and the /// given [`UserId`]. #[derive(Debug, Clone, Serialize, Deserialize)] @@ -82,8 +70,6 @@ pub struct PmInitiate { pub user_id: UserId, } -has_packet_type!(PmInitiate); - /// Provides the PMID for the requested private messaging room. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PmInitiateReply { @@ -93,8 +79,6 @@ pub struct PmInitiateReply { pub to_nick: String, } -has_packet_type!(PmInitiateReply); - /// Send a message to a room. /// /// The session must be successfully joined with the room. This message will be @@ -114,27 +98,19 @@ pub struct Send { pub parent: Option, } -has_packet_type!(Send); - /// The message that was sent. /// /// this includes the message id, which was populated by the server. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SendReply(pub Message); -has_packet_type!(SendReply); - /// Request a list of sessions currently joined in the room. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Who; -has_packet_type!(Who); - /// Lists the sessions currently joined in the room. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WhoReply { /// A list of session views. listing: Vec, } - -has_packet_type!(WhoReply); diff --git a/cove-tui/src/euph/api/session_cmds.rs b/cove-tui/src/euph/api/session_cmds.rs index 8ba846d..e169f7d 100644 --- a/cove-tui/src/euph/api/session_cmds.rs +++ b/cove-tui/src/euph/api/session_cmds.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; -use super::{has_packet_type, AuthOption, HasPacketType, PacketType, Time}; +use super::{AuthOption, Time}; /// Attempt to join a private room. /// @@ -16,8 +16,6 @@ pub struct Auth { pub passcode: Option, } -has_packet_type!(Auth); - /// Reports whether the [`Auth`] command succeeded. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AuthReply { @@ -27,8 +25,6 @@ pub struct AuthReply { pub reason: Option, } -has_packet_type!(AuthReply); - /// Initiate a client-to-server ping. /// /// The server will send back a [`PingReply`] with the same timestamp as soon as @@ -39,13 +35,9 @@ pub struct Ping { pub time: Time, } -has_packet_type!(Ping); - /// Response to a [`Ping`] command or [`PingEvent`](super::PingEvent). #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PingReply { /// The timestamp of the ping being replied to. pub time: Option