Refactor and document api module
This commit is contained in:
parent
5ddffb510d
commit
92ea7f0aa0
10 changed files with 428 additions and 272 deletions
|
|
@ -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]
|
||||
|
|
|
|||
21
src/api.rs
21
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::*};
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#change-email>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#change-name>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#change-password>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#login>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#logout>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#register-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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#resend-verification-email>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#reset-password>
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ResetPassword {
|
||||
pub namespace: String,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#bounce-event>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#disconnect-event>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#edit-message-event>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#hello-event>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#join-event>
|
||||
#[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).
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#login-event>
|
||||
#[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).
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#logout-event>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#network-event>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#nick-event>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#part-event>
|
||||
#[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).
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#ping-event>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#pm-initiate-event>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#send-event>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#snapshot-event>
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SnapshotEvent {
|
||||
/// The id of the agent or account logged into this session.
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
pub r#type: PacketType,
|
||||
pub data: Option<Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub error: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub throttled: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub throttled_reason: Option<String>,
|
||||
}
|
||||
|
||||
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<Self> {
|
||||
Ok(match ptype {
|
||||
$( PacketType::$name => Self::$name(serde_json::from_value(value)?), )*
|
||||
_ => Self::Unimplemented,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn into_value(self) -> serde_json::Result<Value> {
|
||||
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<super::$name> for Data {
|
||||
fn from(p: super::$name) -> Self {
|
||||
Self::$name(p)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Data> for super::$name{
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: Data) -> Result<Self, Self::Error> {
|
||||
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<String>,
|
||||
pub r#type: PacketType,
|
||||
pub content: Result<Data, String>,
|
||||
pub throttled: Option<String>,
|
||||
}
|
||||
|
||||
impl ParsedPacket {
|
||||
pub fn from_packet(packet: Packet) -> serde_json::Result<Self> {
|
||||
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<Packet> {
|
||||
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,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
294
src/api/packets.rs
Normal file
294
src/api/packets.rs
Normal file
|
|
@ -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<String>,
|
||||
/// The type of the command, reply, or event.
|
||||
pub r#type: PacketType,
|
||||
/// The payload of the command, reply, or event.
|
||||
pub data: Option<Value>,
|
||||
/// This field appears in replies if a command fails.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub error: Option<String>,
|
||||
/// 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<String>,
|
||||
}
|
||||
|
||||
/// 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<Self> {
|
||||
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<Value> {
|
||||
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<super::$mod::$name> for Data {
|
||||
fn from(p: super::$mod::$name) -> Self {
|
||||
Self::$name(p)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Data> for super::$mod::$name{
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: Data) -> Result<Self, Self::Error> {
|
||||
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<String>,
|
||||
/// 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<Data, String>,
|
||||
/// A warning to the client that it may be flooding.
|
||||
///
|
||||
/// The client should slow down its command rate.
|
||||
pub throttled: Option<String>,
|
||||
}
|
||||
|
||||
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<Self> {
|
||||
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<Packet> {
|
||||
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<Packet> for ParsedPacket {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(value: Packet) -> Result<Self, Self::Error> {
|
||||
Self::from_packet(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ParsedPacket> for Packet {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(value: ParsedPacket) -> Result<Self, Self::Error> {
|
||||
value.into_packet()
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#get-message>
|
||||
#[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).
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#log>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#nick>
|
||||
#[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`].
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#pm-initiate>
|
||||
#[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`].
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#send>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#who>
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Who {}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#auth>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#ping>
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Ping {
|
||||
/// An arbitrary value, intended to be a unix timestamp.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#accountview>
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AccountView {
|
||||
/// The id of the account.
|
||||
|
|
@ -24,6 +20,8 @@ pub struct AccountView {
|
|||
}
|
||||
|
||||
/// Mode of authentication.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#authoption>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#message>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#packettype>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#personalaccountview>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#sessionview>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#snowflake>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#time>
|
||||
#[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.
|
||||
///
|
||||
/// <https://euphoria.leet.nu/heim/api#userid>
|
||||
#[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<SessionType> {
|
||||
/// 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<UserType> {
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
pub mod api;
|
||||
mod emoji;
|
||||
|
||||
pub use crate::emoji::Emoji;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue