Improved auto updating structs (#184)

This PR does the following things:
- Give `UserMeta` a `GatewayHandle`
- Change struct-fields which are supposed to be "shared" to
`Arc<RwLock<T>>`. "shared" struct-fields are things like a `Channel`
object, which may exist both on their own, and inside a `Guild`s struct
fields. Due to sharing this field, we only need to update a single
object to propagate the changes everywhere.
- Some light refactorings across the project. Example: change
`UserMeta::get()` to `UserMeta::get_user()`
- Add `Composite` trait and proc macro. `Composites` are `Updateable`
(uniquely identifiable) objects, which contain at least one other shared
(`Arc<RwLock<T>>`) field. This trait/macro provides functionality to not
only watch an object, but also all observable struct fields in that
object.
- Gets rid of tokio::channel in favor of Arc<RwLock<T>>

To-do before merging:

- [x] Properly test `Composite` functionality to ensure it works as
intended

I'd love a second pair of eyes over this, especially in case I forgot
something major ^^'
This commit is contained in:
Flori 2023-08-17 19:40:47 +02:00 committed by GitHub
commit ecdab68677
43 changed files with 626 additions and 239 deletions

35
Cargo.lock generated
View File

@ -77,7 +77,7 @@ checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.26", "syn 2.0.28",
] ]
[[package]] [[package]]
@ -220,8 +220,9 @@ dependencies = [
name = "chorus-macros" name = "chorus-macros"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"async-trait",
"quote", "quote",
"syn 2.0.26", "syn 2.0.28",
] ]
[[package]] [[package]]
@ -361,7 +362,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim",
"syn 2.0.26", "syn 2.0.28",
] ]
[[package]] [[package]]
@ -372,7 +373,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn 2.0.26", "syn 2.0.28",
] ]
[[package]] [[package]]
@ -569,7 +570,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.26", "syn 2.0.28",
] ]
[[package]] [[package]]
@ -1186,7 +1187,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.26", "syn 2.0.28",
] ]
[[package]] [[package]]
@ -1302,7 +1303,7 @@ checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.26", "syn 2.0.28",
] ]
[[package]] [[package]]
@ -1683,7 +1684,7 @@ checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.26", "syn 2.0.28",
] ]
[[package]] [[package]]
@ -1705,7 +1706,7 @@ checksum = "1d89a8107374290037607734c0b73a85db7ed80cae314b3c5791f192a496e731"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.26", "syn 2.0.28",
] ]
[[package]] [[package]]
@ -1745,7 +1746,7 @@ dependencies = [
"darling", "darling",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.26", "syn 2.0.28",
] ]
[[package]] [[package]]
@ -1978,9 +1979,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.26" version = "2.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2018,7 +2019,7 @@ checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.26", "syn 2.0.28",
] ]
[[package]] [[package]]
@ -2102,7 +2103,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.26", "syn 2.0.28",
] ]
[[package]] [[package]]
@ -2206,7 +2207,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.26", "syn 2.0.28",
] ]
[[package]] [[package]]
@ -2384,7 +2385,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.26", "syn 2.0.28",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -2418,7 +2419,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.26", "syn 2.0.28",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]

View File

@ -2,10 +2,22 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "async-trait"
version = "0.1.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "chorus-macros" name = "chorus-macros"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"async-trait",
"quote", "quote",
"syn", "syn",
] ]

View File

@ -9,3 +9,4 @@ proc-macro = true
[dependencies] [dependencies]
quote = "1" quote = "1"
syn = "2" syn = "2"
async-trait = "0.1.71"

View File

@ -1,5 +1,6 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Field, Fields, FieldsNamed};
#[proc_macro_derive(Updateable)] #[proc_macro_derive(Updateable)]
pub fn updateable_macro_derive(input: TokenStream) -> TokenStream { pub fn updateable_macro_derive(input: TokenStream) -> TokenStream {
@ -16,3 +17,106 @@ pub fn updateable_macro_derive(input: TokenStream) -> TokenStream {
} }
.into() .into()
} }
#[proc_macro_derive(JsonField)]
pub fn jsonfield_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 JsonField for #name {
fn get_json(&self) -> String {
self.json.clone()
}
fn set_json(&mut self, json: String) {
self.json = json;
}
}
}
.into()
}
#[proc_macro_attribute]
pub fn observe_option(_args: TokenStream, input: TokenStream) -> TokenStream {
input
}
#[proc_macro_attribute]
pub fn observe_option_vec(_args: TokenStream, input: TokenStream) -> TokenStream {
input
}
#[proc_macro_attribute]
pub fn observe(_args: TokenStream, input: TokenStream) -> TokenStream {
input
}
#[proc_macro_attribute]
pub fn observe_vec(_args: TokenStream, input: TokenStream) -> TokenStream {
input
}
#[proc_macro_derive(
Composite,
attributes(observe_option_vec, observe_option, observe, observe_vec)
)]
pub fn composite_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let process_field = |field: &Field| {
let field_name = &field.ident;
let attrs = &field.attrs;
let observe_option = attrs
.iter()
.any(|attr| attr.path().is_ident("observe_option"));
let observe_option_vec = attrs
.iter()
.any(|attr| attr.path().is_ident("observe_option_vec"));
let observe = attrs.iter().any(|attr| attr.path().is_ident("observe"));
let observe_vec = attrs.iter().any(|attr| attr.path().is_ident("observe_vec"));
match (observe_option, observe_option_vec, observe, observe_vec) {
(true, _, _, _) => quote! {
#field_name: Self::option_observe_fn(self.#field_name, gateway).await
},
(_, true, _, _) => quote! {
#field_name: Self::option_vec_observe_fn(self.#field_name, gateway).await
},
(_, _, true, _) => quote! {
#field_name: Self::value_observe_fn(self.#field_name, gateway).await
},
(_, _, _, true) => quote! {
#field_name: Self::vec_observe_fn(self.#field_name, gateway).await
},
_ => quote! {
#field_name: self.#field_name
},
}
};
match &input.data {
Data::Struct(data) => match &data.fields {
Fields::Named(FieldsNamed { named, .. }) => {
let field_exprs = named.iter().map(process_field);
let ident = &input.ident;
let expanded = quote! {
#[async_trait::async_trait(?Send)]
impl<T: Updateable + Clone + Debug> Composite<T> for #ident {
async fn watch_whole(self, gateway: &GatewayHandle) -> Self {
Self {
#(#field_exprs,)*
}
}
}
};
TokenStream::from(expanded)
}
_ => panic!("Composite derive macro only supports named fields"),
},
_ => panic!("Composite derive macro only supports structs"),
}
}

View File

@ -1,6 +1,6 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, RwLock};
use reqwest::Client; use reqwest::Client;
use serde_json::to_string; use serde_json::to_string;
@ -46,7 +46,7 @@ impl Instance {
login_result.token, login_result.token,
self.clone_limits_if_some(), self.clone_limits_if_some(),
login_result.settings, login_result.settings,
Arc::new(Mutex::new(object)), Arc::new(RwLock::new(object)),
gateway, gateway,
); );
Ok(user) Ok(user)

View File

@ -1,4 +1,4 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, RwLock};
use std::{cell::RefCell, rc::Rc}; use std::{cell::RefCell, rc::Rc};
use reqwest::Client; use reqwest::Client;
@ -52,8 +52,8 @@ impl Instance {
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(),
Arc::new(Mutex::new(settings)), Arc::new(RwLock::new(settings)),
Arc::new(Mutex::new(user_object)), Arc::new(RwLock::new(user_object)),
gateway, gateway,
); );
Ok(user) Ok(user)

View File

