From 92ea7f0aa0ad01c2a8b218ecdbcb373b13297a29 Mon Sep 17 00:00:00 2001 From: Joscha Date: Thu, 5 Dec 2024 12:37:24 +0100 Subject: [PATCH] Refactor and document api module --- Cargo.toml | 2 + src/api.rs | 21 ++- src/api/account_cmds.rs | 20 ++- src/api/events.rs | 62 ++++++--- src/api/packet.rs | 223 ------------------------------ src/api/packets.rs | 294 ++++++++++++++++++++++++++++++++++++++++ src/api/room_cmds.rs | 16 ++- src/api/session_cmds.rs | 8 +- src/api/types.rs | 53 +++++--- src/lib.rs | 1 + 10 files changed, 428 insertions(+), 272 deletions(-) delete mode 100644 src/api/packet.rs create mode 100644 src/api/packets.rs diff --git a/Cargo.toml b/Cargo.toml index 95c030b..7d9771d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,8 @@ version = "0.6.0" edition = "2021" [dependencies] +jiff = { version = "0.1.15", default-features = false, features = ["std"] } +serde = { version = "1.0.215", features = ["derive"] } serde_json = "1.0.133" [lints] diff --git a/src/api.rs b/src/api.rs index e24ca1d..23617f3 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,17 +1,12 @@ -//! Models the [euphoria API][0]. +//! Models the [euphoria.leet.nu API][0]. //! //! [0]: https://euphoria.leet.nu/heim/api -mod account_cmds; -mod events; -pub mod packet; -mod room_cmds; -mod session_cmds; -mod types; +pub mod account_cmds; +pub mod events; +pub mod packets; +pub mod room_cmds; +pub mod session_cmds; +pub mod types; -pub use account_cmds::*; -pub use events::*; -pub use packet::Data; -pub use room_cmds::*; -pub use session_cmds::*; -pub use types::*; +pub use self::{account_cmds::*, events::*, packets::*, room_cmds::*, session_cmds::*, types::*}; diff --git a/src/api/account_cmds.rs b/src/api/account_cmds.rs index 01ef7f0..b9ed570 100644 --- a/src/api/account_cmds.rs +++ b/src/api/account_cmds.rs @@ -1,8 +1,10 @@ -//! Account commands. +//! Models [account commands][0] and their replies. //! //! These commands enable a client to register, associate, and dissociate with //! an account. An account allows an identity to be shared across browsers and //! devices, and is a prerequisite for room management +//! +//! [0]: https://euphoria.leet.nu/heim/api#account-commands use serde::{Deserialize, Serialize}; @@ -11,6 +13,8 @@ use super::AccountId; /// Change the primary email address associated with the signed in account. /// /// The email address may need to be verified before the change is fully applied. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ChangeEmail { /// The new primary email address for the account. @@ -32,6 +36,8 @@ pub struct ChangeEmailReply { } /// Change the name associated with the signed in account. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ChangeName { /// The name to associate with the account. @@ -46,6 +52,8 @@ pub struct ChangeNameReply { } /// Change the password of the signed in account. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ChangePassword { /// The current (and soon-to-be former) password. @@ -65,6 +73,8 @@ pub struct ChangePasswordReply {} /// If the login succeeds, the client should expect to receive a /// [`DisconnectEvent`](super::DisconnectEvent) shortly after. The next /// connection the client makes will be a logged in session. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Login { /// The namespace of a personal identifier. @@ -98,6 +108,8 @@ pub struct LoginReply { /// If the logout is successful, the client should expect to receive a /// [`DisconnectEvent`](super::DisconnectEvent) shortly after. The next /// connection the client makes will be a logged out session. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Logout {} @@ -113,6 +125,8 @@ pub struct LogoutReply {} /// [`DisconnectEvent`](super::DisconnectEvent) shortly after. The next /// connection the client makes will be a logged in session using the new /// account. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RegisterAccount { /// The namespace of a personal identifier. @@ -145,6 +159,8 @@ pub struct RegisterAccountReply { /// /// An error will be returned if the account has no unverified email addresses /// associated with it. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ResendVerificationEmail {} @@ -156,6 +172,8 @@ pub struct ResendVerificationEmailReply {} /// /// An email will be sent to the owner of the given personal identifier, with /// instructions and a confirmation code for resetting the password. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ResetPassword { pub namespace: String, diff --git a/src/api/events.rs b/src/api/events.rs index 8abe04d..9729e93 100644 --- a/src/api/events.rs +++ b/src/api/events.rs @@ -1,4 +1,6 @@ -//! Asynchronous events. +//! Models [asynchronous events][0]. +//! +//! [0]: https://euphoria.leet.nu/heim/api#asynchronous-events use serde::{Deserialize, Serialize}; @@ -8,6 +10,8 @@ use super::{ }; /// Indicates that access to a room is denied. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BounceEvent { /// The reason why access was denied. @@ -25,16 +29,37 @@ pub struct BounceEvent { /// /// If the disconnect reason is `authentication changed`, the client should /// immediately reconnect. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DisconnectEvent { /// The reason for disconnection. pub reason: String, } +/// 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 +/// displayed, it should update its display accordingly. +/// +/// The event packet includes a snapshot of the message post-edit. +/// +/// +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct EditMessageEvent { + /// The id of the edit. + pub edit_id: Snowflake, + /// The snapshot of the message post-edit. + #[serde(flatten)] + pub message: Message, +} + /// Sent by the server to the client when a session is started. /// /// It includes information about the client's authentication and associated /// identity. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct HelloEvent { /// The id of the agent or account logged into this session. @@ -55,11 +80,15 @@ pub struct HelloEvent { } /// Indicates a session just joined the room. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct JoinEvent(pub SessionView); /// 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)] pub struct LoginEvent { pub account_id: AccountId, @@ -67,6 +96,8 @@ pub struct 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 {} @@ -75,6 +106,8 @@ pub struct LogoutEvent {} /// /// If the network event type is `partition`, then this should be treated as a /// [`PartEvent`] for all sessions connected to the same server id/era combo. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NetworkEvent { /// The type of network event; for now, always `partition`. @@ -86,6 +119,8 @@ pub struct NetworkEvent { } /// Announces a nick change by another session in the room. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NickEvent { /// The id of the session this name applies to. @@ -98,22 +133,9 @@ pub struct NickEvent { pub to: String, } -/// 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 -/// displayed, it should update its display accordingly. -/// -/// The event packet includes a snapshot of the message post-edit. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct EditMessageEvent { - /// The id of the edit. - pub edit_id: Snowflake, - /// The snapshot of the message post-edit. - #[serde(flatten)] - pub message: Message, -} - /// Indicates a session just disconnected from the room. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PartEvent(pub SessionView); @@ -121,6 +143,8 @@ pub struct PartEvent(pub SessionView); /// /// The client should send back a ping-reply with the same value for the time /// field as soon as possible (or risk disconnection). +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PingEvent { /// A unix timestamp according to the server's clock. @@ -131,6 +155,8 @@ pub struct PingEvent { } /// Informs the client that another user wants to chat with them privately. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PmInitiateEvent { /// The id of the user inviting the client to chat privately. @@ -144,12 +170,16 @@ pub struct PmInitiateEvent { } /// Indicates a message received by the room from another session. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SendEvent(pub Message); /// Indicates that a session has successfully joined a room. /// /// It also offers a snapshot of the room’s state and recent history. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SnapshotEvent { /// The id of the agent or account logged into this session. diff --git a/src/api/packet.rs b/src/api/packet.rs deleted file mode 100644 index 7752a53..0000000 --- a/src/api/packet.rs +++ /dev/null @@ -1,223 +0,0 @@ -use serde::{Deserialize, Serialize}; -use serde_json::Value; - -use super::PacketType; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Packet { - pub id: Option, - pub r#type: PacketType, - pub data: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, - #[serde(default, skip_serializing_if = "std::ops::Not::not")] - pub throttled: bool, - #[serde(skip_serializing_if = "Option::is_none")] - pub throttled_reason: Option, -} - -pub trait Command { - type Reply; -} - -macro_rules! packets { - ( $( $name:ident, )*) => { - #[derive(Debug, Clone)] - #[non_exhaustive] - 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 into_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(_) => 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(()) - } - } - } - )* - }; -} - -macro_rules! commands { - ( $( $cmd:ident => $rpl:ident, )* ) => { - $( - impl Command for super::$cmd { - type Reply = super::$rpl; - } - )* - }; -} - -packets! { - // Events - BounceEvent, - DisconnectEvent, - HelloEvent, - JoinEvent, - LoginEvent, - LogoutEvent, - NetworkEvent, - NickEvent, - EditMessageEvent, - PartEvent, - PingEvent, - PmInitiateEvent, - SendEvent, - SnapshotEvent, - // Session commands - Auth, - AuthReply, - Ping, - PingReply, - // Chat room commands - GetMessage, - GetMessageReply, - Log, - LogReply, - Nick, - NickReply, - PmInitiate, - PmInitiateReply, - Send, - SendReply, - Who, - WhoReply, - // Account commands - ChangeEmail, - ChangeEmailReply, - ChangeName, - ChangeNameReply, - ChangePassword, - ChangePasswordReply, - Login, - LoginReply, - Logout, - LogoutReply, - RegisterAccount, - RegisterAccountReply, - ResendVerificationEmail, - ResendVerificationEmailReply, - ResetPassword, - ResetPasswordReply, -} - -commands! { - // Session commands - Auth => AuthReply, - Ping => PingReply, - // Chat room commands - GetMessage => GetMessageReply, - Log => LogReply, - Nick => NickReply, - PmInitiate => PmInitiateReply, - Send => SendReply, - Who => WhoReply, - // Account commands - ChangeEmail => ChangeEmailReply, - ChangeName => ChangeNameReply, - ChangePassword => ChangePasswordReply, - Login => LoginReply, - Logout => LogoutReply, - RegisterAccount => RegisterAccountReply, - ResendVerificationEmail => ResendVerificationEmailReply, - ResetPassword => ResetPasswordReply, -} - -#[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.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 into_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.into_value()?), - error: None, - throttled, - throttled_reason, - }, - Err(error) => Packet { - id, - r#type, - data: None, - error: Some(error), - throttled, - throttled_reason, - }, - }) - } -} diff --git a/src/api/packets.rs b/src/api/packets.rs new file mode 100644 index 0000000..6c07b6c --- /dev/null +++ b/src/api/packets.rs @@ -0,0 +1,294 @@ +//! Models the [packets][0] sent between the server and client. +//! +//! [0]: https://euphoria.leet.nu/heim/api#packets + +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +use super::PacketType; + +/// A "raw" packet. +/// +/// This packet closely matches the [packet representation defined in the +/// API][0]. It can contain arbitrary data in the form of a JSON [`Value`]. It +/// can also contain both data and an error at the same time. +/// +/// In order to interpret this packet, you probably want to convert it to a +/// [`ParsedPacket`] using [`ParsedPacket::from_packet`]. +/// +/// [0]: https://euphoria.leet.nu/heim/api#packets +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Packet { + /// Client-generated id for associating replies with commands. + pub id: Option, + /// The type of the command, reply, or event. + pub r#type: PacketType, + /// The payload of the command, reply, or event. + pub data: Option, + /// This field appears in replies if a command fails. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// This field appears in replies to warn the client that it may be + /// flooding. + /// + /// The client should slow down its command rate. + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub throttled: bool, + /// If throttled is true, this field describes why. + #[serde(skip_serializing_if = "Option::is_none")] + pub throttled_reason: Option, +} + +/// Models the relationship between command and reply types. +/// +/// This trait is useful for type-safe command-reply APIs. +pub trait Command { + /// The type of reply one can expect from the server when sending this + /// command. + type Reply; +} + +macro_rules! packets { + ( $( $mod:ident::$name:ident, )*) => { + /// A big enum containing most types of packet data. + #[derive(Debug, Clone)] + #[non_exhaustive] + pub enum Data { + $( $name(super::$mod::$name), )* + /// A valid type of packet data that this library does not model as + /// a struct. + Unimplemented(PacketType, Value), + } + + impl Data { + /// Interpret a JSON [`Value`] as packet data of a specific [`PacketType`]. + /// + /// This method may fail if the data is invalid. + 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(ptype, value), + }) + } + + /// Convert the packet data into a JSON [`Value`]. + /// + /// This method may fail if the data fails to serialize. + pub fn into_value(self) -> serde_json::Result { + Ok(match self { + $( Self::$name(p) => serde_json::to_value(p)?, )* + Self::Unimplemented(_, value) => value, + }) + } + + /// The [`PacketType`] of this packet data. + pub fn packet_type(&self) -> PacketType { + match self { + $( Self::$name(_) => PacketType::$name, )* + Self::Unimplemented(ptype, _) => *ptype, + } + } + } + + $( + impl From for Data { + fn from(p: super::$mod::$name) -> Self { + Self::$name(p) + } + } + + impl TryFrom for super::$mod::$name{ + type Error = (); + + fn try_from(value: Data) -> Result { + match value { + Data::$name(p) => Ok(p), + _ => Err(()) + } + } + } + )* + }; +} + +macro_rules! commands { + ( $( $cmd:ident => $rpl:ident, )* ) => { + $( + impl Command for super::$cmd { + type Reply = super::$rpl; + } + )* + }; +} + +packets! { + // Events + events::BounceEvent, + events::DisconnectEvent, + events::EditMessageEvent, + events::HelloEvent, + events::JoinEvent, + events::LoginEvent, + events::LogoutEvent, + events::NetworkEvent, + events::NickEvent, + events::PartEvent, + events::PingEvent, + events::PmInitiateEvent, + events::SendEvent, + events::SnapshotEvent, + // Session commands + session_cmds::Auth, + session_cmds::AuthReply, + session_cmds::Ping, + session_cmds::PingReply, + // Chat room commands + room_cmds::GetMessage, + room_cmds::GetMessageReply, + room_cmds::Log, + room_cmds::LogReply, + room_cmds::Nick, + room_cmds::NickReply, + room_cmds::PmInitiate, + room_cmds::PmInitiateReply, + room_cmds::Send, + room_cmds::SendReply, + room_cmds::Who, + room_cmds::WhoReply, + // Account commands + account_cmds::ChangeEmail, + account_cmds::ChangeEmailReply, + account_cmds::ChangeName, + account_cmds::ChangeNameReply, + account_cmds::ChangePassword, + account_cmds::ChangePasswordReply, + account_cmds::Login, + account_cmds::LoginReply, + account_cmds::Logout, + account_cmds::LogoutReply, + account_cmds::RegisterAccount, + account_cmds::RegisterAccountReply, + account_cmds::ResendVerificationEmail, + account_cmds::ResendVerificationEmailReply, + account_cmds::ResetPassword, + account_cmds::ResetPasswordReply, +} + +commands! { + // Session commands + Auth => AuthReply, + Ping => PingReply, + // Chat room commands + GetMessage => GetMessageReply, + Log => LogReply, + Nick => NickReply, + PmInitiate => PmInitiateReply, + Send => SendReply, + Who => WhoReply, + // Account commands + ChangeEmail => ChangeEmailReply, + ChangeName => ChangeNameReply, + ChangePassword => ChangePasswordReply, + Login => LoginReply, + Logout => LogoutReply, + RegisterAccount => RegisterAccountReply, + ResendVerificationEmail => ResendVerificationEmailReply, + ResetPassword => ResetPasswordReply, +} + +/// A fully parsed and interpreted packet. +/// +/// Compared to [`Packet`], this packet's representation more closely matches +/// the actual use of packets. +#[derive(Debug, Clone)] +pub struct ParsedPacket { + /// Client-generated id for associating replies with commands. + pub id: Option, + /// The type of the command, reply, or event. + pub r#type: PacketType, + /// The payload of the command, reply, or event, or an error message if the + /// command failed. + pub content: Result, + /// A warning to the client that it may be flooding. + /// + /// The client should slow down its command rate. + pub throttled: Option, +} + +impl ParsedPacket { + /// Convert a [`Packet`] into a [`ParsedPacket`]. + /// + /// This method may fail if the packet data is invalid. + 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.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, + }) + } + + /// Convert a [`ParsedPacket`] into a [`Packet`]. + /// + /// This method may fail if the packet data fails to serialize. + pub fn into_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.into_value()?), + error: None, + throttled, + throttled_reason, + }, + Err(error) => Packet { + id, + r#type, + data: None, + error: Some(error), + throttled, + throttled_reason, + }, + }) + } +} + +impl TryFrom for ParsedPacket { + type Error = serde_json::Error; + + fn try_from(value: Packet) -> Result { + Self::from_packet(value) + } +} + +impl TryFrom for Packet { + type Error = serde_json::Error; + + fn try_from(value: ParsedPacket) -> Result { + value.into_packet() + } +} diff --git a/src/api/room_cmds.rs b/src/api/room_cmds.rs index 0a2d553..f02d507 100644 --- a/src/api/room_cmds.rs +++ b/src/api/room_cmds.rs @@ -1,13 +1,17 @@ -//! Chat room commands. +//! Models [chat room commands][0] and their replies. //! //! These commands are available to the client once a session successfully joins //! a room. +//! +//! [0]: https://euphoria.leet.nu/heim/api#chat-room-commands use serde::{Deserialize, Serialize}; use super::{Message, MessageId, PmId, SessionId, SessionView, UserId}; /// Retrieve the full content of a single message in the room. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GetMessage { /// The id of the message to retrieve. @@ -23,6 +27,8 @@ pub struct GetMessageReply(pub Message); /// This can be used to supplement the log provided by /// [`SnapshotEvent`](super::SnapshotEvent) (for example, when scrolling back /// further in history). +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Log { /// Maximum number of messages to return (up to 1000). @@ -44,6 +50,8 @@ pub struct LogReply { /// /// This name applies to all messages sent during this session, until the nick /// command is called again. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Nick { /// The requested name (maximum length 36 bytes). @@ -68,6 +76,8 @@ pub struct NickReply { /// Constructs a virtual room for private messaging between the client and the /// given [`UserId`]. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PmInitiate { /// The id of the user to invite to chat privately. @@ -94,6 +104,8 @@ pub struct PmInitiateReply { /// The caller of this command will not receive the corresponding /// [`SendEvent`](super::SendEvent), but will receive the same information in /// the [`SendReply`]. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Send { /// The content of the message (client-defined). @@ -109,6 +121,8 @@ pub struct Send { pub struct SendReply(pub Message); /// Request a list of sessions currently joined in the room. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Who {} diff --git a/src/api/session_cmds.rs b/src/api/session_cmds.rs index 4dab0d4..e60a76c 100644 --- a/src/api/session_cmds.rs +++ b/src/api/session_cmds.rs @@ -1,7 +1,9 @@ -//! Session commands. +//! Models [session commands][0] and their replies. //! //! Session management commands are involved in the initial handshake and //! maintenance of a session. +//! +//! [0]: https://euphoria.leet.nu/heim/api#session-commands use serde::{Deserialize, Serialize}; @@ -11,6 +13,8 @@ use super::{AuthOption, Time}; /// /// This should be sent in response to a [`BounceEvent`](super::BounceEvent) at /// the beginning of a session. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Auth { /// The method of authentication. @@ -32,6 +36,8 @@ pub struct AuthReply { /// /// The server will send back a [`PingReply`] with the same timestamp as soon as /// possible. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Ping { /// An arbitrary value, intended to be a unix timestamp. diff --git a/src/api/types.rs b/src/api/types.rs index b1408a8..af5426b 100644 --- a/src/api/types.rs +++ b/src/api/types.rs @@ -1,20 +1,16 @@ -//! Field types. +//! Models the [field types][0]. +//! +//! [0]: https://euphoria.leet.nu/heim/api#field-types -// TODO Add newtype wrappers for different kinds of IDs? - -// Serde's derive macros generate this warning and I can't turn it off locally, -// so I'm turning it off for the entire module. -#![allow(clippy::use_self)] - -use std::num::ParseIntError; -use std::str::FromStr; -use std::{error, fmt}; +use std::{error, fmt, num::ParseIntError, str::FromStr}; use jiff::Timestamp; use serde::{de, ser, Deserialize, Serialize}; use serde_json::Value; /// Describes an account and its preferred name. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AccountView { /// The id of the account. @@ -24,6 +20,8 @@ pub struct AccountView { } /// Mode of authentication. +/// +/// #[derive(Debug, Clone, Copy, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum AuthOption { @@ -36,6 +34,8 @@ pub enum AuthOption { /// /// It corresponds to a chat message, or a post, or any broadcasted event in a /// room that should appear in the log. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Message { /// The id of the message (unique within a room). @@ -72,6 +72,8 @@ pub struct Message { /// 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 { @@ -250,6 +252,8 @@ impl fmt::Display for PacketType { } /// Describes an account to its owner. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PersonalAccountView { /// The id of the account. @@ -261,6 +265,8 @@ pub struct PersonalAccountView { } /// Describes a session and its identity. +/// +/// #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SessionView { /// The id of an agent or account (or bot). @@ -290,6 +296,8 @@ pub struct SessionView { /// A 13-character string, usually used as aunique identifier for some type of object. /// /// It is the base-36 encoding of an unsigned, 64-bit integer. +/// +/// #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Snowflake(pub u64); @@ -307,7 +315,7 @@ impl Snowflake { /// representation of message ids to suddenly use the upper parts of the /// range, and since message ids mostly consist of a timestamp, this /// approach should last until at least 2075. - pub const MAX: Self = Snowflake(i64::MAX as u64); + pub const MAX: Self = Self(i64::MAX as u64); } impl fmt::Display for Snowflake { @@ -324,6 +332,7 @@ impl fmt::Display for Snowflake { } } +/// An error that occurred while parsing a [`Snowflake`]. #[derive(Debug)] pub enum ParseSnowflakeError { InvalidLength(usize), @@ -365,7 +374,7 @@ impl FromStr for Snowflake { return Err(ParseSnowflakeError::InvalidLength(s.len())); } let n = u64::from_str_radix(s, 36)?; - Ok(Snowflake(n)) + Ok(Self(n)) } } @@ -402,6 +411,8 @@ impl<'de> Deserialize<'de> for Snowflake { /// Time is specified as a signed 64-bit integer, giving the number of seconds /// since the Unix Epoch. +/// +/// #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct Time(pub i64); @@ -426,6 +437,8 @@ impl Time { /// /// It is possible for this value to have no prefix and colon, and there is no /// fixed format for the unique value. +/// +/// #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub struct UserId(pub String); @@ -435,21 +448,27 @@ impl fmt::Display for UserId { } } +/// What kind of user a [`UserId`] is. #[derive(Debug, PartialEq, Eq)] -pub enum SessionType { +pub enum UserType { Agent, Account, Bot, } impl UserId { - pub fn session_type(&self) -> Option { + /// Retrieve the [`UserType`] of this user. + /// + /// This method can return [`None`] because user IDs used to have no + /// associated type. Such user IDs can still occur in old room logs, so + /// euphoxide supports them. + pub fn user_type(&self) -> Option { if self.0.starts_with("agent:") { - Some(SessionType::Agent) + Some(UserType::Agent) } else if self.0.starts_with("account:") { - Some(SessionType::Account) + Some(UserType::Account) } else if self.0.starts_with("bot:") { - Some(SessionType::Bot) + Some(UserType::Bot) } else { None } diff --git a/src/lib.rs b/src/lib.rs index adb9a54..e700633 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +pub mod api; mod emoji; pub use crate::emoji::Emoji;