Merge branch 'main' into fix/doc-improvements

This commit is contained in:
kozabrada123 2023-07-28 18:21:20 +02:00
commit dcfb59fca1
18 changed files with 264 additions and 42 deletions

9
Cargo.lock generated
View File

@ -189,6 +189,7 @@ dependencies = [
"async-trait", "async-trait",
"base64 0.21.2", "base64 0.21.2",
"bitflags 2.3.3", "bitflags 2.3.3",
"chorus-macros",
"chrono", "chrono",
"custom_error", "custom_error",
"futures-util", "futures-util",
@ -215,6 +216,14 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "chorus-macros"
version = "0.1.0"
dependencies = [
"quote",
"syn 2.0.26",
]
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.26" version = "0.4.26"

View File

@ -36,6 +36,7 @@ thiserror = "1.0.43"
jsonwebtoken = "8.3.0" jsonwebtoken = "8.3.0"
log = "0.4.19" log = "0.4.19"
async-trait = "0.1.71" async-trait = "0.1.71"
chorus-macros = {path = "chorus-macros"}
[dev-dependencies] [dev-dependencies]
tokio = {version = "1.29.1", features = ["full"]} tokio = {version = "1.29.1", features = ["full"]}

46
chorus-macros/Cargo.lock generated Normal file
View File

@ -0,0 +1,46 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "chorus-macros"
version = "0.1.0"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "2.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"

11
chorus-macros/Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "chorus-macros"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
quote = "1"
syn = "2"

18
chorus-macros/src/lib.rs Normal file
View File

@ -0,0 +1,18 @@
use proc_macro::TokenStream;
use quote::quote;
#[proc_macro_derive(Updateable)]
pub fn updateable_macro_derive(input: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse(input).unwrap();
let name = &ast.ident;
// No need for macro hygiene, we're only using this in chorus
quote! {
impl Updateable for #name {
fn id(&self) -> Snowflake {
self.id
}
}
}
.into()
}

View File

