From 49169a1b629893cd8bba52343facb5a822ffe1de Mon Sep 17 00:00:00 2001 From: Joscha Date: Wed, 22 Jun 2022 10:47:26 +0200 Subject: [PATCH] Convert between Packet and individual packet structs --- Cargo.lock | 1 + cove-tui/Cargo.toml | 1 + cove-tui/src/euph/api.rs | 15 +- cove-tui/src/euph/api/events.rs | 33 +++- cove-tui/src/euph/api/packet.rs | 270 ++++++++++++++++++++++++++ cove-tui/src/euph/api/room_cmds.rs | 26 ++- cove-tui/src/euph/api/session_cmds.rs | 10 +- cove-tui/src/euph/api/types.rs | 172 +--------------- 8 files changed, 341 insertions(+), 187 deletions(-) create mode 100644 cove-tui/src/euph/api/packet.rs diff --git a/Cargo.lock b/Cargo.lock index 84dde2d..fbf0dd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -195,6 +195,7 @@ dependencies = [ "rusqlite", "serde", "serde_json", + "thiserror", "tokio", "tokio-tungstenite 0.17.1", "toss", diff --git a/cove-tui/Cargo.toml b/cove-tui/Cargo.toml index 545f0fd..3d6be6e 100644 --- a/cove-tui/Cargo.toml +++ b/cove-tui/Cargo.toml @@ -15,6 +15,7 @@ parking_lot = "0.12.1" rusqlite = { version = "0.27.0", features = ["chrono"] } serde = { version = "1.0.137", features = ["derive"] } serde_json = "1.0.81" +thiserror = "1.0.31" tokio = { version = "1.19.2", features = ["full"] } tokio-tungstenite = "0.17.1" toss = { git = "https://github.com/Garmelon/toss.git", rev = "761519c1a7cdc950eab70fd6539c71bf22919a50" } diff --git a/cove-tui/src/euph/api.rs b/cove-tui/src/euph/api.rs index 9339dde..d4729dd 100644 --- a/cove-tui/src/euph/api.rs +++ b/cove-tui/src/euph/api.rs @@ -1,24 +1,13 @@ //! Models the euphoria API at . mod events; +mod packet; mod room_cmds; mod session_cmds; mod types; -use serde::{Deserialize, Serialize}; -use serde_json::Value; - pub use events::*; +pub use packet::*; pub use room_cmds::*; pub use session_cmds::*; pub use types::*; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Packet { - pub id: Option, - pub r#type: PacketType, - pub data: Option, - pub error: Option, - pub throttled: Option, - pub throttled_reason: Option, -} diff --git a/cove-tui/src/euph/api/events.rs b/cove-tui/src/euph/api/events.rs index 1d79b03..0fa7afd 100644 --- a/cove-tui/src/euph/api/events.rs +++ b/cove-tui/src/euph/api/events.rs @@ -2,7 +2,10 @@ use serde::{Deserialize, Serialize}; -use super::{AuthOption, Message, PersonalAccountView, SessionView, Snowflake, Time, UserId}; +use super::{ + has_packet_type, AuthOption, HasPacketType, Message, PacketType, PersonalAccountView, + SessionView, Snowflake, Time, UserId, +}; /// Indicates that access to a room is denied. #[derive(Debug, Clone, Serialize, Deserialize)] @@ -17,6 +20,8 @@ pub struct BounceEvent { pub ip: Option, } +has_packet_type!(BounceEvent); + /// Indicates that the session is being closed. The client will subsequently be /// disconnected. /// @@ -28,6 +33,8 @@ 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 @@ -51,10 +58,14 @@ 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)] @@ -62,11 +73,15 @@ 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. /// @@ -82,6 +97,8 @@ 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 { @@ -95,6 +112,8 @@ 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 @@ -110,10 +129,14 @@ 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 @@ -127,6 +150,8 @@ 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 { @@ -140,10 +165,14 @@ 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. @@ -168,3 +197,5 @@ 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 new file mode 100644 index 0000000..ddd4d73 --- /dev/null +++ b/cove-tui/src/euph/api/packet.rs @@ -0,0 +1,270 @@ +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, Clone, Serialize, Deserialize)] +pub struct Packet { + pub id: Option, + pub r#type: PacketType, + pub data: Option, + pub error: Option, + #[serde(default)] + pub throttled: bool, + pub throttled_reason: Option, +} + +pub trait HasPacketType { + fn packet_type() -> PacketType; +} + +macro_rules! has_packet_type { + ($name:ident) => { + impl HasPacketType for $name { + fn packet_type() -> PacketType { + PacketType::$name + } + } + }; +} +pub(crate) use has_packet_type; + +pub trait ToPacket { + fn to_packet(self, id: Option) -> Packet; +} + +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, + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum DecodeError { + #[error("incorrect packet type: expected {expected}, got {actual}")] + IncorrectType { + expected: PacketType, + actual: PacketType, + }, + #[error("throttled: {0}")] + Throttled(String), + #[error("error: {0}")] + Error(String), + #[error("no data")] + NoData, + #[error("{0}")] + SerdeJson(#[from] serde_json::Error), +} + +pub trait FromPacket: Sized { + fn from_packet(packet: Packet) -> Result; +} + +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, + }) + } else if packet.throttled { + let reason = packet + .throttled_reason + .unwrap_or_else(|| "no reason given".to_string()); + Err(DecodeError::Throttled(reason)) + } else if let Some(error) = packet.error { + Err(DecodeError::Error(error)) + } else { + let data = packet.data.unwrap_or_default(); + Ok(serde_json::from_value(data)?) + } + } +} diff --git a/cove-tui/src/euph/api/room_cmds.rs b/cove-tui/src/euph/api/room_cmds.rs index 02dddd4..4469478 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::{Message, SessionView, Snowflake, UserId}; +use super::{has_packet_type, HasPacketType, Message, PacketType, SessionView, Snowflake, UserId}; /// Retrieve the full content of a single message in the room. #[derive(Debug, Clone, Serialize, Deserialize)] @@ -11,10 +11,14 @@ 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 @@ -27,6 +31,8 @@ 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 { @@ -36,6 +42,8 @@ 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 @@ -46,6 +54,8 @@ 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 @@ -62,6 +72,8 @@ 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)] @@ -70,6 +82,8 @@ 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 { @@ -79,6 +93,8 @@ 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 @@ -98,19 +114,27 @@ 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 e169f7d..8ba846d 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::{AuthOption, Time}; +use super::{has_packet_type, AuthOption, HasPacketType, PacketType, Time}; /// Attempt to join a private room. /// @@ -16,6 +16,8 @@ pub struct Auth { pub passcode: Option, } +has_packet_type!(Auth); + /// Reports whether the [`Auth`] command succeeded. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AuthReply { @@ -25,6 +27,8 @@ 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 @@ -35,9 +39,13 @@ 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