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