@ -64,9 +64,9 @@ impl Channel {
pub async fn modify( pub async fn modify(
&self, &self,
modify_data: ChannelModifySchema, modify_data: ChannelModifySchema,
channel_id: Snowflake,
user: &mut UserMeta, user: &mut UserMeta,
) -> ChorusResult<Channel> { ) -> ChorusResult<Channel> {
let channel_id = self.id;
let chorus_request = ChorusRequest { let chorus_request = ChorusRequest {
request: Client::new() request: Client::new()
.patch(format!( .patch(format!(

View File

@ -120,13 +120,13 @@ impl types::RoleObject {
.await .await
} }
/// Updates a role in a guild. /// Modifies a role in a guild.
/// ///
/// Requires the [`MANAGE_ROLES`](crate::types::PermissionFlags::MANAGE_ROLES) permission. /// Requires the [`MANAGE_ROLES`](crate::types::PermissionFlags::MANAGE_ROLES) permission.
/// ///
/// # Reference /// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/guild#modify-guild-role> /// See <https://discord-userdoccers.vercel.app/resources/guild#modify-guild-role>
pub async fn update( pub async fn modify(
user: &mut UserMeta, user: &mut UserMeta,
guild_id: Snowflake, guild_id: Snowflake,
role_id: Snowflake, role_id: Snowflake,

View File

@ -1,4 +1,3 @@
use std::sync::{Arc, Mutex};
use std::{cell::RefCell, rc::Rc}; use std::{cell::RefCell, rc::Rc};
use reqwest::Client; use reqwest::Client;
@ -21,8 +20,8 @@ impl UserMeta {
/// # Reference /// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/user#get-user> and /// See <https://discord-userdoccers.vercel.app/resources/user#get-user> and
/// <https://discord-userdoccers.vercel.app/resources/user#get-current-user> /// <https://discord-userdoccers.vercel.app/resources/user#get-current-user>
pub async fn get(user: &mut UserMeta, id: Option<&String>) -> ChorusResult<User> { pub async fn get_user(&mut self, id: Option<&String>) -> ChorusResult<User> {
User::get(user, id).await User::get(self, id).await
} }
/// Gets the user's settings. /// Gets the user's settings.
@ -56,12 +55,7 @@ impl UserMeta {
request, request,
limit_type: LimitType::default(), limit_type: LimitType::default(),
}; };
let user_updated = chorus_request chorus_request.deserialize_response::<User>(self).await
.deserialize_response::<User>(self)
.await
.unwrap();
self.object = Arc::new(Mutex::new(user_updated.clone()));
Ok(user_updated)
} }
/// Deletes the user from the Instance. /// Deletes the user from the Instance.

View File

@ -1,14 +1,15 @@
use crate::errors::GatewayError; use crate::errors::GatewayError;
use crate::gateway::events::Events; use crate::gateway::events::Events;
use crate::types::{self, Channel, ChannelUpdate, Snowflake}; use crate::types::{
use crate::types::{UpdateMessage, WebSocketEvent}; self, Channel, ChannelUpdate, Composite, Guild, GuildRoleCreate, GuildRoleUpdate, JsonField,
RoleObject, Snowflake, UpdateMessage, WebSocketEvent,
};
use async_trait::async_trait; use async_trait::async_trait;
use std::any::Any; use std::any::Any;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Debug; use std::fmt::Debug;
use std::sync::Arc; use std::sync::{Arc, RwLock};
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;
@ -148,6 +149,8 @@ impl GatewayMessage {
} }
} }
pub type ObservableObject = dyn Send + Sync + Any;
/// Represents a handle to a Gateway connection. A Gateway connection will create observable /// Represents a handle to a Gateway connection. A Gateway connection will create observable
/// [`GatewayEvents`](GatewayEvent), which you can subscribe to. Gateway events include all currently /// [`GatewayEvents`](GatewayEvent), which you can subscribe to. Gateway events include all currently
/// implemented types with the trait [`WebSocketEvent`] /// implemented types with the trait [`WebSocketEvent`]
@ -167,7 +170,7 @@ 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>>>>, pub(crate) store: Arc<Mutex<HashMap<Snowflake, Arc<RwLock<ObservableObject>>>>>,
} }
/// 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. /// 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.
@ -196,27 +199,57 @@ impl GatewayHandle {
.unwrap(); .unwrap();
} }
pub async fn observe<T: Updateable>(&self, object: T) -> watch::Receiver<T> { pub async fn observe<T: Updateable + Clone + Debug + Composite<T>>(
&self,
object: Arc<RwLock<T>>,
) -> Arc<RwLock<T>> {
let mut store = self.store.lock().await; let mut store = self.store.lock().await;
if let Some(channel) = store.get(&object.id()) { let id = object.read().unwrap().id();
let (_, rx) = channel if let Some(channel) = store.get(&id) {
.downcast_ref::<(watch::Sender<T>, watch::Receiver<T>)>() let object = channel.clone();
drop(store);
object
.read()
.unwrap()
.downcast_ref::<T>()
.unwrap_or_else(|| { .unwrap_or_else(|| {
panic!( panic!(
"Snowflake {} already exists in the store, but it is not of type T.", "Snowflake {} already exists in the store, but it is not of type T.",
object.id() id
) )
}); });
rx.clone() let ptr = Arc::into_raw(object.clone());
// SAFETY:
// - We have just checked that the typeid of the `dyn Any ...` matches that of `T`.
// - This operation doesn't read or write any shared data, and thus cannot cause a data race
// - The reference count is not being modified
let downcasted = unsafe { Arc::from_raw(ptr as *const RwLock<T>).clone() };
let object = downcasted.read().unwrap().clone();
let watched_object = object.watch_whole(self).await;
*downcasted.write().unwrap() = watched_object;
downcasted
} else { } else {
let id = object.id(); let id = object.read().unwrap().id();
let channel = watch::channel(object); let object = object.read().unwrap().clone();
let receiver = channel.1.clone(); let object = object.clone().watch_whole(self).await;
store.insert(id, Box::new(channel)); let wrapped = Arc::new(RwLock::new(object));
receiver store.insert(id, wrapped.clone());
wrapped
} }
} }
/// Recursively observes and updates all updateable fields on the struct T. Returns an object `T`
/// with all of its observable fields being observed.
pub async fn observe_and_into_inner<T: Updateable + Clone + Debug + Composite<T>>(
&self,
object: Arc<RwLock<T>>,
) -> T {
let channel = self.observe(object.clone()).await;
let object = channel.read().unwrap().clone();
object
}
/// 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();
@ -306,7 +339,7 @@ pub struct Gateway {
>, >,
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>>>>, store: Arc<Mutex<HashMap<Snowflake, Arc<RwLock<ObservableObject>>>>>,
} }
impl Gateway { impl Gateway {
@ -472,14 +505,26 @@ impl Gateway {
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 json = gateway_payload.event_data.unwrap().get();
match serde_json::from_str(json) {
Err(err) => warn!("Failed to parse gateway event {event_name} ({err})"), Err(err) => warn!("Failed to parse gateway event {event_name} ({err})"),
Ok(message) => { Ok(message) => {
$( $(
let message: $message_type = message; let mut message: $message_type = message;
if let Some(to_update) = self.store.lock().await.get(&message.id()) { let store = self.store.lock().await;
if let Some((tx, _)) = to_update.downcast_ref::<(watch::Sender<$update_type>, watch::Receiver<$update_type>)>() { if let Some(to_update) = store.get(&message.id()) {
tx.send_modify(|object| message.update(object)); let object = to_update.clone();
let inner_object = object.read().unwrap();
if let Some(_) = inner_object.downcast_ref::<$update_type>() {
let ptr = Arc::into_raw(object.clone());
// SAFETY:
// - We have just checked that the typeid of the `dyn Any ...` matches that of `T`.
// - This operation doesn't read or write any shared data, and thus cannot cause a data race
// - The reference count is not being modified
let downcasted = unsafe { Arc::from_raw(ptr as *const RwLock<$update_type>).clone() };
drop(inner_object);
message.set_json(json.to_string());
message.update(downcasted.clone());
} else { } else {
warn!("Received {} for {}, but it has been observed to be a different type!", $name, message.id()) warn!("Received {} for {}, but it has been observed to be a different type!", $name, message.id())
} }
@ -553,8 +598,8 @@ impl Gateway {
"GUILD_MEMBER_REMOVE" => guild.member_remove, "GUILD_MEMBER_REMOVE" => guild.member_remove,
"GUILD_MEMBER_UPDATE" => guild.member_update, "GUILD_MEMBER_UPDATE" => guild.member_update,
"GUILD_MEMBERS_CHUNK" => guild.members_chunk, "GUILD_MEMBERS_CHUNK" => guild.members_chunk,
"GUILD_ROLE_CREATE" => guild.role_create, "GUILD_ROLE_CREATE" => guild.role_create GuildRoleCreate: Guild,
"GUILD_ROLE_UPDATE" => guild.role_update, "GUILD_ROLE_UPDATE" => guild.role_update GuildRoleUpdate: RoleObject,
"GUILD_ROLE_DELETE" => guild.role_delete, "GUILD_ROLE_DELETE" => guild.role_delete,
"GUILD_SCHEDULED_EVENT_CREATE" => guild.role_scheduled_event_create, "GUILD_SCHEDULED_EVENT_CREATE" => guild.role_scheduled_event_create,
"GUILD_SCHEDULED_EVENT_UPDATE" => guild.role_scheduled_event_update, "GUILD_SCHEDULED_EVENT_UPDATE" => guild.role_scheduled_event_update,

View File

@ -2,7 +2,7 @@ use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt; use std::fmt;
use std::rc::Rc; use std::rc::Rc;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, RwLock};
use reqwest::Client; use reqwest::Client;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -91,8 +91,8 @@ 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: Arc<Mutex<UserSettings>>, pub settings: Arc<RwLock<UserSettings>>,
pub object: Arc<Mutex<User>>, pub object: Arc<RwLock<User>>,
pub gateway: GatewayHandle, pub gateway: GatewayHandle,
} }
@ -114,8 +114,8 @@ impl UserMeta {
belongs_to: Rc<RefCell<Instance>>, belongs_to: Rc<RefCell<Instance>>,
token: String, token: String,
limits: Option<HashMap<LimitType, Limit>>, limits: Option<HashMap<LimitType, Limit>>,
settings: Arc<Mutex<UserSettings>>, settings: Arc<RwLock<UserSettings>>,
object: Arc<Mutex<User>>, object: Arc<RwLock<User>>,
gateway: GatewayHandle, gateway: GatewayHandle,
) -> UserMeta { ) -> UserMeta {
UserMeta { UserMeta {
@ -134,8 +134,8 @@ impl UserMeta {
/// need to make a RateLimited request. To use the [`GatewayHandle`], you will have to identify /// need to make a RateLimited request. To use the [`GatewayHandle`], you will have to identify
/// first. /// first.
pub(crate) async fn shell(instance: Rc<RefCell<Instance>>, token: String) -> UserMeta { pub(crate) async fn shell(instance: Rc<RefCell<Instance>>, token: String) -> UserMeta {
let settings = Arc::new(Mutex::new(UserSettings::default())); let settings = Arc::new(RwLock::new(UserSettings::default()));
let object = Arc::new(Mutex::new(User::default())); let object = Arc::new(RwLock::new(User::default()));
let wss_url = instance.borrow().urls.wss.clone(); let wss_url = instance.borrow().urls.wss.clone();
// Dummy gateway object // Dummy gateway object
let gateway = Gateway::new(wss_url).await.unwrap(); let gateway = Gateway::new(wss_url).await.unwrap();

View File

@ -1,4 +1,4 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, RwLock};
use bitflags::bitflags; use bitflags::bitflags;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -27,7 +27,7 @@ pub struct Application {
pub bot_require_code_grant: bool, pub bot_require_code_grant: bool,
pub verify_key: String, pub verify_key: String,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub owner: Arc<Mutex<User>>, pub owner: Arc<RwLock<User>>,
pub flags: u64, pub flags: u64,
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
pub redirect_uris: Option<sqlx::types::Json<Vec<String>>>, pub redirect_uris: Option<sqlx::types::Json<Vec<String>>>,
@ -49,7 +49,7 @@ pub struct Application {
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
pub install_params: Option<sqlx::types::Json<InstallParams>>, pub install_params: Option<sqlx::types::Json<InstallParams>>,
#[cfg(not(feature = "sqlx"))] #[cfg(not(feature = "sqlx"))]
pub install_params: Option<Arc<Mutex<InstallParams>>>, pub install_params: Option<Arc<RwLock<InstallParams>>>,
pub terms_of_service_url: Option<String>, pub terms_of_service_url: Option<String>,
pub privacy_policy_url: Option<String>, pub privacy_policy_url: Option<String>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
@ -142,7 +142,7 @@ pub struct ApplicationCommand {
pub application_id: Snowflake, pub application_id: Snowflake,
pub name: String, pub name: String,
pub description: String, pub description: String,
pub options: Vec<Arc<Mutex<ApplicationCommandOption>>>, pub options: Vec<Arc<RwLock<ApplicationCommandOption>>>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -154,7 +154,7 @@ pub struct ApplicationCommandOption {
pub description: String, pub description: String,
pub required: bool, pub required: bool,
pub choices: Vec<ApplicationCommandOptionChoice>, pub choices: Vec<ApplicationCommandOptionChoice>,
pub options: Arc<Mutex<Vec<ApplicationCommandOption>>>, pub options: Arc<RwLock<Vec<ApplicationCommandOption>>>,
} }
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
@ -190,14 +190,14 @@ pub enum ApplicationCommandOptionType {
pub struct ApplicationCommandInteractionData { pub struct ApplicationCommandInteractionData {
pub id: Snowflake, pub id: Snowflake,
pub name: String, pub name: String,
pub options: Vec<Arc<Mutex<ApplicationCommandInteractionDataOption>>>, pub options: Vec<Arc<RwLock<ApplicationCommandInteractionDataOption>>>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApplicationCommandInteractionDataOption { pub struct ApplicationCommandInteractionDataOption {
pub name: String, pub name: String,
pub value: Value, pub value: Value,
pub options: Vec<Arc<Mutex<ApplicationCommandInteractionDataOption>>>, pub options: Vec<Arc<RwLock<ApplicationCommandInteractionDataOption>>>,
} }
#[derive(Debug, Default, Clone, Serialize, Deserialize)] #[derive(Debug, Default, Clone, Serialize, Deserialize)]
@ -206,7 +206,7 @@ pub struct GuildApplicationCommandPermissions {
pub id: Snowflake, pub id: Snowflake,
pub application_id: Snowflake, pub application_id: Snowflake,
pub guild_id: Snowflake, pub guild_id: Snowflake,
pub permissions: Vec<Arc<Mutex<ApplicationCommandPermission>>>, pub permissions: Vec<Arc<RwLock<ApplicationCommandPermission>>>,
} }
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]

View File

@ -1,4 +1,4 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, RwLock};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -8,7 +8,7 @@ use crate::types::utils::Snowflake;
/// See <https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object> /// See <https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object>
pub struct AuditLogEntry { pub struct AuditLogEntry {
pub target_id: Option<String>, pub target_id: Option<String>,
pub changes: Option<Vec<Arc<Mutex<AuditLogChange>>>>, pub changes: Option<Vec<Arc<RwLock<AuditLogChange>>>>,
pub user_id: Option<Snowflake>, pub user_id: Option<Snowflake>,
pub id: Snowflake, pub id: Snowflake,
// to:do implement an enum for these types // to:do implement an enum for these types

View File

@ -1,4 +1,4 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, RwLock};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_repr::{Deserialize_repr, Serialize_repr};
@ -14,8 +14,8 @@ pub struct AutoModerationRule {
pub creator_id: Snowflake, pub creator_id: Snowflake,
pub event_type: AutoModerationRuleEventType, pub event_type: AutoModerationRuleEventType,
pub trigger_type: AutoModerationRuleTriggerType, pub trigger_type: AutoModerationRuleTriggerType,
pub trigger_metadata: Arc<Mutex<AutoModerationRuleTriggerMetadata>>, pub trigger_metadata: Arc<RwLock<AutoModerationRuleTriggerMetadata>>,
pub actions: Vec<Arc<Mutex<AutoModerationAction>>>, pub actions: Vec<Arc<RwLock<AutoModerationAction>>>,
pub enabled: bool, pub enabled: bool,
pub exempt_roles: Vec<Snowflake>, pub exempt_roles: Vec<Snowflake>,
pub exempt_channels: Vec<Snowflake>, pub exempt_channels: Vec<Snowflake>,
@ -92,7 +92,7 @@ pub enum AutoModerationRuleKeywordPresetType {
pub struct AutoModerationAction { pub struct AutoModerationAction {
#[serde(rename = "type")] #[serde(rename = "type")]
pub action_type: AutoModerationActionType, pub action_type: AutoModerationActionType,
pub metadata: Option<Arc<Mutex<AutoModerationActionMetadata>>>, pub metadata: Option<Arc<RwLock<AutoModerationActionMetadata>>>,
} }
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)] #[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)]

View File

@ -1,18 +1,20 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, RwLock};
use chorus_macros::Updateable; use chorus_macros::{observe_option_vec, Composite, 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 std::fmt::Debug;
use crate::gateway::Updateable; use crate::gateway::{GatewayHandle, Updateable};
use crate::types::{ use crate::types::{
entities::{GuildMember, User}, entities::{GuildMember, User},
utils::Snowflake, utils::Snowflake,
Composite,
}; };
#[derive(Default, Debug, Serialize, Deserialize, Clone, Updateable)] #[derive(Default, Debug, Serialize, Deserialize, Clone, Updateable, Composite)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
/// Represents a guild of private channel /// Represents a guild of private channel
/// ///
@ -48,7 +50,7 @@ pub struct Channel {
pub last_pin_timestamp: Option<String>, pub last_pin_timestamp: Option<String>,
pub managed: Option<bool>, pub managed: Option<bool>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub member: Option<Arc<Mutex<ThreadMember>>>, pub member: Option<ThreadMember>,
pub member_count: Option<i32>, pub member_count: Option<i32>,
pub message_count: Option<i32>, pub message_count: Option<i32>,
pub name: Option<String>, pub name: Option<String>,
@ -58,12 +60,14 @@ pub struct Channel {
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
pub permission_overwrites: Option<sqlx::types::Json<Vec<PermissionOverwrite>>>, pub permission_overwrites: Option<sqlx::types::Json<Vec<PermissionOverwrite>>>,
#[cfg(not(feature = "sqlx"))] #[cfg(not(feature = "sqlx"))]
pub permission_overwrites: Option<Vec<Arc<Mutex<PermissionOverwrite>>>>, #[observe_option_vec]
pub permission_overwrites: Option<Vec<Arc<RwLock<PermissionOverwrite>>>>,
pub permissions: Option<String>, pub permissions: Option<String>,
pub position: Option<i32>, pub position: Option<i32>,
pub rate_limit_per_user: Option<i32>, pub rate_limit_per_user: Option<i32>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub recipients: Option<Vec<Arc<Mutex<User>>>>, #[observe_option_vec]
pub recipients: Option<Vec<Arc<RwLock<User>>>>,
pub rtc_region: Option<String>, pub rtc_region: Option<String>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub thread_metadata: Option<ThreadMetadata>, pub thread_metadata: Option<ThreadMetadata>,
@ -122,7 +126,9 @@ pub struct Tag {
pub emoji_name: Option<String>, pub emoji_name: Option<String>,
} }
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd)] #[derive(
Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Updateable, Composite,
)]
pub struct PermissionOverwrite { pub struct PermissionOverwrite {
pub id: Snowflake, pub id: Snowflake,
#[serde(rename = "type")] #[serde(rename = "type")]
@ -156,7 +162,7 @@ pub struct ThreadMember {
pub user_id: Option<Snowflake>, pub user_id: Option<Snowflake>,
pub join_timestamp: Option<String>, pub join_timestamp: Option<String>,
pub flags: Option<u64>, pub flags: Option<u64>,
pub member: Option<Arc<Mutex<GuildMember>>>, pub member: Option<Arc<RwLock<GuildMember>>>,
} }
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]

View File

@ -1,23 +1,26 @@
use std::sync::{Arc, Mutex}; use std::fmt::Debug;
use std::sync::{Arc, RwLock};
use chorus_macros::{Composite, Updateable};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::gateway::{GatewayHandle, Updateable};
use crate::types::entities::User; use crate::types::entities::User;
use crate::types::Snowflake; use crate::types::{Composite, Snowflake};
#[derive(Debug, Clone, Deserialize, Serialize, Default)] #[derive(Debug, Clone, Deserialize, Serialize, Default, Updateable, Composite)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
/// # Reference /// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/emoji#emoji-object> /// See <https://discord-userdoccers.vercel.app/resources/emoji#emoji-object>
pub struct Emoji { pub struct Emoji {
pub id: Option<Snowflake>, pub id: Snowflake,
pub name: Option<String>, pub name: Option<String>,
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
pub roles: Option<sqlx::types::Json<Vec<Snowflake>>>, pub roles: Option<sqlx::types::Json<Vec<Snowflake>>>,
#[cfg(not(feature = "sqlx"))] #[cfg(not(feature = "sqlx"))]
pub roles: Option<Vec<Snowflake>>, pub roles: Option<Vec<Snowflake>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub user: Option<Arc<Mutex<User>>>, pub user: Option<Arc<RwLock<User>>>,
pub require_colons: Option<bool>, pub require_colons: Option<bool>,
pub managed: Option<bool>, pub managed: Option<bool>,
pub animated: Option<bool>, pub animated: Option<bool>,

View File

@ -1,18 +1,22 @@
use std::sync::{Arc, Mutex}; use std::fmt::Debug;
use std::sync::{Arc, RwLock};
use chorus_macros::{observe_option_vec, observe_vec, Composite, Updateable};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_repr::{Deserialize_repr, Serialize_repr};
use crate::gateway::{GatewayHandle, Updateable};
use crate::types::types::guild_configuration::GuildFeaturesList; use crate::types::types::guild_configuration::GuildFeaturesList;
use crate::types::{ use crate::types::{
entities::{Channel, Emoji, RoleObject, Sticker, User, VoiceState, Webhook}, entities::{Channel, Emoji, RoleObject, Sticker, User, VoiceState, Webhook},
interfaces::WelcomeScreenObject, interfaces::WelcomeScreenObject,
utils::Snowflake, utils::Snowflake,
Composite,
}; };
/// See <https://discord.com/developers/docs/resources/guild> /// See <https://discord.com/developers/docs/resources/guild>
#[derive(Serialize, Deserialize, Debug, Default, Clone)] #[derive(Serialize, Deserialize, Debug, Default, Clone, Updateable, Composite)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct Guild { pub struct Guild {
pub afk_channel_id: Option<Snowflake>, pub afk_channel_id: Option<Snowflake>,
@ -27,13 +31,15 @@ pub struct Guild {
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub bans: Option<Vec<GuildBan>>, pub bans: Option<Vec<GuildBan>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub channels: Option<Vec<Arc<Mutex<Channel>>>>, #[observe_option_vec]
pub channels: Option<Vec<Arc<RwLock<Channel>>>>,
pub default_message_notifications: Option<i32>, pub default_message_notifications: Option<i32>,
pub description: Option<String>, pub description: Option<String>,
pub discovery_splash: Option<String>, pub discovery_splash: Option<String>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
#[serde(default)] #[serde(default)]
pub emojis: Vec<Arc<Mutex<Emoji>>>, #[observe_vec]
pub emojis: Vec<Arc<RwLock<Emoji>>>,
pub explicit_content_filter: Option<i32>, pub explicit_content_filter: Option<i32>,
//#[cfg_attr(feature = "sqlx", sqlx(try_from = "String"))] //#[cfg_attr(feature = "sqlx", sqlx(try_from = "String"))]
pub features: Option<GuildFeaturesList>, pub features: Option<GuildFeaturesList>,
@ -42,7 +48,7 @@ pub struct Guild {
pub icon_hash: Option<String>, pub icon_hash: Option<String>,
pub id: Snowflake, pub id: Snowflake,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub invites: Option<Vec<Arc<Mutex<GuildInvite>>>>, pub invites: Option<Vec<GuildInvite>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub joined_at: Option<String>, pub joined_at: Option<String>,
pub large: Option<bool>, pub large: Option<bool>,
@ -68,7 +74,8 @@ pub struct Guild {
pub public_updates_channel_id: Option<Snowflake>, pub public_updates_channel_id: Option<Snowflake>,
pub region: Option<String>, pub region: Option<String>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub roles: Option<Vec<Arc<Mutex<RoleObject>>>>, #[observe_option_vec]
pub roles: Option<Vec<Arc<RwLock<RoleObject>>>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub rules_channel: Option<String>, pub rules_channel: Option<String>,
pub rules_channel_id: Option<Snowflake>, pub rules_channel_id: Option<Snowflake>,
@ -81,13 +88,15 @@ pub struct Guild {
pub vanity_url_code: Option<String>, pub vanity_url_code: Option<String>,
pub verification_level: Option<i32>, pub verification_level: Option<i32>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub voice_states: Option<Vec<Arc<Mutex<VoiceState>>>>, #[observe_option_vec]
pub voice_states: Option<Vec<Arc<RwLock<VoiceState>>>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub webhooks: Option<Vec<Arc<Mutex<Webhook>>>>, #[observe_option_vec]
pub webhooks: Option<Vec<Arc<RwLock<Webhook>>>>,
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
pub welcome_screen: Option<sqlx::types::Json<WelcomeScreenObject>>, pub welcome_screen: Option<sqlx::types::Json<WelcomeScreenObject>>,
#[cfg(not(feature = "sqlx"))] #[cfg(not(feature = "sqlx"))]
pub welcome_screen: Option<Arc<Mutex<WelcomeScreenObject>>>, pub welcome_screen: Option<WelcomeScreenObject>,
pub widget_channel_id: Option<Snowflake>, pub widget_channel_id: Option<Snowflake>,
pub widget_enabled: Option<bool>, pub widget_enabled: Option<bool>,
} }
@ -113,11 +122,11 @@ pub struct GuildInvite {
pub created_at: DateTime<Utc>, pub created_at: DateTime<Utc>,
pub expires_at: Option<DateTime<Utc>>, pub expires_at: Option<DateTime<Utc>>,
pub guild_id: Snowflake, pub guild_id: Snowflake,
pub guild: Option<Arc<Mutex<Guild>>>, pub guild: Option<Arc<RwLock<Guild>>>,
pub channel_id: Snowflake, pub channel_id: Snowflake,
pub channel: Option<Arc<Mutex<Channel>>>, pub channel: Option<Arc<RwLock<Channel>>>,
pub inviter_id: Option<Snowflake>, pub inviter_id: Option<Snowflake>,
pub inviter: Option<Arc<Mutex<User>>>, pub inviter: Option<Arc<RwLock<User>>>,
pub target_user_id: Option<Snowflake>, pub target_user_id: Option<Snowflake>,
pub target_user: Option<String>, pub target_user: Option<String>,
pub target_user_type: Option<i32>, pub target_user_type: Option<i32>,
@ -151,7 +160,7 @@ pub struct GuildScheduledEvent {
pub entity_type: GuildScheduledEventEntityType, pub entity_type: GuildScheduledEventEntityType,
pub entity_id: Option<Snowflake>, pub entity_id: Option<Snowflake>,
pub entity_metadata: Option<GuildScheduledEventEntityMetadata>, pub entity_metadata: Option<GuildScheduledEventEntityMetadata>,
pub creator: Option<Arc<Mutex<User>>>, pub creator: Option<Arc<RwLock<User>>>,
pub user_count: Option<u64>, pub user_count: Option<u64>,
pub image: Option<String>, pub image: Option<String>,
} }

View File

@ -1,4 +1,4 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, RwLock};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -10,7 +10,7 @@ use crate::types::{entities::PublicUser, Snowflake};
/// # Reference /// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/guild#guild-member-object> /// See <https://discord-userdoccers.vercel.app/resources/guild#guild-member-object>
pub struct GuildMember { pub struct GuildMember {
pub user: Option<Arc<Mutex<PublicUser>>>, pub user: Option<Arc<RwLock<PublicUser>>>,
pub nick: Option<String>, pub nick: Option<String>,
pub avatar: Option<String>, pub avatar: Option<String>,
pub roles: Vec<Snowflake>, pub roles: Vec<Snowflake>,

View File

@ -1,4 +1,4 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, RwLock};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -23,14 +23,14 @@ pub struct Integration {
pub expire_behaviour: Option<u8>, pub expire_behaviour: Option<u8>,
pub expire_grace_period: Option<u16>, pub expire_grace_period: Option<u16>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub user: Option<Arc<Mutex<User>>>, pub user: Option<Arc<RwLock<User>>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub account: IntegrationAccount, pub account: IntegrationAccount,
pub synced_at: Option<DateTime<Utc>>, pub synced_at: Option<DateTime<Utc>>,
pub subscriber_count: Option<f64>, pub subscriber_count: Option<f64>,
pub revoked: Option<bool>, pub revoked: Option<bool>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub application: Option<Arc<Mutex<Application>>>, pub application: Option<Arc<RwLock<Application>>>,
pub scopes: Option<Vec<String>>, pub scopes: Option<Vec<String>>,
} }

View File

@ -1,4 +1,4 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, RwLock};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -70,7 +70,7 @@ pub enum NSFWLevel {
/// See <https://discord-userdoccers.vercel.app/resources/invite#invite-stage-instance-object> /// See <https://discord-userdoccers.vercel.app/resources/invite#invite-stage-instance-object>
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct InviteStageInstance { pub struct InviteStageInstance {
pub members: Vec<Arc<Mutex<GuildMember>>>, pub members: Vec<Arc<RwLock<GuildMember>>>,
pub participant_count: i32, pub participant_count: i32,
pub speaker_count: i32, pub speaker_count: i32,
pub topic: String, pub topic: String,

View File

@ -22,6 +22,11 @@ pub use user_settings::*;
pub use voice_state::*; pub use voice_state::*;
pub use webhook::*; pub use webhook::*;
use crate::gateway::{GatewayHandle, Updateable};
use async_trait::async_trait;
use std::fmt::Debug;
use std::sync::{Arc, RwLock};
mod application; mod application;
mod attachment; mod attachment;
mod audit_log; mod audit_log;
@ -45,3 +50,62 @@ mod user;
mod user_settings; mod user_settings;
mod voice_state; mod voice_state;
mod webhook; mod webhook;
#[async_trait(?Send)]
pub trait Composite<T: Updateable + Clone + Debug> {
async fn watch_whole(self, gateway: &GatewayHandle) -> Self;
async fn option_observe_fn(
value: Option<Arc<RwLock<T>>>,
gateway: &GatewayHandle,
) -> Option<Arc<RwLock<T>>>
where
T: Composite<T> + Debug,
{
if let Some(value) = value {
let value = value.clone();
Some(gateway.observe(value).await)
} else {
None
}
}
async fn option_vec_observe_fn(
value: Option<Vec<Arc<RwLock<T>>>>,
gateway: &GatewayHandle,
) -> Option<Vec<Arc<RwLock<T>>>>
where
T: Composite<T>,
{
if let Some(value) = value {
let mut vec = Vec::new();
for component in value.into_iter() {
vec.push(gateway.observe(component).await);
}
Some(vec)
} else {
None
}
}
async fn value_observe_fn(value: Arc<RwLock<T>>, gateway: &GatewayHandle) -> Arc<RwLock<T>>
where
T: Composite<T>,
{
gateway.observe(value).await
}
async fn vec_observe_fn(
value: Vec<Arc<RwLock<T>>>,
gateway: &GatewayHandle,
) -> Vec<Arc<RwLock<T>>>
where
T: Composite<T>,
{
let mut vec = Vec::new();
for component in value.into_iter() {
vec.push(gateway.observe(component).await);
}
vec
}
}

View File

@ -1,4 +1,4 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, RwLock};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -15,7 +15,7 @@ pub struct Relationship {
#[serde(rename = "type")] #[serde(rename = "type")]
pub relationship_type: RelationshipType, pub relationship_type: RelationshipType,
pub nickname: Option<String>, pub nickname: Option<String>,
pub user: Arc<Mutex<PublicUser>>, pub user: Arc<RwLock<PublicUser>>,
pub since: Option<DateTime<Utc>>, pub since: Option<DateTime<Utc>>,
} }

View File

@ -1,10 +1,13 @@
use bitflags::bitflags; use bitflags::bitflags;
use chorus_macros::{Composite, Updateable};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_aux::prelude::{deserialize_option_number_from_string, deserialize_string_from_number}; use serde_aux::prelude::{deserialize_option_number_from_string, deserialize_string_from_number};
use std::fmt::Debug;
use crate::types::utils::Snowflake; use crate::gateway::{GatewayHandle, Updateable};
use crate::types::{utils::Snowflake, Composite};
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)] #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Updateable, Composite)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
/// See <https://discord.com/developers/docs/topics/permissions#role-object> /// See <https://discord.com/developers/docs/topics/permissions#role-object>
pub struct RoleObject { pub struct RoleObject {

View File

@ -1,4 +1,4 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, RwLock};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -24,7 +24,7 @@ pub struct Sticker {
pub available: Option<bool>, pub available: Option<bool>,
pub guild_id: Option<Snowflake>, pub guild_id: Option<Snowflake>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub user: Option<Arc<Mutex<User>>>, pub user: Option<Arc<RwLock<User>>>,
pub sort_value: Option<u8>, pub sort_value: Option<u8>,
} }

View File

@ -1,4 +1,4 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, RwLock};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -21,5 +21,5 @@ pub struct TeamMember {
pub membership_state: u8, pub membership_state: u8,
pub permissions: Vec<String>, pub permissions: Vec<String>,
pub team_id: Snowflake, pub team_id: Snowflake,
pub user: Arc<Mutex<User>>, pub user: Arc<RwLock<User>>,
} }

View File

@ -1,4 +1,4 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, RwLock};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -18,13 +18,13 @@ pub struct GuildTemplate {
pub usage_count: Option<u64>, pub usage_count: Option<u64>,
pub creator_id: Snowflake, pub creator_id: Snowflake,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub creator: Arc<Mutex<User>>, pub creator: Arc<RwLock<User>>,
pub created_at: DateTime<Utc>, pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>, pub updated_at: DateTime<Utc>,
pub source_guild_id: Snowflake, pub source_guild_id: Snowflake,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub source_guild: Vec<Arc<Mutex<Guild>>>, pub source_guild: Vec<Arc<RwLock<Guild>>>,
// Unsure how a {recursive: Guild} looks like, might be a Vec? // Unsure how a {recursive: Guild} looks like, might be a Vec?
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub serialized_source_guild: Vec<Arc<Mutex<Guild>>>, pub serialized_source_guild: Vec<Arc<RwLock<Guild>>>,
} }

View File

@ -1,7 +1,11 @@
use crate::types::utils::Snowflake; use chorus_macros::{Composite, Updateable};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_aux::prelude::deserialize_option_number_from_string; use serde_aux::prelude::deserialize_option_number_from_string;
use std::fmt::Debug;
use crate::gateway::{GatewayHandle, Updateable};
use crate::types::{utils::Snowflake, Composite};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
@ -15,7 +19,7 @@ impl User {
PublicUser::from(self) PublicUser::from(self)
} }
} }
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)] #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Updateable, Composite)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct User { pub struct User {
pub id: Snowflake, pub id: Snowflake,

View File

@ -1,4 +1,4 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, RwLock};
use chrono::{serde::ts_milliseconds_option, Utc}; use chrono::{serde::ts_milliseconds_option, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -75,7 +75,7 @@ pub struct UserSettings {
#[cfg(not(feature = "sqlx"))] #[cfg(not(feature = "sqlx"))]
pub restricted_guilds: Vec<String>, pub restricted_guilds: Vec<String>,
pub show_current_game: bool, pub show_current_game: bool,
pub status: Arc<Mutex<UserStatus>>, pub status: Arc<RwLock<UserStatus>>,
pub stream_notifications_enabled: bool, pub stream_notifications_enabled: bool,
pub theme: UserTheme, pub theme: UserTheme,
pub timezone_offset: i16, pub timezone_offset: i16,
@ -111,7 +111,7 @@ impl Default for UserSettings {
render_reactions: true, render_reactions: true,
restricted_guilds: Default::default(), restricted_guilds: Default::default(),
show_current_game: true, show_current_game: true,
status: Arc::new(Mutex::new(UserStatus::Online)), status: Arc::new(RwLock::new(UserStatus::Online)),
stream_notifications_enabled: false, stream_notifications_enabled: false,
theme: UserTheme::Dark, theme: UserTheme::Dark,
timezone_offset: 0, timezone_offset: 0,
@ -151,5 +151,5 @@ pub struct GuildFolder {
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct LoginResult { pub struct LoginResult {
pub token: String, pub token: String,
pub settings: Arc<Mutex<UserSettings>>, pub settings: Arc<RwLock<UserSettings>>,
} }

View File

@ -1,22 +1,26 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, RwLock};
use chorus_macros::{Composite, Updateable};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use crate::gateway::{GatewayHandle, Updateable};
use crate::types::{ use crate::types::{
entities::{Guild, GuildMember}, entities::{Guild, GuildMember},
utils::Snowflake, utils::Snowflake,
Composite,
}; };
/// See <https://docs.spacebar.chat/routes/#cmp--schemas-voicestate> /// See <https://docs.spacebar.chat/routes/#cmp--schemas-voicestate>
#[derive(Serialize, Deserialize, Debug, Default, Clone)] #[derive(Serialize, Deserialize, Debug, Default, Clone, Updateable, Composite)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct VoiceState { pub struct VoiceState {
pub guild_id: Option<Snowflake>, pub guild_id: Option<Snowflake>,
pub guild: Option<Guild>, pub guild: Option<Guild>,
pub channel_id: Option<Snowflake>, pub channel_id: Option<Snowflake>,
pub user_id: Snowflake, pub user_id: Snowflake,
pub member: Option<Arc<Mutex<GuildMember>>>, pub member: Option<Arc<RwLock<GuildMember>>>,
pub session_id: Snowflake, pub session_id: Snowflake,
pub token: Option<String>, pub token: Option<String>,
pub deaf: bool, pub deaf: bool,
@ -27,5 +31,5 @@ pub struct VoiceState {
pub self_video: bool, pub self_video: bool,
pub suppress: bool, pub suppress: bool,
pub request_to_speak_timestamp: Option<DateTime<Utc>>, pub request_to_speak_timestamp: Option<DateTime<Utc>>,
pub id: Option<Snowflake>, pub id: Snowflake,
} }

View File

@ -1,14 +1,18 @@
use std::sync::{Arc, Mutex}; use std::fmt::Debug;
use std::sync::{Arc, RwLock};
use chorus_macros::{Composite, Updateable};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::gateway::{GatewayHandle, Updateable};
use crate::types::{ use crate::types::{
entities::{Guild, User}, entities::{Guild, User},
utils::Snowflake, utils::Snowflake,
Composite,
}; };
/// See <https://docs.spacebar.chat/routes/#cmp--schemas-webhook> /// See <https://docs.spacebar.chat/routes/#cmp--schemas-webhook>
#[derive(Serialize, Deserialize, Debug, Default, Clone)] #[derive(Serialize, Deserialize, Debug, Default, Clone, Updateable, Composite)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct Webhook { pub struct Webhook {
pub id: Snowflake, pub id: Snowflake,
@ -22,10 +26,10 @@ pub struct Webhook {
pub application_id: Snowflake, pub application_id: Snowflake,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub user: Option<Arc<Mutex<User>>>, pub user: Option<Arc<RwLock<User>>>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub source_guild: Option<Arc<Mutex<Guild>>>, pub source_guild: Option<Arc<RwLock<Guild>>>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>, pub url: Option<String>,
} }

View File

@ -1,5 +1,8 @@
use std::sync::{Arc, RwLock};
use crate::types::events::WebSocketEvent; use crate::types::events::WebSocketEvent;
use crate::types::{entities::Channel, Snowflake}; use crate::types::{entities::Channel, JsonField, Snowflake};
use chorus_macros::JsonField;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -24,18 +27,21 @@ pub struct ChannelCreate {
impl WebSocketEvent for ChannelCreate {} impl WebSocketEvent for ChannelCreate {}
#[derive(Debug, Default, Deserialize, Serialize, Clone)] #[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField)]
/// See <https://discord.com/developers/docs/topics/gateway-events#channel-update> /// See <https://discord.com/developers/docs/topics/gateway-events#channel-update>
pub struct ChannelUpdate { pub struct ChannelUpdate {
#[serde(flatten)] #[serde(flatten)]
pub channel: Channel, pub channel: Channel,
#[serde(skip)]
pub json: String,
} }
impl WebSocketEvent for ChannelUpdate {} impl WebSocketEvent for ChannelUpdate {}
impl UpdateMessage<Channel> for ChannelUpdate { impl UpdateMessage<Channel> for ChannelUpdate {
fn update(&self, object_to_update: &mut Channel) { fn update(&mut self, object_to_update: Arc<RwLock<Channel>>) {
*object_to_update = self.channel.clone(); let mut write = object_to_update.write().unwrap();
*write = self.channel.clone();
} }
fn id(&self) -> Snowflake { fn id(&self) -> Snowflake {
self.channel.id self.channel.id

View File

@ -1,13 +1,17 @@
use std::sync::{Arc, RwLock};
use chorus_macros::JsonField;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::entities::{Guild, PublicUser, UnavailableGuild}; use crate::types::entities::{Guild, PublicUser, UnavailableGuild};
use crate::types::events::WebSocketEvent; use crate::types::events::WebSocketEvent;
use crate::types::{ use crate::types::{
AuditLogEntry, Emoji, GuildMember, GuildScheduledEvent, RoleObject, Snowflake, Sticker, AuditLogEntry, Emoji, GuildMember, GuildScheduledEvent, JsonField, RoleObject, Snowflake,
Sticker,
}; };
use super::PresenceUpdate; use super::{PresenceUpdate, UpdateMessage};
#[derive(Debug, Deserialize, Serialize, Default, Clone)] #[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-create>; /// See <https://discord.com/developers/docs/topics/gateway-events#guild-create>;
@ -164,24 +168,60 @@ pub struct GuildMembersChunk {
impl WebSocketEvent for GuildMembersChunk {} impl WebSocketEvent for GuildMembersChunk {}
#[derive(Debug, Default, Deserialize, Serialize, Clone)] #[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-role-create> /// See <https://discord.com/developers/docs/topics/gateway-events#guild-role-create>
pub struct GuildRoleCreate { pub struct GuildRoleCreate {
pub guild_id: Snowflake, pub guild_id: Snowflake,
pub role: RoleObject, pub role: RoleObject,
#[serde(skip)]
pub json: String,
} }
impl WebSocketEvent for GuildRoleCreate {} impl WebSocketEvent for GuildRoleCreate {}
#[derive(Debug, Default, Deserialize, Serialize, Clone)] impl UpdateMessage<Guild> for GuildRoleCreate {
fn id(&self) -> Snowflake {
self.guild_id
}
fn update(&mut self, object_to_update: Arc<RwLock<Guild>>) {
let mut object_to_update = object_to_update.write().unwrap();
if object_to_update.roles.is_some() {
object_to_update
.roles
.as_mut()
.unwrap()
.push(Arc::new(RwLock::new(self.role.clone())));
} else {
object_to_update.roles = Some(Vec::from([Arc::new(RwLock::new(self.role.clone()))]));
}
}
}
#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-role-update> /// See <https://discord.com/developers/docs/topics/gateway-events#guild-role-update>
pub struct GuildRoleUpdate { pub struct GuildRoleUpdate {
pub guild_id: Snowflake, pub guild_id: Snowflake,
pub role: RoleObject, pub role: RoleObject,
#[serde(skip)]
pub json: String,
} }
impl WebSocketEvent for GuildRoleUpdate {} impl WebSocketEvent for GuildRoleUpdate {}
impl UpdateMessage<RoleObject> for GuildRoleUpdate {
fn id(&self) -> Snowflake {
self.role.id
}
fn update(&mut self, object_to_update: Arc<RwLock<RoleObject>>) {
println!("Processing Role Update. Name: {}", self.role.name);
let mut write = object_to_update.write().unwrap();
*write = self.role.clone();
println!("Updated role: Name: {}", write.name);
}
}
#[derive(Debug, Default, Deserialize, Serialize, Clone)] #[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-role-delete> /// See <https://discord.com/developers/docs/topics/gateway-events#guild-role-delete>
pub struct GuildRoleDelete { pub struct GuildRoleDelete {

View File

@ -1,3 +1,8 @@
use std::sync::{Arc, RwLock};
use std::collections::HashMap;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub use application::*; pub use application::*;
@ -19,6 +24,7 @@ pub use ready::*;
pub use relationship::*; pub use relationship::*;
pub use request_members::*; pub use request_members::*;
pub use resume::*; pub use resume::*;
use serde_json::{from_str, from_value, to_value, Value};
pub use session::*; pub use session::*;
pub use stage_instance::*; pub use stage_instance::*;
pub use thread::*; pub use thread::*;
@ -109,10 +115,31 @@ impl<'a> WebSocketEvent for GatewayReceivePayload<'a> {}
/// This would imply, that the [`WebSocketEvent`] "[`ChannelUpdate`]" contains new/updated information /// 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 /// about a [`Channel`]. The update method describes how this new information will be turned into
/// a [`Channel`] object. /// a [`Channel`] object.
pub(crate) trait UpdateMessage<T>: Clone pub(crate) trait UpdateMessage<T>: Clone + JsonField
where where
T: Updateable, T: Updateable + Serialize + DeserializeOwned + Clone,
{ {
fn update(&self, object_to_update: &mut T); fn update(&mut self, object_to_update: Arc<RwLock<T>>) {
update_object(self.get_json(), object_to_update)
}
fn id(&self) -> Snowflake; fn id(&self) -> Snowflake;
} }
pub(crate) trait JsonField: Clone {
fn set_json(&mut self, json: String);
fn get_json(&self) -> String;
}
/// Only applicable for events where the Update struct is the same as the Entity struct
pub(crate) fn update_object(
value: String,
object: Arc<RwLock<(impl Updateable + Serialize + DeserializeOwned + Clone)>>,
) {
let data_from_event: HashMap<String, Value> = from_str(&value).unwrap();
let mut original_data: HashMap<String, Value> =
from_value(to_value(object.clone()).unwrap()).unwrap();
for (updated_entry_key, updated_entry_value) in data_from_event.into_iter() {
original_data.insert(updated_entry_key.clone(), updated_entry_value);
}
*object.write().unwrap() = from_value(to_value(original_data).unwrap()).unwrap();
}

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
/// Represents the schema which needs to be sent to create or modify a Role. /// Represents the schema which needs to be sent to create or modify a Role.
/// See: [https://docs.spacebar.chat/routes/#cmp--schemas-rolemodifyschema](https://docs.spacebar.chat/routes/#cmp--schemas-rolemodifyschema) /// See: [https://docs.spacebar.chat/routes/#cmp--schemas-rolemodifyschema](https://docs.spacebar.chat/routes/#cmp--schemas-rolemodifyschema)

View File

@ -8,7 +8,7 @@ mod common;
#[tokio::test] #[tokio::test]
async fn get_channel() { async fn get_channel() {
let mut bundle = common::setup().await; let mut bundle = common::setup().await;
let bundle_channel = bundle.channel.clone(); let bundle_channel = bundle.channel.read().unwrap().clone();
let bundle_user = &mut bundle.user; let bundle_user = &mut bundle.user;
assert_eq!( assert_eq!(
@ -21,7 +21,8 @@ async fn get_channel() {
#[tokio::test] #[tokio::test]
async fn delete_channel() { async fn delete_channel() {
let mut bundle = common::setup().await; let mut bundle = common::setup().await;
let result = Channel::delete(bundle.channel.clone(), &mut bundle.user).await; let channel_guard = bundle.channel.write().unwrap().clone();
let result = Channel::delete(channel_guard, &mut bundle.user).await;
assert!(result.is_ok()); assert!(result.is_ok());
common::teardown(bundle).await common::teardown(bundle).await
} }
@ -30,7 +31,7 @@ async fn delete_channel() {
async fn modify_channel() { async fn modify_channel() {
const CHANNEL_NAME: &str = "beepboop"; 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.read().unwrap().clone();
let modify_data: types::ChannelModifySchema = types::ChannelModifySchema { let modify_data: types::ChannelModifySchema = types::ChannelModifySchema {
name: Some(CHANNEL_NAME.to_string()), name: Some(CHANNEL_NAME.to_string()),
channel_type: None, channel_type: None,
@ -50,32 +51,26 @@ 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,
}; };
let modified_channel = Channel::modify(channel, modify_data, channel.id, &mut bundle.user) let modified_channel = channel.modify(modify_data, &mut bundle.user).await.unwrap();
.await
.unwrap();
assert_eq!(modified_channel.name, Some(CHANNEL_NAME.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,
PermissionFlags::MANAGE_MESSAGES, PermissionFlags::MANAGE_MESSAGES,
])); ]));
let user_id: types::Snowflake = bundle.user.object.lock().unwrap().id; let user_id: types::Snowflake = bundle.user.object.read().unwrap().id;
let permission_override = PermissionOverwrite { let permission_override = PermissionOverwrite {
id: user_id, id: user_id,
overwrite_type: "1".to_string(), overwrite_type: "1".to_string(),
allow: permission_override, allow: permission_override,
deny: "0".to_string(), deny: "0".to_string(),
}; };
let channel_id: Snowflake = bundle.channel.read().unwrap().id;
Channel::edit_permissions(&mut bundle.user, channel_id, permission_override.clone())
.await
.unwrap();
Channel::edit_permissions( Channel::delete_permission(&mut bundle.user, channel_id, permission_override.id)
&mut bundle.user,
bundle.channel.id,
permission_override.clone(),
)
.await
.unwrap();
Channel::delete_permission(&mut bundle.user, bundle.channel.id, permission_override.id)
.await .await
.unwrap(); .unwrap();
@ -85,7 +80,7 @@ async fn modify_channel() {
#[tokio::test] #[tokio::test]
async fn get_channel_messages() { async fn get_channel_messages() {
let mut bundle = common::setup().await; let mut bundle = common::setup().await;
let channel_id: Snowflake = bundle.channel.read().unwrap().id;
// First create some messages to read // First create some messages to read
for _ in 0..10 { for _ in 0..10 {
let _ = bundle let _ = bundle
@ -95,7 +90,7 @@ async fn get_channel_messages() {
content: Some("A Message!".to_string()), content: Some("A Message!".to_string()),
..Default::default() ..Default::default()
}, },
bundle.channel.id, channel_id,
) )
.await .await
.unwrap(); .unwrap();
@ -104,7 +99,7 @@ async fn get_channel_messages() {
assert_eq!( assert_eq!(
Channel::messages( Channel::messages(
GetChannelMessagesSchema::before(Snowflake::generate()), GetChannelMessagesSchema::before(Snowflake::generate()),
bundle.channel.id, channel_id,
&mut bundle.user, &mut bundle.user,
) )
.await .await
@ -128,7 +123,7 @@ async fn get_channel_messages() {
assert!(Channel::messages( assert!(Channel::messages(
GetChannelMessagesSchema::after(Snowflake::generate()), GetChannelMessagesSchema::after(Snowflake::generate()),
bundle.channel.id, channel_id,
&mut bundle.user, &mut bundle.user,
) )
.await .await
@ -144,7 +139,7 @@ async fn create_dm() {
let other_user = bundle.create_user("integrationtestuser2").await; let other_user = bundle.create_user("integrationtestuser2").await;
let user = &mut bundle.user; let user = &mut bundle.user;
let private_channel_create_schema = PrivateChannelCreateSchema { let private_channel_create_schema = PrivateChannelCreateSchema {
recipients: Some(Vec::from([other_user.object.lock().unwrap().id])), recipients: Some(Vec::from([other_user.object.read().unwrap().id])),
access_tokens: None, access_tokens: None,
nicks: None, nicks: None,
}; };
@ -160,11 +155,11 @@ async fn create_dm() {
.unwrap() .unwrap()
.get(0) .get(0)
.unwrap() .unwrap()
.lock() .read()
.unwrap() .unwrap()
.id .id
.clone(), .clone(),
other_user.object.lock().unwrap().id other_user.object.read().unwrap().id
); );
assert_eq!( assert_eq!(
dm_channel dm_channel
@ -173,11 +168,11 @@ async fn create_dm() {
.unwrap() .unwrap()
.get(1) .get(1)
.unwrap() .unwrap()
.lock() .read()
.unwrap() .unwrap()
.id .id
.clone(), .clone(),
user.object.lock().unwrap().id.clone() user.object.read().unwrap().id.clone()
); );
common::teardown(bundle).await; common::teardown(bundle).await;
} }
@ -189,9 +184,9 @@ async fn remove_add_person_from_to_dm() {
let mut bundle = common::setup().await; let mut bundle = common::setup().await;
let mut other_user = bundle.create_user("integrationtestuser2").await; let mut other_user = bundle.create_user("integrationtestuser2").await;
let mut third_user = bundle.create_user("integrationtestuser3").await; let mut third_user = bundle.create_user("integrationtestuser3").await;
let third_user_id = third_user.object.lock().unwrap().id; let third_user_id = third_user.object.read().unwrap().id;
let other_user_id = other_user.object.lock().unwrap().id; let other_user_id = other_user.object.read().unwrap().id;
let user_id = bundle.user.object.lock().unwrap().id; let user_id = bundle.user.object.read().unwrap().id;
let user = &mut bundle.user; let user = &mut bundle.user;
let private_channel_create_schema = PrivateChannelCreateSchema { let private_channel_create_schema = PrivateChannelCreateSchema {
recipients: Some(Vec::from([other_user_id, third_user_id])), recipients: Some(Vec::from([other_user_id, third_user_id])),
@ -234,7 +229,7 @@ async fn remove_add_person_from_to_dm() {
.unwrap() .unwrap()
.get(0) .get(0)
.unwrap() .unwrap()
.lock() .read()
.unwrap() .unwrap()
.id, .id,
other_user_id other_user_id
@ -246,7 +241,7 @@ async fn remove_add_person_from_to_dm() {
.unwrap() .unwrap()
.get(1) .get(1)
.unwrap() .unwrap()
.lock() .read()
.unwrap() .unwrap()
.id, .id,
user_id user_id

View File

@ -1,3 +1,5 @@
use std::sync::{Arc, RwLock};
use chorus::gateway::Gateway; use chorus::gateway::Gateway;
use chorus::{ use chorus::{
instance::{Instance, UserMeta}, instance::{Instance, UserMeta},
@ -14,9 +16,9 @@ pub(crate) struct TestBundle {
pub urls: UrlBundle, pub urls: UrlBundle,
pub user: UserMeta, pub user: UserMeta,
pub instance: Instance, pub instance: Instance,
pub guild: Guild, pub guild: Arc<RwLock<Guild>>,
pub role: RoleObject, pub role: Arc<RwLock<RoleObject>>,
pub channel: Channel, pub channel: Arc<RwLock<Channel>>,
} }
#[allow(unused)] #[allow(unused)]
@ -113,17 +115,16 @@ pub(crate) async fn setup() -> TestBundle {
urls, urls,
user, user,
instance, instance,
guild, guild: Arc::new(RwLock::new(guild)),
role, role: Arc::new(RwLock::new(role)),
channel, channel: Arc::new(RwLock::new(channel)),
} }
} }
// Teardown method to clean up after a test. // Teardown method to clean up after a test.
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) async fn teardown(mut bundle: TestBundle) { pub(crate) async fn teardown(mut bundle: TestBundle) {
Guild::delete(&mut bundle.user, bundle.guild.id) let id = bundle.guild.read().unwrap().id;
.await Guild::delete(&mut bundle.user, id).await.unwrap();
.unwrap();
bundle.user.delete().await.unwrap() bundle.user.delete().await.unwrap()
} }

View File

@ -1,7 +1,9 @@
mod common; mod common;
use std::sync::{Arc, RwLock};
use chorus::gateway::*; use chorus::gateway::*;
use chorus::types::{self, Channel}; use chorus::types::{self, ChannelModifySchema, RoleCreateModifySchema, RoleObject};
#[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;
@ -29,18 +31,88 @@ async fn test_gateway_authenticate() {
#[tokio::test] #[tokio::test]
async fn test_self_updating_structs() { async fn test_self_updating_structs() {
let mut bundle = common::setup().await; let mut bundle = common::setup().await;
let channel_updater = bundle.user.gateway.observe(bundle.channel.clone()).await; let received_channel = bundle
let received_channel = channel_updater.borrow().clone(); .user
assert_eq!(received_channel, bundle.channel); .gateway
let channel = &mut bundle.channel; .observe_and_into_inner(bundle.channel.clone())
let modify_data = types::ChannelModifySchema { .await;
name: Some("beepboop".to_string()),
assert_eq!(received_channel, bundle.channel.read().unwrap().clone());
let modify_schema = ChannelModifySchema {
name: Some("selfupdating".to_string()),
..Default::default() ..Default::default()
}; };
Channel::modify(channel, modify_data, channel.id, &mut bundle.user) received_channel
.modify(modify_schema, &mut bundle.user)
.await .await
.unwrap(); .unwrap();
let received_channel = channel_updater.borrow(); assert_eq!(
assert_eq!(received_channel.name.as_ref().unwrap(), "beepboop"); bundle
.user
.gateway
.observe_and_into_inner(bundle.channel.clone())
.await
.name
.unwrap(),
"selfupdating".to_string()
);
common::teardown(bundle).await common::teardown(bundle).await
} }
#[tokio::test]
async fn test_recursive_self_updating_structs() {
// Setup
let mut bundle = common::setup().await;
let guild = bundle.guild.clone();
// Observe Guild, make sure it has no channels
let guild = bundle.user.gateway.observe(guild.clone()).await;
let inner_guild = guild.read().unwrap().clone();
assert!(inner_guild.roles.is_none());
// Create Role
let permissions = types::PermissionFlags::CONNECT | types::PermissionFlags::MANAGE_EVENTS;
let permissions = Some(permissions.to_string());
let mut role_create_schema: types::RoleCreateModifySchema = RoleCreateModifySchema {
name: Some("cool person".to_string()),
permissions,
hoist: Some(true),
icon: None,
unicode_emoji: Some("".to_string()),
mentionable: Some(true),
position: None,
color: None,
};
let guild_id = inner_guild.id;
let role = RoleObject::create(&mut bundle.user, guild_id, role_create_schema.clone())
.await
.unwrap();
// Watch role;
bundle
.user
.gateway
.observe(Arc::new(RwLock::new(role.clone())))
.await;
// Update Guild and check for Guild
let inner_guild = guild.read().unwrap().clone();
assert!(inner_guild.roles.is_some());
// Update the Role
role_create_schema.name = Some("yippieee".to_string());
RoleObject::modify(&mut bundle.user, guild_id, role.id, role_create_schema)
.await
.unwrap();
let role_inner = bundle
.user
.gateway
.observe_and_into_inner(Arc::new(RwLock::new(role.clone())))
.await;
assert_eq!(role_inner.name, "yippieee");
// Check if the change propagated
let guild = bundle.user.gateway.observe(bundle.guild.clone()).await;
let inner_guild = guild.read().unwrap().clone();
let guild_roles = inner_guild.roles;
let guild_role = guild_roles.unwrap();
let guild_role_inner = guild_role.get(0).unwrap().read().unwrap().clone();
assert_eq!(guild_role_inner.name, "yippieee".to_string());
common::teardown(bundle).await;
}

View File

@ -27,9 +27,7 @@ async fn guild_creation_deletion() {
#[tokio::test] #[tokio::test]
async fn get_channels() { async fn get_channels() {
let mut bundle = common::setup().await; let mut bundle = common::setup().await;
println!( let guild = bundle.guild.read().unwrap().clone();
"{:?}", println!("{:?}", guild.channels(&mut bundle.user).await.unwrap());
bundle.guild.channels(&mut bundle.user).await.unwrap()
);
common::teardown(bundle).await; common::teardown(bundle).await;
} }

View File

@ -3,11 +3,12 @@ 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.read().unwrap().clone();
let mut other_user = bundle.create_user("testuser1312").await; let mut other_user = bundle.create_user("testuser1312").await;
let user = &mut bundle.user; let user = &mut bundle.user;
let create_channel_invite_schema = CreateChannelInviteSchema::default(); let create_channel_invite_schema = CreateChannelInviteSchema::default();
assert!(chorus::types::Guild::get(bundle.guild.id, &mut other_user) let guild = bundle.guild.read().unwrap().clone();
assert!(chorus::types::Guild::get(guild.id, &mut other_user)
.await .await
.is_err()); .is_err());
let invite = user let invite = user
@ -16,7 +17,7 @@ async fn create_accept_invite() {
.unwrap(); .unwrap();
other_user.accept_invite(&invite.code, None).await.unwrap(); other_user.accept_invite(&invite.code, None).await.unwrap();
assert!(chorus::types::Guild::get(bundle.guild.id, &mut other_user) assert!(chorus::types::Guild::get(guild.id, &mut other_user)
.await .await
.is_ok()); .is_ok());
common::teardown(bundle).await; common::teardown(bundle).await;

View File

@ -5,9 +5,9 @@ mod common;
#[tokio::test] #[tokio::test]
async fn add_remove_role() -> ChorusResult<()> { async fn add_remove_role() -> ChorusResult<()> {
let mut bundle = common::setup().await; let mut bundle = common::setup().await;
let guild = bundle.guild.id; let guild = bundle.guild.read().unwrap().id;
let role = bundle.role.id; let role = bundle.role.read().unwrap().id;
let member_id = bundle.user.object.lock().unwrap().id; let member_id = bundle.user.object.read().unwrap().id;
GuildMember::add_role(&mut bundle.user, guild, member_id, role).await?; GuildMember::add_role(&mut bundle.user, guild, member_id, role).await?;
let member = GuildMember::get(&mut bundle.user, guild, member_id) let member = GuildMember::get(&mut bundle.user, guild, member_id)
.await .await

View File

@ -12,11 +12,8 @@ async fn send_message() {
content: Some("A Message!".to_string()), content: Some("A Message!".to_string()),
..Default::default() ..Default::default()
}; };
let _ = bundle let channel = bundle.channel.read().unwrap().clone();
.user let _ = bundle.user.send_message(message, channel.id).await.unwrap();
.send_message(message, bundle.channel.id)
.await
.unwrap();
common::teardown(bundle).await common::teardown(bundle).await
} }
@ -50,13 +47,9 @@ async fn send_message_attachment() {
attachments: Some(vec![attachment.clone()]), attachments: Some(vec![attachment.clone()]),
..Default::default() ..Default::default()
}; };
let channel = bundle.channel.read().unwrap().clone();
let vec_attach = vec![attachment.clone()]; let vec_attach = vec![attachment.clone()];
let _arg = Some(&vec_attach); let _arg = Some(&vec_attach);
bundle bundle.user.send_message(message, channel.id).await.unwrap();
.user
.send_message(message, bundle.channel.id)
.await
.unwrap();
common::teardown(bundle).await common::teardown(bundle).await
} }

View File

@ -7,9 +7,9 @@ async fn test_get_mutual_relationships() {
let mut bundle = common::setup().await; let mut bundle = common::setup().await;
let mut other_user = bundle.create_user("integrationtestuser2").await; let mut other_user = bundle.create_user("integrationtestuser2").await;
let user = &mut bundle.user; let user = &mut bundle.user;
let username = user.object.lock().unwrap().username.clone(); let username = user.object.read().unwrap().username.clone();
let discriminator = user.object.lock().unwrap().discriminator.clone(); let discriminator = user.object.read().unwrap().discriminator.clone();
let other_user_id: types::Snowflake = other_user.object.lock().unwrap().id; let other_user_id: types::Snowflake = other_user.object.read().unwrap().id;
let friend_request_schema = types::FriendRequestSendSchema { let friend_request_schema = types::FriendRequestSendSchema {
username, username,
discriminator: Some(discriminator), discriminator: Some(discriminator),
@ -28,8 +28,8 @@ async fn test_get_relationships() {
let mut bundle = common::setup().await; let mut bundle = common::setup().await;
let mut other_user = bundle.create_user("integrationtestuser2").await; let mut other_user = bundle.create_user("integrationtestuser2").await;
let user = &mut bundle.user; let user = &mut bundle.user;
let username = user.object.lock().unwrap().username.clone(); let username = user.object.read().unwrap().username.clone();
let discriminator = user.object.lock().unwrap().discriminator.clone(); let discriminator = user.object.read().unwrap().discriminator.clone();
let friend_request_schema = types::FriendRequestSendSchema { let friend_request_schema = types::FriendRequestSendSchema {
username, username,
discriminator: Some(discriminator), discriminator: Some(discriminator),
@ -41,7 +41,7 @@ async fn test_get_relationships() {
let relationships = user.get_relationships().await.unwrap(); let relationships = user.get_relationships().await.unwrap();
assert_eq!( assert_eq!(
relationships.get(0).unwrap().id, relationships.get(0).unwrap().id,
other_user.object.lock().unwrap().id other_user.object.read().unwrap().id
); );
common::teardown(bundle).await common::teardown(bundle).await
} }
@ -51,8 +51,8 @@ async fn test_modify_relationship_friends() {
let mut bundle = common::setup().await; let mut bundle = common::setup().await;
let mut other_user = bundle.create_user("integrationtestuser2").await; let mut other_user = bundle.create_user("integrationtestuser2").await;
let user = &mut bundle.user; let user = &mut bundle.user;
let user_id: types::Snowflake = user.object.lock().unwrap().id; let user_id: types::Snowflake = user.object.read().unwrap().id;
let other_user_id: types::Snowflake = other_user.object.lock().unwrap().id; let other_user_id: types::Snowflake = other_user.object.read().unwrap().id;
other_user other_user
.modify_user_relationship(user_id, types::RelationshipType::Friends) .modify_user_relationship(user_id, types::RelationshipType::Friends)
@ -61,7 +61,7 @@ async fn test_modify_relationship_friends() {
let relationships = user.get_relationships().await.unwrap(); let relationships = user.get_relationships().await.unwrap();
assert_eq!( assert_eq!(
relationships.get(0).unwrap().id, relationships.get(0).unwrap().id,
other_user.object.lock().unwrap().id other_user.object.read().unwrap().id
); );
assert_eq!( assert_eq!(
relationships.get(0).unwrap().relationship_type, relationships.get(0).unwrap().relationship_type,
@ -70,7 +70,7 @@ async fn test_modify_relationship_friends() {
let relationships = other_user.get_relationships().await.unwrap(); let relationships = other_user.get_relationships().await.unwrap();
assert_eq!( assert_eq!(
relationships.get(0).unwrap().id, relationships.get(0).unwrap().id,
user.object.lock().unwrap().id user.object.read().unwrap().id
); );
assert_eq!( assert_eq!(
relationships.get(0).unwrap().relationship_type, relationships.get(0).unwrap().relationship_type,
@ -102,7 +102,7 @@ async fn test_modify_relationship_block() {
let mut bundle = common::setup().await; let mut bundle = common::setup().await;
let mut other_user = bundle.create_user("integrationtestuser2").await; let mut other_user = bundle.create_user("integrationtestuser2").await;
let user = &mut bundle.user; let user = &mut bundle.user;
let user_id: types::Snowflake = user.object.lock().unwrap().id; let user_id: types::Snowflake = user.object.read().unwrap().id;
other_user other_user
.modify_user_relationship(user_id, types::RelationshipType::Blocked) .modify_user_relationship(user_id, types::RelationshipType::Blocked)
@ -113,7 +113,7 @@ async fn test_modify_relationship_block() {
let relationships = other_user.get_relationships().await.unwrap(); let relationships = other_user.get_relationships().await.unwrap();
assert_eq!( assert_eq!(
relationships.get(0).unwrap().id, relationships.get(0).unwrap().id,
user.object.lock().unwrap().id user.object.read().unwrap().id
); );
assert_eq!( assert_eq!(
relationships.get(0).unwrap().relationship_type, relationships.get(0).unwrap().relationship_type,

View File

@ -17,12 +17,12 @@ async fn create_and_get_roles() {
position: None, position: None,
color: None, color: None,
}; };
let guild = bundle.guild.id; let guild_id = bundle.guild.read().unwrap().id;
let role = types::RoleObject::create(&mut bundle.user, guild, role_create_schema) let role = types::RoleObject::create(&mut bundle.user, guild_id, role_create_schema)
.await .await
.unwrap(); .unwrap();
let expected = types::RoleObject::get_all(&mut bundle.user, guild) let expected = types::RoleObject::get_all(&mut bundle.user, guild_id)
.await .await
.unwrap()[2] .unwrap()[2]
.clone(); .clone();
@ -34,9 +34,9 @@ async fn create_and_get_roles() {
#[tokio::test] #[tokio::test]
async fn get_singular_role() { async fn get_singular_role() {
let mut bundle = common::setup().await; let mut bundle = common::setup().await;
let guild_id = bundle.guild.id; let guild_id = bundle.guild.read().unwrap().id;
let role_id = bundle.role.id; let role_id = bundle.role.read().unwrap().id;
let role = bundle.role.clone(); let role = bundle.role.read().unwrap().clone();
let same_role = chorus::types::RoleObject::get(&mut bundle.user, guild_id, role_id) let same_role = chorus::types::RoleObject::get(&mut bundle.user, guild_id, role_id)
.await .await
.unwrap(); .unwrap();