@ -6,9 +6,10 @@ use serde_json::to_string;
use crate::api::LimitType; use crate::api::LimitType;
use crate::errors::ChorusResult; use crate::errors::ChorusResult;
use crate::gateway::Gateway;
use crate::instance::{Instance, UserMeta}; use crate::instance::{Instance, UserMeta};
use crate::ratelimiter::ChorusRequest; use crate::ratelimiter::ChorusRequest;
use crate::types::{LoginResult, LoginSchema}; use crate::types::{GatewayIdentifyPayload, LoginResult, LoginSchema};
impl Instance { impl Instance {
/// Logs into an existing account on the spacebar server. /// Logs into an existing account on the spacebar server.
@ -23,7 +24,8 @@ impl Instance {
// We do not have a user yet, and the UserRateLimits will not be affected by a login // We do not have a user yet, and the UserRateLimits will not be affected by a login
// request (since login is an instance wide limit), which is why we are just cloning the // request (since login is an instance wide limit), which is why we are just cloning the
// instances' limits to pass them on as user_rate_limits later. // instances' limits to pass them on as user_rate_limits later.
let mut shell = UserMeta::shell(Rc::new(RefCell::new(self.clone())), "None".to_string()); let mut shell =
UserMeta::shell(Rc::new(RefCell::new(self.clone())), "None".to_string()).await;
let login_result = chorus_request let login_result = chorus_request
.deserialize_response::<LoginResult>(&mut shell) .deserialize_response::<LoginResult>(&mut shell)
.await?; .await?;
@ -31,12 +33,17 @@ impl Instance {
if self.limits_information.is_some() { if self.limits_information.is_some() {
self.limits_information.as_mut().unwrap().ratelimits = shell.limits.clone().unwrap(); self.limits_information.as_mut().unwrap().ratelimits = shell.limits.clone().unwrap();
} }
let mut identify = GatewayIdentifyPayload::common();
let gateway = Gateway::new(self.urls.wss.clone()).await.unwrap();
identify.token = login_result.token.clone();
gateway.send_identify(identify).await;
let user = UserMeta::new( let user = UserMeta::new(
Rc::new(RefCell::new(self.clone())), Rc::new(RefCell::new(self.clone())),
login_result.token, login_result.token,
self.clone_limits_if_some(), self.clone_limits_if_some(),
login_result.settings, login_result.settings,
object, object,
gateway,
); );
Ok(user) Ok(user)
} }

View File

@ -3,6 +3,8 @@ use std::{cell::RefCell, rc::Rc};
use reqwest::Client; use reqwest::Client;
use serde_json::to_string; use serde_json::to_string;
use crate::gateway::Gateway;
use crate::types::GatewayIdentifyPayload;
use crate::{ use crate::{
api::policies::instance::LimitType, api::policies::instance::LimitType,
errors::ChorusResult, errors::ChorusResult,
@ -27,7 +29,8 @@ impl Instance {
// We do not have a user yet, and the UserRateLimits will not be affected by a login // We do not have a user yet, and the UserRateLimits will not be affected by a login
// request (since register is an instance wide limit), which is why we are just cloning // request (since register is an instance wide limit), which is why we are just cloning
// the instances' limits to pass them on as user_rate_limits later. // the instances' limits to pass them on as user_rate_limits later.
let mut shell = UserMeta::shell(Rc::new(RefCell::new(self.clone())), "None".to_string()); let mut shell =
UserMeta::shell(Rc::new(RefCell::new(self.clone())), "None".to_string()).await;
let token = chorus_request let token = chorus_request
.deserialize_response::<Token>(&mut shell) .deserialize_response::<Token>(&mut shell)
.await? .await?
@ -37,12 +40,17 @@ impl Instance {
} }
let user_object = self.get_user(token.clone(), None).await.unwrap(); let user_object = self.get_user(token.clone(), None).await.unwrap();
let settings = UserMeta::get_settings(&token, &self.urls.api.clone(), self).await?; let settings = UserMeta::get_settings(&token, &self.urls.api.clone(), self).await?;
let mut identify = GatewayIdentifyPayload::common();
let gateway = Gateway::new(self.urls.wss.clone()).await.unwrap();
identify.token = token.clone();
gateway.send_identify(identify).await;
let user = UserMeta::new( let user = UserMeta::new(
Rc::new(RefCell::new(self.clone())), Rc::new(RefCell::new(self.clone())),
token.clone(), token.clone(),
self.clone_limits_if_some(), self.clone_limits_if_some(),
settings, settings,
user_object, user_object,
gateway,
); );
Ok(user) Ok(user)
} }

View File

@ -41,11 +41,11 @@ impl Channel {
/// Modifies a channel with the provided data. /// Modifies a channel with the provided data.
/// Replaces self with the new channel object. /// Replaces self with the new channel object.
pub async fn modify( pub async fn modify(
&mut self, &self,
modify_data: ChannelModifySchema, modify_data: ChannelModifySchema,
channel_id: Snowflake, channel_id: Snowflake,
user: &mut UserMeta, user: &mut UserMeta,
) -> ChorusResult<()> { ) -> ChorusResult<Channel> {
let chorus_request = ChorusRequest { let chorus_request = ChorusRequest {
request: Client::new() request: Client::new()
.patch(format!( .patch(format!(
@ -57,9 +57,7 @@ impl Channel {
.body(to_string(&modify_data).unwrap()), .body(to_string(&modify_data).unwrap()),
limit_type: LimitType::Channel(channel_id), limit_type: LimitType::Channel(channel_id),
}; };
let new_channel = chorus_request.deserialize_response::<Channel>(user).await?; chorus_request.deserialize_response::<Channel>(user).await
let _ = std::mem::replace(self, new_channel);
Ok(())
} }
/// Fetches recent messages from a channel. /// Fetches recent messages from a channel.

View File

@ -121,7 +121,8 @@ impl User {
let request: reqwest::RequestBuilder = Client::new() let request: reqwest::RequestBuilder = Client::new()
.get(format!("{}/users/@me/settings/", url_api)) .get(format!("{}/users/@me/settings/", url_api))
.bearer_auth(token); .bearer_auth(token);
let mut user = UserMeta::shell(Rc::new(RefCell::new(instance.clone())), token.clone()); let mut user =
UserMeta::shell(Rc::new(RefCell::new(instance.clone())), token.clone()).await;
let chorus_request = ChorusRequest { let chorus_request = ChorusRequest {
request, request,
limit_type: LimitType::Global, limit_type: LimitType::Global,
@ -148,7 +149,7 @@ impl Instance {
// # Notes // # Notes
// This function is a wrapper around [`User::get`]. // This function is a wrapper around [`User::get`].
pub async fn get_user(&mut self, token: String, id: Option<&String>) -> ChorusResult<User> { pub async fn get_user(&mut self, token: String, id: Option<&String>) -> ChorusResult<User> {
let mut user = UserMeta::shell(Rc::new(RefCell::new(self.clone())), token); let mut user = UserMeta::shell(Rc::new(RefCell::new(self.clone())), token).await;
let result = User::get(&mut user, id).await; let result = User::get(&mut user, id).await;
if self.limits_information.is_some() { if self.limits_information.is_some() {
self.limits_information.as_mut().unwrap().ratelimits = self.limits_information.as_mut().unwrap().ratelimits =

View File

@ -1,10 +1,14 @@
use crate::errors::GatewayError; use crate::errors::GatewayError;
use crate::gateway::events::Events; use crate::gateway::events::Events;
use crate::types; use crate::types::{self, Channel, ChannelUpdate, Snowflake};
use crate::types::WebSocketEvent; use crate::types::{UpdateMessage, WebSocketEvent};
use async_trait::async_trait; use async_trait::async_trait;
use std::any::Any;
use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use tokio::sync::watch;
use tokio::time::sleep_until; use tokio::time::sleep_until;
use futures_util::stream::SplitSink; use futures_util::stream::SplitSink;
@ -163,6 +167,12 @@ pub struct GatewayHandle {
pub handle: JoinHandle<()>, pub handle: JoinHandle<()>,
/// Tells gateway tasks to close /// Tells gateway tasks to close
kill_send: tokio::sync::broadcast::Sender<()>, kill_send: tokio::sync::broadcast::Sender<()>,
store: Arc<Mutex<HashMap<Snowflake, Box<dyn Send + Any>>>>,
}
/// An entity type which is supposed to be updateable via the Gateway. This is implemented for all such types chorus supports, implementing it for your own types is likely a mistake.
pub trait Updateable: 'static + Send + Sync {
fn id(&self) -> Snowflake;
} }
impl GatewayHandle { impl GatewayHandle {
@ -186,6 +196,27 @@ impl GatewayHandle {
.unwrap(); .unwrap();
} }
pub async fn observe<T: Updateable>(&self, object: T) -> watch::Receiver<T> {
let mut store = self.store.lock().await;
if let Some(channel) = store.get(&object.id()) {
let (_, rx) = channel
.downcast_ref::<(watch::Sender<T>, watch::Receiver<T>)>()
.unwrap_or_else(|| {
panic!(
"Snowflake {} already exists in the store, but it is not of type T.",
object.id()
)
});
rx.clone()
} else {
let id = object.id();
let channel = watch::channel(object);
let receiver = channel.1.clone();
store.insert(id, Box::new(channel));
receiver
}
}
/// Sends an identify event to the gateway /// Sends an identify event to the gateway
pub async fn send_identify(&self, to_send: types::GatewayIdentifyPayload) { pub async fn send_identify(&self, to_send: types::GatewayIdentifyPayload) {
let to_send_value = serde_json::to_value(&to_send).unwrap(); let to_send_value = serde_json::to_value(&to_send).unwrap();
@ -263,9 +294,9 @@ impl GatewayHandle {
} }
pub struct Gateway { pub struct Gateway {
pub events: Arc<Mutex<Events>>, events: Arc<Mutex<Events>>,
heartbeat_handler: HeartbeatHandler, heartbeat_handler: HeartbeatHandler,
pub websocket_send: Arc< websocket_send: Arc<
Mutex< Mutex<
SplitSink< SplitSink<
WebSocketStream<MaybeTlsStream<TcpStream>>, WebSocketStream<MaybeTlsStream<TcpStream>>,
@ -273,8 +304,9 @@ pub struct Gateway {
>, >,
>, >,
>, >,
pub websocket_receive: SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>, websocket_receive: SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>,
kill_send: tokio::sync::broadcast::Sender<()>, kill_send: tokio::sync::broadcast::Sender<()>,
store: Arc<Mutex<HashMap<Snowflake, Box<dyn Send + Any>>>>,
} }
impl Gateway { impl Gateway {
@ -325,6 +357,8 @@ impl Gateway {
let events = Events::default(); let events = Events::default();
let shared_events = Arc::new(Mutex::new(events)); let shared_events = Arc::new(Mutex::new(events));
let store = Arc::new(Mutex::new(HashMap::new()));
let mut gateway = Gateway { let mut gateway = Gateway {
events: shared_events.clone(), events: shared_events.clone(),
heartbeat_handler: HeartbeatHandler::new( heartbeat_handler: HeartbeatHandler::new(
@ -335,6 +369,7 @@ impl Gateway {
websocket_send: shared_websocket_send.clone(), websocket_send: shared_websocket_send.clone(),
websocket_receive, websocket_receive,
kill_send: kill_send.clone(), kill_send: kill_send.clone(),
store: store.clone(),
}; };
// Now we can continuously check for messages in a different task, since we aren't going to receive another hello // Now we can continuously check for messages in a different task, since we aren't going to receive another hello
@ -348,6 +383,7 @@ impl Gateway {
websocket_send: shared_websocket_send.clone(), websocket_send: shared_websocket_send.clone(),
handle, handle,
kill_send: kill_send.clone(), kill_send: kill_send.clone(),
store,
}) })
} }
@ -379,6 +415,7 @@ impl Gateway {
/// Deserializes and updates a dispatched event, when we already know its type; /// Deserializes and updates a dispatched event, when we already know its type;
/// (Called for every event in handle_message) /// (Called for every event in handle_message)
#[allow(dead_code)] // TODO: Remove this allow annotation
async fn handle_event<'a, T: WebSocketEvent + serde::Deserialize<'a>>( async fn handle_event<'a, T: WebSocketEvent + serde::Deserialize<'a>>(
data: &'a str, data: &'a str,
event: &mut GatewayEvent<T>, event: &mut GatewayEvent<T>,
@ -431,17 +468,25 @@ impl Gateway {
trace!("Gateway: Received {event_name}"); trace!("Gateway: Received {event_name}");
macro_rules! handle { macro_rules! handle {
($($name:literal => $($path:ident).+),*) => { ($($name:literal => $($path:ident).+ $( $message_type:ty: $update_type:ty)?),*) => {
match event_name.as_str() { match event_name.as_str() {
$($name => { $($name => {
let event = &mut self.events.lock().await.$($path).+; let event = &mut self.events.lock().await.$($path).+;
match serde_json::from_str(gateway_payload.event_data.unwrap().get()) {
let result = Err(err) => warn!("Failed to parse gateway event {event_name} ({err})"),
Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) Ok(message) => {
.await; $(
let message: $message_type = message;
if let Err(err) = result { if let Some(to_update) = self.store.lock().await.get(&message.id()) {
warn!("Failed to parse gateway event {event_name} ({err})"); if let Some((tx, _)) = to_update.downcast_ref::<(watch::Sender<$update_type>, watch::Receiver<$update_type>)>() {
tx.send_modify(|object| message.update(object));
} else {
warn!("Received {} for {}, but it has been observed to be a different type!", $name, message.id())
}
}
)?
event.notify(message).await;
}
} }
},)* },)*
"RESUMED" => (), "RESUMED" => (),
@ -482,7 +527,7 @@ impl Gateway {
"AUTO_MODERATION_RULE_DELETE" => auto_moderation.rule_delete, "AUTO_MODERATION_RULE_DELETE" => auto_moderation.rule_delete,
"AUTO_MODERATION_ACTION_EXECUTION" => auto_moderation.action_execution, "AUTO_MODERATION_ACTION_EXECUTION" => auto_moderation.action_execution,
"CHANNEL_CREATE" => channel.create, "CHANNEL_CREATE" => channel.create,
"CHANNEL_UPDATE" => channel.update, "CHANNEL_UPDATE" => channel.update ChannelUpdate: Channel,
"CHANNEL_UNREAD_UPDATE" => channel.unread_update, "CHANNEL_UNREAD_UPDATE" => channel.unread_update,
"CHANNEL_DELETE" => channel.delete, "CHANNEL_DELETE" => channel.delete,
"CHANNEL_PINS_UPDATE" => channel.pins_update, "CHANNEL_PINS_UPDATE" => channel.pins_update,

View File

@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize};
use crate::api::{Limit, LimitType}; use crate::api::{Limit, LimitType};
use crate::errors::ChorusResult; use crate::errors::ChorusResult;
use crate::gateway::{Gateway, GatewayHandle};
use crate::ratelimiter::ChorusRequest; use crate::ratelimiter::ChorusRequest;
use crate::types::types::subconfigs::limits::rates::RateLimits; use crate::types::types::subconfigs::limits::rates::RateLimits;
use crate::types::{GeneralConfiguration, User, UserSettings}; use crate::types::{GeneralConfiguration, User, UserSettings};
@ -81,13 +82,14 @@ impl fmt::Display for Token {
} }
} }
#[derive(Debug, Clone)] #[derive(Debug)]
pub struct UserMeta { pub struct UserMeta {
pub belongs_to: Rc<RefCell<Instance>>, pub belongs_to: Rc<RefCell<Instance>>,
pub token: String, pub token: String,
pub limits: Option<HashMap<LimitType, Limit>>, pub limits: Option<HashMap<LimitType, Limit>>,
pub settings: UserSettings, pub settings: UserSettings,
pub object: User, pub object: User,
pub gateway: GatewayHandle,
} }
impl UserMeta { impl UserMeta {
@ -105,6 +107,7 @@ impl UserMeta {
limits: Option<HashMap<LimitType, Limit>>, limits: Option<HashMap<LimitType, Limit>>,
settings: UserSettings, settings: UserSettings,
object: User, object: User,
gateway: GatewayHandle,
) -> UserMeta { ) -> UserMeta {
UserMeta { UserMeta {
belongs_to, belongs_to,
@ -112,19 +115,24 @@ impl UserMeta {
limits, limits,
settings, settings,
object, object,
gateway,
} }
} }
/// Creates a new 'shell' of a user. The user does not exist as an object, and exists so that you have /// Creates a new 'shell' of a user. The user does not exist as an object, and exists so that you have
/// a UserMeta object to make Rate Limited requests with. This is useful in scenarios like /// a UserMeta object to make Rate Limited requests with. This is useful in scenarios like
/// registering or logging in to the Instance, where you do not yet have a User object, but still /// registering or logging in to the Instance, where you do not yet have a User object, but still
/// need to make a RateLimited request. /// need to make a RateLimited request. To use the [`GatewayHandle`], you will have to identify
pub(crate) fn shell(instance: Rc<RefCell<Instance>>, token: String) -> UserMeta { /// first.
pub(crate) async fn shell(instance: Rc<RefCell<Instance>>, token: String) -> UserMeta {
let settings = UserSettings::default(); let settings = UserSettings::default();
let object = User::default(); let object = User::default();
let wss_url = instance.borrow().urls.wss.clone();
// Dummy gateway object
let gateway = Gateway::new(wss_url).await.unwrap();
UserMeta { UserMeta {
belongs_to: instance.clone(),
token, token,
belongs_to: instance.clone(),
limits: instance limits: instance
.borrow() .borrow()
.limits_information .limits_information
@ -132,6 +140,7 @@ impl UserMeta {
.map(|info| info.ratelimits.clone()), .map(|info| info.ratelimits.clone()),
settings, settings,
object, object,
gateway,
} }
} }
} }

View File

@ -1,14 +1,16 @@
use chorus_macros::Updateable;
use chrono::Utc; use chrono::Utc;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_aux::prelude::deserialize_string_from_number; use serde_aux::prelude::deserialize_string_from_number;
use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_repr::{Deserialize_repr, Serialize_repr};
use crate::gateway::Updateable;
use crate::types::{ use crate::types::{
entities::{GuildMember, User}, entities::{GuildMember, User},
utils::Snowflake, utils::Snowflake,
}; };
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] #[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Updateable)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct Channel { pub struct Channel {
pub application_id: Option<Snowflake>, pub application_id: Option<Snowflake>,

View File

@ -3,6 +3,8 @@ use crate::types::{entities::Channel, Snowflake};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::UpdateMessage;
#[derive(Debug, Default, Deserialize, Serialize)] #[derive(Debug, Default, Deserialize, Serialize)]
/// See <https://discord.com/developers/docs/topics/gateway-events#channel-pins-update> /// See <https://discord.com/developers/docs/topics/gateway-events#channel-pins-update>
pub struct ChannelPinsUpdate { pub struct ChannelPinsUpdate {
@ -31,6 +33,15 @@ pub struct ChannelUpdate {
impl WebSocketEvent for ChannelUpdate {} impl WebSocketEvent for ChannelUpdate {}
impl UpdateMessage<Channel> for ChannelUpdate {
fn update(&self, object_to_update: &mut Channel) {
*object_to_update = self.channel.clone();
}
fn id(&self) -> Snowflake {
self.channel.id
}
}
#[derive(Debug, Default, Deserialize, Serialize, Clone)] #[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// Officially undocumented. /// Officially undocumented.
/// Sends updates to client about a new message with its id /// Sends updates to client about a new message with its id

View File

@ -26,6 +26,10 @@ pub use user::*;
pub use voice::*; pub use voice::*;
pub use webhooks::*; pub use webhooks::*;
use crate::gateway::Updateable;
use super::Snowflake;
mod application; mod application;
mod auto_moderation; mod auto_moderation;
mod call; mod call;
@ -92,3 +96,23 @@ pub struct GatewayReceivePayload<'a> {
} }
impl<'a> WebSocketEvent for GatewayReceivePayload<'a> {} impl<'a> WebSocketEvent for GatewayReceivePayload<'a> {}
/// An [`UpdateMessage<T>`] represents a received Gateway Message which contains updated
/// information for an [`Updateable`] of Type T.
/// # Example:
/// ```rs
/// impl UpdateMessage<Channel> for ChannelUpdate {
/// fn update(...) {...}
/// fn id(...) {...}
/// }
/// ```
/// This would imply, that the [`WebSocketEvent`] "[`ChannelUpdate`]" contains new/updated information
/// about a [`Channel`]. The update method describes how this new information will be turned into
/// a [`Channel`] object.
pub(crate) trait UpdateMessage<T>: Clone
where
T: Updateable,
{
fn update(&self, object_to_update: &mut T);
fn id(&self) -> Snowflake;
}

View File

@ -28,10 +28,11 @@ async fn delete_channel() {
#[tokio::test] #[tokio::test]
async fn modify_channel() { async fn modify_channel() {
const CHANNEL_NAME: &str = "beepboop";
let mut bundle = common::setup().await; let mut bundle = common::setup().await;
let channel = &mut bundle.channel; let channel = &mut bundle.channel;
let modify_data: types::ChannelModifySchema = types::ChannelModifySchema { let modify_data: types::ChannelModifySchema = types::ChannelModifySchema {
name: Some("beepboop".to_string()), name: Some(CHANNEL_NAME.to_string()),
channel_type: None, channel_type: None,
topic: None, topic: None,
icon: None, icon: None,
@ -49,10 +50,10 @@ async fn modify_channel() {
default_thread_rate_limit_per_user: None, default_thread_rate_limit_per_user: None,
video_quality_mode: None, video_quality_mode: None,
}; };
Channel::modify(channel, modify_data, channel.id, &mut bundle.user) let modified_channel = Channel::modify(channel, modify_data, channel.id, &mut bundle.user)
.await .await
.unwrap(); .unwrap();
assert_eq!(channel.name, Some("beepboop".to_string())); assert_eq!(modified_channel.name, Some(CHANNEL_NAME.to_string()));
let permission_override = PermissionFlags::from_vec(Vec::from([ let permission_override = PermissionFlags::from_vec(Vec::from([
PermissionFlags::MANAGE_CHANNELS, PermissionFlags::MANAGE_CHANNELS,

View File

@ -1,3 +1,4 @@
use chorus::gateway::Gateway;
use chorus::{ use chorus::{
instance::{Instance, UserMeta}, instance::{Instance, UserMeta},
types::{ types::{
@ -18,8 +19,8 @@ pub(crate) struct TestBundle {
pub channel: Channel, pub channel: Channel,
} }
impl TestBundle {
#[allow(unused)] #[allow(unused)]
impl TestBundle {
pub(crate) async fn create_user(&mut self, username: &str) -> UserMeta { pub(crate) async fn create_user(&mut self, username: &str) -> UserMeta {
let register_schema = RegisterSchema { let register_schema = RegisterSchema {
username: username.to_string(), username: username.to_string(),
@ -32,6 +33,16 @@ impl TestBundle {
.await .await
.unwrap() .unwrap()
} }
pub(crate) async fn clone_user_without_gateway(&self) -> UserMeta {
UserMeta {
belongs_to: self.user.belongs_to.clone(),
token: self.user.token.clone(),
limits: self.user.limits.clone(),
settings: self.user.settings.clone(),
object: self.user.object.clone(),
gateway: Gateway::new(self.instance.urls.wss.clone()).await.unwrap(),
}
}
} }
// Set up a test by creating an Instance and a User. Reduces Test boilerplate. // Set up a test by creating an Instance and a User. Reduces Test boilerplate.

View File

@ -1,13 +1,15 @@
mod common; mod common;
use chorus::gateway::*; use chorus::gateway::*;
use chorus::types; use chorus::types::{self, Channel};
#[tokio::test] #[tokio::test]
/// Tests establishing a connection (hello and heartbeats) on the local gateway; /// Tests establishing a connection (hello and heartbeats) on the local gateway;
async fn test_gateway_establish() { async fn test_gateway_establish() {
let bundle = common::setup().await; let bundle = common::setup().await;
Gateway::new(bundle.urls.wss).await.unwrap(); Gateway::new(bundle.urls.wss.clone()).await.unwrap();
common::teardown(bundle).await
} }
#[tokio::test] #[tokio::test]
@ -15,10 +17,30 @@ async fn test_gateway_establish() {
async fn test_gateway_authenticate() { async fn test_gateway_authenticate() {
let bundle = common::setup().await; let bundle = common::setup().await;
let gateway = Gateway::new(bundle.urls.wss).await.unwrap(); let gateway = Gateway::new(bundle.urls.wss.clone()).await.unwrap();
let mut identify = types::GatewayIdentifyPayload::common(); let mut identify = types::GatewayIdentifyPayload::common();
identify.token = bundle.user.token; identify.token = bundle.user.token.clone();
gateway.send_identify(identify).await; gateway.send_identify(identify).await;
common::teardown(bundle).await
}
#[tokio::test]
async fn test_self_updating_structs() {
let mut bundle = common::setup().await;
let channel_updater = bundle.user.gateway.observe(bundle.channel.clone()).await;
let received_channel = channel_updater.borrow().clone();
assert_eq!(received_channel, bundle.channel);
let channel = &mut bundle.channel;
let modify_data = types::ChannelModifySchema {
name: Some("beepboop".to_string()),
..Default::default()
};
Channel::modify(channel, modify_data, channel.id, &mut bundle.user)
.await
.unwrap();
let received_channel = channel_updater.borrow();
assert_eq!(received_channel.name.as_ref().unwrap(), "beepboop");
common::teardown(bundle).await
} }

View File

@ -1,14 +1,12 @@
use chorus::types::CreateChannelInviteSchema;
mod common; mod common;
use chorus::types::CreateChannelInviteSchema;
#[tokio::test] #[tokio::test]
async fn create_accept_invite() { async fn create_accept_invite() {
let mut bundle = common::setup().await; let mut bundle = common::setup().await;
let channel = bundle.channel.clone(); let channel = bundle.channel.clone();
let mut user = bundle.user.clone();
let create_channel_invite_schema = CreateChannelInviteSchema::default();
let mut other_user = bundle.create_user("testuser1312").await; let mut other_user = bundle.create_user("testuser1312").await;
let user = &mut bundle.user;
let create_channel_invite_schema = CreateChannelInviteSchema::default();
assert!(chorus::types::Guild::get(bundle.guild.id, &mut other_user) assert!(chorus::types::Guild::get(bundle.guild.id, &mut other_user)
.await .await
.is_err()); .is_err());