diff --git a/src/api/channels/messages.rs b/src/api/channels/messages.rs index 6dfdfbf..3ee5228 100644 --- a/src/api/channels/messages.rs +++ b/src/api/channels/messages.rs @@ -36,7 +36,7 @@ impl Message { chorus_request.deserialize_response::(user).await } else { for (index, attachment) in message.attachments.iter_mut().enumerate() { - attachment.get_mut(index).unwrap().set_id(index as i16); + attachment.get_mut(index).unwrap().id = Some(index as i16); } let mut form = reqwest::multipart::Form::new(); let payload_json = to_string(&message).unwrap(); @@ -45,8 +45,8 @@ impl Message { form = form.part("payload_json", payload_field); for (index, attachment) in message.attachments.unwrap().into_iter().enumerate() { - let (attachment_content, current_attachment) = attachment.move_content(); - let (attachment_filename, _) = current_attachment.move_filename(); + let attachment_content = attachment.content; + let attachment_filename = attachment.filename; let part_name = format!("files[{}]", index); let content_disposition = format!( "form-data; name=\"{}\"'; filename=\"{}\"", diff --git a/src/gateway/handle.rs b/src/gateway/handle.rs index dede40e..2a18f36 100644 --- a/src/gateway/handle.rs +++ b/src/gateway/handle.rs @@ -40,10 +40,19 @@ impl GatewayHandle { .unwrap(); } + /// Recursively observes a [`Shared`] object, by making sure all [`Composite `] fields within + /// that object and its children are being watched. + /// + /// Observing means, that if new information arrives about the observed object or its children, + /// the object automatically gets updated, without you needing to request new information about + /// the object in question from the API, which is expensive and can lead to rate limiting. + /// + /// The [`Shared`] object returned by this method points to a different object than the one + /// being supplied as a &self function argument. pub async fn observe>( &self, - object: Arc>, - ) -> Arc> { + object: Shared, + ) -> Shared { let mut store = self.store.lock().await; let id = object.read().unwrap().id(); if let Some(channel) = store.get(&id) { @@ -84,7 +93,7 @@ impl GatewayHandle { /// with all of its observable fields being observed. pub async fn observe_and_into_inner>( &self, - object: Arc>, + object: Shared, ) -> T { let channel = self.observe(object.clone()).await; let object = channel.read().unwrap().clone(); diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 7586d6f..15e0087 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -128,3 +128,11 @@ impl GatewayEvent { } } } + +/// A type alias for [`Arc>`], used to make the public facing API concerned with +/// Composite structs more ergonomic. +/// ## Note +/// +/// While `T` does not have to implement `Composite` to be used with `Shared`, +/// the primary use of `Shared` is with types that implement `Composite`. +pub type Shared = Arc>; diff --git a/src/instance.rs b/src/instance.rs index a4967e8..cd5f6b2 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -9,7 +9,7 @@ use reqwest::Client; use serde::{Deserialize, Serialize}; use crate::errors::ChorusResult; -use crate::gateway::{Gateway, GatewayHandle}; +use crate::gateway::{Gateway, GatewayHandle, Shared}; use crate::ratelimiter::ChorusRequest; use crate::types::types::subconfigs::limits::rates::RateLimits; use crate::types::{ @@ -19,6 +19,7 @@ use crate::UrlBundle; #[derive(Debug, Clone, Default, Serialize, Deserialize)] /// The [`Instance`]; what you will be using to perform all sorts of actions on the Spacebar server. +/// /// If `limits_information` is `None`, then the instance will not be rate limited. pub struct Instance { pub urls: UrlBundle, @@ -36,8 +37,6 @@ impl PartialEq for Instance { } } -impl Eq for Instance {} - impl std::hash::Hash for Instance { fn hash(&self, state: &mut H) { self.urls.hash(state); @@ -72,8 +71,17 @@ impl PartialEq for LimitsInformation { } impl Instance { - /// Creates a new [`Instance`] from the [relevant instance urls](UrlBundle). To create an Instance from one singular url, use [`Instance::from_root_url()`]. - async fn from_url_bundle(urls: UrlBundle) -> ChorusResult { + pub(crate) fn clone_limits_if_some(&self) -> Option> { + if self.limits_information.is_some() { + return Some(self.limits_information.as_ref().unwrap().ratelimits.clone()); + } + None + } + + /// Creates a new [`Instance`] from the [relevant instance urls](UrlBundle). + /// + /// To create an Instance from one singular url, use [`Instance::new()`]. + pub async fn from_url_bundle(urls: UrlBundle) -> ChorusResult { let is_limited: Option = Instance::is_limited(&urls.api).await?; let limit_information; @@ -103,17 +111,9 @@ impl Instance { Ok(instance) } - pub(crate) fn clone_limits_if_some(&self) -> Option> { - if self.limits_information.is_some() { - return Some(self.limits_information.as_ref().unwrap().ratelimits.clone()); - } - None - } - /// Creates a new [`Instance`] by trying to get the [relevant instance urls](UrlBundle) from a root url. - /// Shorthand for `Instance::new(UrlBundle::from_root_domain(root_domain).await?)`. /// - /// If `limited` is `true`, then Chorus will track and enforce rate limits for this instance. + /// Shorthand for `Instance::from_url_bundle(UrlBundle::from_root_domain(root_domain).await?)`. pub async fn new(root_url: &str) -> ChorusResult { let urls = UrlBundle::from_root_url(root_url).await?; Instance::from_url_bundle(urls).await @@ -153,11 +153,11 @@ impl fmt::Display for Token { /// It is used for most authenticated actions on a Spacebar server. /// It also has its own [Gateway] connection. pub struct ChorusUser { - pub belongs_to: Arc>, + pub belongs_to: Shared, pub token: String, pub limits: Option>, - pub settings: Arc>, - pub object: Arc>, + pub settings: Shared, + pub object: Shared, pub gateway: GatewayHandle, } @@ -169,8 +169,6 @@ impl PartialEq for ChorusUser { } } -impl Eq for ChorusUser {} - impl ChorusUser { pub fn token(&self) -> String { self.token.clone() @@ -186,11 +184,11 @@ impl ChorusUser { /// This isn't the prefered way to create a ChorusUser. /// See [Instance::login_account] and [Instance::register_account] instead. pub fn new( - belongs_to: Arc>, + belongs_to: Shared, token: String, limits: Option>, - settings: Arc>, - object: Arc>, + settings: Shared, + object: Shared, gateway: GatewayHandle, ) -> ChorusUser { ChorusUser { @@ -208,7 +206,7 @@ impl ChorusUser { /// registering or logging in to the Instance, where you do not yet have a User object, but still /// need to make a RateLimited request. To use the [`GatewayHandle`], you will have to identify /// first. - pub(crate) async fn shell(instance: Arc>, token: String) -> ChorusUser { + pub(crate) async fn shell(instance: Shared, token: String) -> ChorusUser { let settings = Arc::new(RwLock::new(UserSettings::default())); let object = Arc::new(RwLock::new(User::default())); let wss_url = instance.read().unwrap().urls.wss.clone(); diff --git a/src/types/entities/application.rs b/src/types/entities/application.rs index 0b55626..95df4f2 100644 --- a/src/types/entities/application.rs +++ b/src/types/entities/application.rs @@ -1,10 +1,9 @@ -use std::sync::{Arc, RwLock}; - use bitflags::bitflags; use serde::{Deserialize, Serialize}; use serde_json::Value; use serde_repr::{Deserialize_repr, Serialize_repr}; +use crate::gateway::Shared; use crate::types::utils::Snowflake; use crate::types::{Team, User}; @@ -27,7 +26,7 @@ pub struct Application { pub bot_require_code_grant: bool, pub verify_key: String, #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub owner: Arc>, + pub owner: Shared, pub flags: u64, #[cfg(feature = "sqlx")] pub redirect_uris: Option>>, @@ -49,7 +48,7 @@ pub struct Application { #[cfg(feature = "sqlx")] pub install_params: Option>, #[cfg(not(feature = "sqlx"))] - pub install_params: Option>>, + pub install_params: Option>, pub terms_of_service_url: Option, pub privacy_policy_url: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] @@ -142,7 +141,7 @@ pub struct ApplicationCommand { pub application_id: Snowflake, pub name: String, pub description: String, - pub options: Vec>>, + pub options: Vec>, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -154,7 +153,7 @@ pub struct ApplicationCommandOption { pub description: String, pub required: bool, pub choices: Vec, - pub options: Arc>>, + pub options: Shared>, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -190,14 +189,14 @@ pub enum ApplicationCommandOptionType { pub struct ApplicationCommandInteractionData { pub id: Snowflake, pub name: String, - pub options: Vec>>, + pub options: Vec>, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ApplicationCommandInteractionDataOption { pub name: String, pub value: Value, - pub options: Vec>>, + pub options: Vec>, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] @@ -206,7 +205,7 @@ pub struct GuildApplicationCommandPermissions { pub id: Snowflake, pub application_id: Snowflake, pub guild_id: Snowflake, - pub permissions: Vec>>, + pub permissions: Vec>, } #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] diff --git a/src/types/entities/attachment.rs b/src/types/entities/attachment.rs index ffbc520..9aacc08 100644 --- a/src/types/entities/attachment.rs +++ b/src/types/entities/attachment.rs @@ -55,73 +55,3 @@ pub struct PartialDiscordFileAttachment { #[serde(skip_serializing)] pub content: Vec, } - -impl PartialDiscordFileAttachment { - /// Moves `self.content` out of `self` and returns it. - pub fn move_content(self) -> (Vec, PartialDiscordFileAttachment) { - let content = self.content; - let updated_struct = PartialDiscordFileAttachment { - id: self.id, - filename: self.filename, - description: self.description, - content_type: self.content_type, - size: self.size, - url: self.url, - proxy_url: self.proxy_url, - height: self.height, - width: self.width, - ephemeral: self.ephemeral, - duration_secs: self.duration_secs, - waveform: self.waveform, - content: Vec::new(), - }; - (content, updated_struct) - } - - /// Moves `self.filename` out of `self` and returns it. - pub fn move_filename(self) -> (String, PartialDiscordFileAttachment) { - let filename = self.filename; - let updated_struct = PartialDiscordFileAttachment { - id: self.id, - filename: String::new(), - description: self.description, - content_type: self.content_type, - size: self.size, - url: self.url, - proxy_url: self.proxy_url, - height: self.height, - width: self.width, - - ephemeral: self.ephemeral, - duration_secs: self.duration_secs, - waveform: self.waveform, - content: self.content, - }; - (filename, updated_struct) - } - - /// Moves `self.content_type` out of `self` and returns it. - pub fn move_content_type(self) -> (Option, PartialDiscordFileAttachment) { - let content_type = self.content_type; - let updated_struct = PartialDiscordFileAttachment { - id: self.id, - filename: self.filename, - description: self.description, - content_type: None, - size: self.size, - url: self.url, - proxy_url: self.proxy_url, - height: self.height, - width: self.width, - ephemeral: self.ephemeral, - duration_secs: self.duration_secs, - waveform: self.waveform, - content: self.content, - }; - (content_type, updated_struct) - } - - pub fn set_id(&mut self, id: i16) { - self.id = Some(id); - } -} diff --git a/src/types/entities/audit_log.rs b/src/types/entities/audit_log.rs index be14f0f..4023b7a 100644 --- a/src/types/entities/audit_log.rs +++ b/src/types/entities/audit_log.rs @@ -1,14 +1,13 @@ -use std::sync::{Arc, RwLock}; - use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::utils::Snowflake; #[derive(Serialize, Deserialize, Debug, Default, Clone)] /// See pub struct AuditLogEntry { pub target_id: Option, - pub changes: Option>>>, + pub changes: Option>>, pub user_id: Option, pub id: Snowflake, // to:do implement an enum for these types diff --git a/src/types/entities/auto_moderation.rs b/src/types/entities/auto_moderation.rs index a8910b1..779f58b 100644 --- a/src/types/entities/auto_moderation.rs +++ b/src/types/entities/auto_moderation.rs @@ -1,5 +1,4 @@ -use std::sync::{Arc, RwLock}; - +use crate::gateway::Shared; #[cfg(feature = "client")] use crate::gateway::Updateable; @@ -21,8 +20,8 @@ pub struct AutoModerationRule { pub creator_id: Snowflake, pub event_type: AutoModerationRuleEventType, pub trigger_type: AutoModerationRuleTriggerType, - pub trigger_metadata: Arc>, - pub actions: Vec>>, + pub trigger_metadata: Shared, + pub actions: Vec>, pub enabled: bool, pub exempt_roles: Vec, pub exempt_channels: Vec, @@ -99,7 +98,7 @@ pub enum AutoModerationRuleKeywordPresetType { pub struct AutoModerationAction { #[serde(rename = "type")] pub action_type: AutoModerationActionType, - pub metadata: Option>>, + pub metadata: Option>, } #[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)] diff --git a/src/types/entities/channel.rs b/src/types/entities/channel.rs index 5acf2ae..c268859 100644 --- a/src/types/entities/channel.rs +++ b/src/types/entities/channel.rs @@ -1,11 +1,10 @@ -use std::sync::{Arc, RwLock}; - use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_aux::prelude::deserialize_string_from_number; use serde_repr::{Deserialize_repr, Serialize_repr}; use std::fmt::Debug; +use crate::gateway::Shared; use crate::types::{ entities::{GuildMember, User}, utils::Snowflake, @@ -71,13 +70,13 @@ pub struct Channel { pub permission_overwrites: Option>>, #[cfg(not(feature = "sqlx"))] #[cfg_attr(feature = "client", observe_option_vec)] - pub permission_overwrites: Option>>>, + pub permission_overwrites: Option>>, pub permissions: Option, pub position: Option, pub rate_limit_per_user: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "client", observe_option_vec)] - pub recipients: Option>>>, + pub recipients: Option>>, pub rtc_region: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] pub thread_metadata: Option, @@ -171,7 +170,7 @@ pub struct ThreadMember { pub user_id: Option, pub join_timestamp: Option, pub flags: Option, - pub member: Option>>, + pub member: Option>, } #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] diff --git a/src/types/entities/config.rs b/src/types/entities/config.rs index 25b1ef1..6244e9d 100644 --- a/src/types/entities/config.rs +++ b/src/types/entities/config.rs @@ -15,20 +15,29 @@ impl ConfigEntity { let Some(v) = self.value.as_ref() else { return None; }; - Some(v.as_str().expect("value is not a string").to_string()) + let Some(v) = v.as_str() else { + return None; + }; + Some(v.to_string()) } pub fn as_bool(&self) -> Option { let Some(v) = self.value.as_ref() else { return None; }; - Some(v.as_bool().expect("value is not a boolean")) + let Some(v) = v.as_bool() else { + return None; + }; + Some(v) } pub fn as_int(&self) -> Option { let Some(v) = self.value.as_ref() else { return None; }; - Some(v.as_i64().expect("value is not a number")) + let Some(v) = v.as_i64() else { + return None; + }; + Some(v) } } diff --git a/src/types/entities/emoji.rs b/src/types/entities/emoji.rs index b3916e2..1c78cba 100644 --- a/src/types/entities/emoji.rs +++ b/src/types/entities/emoji.rs @@ -1,8 +1,8 @@ use std::fmt::Debug; -use std::sync::{Arc, RwLock}; use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::entities::User; use crate::types::Snowflake; @@ -31,7 +31,7 @@ pub struct Emoji { #[cfg(not(feature = "sqlx"))] pub roles: Option>, #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub user: Option>>, + pub user: Option>, pub require_colons: Option, pub managed: Option, pub animated: Option, @@ -62,37 +62,3 @@ impl PartialEq for Emoji { || self.available != other.available) } } - -impl PartialOrd for Emoji { - fn partial_cmp(&self, other: &Self) -> Option { - match self.id.partial_cmp(&other.id) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.name.partial_cmp(&other.name) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.roles.partial_cmp(&other.roles) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.roles.partial_cmp(&other.roles) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.require_colons.partial_cmp(&other.require_colons) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.managed.partial_cmp(&other.managed) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.animated.partial_cmp(&other.animated) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - self.available.partial_cmp(&other.available) - } -} diff --git a/src/types/entities/guild.rs b/src/types/entities/guild.rs index 1fe235b..4a3d952 100644 --- a/src/types/entities/guild.rs +++ b/src/types/entities/guild.rs @@ -1,11 +1,11 @@ use std::fmt::Debug; -use std::sync::{Arc, RwLock}; use bitflags::bitflags; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; +use crate::gateway::Shared; use crate::types::types::guild_configuration::GuildFeaturesList; use crate::types::{ entities::{Channel, Emoji, RoleObject, Sticker, User, VoiceState, Webhook}, @@ -45,14 +45,14 @@ pub struct Guild { pub bans: Option>, #[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "client", observe_option_vec)] - pub channels: Option>>>, + pub channels: Option>>, pub default_message_notifications: Option, pub description: Option, pub discovery_splash: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "client", observe_vec)] #[serde(default)] - pub emojis: Vec>>, + pub emojis: Vec>, pub explicit_content_filter: Option, //#[cfg_attr(feature = "sqlx", sqlx(try_from = "String"))] pub features: Option, @@ -88,7 +88,7 @@ pub struct Guild { pub region: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "client", observe_option_vec)] - pub roles: Option>>>, + pub roles: Option>>, #[cfg_attr(feature = "sqlx", sqlx(skip))] pub rules_channel: Option, pub rules_channel_id: Option, @@ -102,10 +102,10 @@ pub struct Guild { pub verification_level: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "client", observe_option_vec)] - pub voice_states: Option>>>, + pub voice_states: Option>>, #[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "client", observe_option_vec)] - pub webhooks: Option>>>, + pub webhooks: Option>>, #[cfg(feature = "sqlx")] pub welcome_screen: Option>, #[cfg(not(feature = "sqlx"))] @@ -217,8 +217,6 @@ impl std::cmp::PartialEq for Guild { } } -impl std::cmp::Eq for Guild {} - /// See #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] @@ -239,11 +237,11 @@ pub struct GuildInvite { pub created_at: DateTime, pub expires_at: Option>, pub guild_id: Snowflake, - pub guild: Option>>, + pub guild: Option>, pub channel_id: Snowflake, - pub channel: Option>>, + pub channel: Option>, pub inviter_id: Option, - pub inviter: Option>>, + pub inviter: Option>, pub target_user_id: Option, pub target_user: Option, pub target_user_type: Option, @@ -296,7 +294,7 @@ pub struct GuildScheduledEvent { pub entity_type: GuildScheduledEventEntityType, pub entity_id: Option, pub entity_metadata: Option, - pub creator: Option>>, + pub creator: Option>, pub user_count: Option, pub image: Option, } diff --git a/src/types/entities/guild_member.rs b/src/types/entities/guild_member.rs index bf2f93b..a18afbc 100644 --- a/src/types/entities/guild_member.rs +++ b/src/types/entities/guild_member.rs @@ -1,7 +1,6 @@ -use std::sync::{Arc, RwLock}; - use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::{entities::PublicUser, Snowflake}; #[derive(Debug, Deserialize, Default, Serialize, Clone)] @@ -10,7 +9,7 @@ use crate::types::{entities::PublicUser, Snowflake}; /// # Reference /// See pub struct GuildMember { - pub user: Option>>, + pub user: Option>, pub nick: Option, pub avatar: Option, pub roles: Vec, diff --git a/src/types/entities/integration.rs b/src/types/entities/integration.rs index 0913213..580590a 100644 --- a/src/types/entities/integration.rs +++ b/src/types/entities/integration.rs @@ -1,8 +1,7 @@ -use std::sync::{Arc, RwLock}; - use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::{ entities::{Application, User}, utils::Snowflake, @@ -23,14 +22,14 @@ pub struct Integration { pub expire_behaviour: Option, pub expire_grace_period: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub user: Option>>, + pub user: Option>, #[cfg_attr(feature = "sqlx", sqlx(skip))] pub account: IntegrationAccount, pub synced_at: Option>, pub subscriber_count: Option, pub revoked: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub application: Option>>, + pub application: Option>, pub scopes: Option>, } diff --git a/src/types/entities/invite.rs b/src/types/entities/invite.rs index d08ad1d..f9eaaa3 100644 --- a/src/types/entities/invite.rs +++ b/src/types/entities/invite.rs @@ -1,8 +1,7 @@ -use std::sync::{Arc, RwLock}; - use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::{Snowflake, WelcomeScreenObject}; use super::guild::GuildScheduledEvent; @@ -21,7 +20,7 @@ pub struct Invite { pub flags: Option, pub guild: Option, pub guild_id: Option, - pub guild_scheduled_event: Option>>, + pub guild_scheduled_event: Option>, #[serde(rename = "type")] pub invite_type: Option, pub inviter: Option, @@ -59,7 +58,7 @@ pub struct InviteGuild { /// See #[derive(Debug, Serialize, Deserialize)] pub struct InviteStageInstance { - pub members: Vec>>, + pub members: Vec>, pub participant_count: i32, pub speaker_count: i32, pub topic: String, diff --git a/src/types/entities/message.rs b/src/types/entities/message.rs index 41c4d51..1e6810d 100644 --- a/src/types/entities/message.rs +++ b/src/types/entities/message.rs @@ -1,7 +1,6 @@ -use std::sync::{Arc, RwLock}; - use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::{ entities::{ Application, Attachment, Channel, Emoji, GuildMember, PublicUser, RoleSubscriptionData, @@ -121,7 +120,7 @@ pub struct MessageInteraction { pub interaction_type: u8, pub name: String, pub user: User, - pub member: Option>>, + pub member: Option>, } #[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, Eq, PartialOrd, Ord)] @@ -219,7 +218,7 @@ pub struct EmbedField { inline: Option, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialOrd, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Reaction { pub count: u32, pub burst_count: u32, diff --git a/src/types/entities/mod.rs b/src/types/entities/mod.rs index 8343628..7e88a62 100644 --- a/src/types/entities/mod.rs +++ b/src/types/entities/mod.rs @@ -23,6 +23,7 @@ pub use user_settings::*; pub use voice_state::*; pub use webhook::*; +use crate::gateway::Shared; #[cfg(feature = "client")] use crate::gateway::Updateable; @@ -34,7 +35,6 @@ use async_trait::async_trait; #[cfg(feature = "client")] use std::fmt::Debug; - #[cfg(feature = "client")] use std::sync::{Arc, RwLock}; @@ -69,9 +69,9 @@ pub trait Composite { async fn watch_whole(self, gateway: &GatewayHandle) -> Self; async fn option_observe_fn( - value: Option>>, + value: Option>, gateway: &GatewayHandle, - ) -> Option>> + ) -> Option> where T: Composite + Debug, { @@ -84,9 +84,9 @@ pub trait Composite { } async fn option_vec_observe_fn( - value: Option>>>, + value: Option>>, gateway: &GatewayHandle, - ) -> Option>>> + ) -> Option>> where T: Composite, { @@ -101,17 +101,14 @@ pub trait Composite { } } - async fn value_observe_fn(value: Arc>, gateway: &GatewayHandle) -> Arc> + async fn value_observe_fn(value: Shared, gateway: &GatewayHandle) -> Shared where T: Composite, { gateway.observe(value).await } - async fn vec_observe_fn( - value: Vec>>, - gateway: &GatewayHandle, - ) -> Vec>> + async fn vec_observe_fn(value: Vec>, gateway: &GatewayHandle) -> Vec> where T: Composite, { @@ -122,3 +119,19 @@ pub trait Composite { vec } } + +pub trait IntoShared { + /// Uses [`Shared`] to provide an ergonomic alternative to `Arc::new(RwLock::new(obj))`. + /// + /// [`Shared`] can then be observed using the [`Gateway`], turning the underlying + /// `dyn Composite` into a self-updating struct, which is a tracked variant of a chorus + /// entity struct, updating its' held information when new information concerning itself arrives + /// over the [`Gateway`] connection, reducing the need for expensive network-API calls. + fn into_shared(self) -> Shared; +} + +impl IntoShared for T { + fn into_shared(self) -> Shared { + Arc::new(RwLock::new(self)) + } +} diff --git a/src/types/entities/relationship.rs b/src/types/entities/relationship.rs index 576d99a..6f207f2 100644 --- a/src/types/entities/relationship.rs +++ b/src/types/entities/relationship.rs @@ -1,9 +1,8 @@ -use std::sync::{Arc, RwLock}; - use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; +use crate::gateway::Shared; use crate::types::Snowflake; use super::PublicUser; @@ -15,7 +14,7 @@ pub struct Relationship { #[serde(rename = "type")] pub relationship_type: RelationshipType, pub nickname: Option, - pub user: Arc>, + pub user: Shared, pub since: Option>, } diff --git a/src/types/entities/sticker.rs b/src/types/entities/sticker.rs index 593206d..d4331fd 100644 --- a/src/types/entities/sticker.rs +++ b/src/types/entities/sticker.rs @@ -1,7 +1,6 @@ -use std::sync::{Arc, RwLock}; - use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::{entities::User, utils::Snowflake}; #[derive(Debug, Serialize, Deserialize, Clone, Default)] @@ -24,7 +23,7 @@ pub struct Sticker { pub available: Option, pub guild_id: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub user: Option>>, + pub user: Option>, pub sort_value: Option, } diff --git a/src/types/entities/team.rs b/src/types/entities/team.rs index 8e32f55..daac58c 100644 --- a/src/types/entities/team.rs +++ b/src/types/entities/team.rs @@ -1,7 +1,6 @@ -use std::sync::{Arc, RwLock}; - use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::entities::User; use crate::types::Snowflake; @@ -21,5 +20,5 @@ pub struct TeamMember { pub membership_state: u8, pub permissions: Vec, pub team_id: Snowflake, - pub user: Arc>, + pub user: Shared, } diff --git a/src/types/entities/template.rs b/src/types/entities/template.rs index 1305a98..167697f 100644 --- a/src/types/entities/template.rs +++ b/src/types/entities/template.rs @@ -1,8 +1,7 @@ -use std::sync::{Arc, RwLock}; - use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::{ entities::{Guild, User}, utils::Snowflake, @@ -18,13 +17,13 @@ pub struct GuildTemplate { pub usage_count: Option, pub creator_id: Snowflake, #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub creator: Arc>, + pub creator: Shared, pub created_at: DateTime, pub updated_at: DateTime, pub source_guild_id: Snowflake, #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub source_guild: Vec>>, + pub source_guild: Vec>, // Unsure how a {recursive: Guild} looks like, might be a Vec? #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub serialized_source_guild: Vec>>, + pub serialized_source_guild: Vec>, } diff --git a/src/types/entities/user.rs b/src/types/entities/user.rs index a7bdf63..90221ca 100644 --- a/src/types/entities/user.rs +++ b/src/types/entities/user.rs @@ -26,7 +26,7 @@ pub struct UserData { } impl User { - pub fn to_public_user(self) -> PublicUser { + pub fn into_public_user(self) -> PublicUser { PublicUser::from(self) } } @@ -133,7 +133,7 @@ bitflags::bitflags! { } } -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd)] +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] pub struct UserProfileMetadata { pub guild_id: Option, pub pronouns: String, diff --git a/src/types/entities/user_settings.rs b/src/types/entities/user_settings.rs index e6db7e7..a27e748 100644 --- a/src/types/entities/user_settings.rs +++ b/src/types/entities/user_settings.rs @@ -3,6 +3,8 @@ use std::sync::{Arc, RwLock}; use chrono::{serde::ts_milliseconds_option, Utc}; use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[serde(rename_all = "lowercase")] @@ -77,7 +79,7 @@ pub struct UserSettings { #[cfg(not(feature = "sqlx"))] pub restricted_guilds: Vec, pub show_current_game: bool, - pub status: Arc>, + pub status: Shared, pub stream_notifications_enabled: bool, pub theme: UserTheme, pub timezone_offset: i16, @@ -153,5 +155,5 @@ pub struct GuildFolder { #[derive(Debug, Serialize, Deserialize)] pub struct LoginResult { pub token: String, - pub settings: Arc>, + pub settings: Shared, } diff --git a/src/types/entities/voice_state.rs b/src/types/entities/voice_state.rs index 06bd0cc..189fcac 100644 --- a/src/types/entities/voice_state.rs +++ b/src/types/entities/voice_state.rs @@ -1,8 +1,7 @@ -use std::sync::{Arc, RwLock}; - #[cfg(feature = "client")] use chorus_macros::Composite; +use crate::gateway::Shared; #[cfg(feature = "client")] use crate::types::Composite; @@ -33,7 +32,7 @@ pub struct VoiceState { pub guild: Option, pub channel_id: Option, pub user_id: Snowflake, - pub member: Option>>, + pub member: Option>, /// Includes alphanumeric characters, not a snowflake pub session_id: String, pub token: Option, @@ -49,6 +48,7 @@ pub struct VoiceState { } impl Updateable for VoiceState { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Snowflake { if let Some(id) = self.id { id // ID exists: Only the case for Spacebar Server impls diff --git a/src/types/entities/webhook.rs b/src/types/entities/webhook.rs index cf5716c..7eb2b66 100644 --- a/src/types/entities/webhook.rs +++ b/src/types/entities/webhook.rs @@ -1,8 +1,8 @@ use std::fmt::Debug; -use std::sync::{Arc, RwLock}; use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; #[cfg(feature = "client")] use crate::gateway::Updateable; @@ -36,10 +36,10 @@ pub struct Webhook { pub application_id: Snowflake, #[serde(skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub user: Option>>, + pub user: Option>, #[serde(skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub source_guild: Option>>, + pub source_guild: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub url: Option, } diff --git a/src/types/events/auto_moderation.rs b/src/types/events/auto_moderation.rs index 2a2eb6b..b667486 100644 --- a/src/types/events/auto_moderation.rs +++ b/src/types/events/auto_moderation.rs @@ -31,7 +31,9 @@ pub struct AutoModerationRuleUpdate { } #[cfg(feature = "client")] +#[cfg(not(tarpaulin_include))] impl UpdateMessage for AutoModerationRuleUpdate { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { Some(self.rule.id) } diff --git a/src/types/events/channel.rs b/src/types/events/channel.rs index eb557d7..10e8561 100644 --- a/src/types/events/channel.rs +++ b/src/types/events/channel.rs @@ -1,4 +1,5 @@ use crate::types::events::WebSocketEvent; +use crate::types::IntoShared; use crate::types::{entities::Channel, JsonField, Snowflake, SourceUrlField}; use chorus_macros::{JsonField, SourceUrlField}; use chrono::{DateTime, Utc}; @@ -8,7 +9,7 @@ use serde::{Deserialize, Serialize}; use super::UpdateMessage; #[cfg(feature = "client")] -use std::sync::{Arc, RwLock}; +use crate::gateway::Shared; #[cfg(feature = "client")] use crate::types::Guild; @@ -38,13 +39,14 @@ impl WebSocketEvent for ChannelCreate {} #[cfg(feature = "client")] impl UpdateMessage for ChannelCreate { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { self.channel.guild_id } - fn update(&mut self, object_to_update: Arc>) { + fn update(&mut self, object_to_update: Shared) { let mut write = object_to_update.write().unwrap(); - let update = Arc::new(RwLock::new(self.channel.clone())); + let update = self.channel.clone().into_shared(); if write.channels.is_some() { write.channels.as_mut().unwrap().push(update); } else { @@ -68,10 +70,12 @@ impl WebSocketEvent for ChannelUpdate {} #[cfg(feature = "client")] impl UpdateMessage for ChannelUpdate { - fn update(&mut self, object_to_update: Arc>) { + fn update(&mut self, object_to_update: Shared) { let mut write = object_to_update.write().unwrap(); *write = self.channel.clone(); } + + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { Some(self.channel.id) } @@ -110,11 +114,12 @@ pub struct ChannelDelete { #[cfg(feature = "client")] impl UpdateMessage for ChannelDelete { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { self.channel.guild_id } - fn update(&mut self, object_to_update: Arc>) { + fn update(&mut self, object_to_update: Shared) { if self.id().is_none() { return; } diff --git a/src/types/events/guild.rs b/src/types/events/guild.rs index 89e4a75..a460403 100644 --- a/src/types/events/guild.rs +++ b/src/types/events/guild.rs @@ -5,8 +5,8 @@ use serde::{Deserialize, Serialize}; use crate::types::entities::{Guild, PublicUser, UnavailableGuild}; use crate::types::events::WebSocketEvent; use crate::types::{ - AuditLogEntry, Emoji, GuildMember, GuildScheduledEvent, JsonField, RoleObject, Snowflake, - SourceUrlField, Sticker, + AuditLogEntry, Emoji, GuildMember, GuildScheduledEvent, IntoShared, JsonField, RoleObject, + Snowflake, SourceUrlField, Sticker, }; use super::PresenceUpdate; @@ -14,7 +14,7 @@ use super::PresenceUpdate; #[cfg(feature = "client")] use super::UpdateMessage; #[cfg(feature = "client")] -use std::sync::{Arc, RwLock}; +use crate::gateway::Shared; #[derive(Debug, Deserialize, Serialize, Default, Clone, SourceUrlField, JsonField)] /// See ; @@ -30,7 +30,9 @@ pub struct GuildCreate { } #[cfg(feature = "client")] +#[cfg(not(tarpaulin_include))] impl UpdateMessage for GuildCreate { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { match &self.d { GuildCreateDataOption::UnavailableGuild(unavailable) => Some(unavailable.id), @@ -38,7 +40,7 @@ impl UpdateMessage for GuildCreate { } } - fn update(&mut self, _: Arc>) {} + fn update(&mut self, _: Shared) {} } #[derive(Debug, Deserialize, Serialize, Clone)] @@ -92,6 +94,7 @@ impl WebSocketEvent for GuildUpdate {} #[cfg(feature = "client")] impl UpdateMessage for GuildUpdate { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { Some(self.guild.id) } @@ -111,10 +114,11 @@ pub struct GuildDelete { #[cfg(feature = "client")] impl UpdateMessage for GuildDelete { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { Some(self.guild.id) } - fn update(&mut self, _: Arc>) {} + fn update(&mut self, _: Shared) {} } impl WebSocketEvent for GuildDelete {} @@ -225,20 +229,21 @@ impl WebSocketEvent for GuildRoleCreate {} #[cfg(feature = "client")] impl UpdateMessage for GuildRoleCreate { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { Some(self.guild_id) } - fn update(&mut self, object_to_update: Arc>) { + fn update(&mut self, object_to_update: Shared) { 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()))); + .push(self.role.clone().into_shared()); } else { - object_to_update.roles = Some(Vec::from([Arc::new(RwLock::new(self.role.clone()))])); + object_to_update.roles = Some(Vec::from([self.role.clone().into_shared()])); } } } @@ -258,11 +263,12 @@ impl WebSocketEvent for GuildRoleUpdate {} #[cfg(feature = "client")] impl UpdateMessage for GuildRoleUpdate { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { Some(self.role.id) } - fn update(&mut self, object_to_update: Arc>) { + fn update(&mut self, object_to_update: Shared) { let mut write = object_to_update.write().unwrap(); *write = self.role.clone(); } diff --git a/src/types/events/mod.rs b/src/types/events/mod.rs index 63ea13a..d4722bf 100644 --- a/src/types/events/mod.rs +++ b/src/types/events/mod.rs @@ -39,9 +39,9 @@ use serde_json::{from_str, from_value, to_value, Value}; #[cfg(feature = "client")] use std::collections::HashMap; -use std::fmt::Debug; #[cfg(feature = "client")] -use std::sync::{Arc, RwLock}; +use crate::gateway::Shared; +use std::fmt::Debug; #[cfg(feature = "client")] use serde::de::DeserializeOwned; @@ -132,9 +132,10 @@ pub(crate) trait UpdateMessage: Clone + JsonField + SourceUrlField where T: Updateable + Serialize + DeserializeOwned + Clone, { - fn update(&mut self, object_to_update: Arc>) { + fn update(&mut self, object_to_update: Shared) { update_object(self.get_json(), object_to_update) } + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option; } @@ -152,7 +153,7 @@ pub trait SourceUrlField: Clone { /// Only applicable for events where the Update struct is the same as the Entity struct pub(crate) fn update_object( value: String, - object: Arc>, + object: Shared<(impl Updateable + Serialize + DeserializeOwned + Clone)>, ) { let data_from_event: HashMap = from_str(&value).unwrap(); let mut original_data: HashMap = diff --git a/src/types/events/thread.rs b/src/types/events/thread.rs index cff5f6f..34dd1a2 100644 --- a/src/types/events/thread.rs +++ b/src/types/events/thread.rs @@ -32,6 +32,7 @@ impl WebSocketEvent for ThreadUpdate {} #[cfg(feature = "client")] impl UpdateMessage for ThreadUpdate { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { Some(self.thread.id) } diff --git a/src/types/schema/guild.rs b/src/types/schema/guild.rs index 56c0f9e..9d2c27d 100644 --- a/src/types/schema/guild.rs +++ b/src/types/schema/guild.rs @@ -78,7 +78,7 @@ impl std::default::Default for GetUserGuildSchema { } } -#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, PartialOrd)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)] pub struct GuildPreview { pub id: Snowflake, pub name: String, diff --git a/src/voice/handler.rs b/src/voice/handler.rs index 82ebc6e..9a0ede8 100644 --- a/src/voice/handler.rs +++ b/src/voice/handler.rs @@ -111,7 +111,8 @@ impl Observer for VoiceHandler { *self.voice_udp_connection.lock().await = Some(udp_handle.clone()); - let string_ip_address = String::from_utf8(ip_discovery.address).expect("Ip discovery gave non string ip"); + let string_ip_address = + String::from_utf8(ip_discovery.address).expect("Ip discovery gave non string ip"); self.voice_gateway_connection .lock() diff --git a/tests/common/mod.rs b/tests/common/mod.rs index bce419a..e18f92b 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,6 +1,5 @@ -use std::sync::{Arc, RwLock}; - -use chorus::gateway::Gateway; +use chorus::gateway::{Gateway, Shared}; +use chorus::types::IntoShared; use chorus::{ instance::{ChorusUser, Instance}, types::{ @@ -16,9 +15,9 @@ pub(crate) struct TestBundle { pub urls: UrlBundle, pub user: ChorusUser, pub instance: Instance, - pub guild: Arc>, - pub role: Arc>, - pub channel: Arc>, + pub guild: Shared, + pub role: Shared, + pub channel: Shared, } #[allow(unused)] @@ -119,9 +118,9 @@ pub(crate) async fn setup() -> TestBundle { urls, user, instance, - guild: Arc::new(RwLock::new(guild)), - role: Arc::new(RwLock::new(role)), - channel: Arc::new(RwLock::new(channel)), + guild: guild.into_shared(), + role: role.into_shared(), + channel: channel.into_shared(), } } diff --git a/tests/gateway.rs b/tests/gateway.rs index e539775..2b627f2 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -1,12 +1,15 @@ mod common; -use std::sync::{Arc, RwLock}; +use std::sync::Arc; use std::time::Duration; use async_trait::async_trait; use chorus::errors::GatewayError; use chorus::gateway::*; -use chorus::types::{self, ChannelModifySchema, GatewayReady, RoleCreateModifySchema, RoleObject}; +use chorus::types::{ + self, Channel, ChannelCreateSchema, ChannelModifySchema, GatewayReady, IntoShared, + RoleCreateModifySchema, RoleObject, +}; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] @@ -82,7 +85,10 @@ async fn test_gateway_authenticate() { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_self_updating_structs() { + // PRETTYFYME: This test is a bit of a mess, but it works. Ideally, each self-updating struct + // would have its own test. let mut bundle = common::setup().await; + let received_channel = bundle .user .gateway @@ -110,6 +116,34 @@ async fn test_self_updating_structs() { "selfupdating".to_string() ); + let guild = bundle + .user + .gateway + .observe_and_into_inner(bundle.guild.clone()) + .await; + assert!(guild.channels.is_none()); + + Channel::create( + &mut bundle.user, + guild.id, + None, + ChannelCreateSchema { + name: "selfupdating2".to_string(), + channel_type: Some(types::ChannelType::GuildText), + ..Default::default() + }, + ) + .await + .unwrap(); + + let guild = bundle + .user + .gateway + .observe_and_into_inner(guild.into_shared()) + .await; + assert!(guild.channels.is_some()); + assert!(guild.channels.as_ref().unwrap().len() == 1); + common::teardown(bundle).await } @@ -144,7 +178,7 @@ async fn test_recursive_self_updating_structs() { bundle .user .gateway - .observe(Arc::new(RwLock::new(role.clone()))) + .observe(role.clone().into_shared()) .await; // Update Guild and check for Guild let inner_guild = guild.read().unwrap().clone(); @@ -157,7 +191,7 @@ async fn test_recursive_self_updating_structs() { let role_inner = bundle .user .gateway - .observe_and_into_inner(Arc::new(RwLock::new(role.clone()))) + .observe_and_into_inner(role.clone().into_shared()) .await; assert_eq!(role_inner.name, "yippieee"); // Check if the change propagated diff --git a/tests/ratelimit.rs b/tests/ratelimit.rs new file mode 100644 index 0000000..8843ed0 --- /dev/null +++ b/tests/ratelimit.rs @@ -0,0 +1,26 @@ +use chorus::ratelimiter::ChorusRequest; + +mod common; + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +async fn get_limit_config() { + let conf = ChorusRequest::get_limits_config("http://localhost:3001/api") + .await + .unwrap(); + assert!(conf.channel.max_pins > 0); + assert!(conf.channel.max_topic > 0); + assert!(conf.channel.max_webhooks > 0); + assert!(conf.guild.max_roles > 0); + assert!(conf.guild.max_channels > 0); + assert!(conf.guild.max_emojis > 0); + assert!(conf.guild.max_channels_in_category > 0); + assert!(conf.guild.max_members > 0); + assert!(conf.message.max_attachment_size > 0); + assert!(conf.message.max_bulk_delete > 0); + assert!(conf.message.max_reactions > 0); + assert!(conf.message.max_characters > 0); + assert!(conf.message.max_tts_characters == 0); + assert!(conf.user.max_guilds > 0); + assert!(conf.user.max_friends > 0); +} diff --git a/tests/types.rs b/tests/types.rs new file mode 100644 index 0000000..5acd4c6 --- /dev/null +++ b/tests/types.rs @@ -0,0 +1,1054 @@ +mod config { + mod subconfigs { + mod client { + use chorus::types::types::subconfigs::client::ClientReleaseConfiguration; + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn client_release_configuration() { + let _client_release_configuration = ClientReleaseConfiguration::default(); + } + } + + mod limits { + use chorus::types::types::subconfigs::limits::rates::RateLimits; + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn rates() { + let rate_limits = RateLimits::default(); + let hash_map = rate_limits.to_hash_map(); + assert!(hash_map.contains_key(&chorus::types::LimitType::ChannelBaseline)); + assert!(hash_map.contains_key(&chorus::types::LimitType::GuildBaseline)); + assert!(hash_map.contains_key(&chorus::types::LimitType::AuthLogin)); + assert!(hash_map.contains_key(&chorus::types::LimitType::AuthRegister)); + assert!(hash_map.contains_key(&chorus::types::LimitType::Error)); + assert!(hash_map.contains_key(&chorus::types::LimitType::Global)); + assert!(hash_map.contains_key(&chorus::types::LimitType::Ip)); + assert!(hash_map.contains_key(&chorus::types::LimitType::WebhookBaseline)); + assert!(hash_map.len() == 8) + } + } + } + + mod guild_configuration { + use std::ops::Deref; + use std::str::FromStr; + + use chorus::types::types::guild_configuration::{GuildFeatures, GuildFeaturesList}; + use chorus::types::{Error, GuildError}; + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn deref_guild_features_list() { + let guild_features_list = &GuildFeaturesList::default(); + let _guild_features_list_deref = guild_features_list.deref().clone(); + } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn test_deref_mut() { + let mut guild_features_list = GuildFeaturesList::default(); + guild_features_list.clear(); + let mut list = GuildFeaturesList::default().to_vec(); + list.push(GuildFeatures::ActivitiesAlpha); + *guild_features_list = list.to_vec(); + assert_eq!(guild_features_list.len(), 1); + } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn test_display() { + let mut guild_features_list = GuildFeaturesList::default(); + guild_features_list.push(GuildFeatures::ActivitiesAlpha); + guild_features_list.push(GuildFeatures::AnimatedBanner); + assert_eq!( + format!("{}", guild_features_list), + "ACTIVITIES_ALPHA,ANIMATED_BANNER" + ); + } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn test_from_str() { + // GPT moment + assert_eq!( + GuildFeatures::from_str("ACTIVITIES_ALPHA").unwrap(), + GuildFeatures::ActivitiesAlpha + ); + assert_eq!( + GuildFeatures::from_str("ACTIVITIES_EMPLOYEE").unwrap(), + GuildFeatures::ActivitiesEmployee + ); + assert_eq!( + GuildFeatures::from_str("ACTIVITIES_INTERNAL_DEV").unwrap(), + GuildFeatures::ActivitiesInternalDev + ); + assert_eq!( + GuildFeatures::from_str("ANIMATED_BANNER").unwrap(), + GuildFeatures::AnimatedBanner + ); + assert_eq!( + GuildFeatures::from_str("ANIMATED_ICON").unwrap(), + GuildFeatures::AnimatedIcon + ); + assert_eq!( + GuildFeatures::from_str("APPLICATION_COMMAND_PERMISSIONS_V2").unwrap(), + GuildFeatures::ApplicationCommandPermissionsV2 + ); + assert_eq!( + GuildFeatures::from_str("AUTO_MODERATION").unwrap(), + GuildFeatures::AutoModeration + ); + assert_eq!( + GuildFeatures::from_str("AUTO_MOD_TRIGGER_KEYWORD_FILTER").unwrap(), + GuildFeatures::AutoModTriggerKeywordFilter + ); + assert_eq!( + GuildFeatures::from_str("AUTO_MOD_TRIGGER_ML_SPAM_FILTER").unwrap(), + GuildFeatures::AutoModTriggerMLSpamFilter + ); + assert_eq!( + GuildFeatures::from_str("AUTO_MOD_TRIGGER_SPAM_LINK_FILTER").unwrap(), + GuildFeatures::AutoModTriggerSpamLinkFilter + ); + assert_eq!( + GuildFeatures::from_str("AUTO_MOD_TRIGGER_USER_PROFILE").unwrap(), + GuildFeatures::AutoModTriggerUserProfile + ); + assert_eq!( + GuildFeatures::from_str("BANNER").unwrap(), + GuildFeatures::Banner + ); + assert_eq!(GuildFeatures::from_str("BFG").unwrap(), GuildFeatures::Bfg); + assert_eq!( + GuildFeatures::from_str("BOOSTING_TIERS_EXPERIMENT_MEDIUM_GUILD").unwrap(), + GuildFeatures::BoostingTiersExperimentMediumGuild + ); + assert_eq!( + GuildFeatures::from_str("BOOSTING_TIERS_EXPERIMENT_SMALL_GUILD").unwrap(), + GuildFeatures::BoostingTiersExperimentSmallGuild + ); + assert_eq!( + GuildFeatures::from_str("BOT_DEVELOPER_EARLY_ACCESS").unwrap(), + GuildFeatures::BotDeveloperEarlyAccess + ); + assert_eq!( + GuildFeatures::from_str("BURST_REACTIONS").unwrap(), + GuildFeatures::BurstReactions + ); + assert_eq!( + GuildFeatures::from_str("COMMUNITY_CANARY").unwrap(), + GuildFeatures::CommunityCanary + ); + assert_eq!( + GuildFeatures::from_str("COMMUNITY_EXP_LARGE_GATED").unwrap(), + GuildFeatures::CommunityExpLargeGated + ); + assert_eq!( + GuildFeatures::from_str("COMMUNITY_EXP_LARGE_UNGATED").unwrap(), + GuildFeatures::CommunityExpLargeUngated + ); + assert_eq!( + GuildFeatures::from_str("COMMUNITY_EXP_MEDIUM").unwrap(), + GuildFeatures::CommunityExpMedium + ); + assert_eq!( + GuildFeatures::from_str("CHANNEL_EMOJIS_GENERATED").unwrap(), + GuildFeatures::ChannelEmojisGenerated + ); + assert_eq!( + GuildFeatures::from_str("CHANNEL_HIGHLIGHTS").unwrap(), + GuildFeatures::ChannelHighlights + ); + assert_eq!( + GuildFeatures::from_str("CHANNEL_HIGHLIGHTS_DISABLED").unwrap(), + GuildFeatures::ChannelHighlightsDisabled + ); + assert_eq!( + GuildFeatures::from_str("CLYDE_ENABLED").unwrap(), + GuildFeatures::ClydeEnabled + ); + assert_eq!( + GuildFeatures::from_str("CLYDE_EXPERIMENT_ENABLED").unwrap(), + GuildFeatures::ClydeExperimentEnabled + ); + assert_eq!( + GuildFeatures::from_str("CLYDE_DISABLED").unwrap(), + GuildFeatures::ClydeDisabled + ); + assert_eq!( + GuildFeatures::from_str("COMMUNITY").unwrap(), + GuildFeatures::Community + ); + assert_eq!( + GuildFeatures::from_str("CREATOR_ACCEPTED_NEW_TERMS").unwrap(), + GuildFeatures::CreatorAcceptedNewTerms + ); + assert_eq!( + GuildFeatures::from_str("CREATOR_MONETIZABLE").unwrap(), + GuildFeatures::CreatorMonetizable + ); + assert_eq!( + GuildFeatures::from_str("CREATOR_MONETIZABLE_DISABLED").unwrap(), + GuildFeatures::CreatorMonetizableDisabled + ); + assert_eq!( + GuildFeatures::from_str("CREATOR_MONETIZABLE_PENDING_NEW_OWNER_ONBOARDING") + .unwrap(), + GuildFeatures::CreatorMonetizablePendingNewOwnerOnboarding + ); + assert_eq!( + GuildFeatures::from_str("CREATOR_MONETIZABLE_PROVISIONAL").unwrap(), + GuildFeatures::CreatorMonetizableProvisional + ); + assert_eq!( + GuildFeatures::from_str("CREATOR_MONETIZABLE_RESTRICTED").unwrap(), + GuildFeatures::CreatorMonetizableRestricted + ); + assert_eq!( + GuildFeatures::from_str("CREATOR_MONETIZABLE_WHITEGLOVE").unwrap(), + GuildFeatures::CreatorMonetizableWhiteglove + ); + assert_eq!( + GuildFeatures::from_str("CREATOR_MONETIZABLE_APPLICATION_ALLOWLIST").unwrap(), + GuildFeatures::CreatorMonetizableApplicationAllowlist + ); + assert_eq!( + GuildFeatures::from_str("CREATE_STORE_PAGE").unwrap(), + GuildFeatures::CreateStorePage + ); + assert_eq!( + GuildFeatures::from_str("DEVELOPER_SUPPORT_SERVER").unwrap(), + GuildFeatures::DeveloperSupportServer + ); + assert_eq!( + GuildFeatures::from_str("DISCOVERABLE_DISABLED").unwrap(), + GuildFeatures::DiscoverableDisabled + ); + assert_eq!( + GuildFeatures::from_str("DISCOVERABLE").unwrap(), + GuildFeatures::Discoverable + ); + assert_eq!( + GuildFeatures::from_str("ENABLED_DISCOVERABLE_BEFORE").unwrap(), + GuildFeatures::EnabledDiscoverableBefore + ); + assert_eq!( + GuildFeatures::from_str("EXPOSED_TO_ACTIVITIES_WTP_EXPERIMENT").unwrap(), + GuildFeatures::ExposedToActivitiesWTPExperiment + ); + assert_eq!( + GuildFeatures::from_str("GUESTS_ENABLED").unwrap(), + GuildFeatures::GuestsEnabled + ); + assert_eq!( + GuildFeatures::from_str("GUILD_AUTOMOD_DEFAULT_LIST").unwrap(), + GuildFeatures::GuildAutomodDefaultList + ); + assert_eq!( + GuildFeatures::from_str("GUILD_COMMUNICATION_DISABLED_GUILDS").unwrap(), + GuildFeatures::GuildCommunicationDisabledGuilds + ); + assert_eq!( + GuildFeatures::from_str("GUILD_HOME_DEPRECATION_OVERRIDE").unwrap(), + GuildFeatures::GuildHomeDeprecationOverride + ); + assert_eq!( + GuildFeatures::from_str("GUILD_HOME_OVERRIDE").unwrap(), + GuildFeatures::GuildHomeOverride + ); + assert_eq!( + GuildFeatures::from_str("GUILD_HOME_TEST").unwrap(), + GuildFeatures::GuildHomeTest + ); + assert_eq!( + GuildFeatures::from_str("GUILD_MEMBER_VERIFICATION_EXPERIMENT").unwrap(), + GuildFeatures::GuildMemberVerificationExperiment + ); + assert_eq!( + GuildFeatures::from_str("GUILD_ONBOARDING").unwrap(), + GuildFeatures::GuildOnboarding + ); + assert_eq!( + GuildFeatures::from_str("GUILD_ONBOARDING_ADMIN_ONLY").unwrap(), + GuildFeatures::GuildOnboardingAdminOnly + ); + assert_eq!( + GuildFeatures::from_str("GUILD_ONBOARDING_EVER_ENABLED").unwrap(), + GuildFeatures::GuildOnboardingEverEnabled + ); + assert_eq!( + GuildFeatures::from_str("GUILD_ONBOARDING_HAS_PROMPTS").unwrap(), + GuildFeatures::GuildOnboardingHasPrompts + ); + assert_eq!( + GuildFeatures::from_str("GUILD_ROLE_SUBSCRIPTION").unwrap(), + GuildFeatures::GuildRoleSubscription + ); + assert_eq!( + GuildFeatures::from_str("GUILD_ROLE_SUBSCRIPTION_PURCHASE_FEEDBACK_LOOP").unwrap(), + GuildFeatures::GuildRoleSubscriptionPurchaseFeedbackLoop + ); + assert_eq!( + GuildFeatures::from_str("GUILD_ROLE_SUBSCRIPTION_TRIALS").unwrap(), + GuildFeatures::GuildRoleSubscriptionTrials + ); + assert_eq!( + GuildFeatures::from_str("GUILD_SERVER_GUIDE").unwrap(), + GuildFeatures::GuildServerGuide + ); + assert_eq!( + GuildFeatures::from_str("GUILD_WEB_PAGE_VANITY_URL").unwrap(), + GuildFeatures::GuildWebPageVanityURL + ); + assert_eq!( + GuildFeatures::from_str("HAD_EARLY_ACTIVITIES_ACCESS").unwrap(), + GuildFeatures::HadEarlyActivitiesAccess + ); + assert_eq!( + GuildFeatures::from_str("HAS_DIRECTORY_ENTRY").unwrap(), + GuildFeatures::HasDirectoryEntry + ); + assert_eq!( + GuildFeatures::from_str("HIDE_FROM_EXPERIMENT_UI").unwrap(), + GuildFeatures::HideFromExperimentUi + ); + assert_eq!(GuildFeatures::from_str("HUB").unwrap(), GuildFeatures::Hub); + assert_eq!( + GuildFeatures::from_str("INCREASED_THREAD_LIMIT").unwrap(), + GuildFeatures::IncreasedThreadLimit + ); + assert_eq!( + GuildFeatures::from_str("INTERNAL_EMPLOYEE_ONLY").unwrap(), + GuildFeatures::InternalEmployeeOnly + ); + assert_eq!( + GuildFeatures::from_str("INVITE_SPLASH").unwrap(), + GuildFeatures::InviteSplash + ); + assert_eq!( + GuildFeatures::from_str("INVITES_DISABLED").unwrap(), + GuildFeatures::InvitesDisabled + ); + assert_eq!( + GuildFeatures::from_str("LINKED_TO_HUB").unwrap(), + GuildFeatures::LinkedToHub + ); + assert_eq!( + GuildFeatures::from_str("MARKETPLACES_CONNECTION_ROLES").unwrap(), + GuildFeatures::MarketplacesConnectionRoles + ); + assert_eq!( + GuildFeatures::from_str("MEMBER_PROFILES").unwrap(), + GuildFeatures::MemberProfiles + ); + assert_eq!( + GuildFeatures::from_str("MEMBER_VERIFICATION_GATE_ENABLED").unwrap(), + GuildFeatures::MemberVerificationGateEnabled + ); + assert_eq!( + GuildFeatures::from_str("MEMBER_VERIFICATION_MANUAL_APPROVAL").unwrap(), + GuildFeatures::MemberVerificationManualApproval + ); + assert_eq!( + GuildFeatures::from_str("MOBILE_WEB_ROLE_SUBSCRIPTION_PURCHASE_PAGE").unwrap(), + GuildFeatures::MobileWebRoleSubscriptionPurchasePage + ); + assert_eq!( + GuildFeatures::from_str("MONETIZATION_ENABLED").unwrap(), + GuildFeatures::MonetizationEnabled + ); + assert_eq!( + GuildFeatures::from_str("MORE_EMOJI").unwrap(), + GuildFeatures::MoreEmoji + ); + assert_eq!( + GuildFeatures::from_str("MORE_STICKERS").unwrap(), + GuildFeatures::MoreStickers + ); + assert_eq!( + GuildFeatures::from_str("NEWS").unwrap(), + GuildFeatures::News + ); + assert_eq!( + GuildFeatures::from_str("NEW_THREAD_PERMISSIONS").unwrap(), + GuildFeatures::NewThreadPermissions + ); + assert_eq!( + GuildFeatures::from_str("PARTNERED").unwrap(), + GuildFeatures::Partnered + ); + assert_eq!( + GuildFeatures::from_str("PREMIUM_TIER_3_OVERRIDE").unwrap(), + GuildFeatures::PremiumTier3Override + ); + assert_eq!( + GuildFeatures::from_str("PREVIEW_ENABLED").unwrap(), + GuildFeatures::PreviewEnabled + ); + assert_eq!( + GuildFeatures::from_str("RAID_ALERTS_DISABLED").unwrap(), + GuildFeatures::RaidAlertsDisabled + ); + assert_eq!( + GuildFeatures::from_str("RELAY_ENABLED").unwrap(), + GuildFeatures::RelayEnabled + ); + assert_eq!( + GuildFeatures::from_str("RESTRICT_SPAM_RISK_GUILD").unwrap(), + GuildFeatures::RestrictSpamRiskGuild + ); + assert_eq!( + GuildFeatures::from_str("ROLE_ICONS").unwrap(), + GuildFeatures::RoleIcons + ); + assert_eq!( + GuildFeatures::from_str("ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE").unwrap(), + GuildFeatures::RoleSubscriptionsAvailableForPurchase + ); + assert_eq!( + GuildFeatures::from_str("ROLE_SUBSCRIPTIONS_ENABLED").unwrap(), + GuildFeatures::RoleSubscriptionsEnabled + ); + assert_eq!( + GuildFeatures::from_str("ROLE_SUBSCRIPTIONS_ENABLED_FOR_PURCHASE").unwrap(), + GuildFeatures::RoleSubscriptionsEnabledForPurchase + ); + assert_eq!( + GuildFeatures::from_str("SHARD").unwrap(), + GuildFeatures::Shard + ); + assert_eq!( + GuildFeatures::from_str("SHARED_CANVAS_FRIENDS_AND_FAMILY_TEST").unwrap(), + GuildFeatures::SharedCanvasFriendsAndFamilyTest + ); + assert_eq!( + GuildFeatures::from_str("SOUNDBOARD").unwrap(), + GuildFeatures::Soundboard + ); + assert_eq!( + GuildFeatures::from_str("SUMMARIES_ENABLED").unwrap(), + GuildFeatures::SummariesEnabled + ); + assert_eq!( + GuildFeatures::from_str("SUMMARIES_ENABLED_GA").unwrap(), + GuildFeatures::SummariesEnabledGa + ); + assert_eq!( + GuildFeatures::from_str("SUMMARIES_DISABLED_BY_USER").unwrap(), + GuildFeatures::SummariesDisabledByUser + ); + assert_eq!( + GuildFeatures::from_str("SUMMARIES_ENABLED_BY_USER").unwrap(), + GuildFeatures::SummariesEnabledByUser + ); + assert_eq!( + GuildFeatures::from_str("TEXT_IN_STAGE_ENABLED").unwrap(), + GuildFeatures::TextInStageEnabled + ); + assert_eq!( + GuildFeatures::from_str("TEXT_IN_VOICE_ENABLED").unwrap(), + GuildFeatures::TextInVoiceEnabled + ); + assert_eq!( + GuildFeatures::from_str("THREADS_ENABLED_TESTING").unwrap(), + GuildFeatures::ThreadsEnabledTesting + ); + assert_eq!( + GuildFeatures::from_str("THREADS_ENABLED").unwrap(), + GuildFeatures::ThreadsEnabled + ); + assert_eq!( + GuildFeatures::from_str("THREAD_DEFAULT_AUTO_ARCHIVE_DURATION").unwrap(), + GuildFeatures::ThreadDefaultAutoArchiveDuration + ); + assert_eq!( + GuildFeatures::from_str("THREADS_ONLY_CHANNEL").unwrap(), + GuildFeatures::ThreadsOnlyChannel + ); + assert_eq!( + GuildFeatures::from_str("TICKETED_EVENTS_ENABLED").unwrap(), + GuildFeatures::TicketedEventsEnabled + ); + assert_eq!( + GuildFeatures::from_str("TICKETING_ENABLED").unwrap(), + GuildFeatures::TicketingEnabled + ); + assert_eq!( + GuildFeatures::from_str("VANITY_URL").unwrap(), + GuildFeatures::VanityUrl + ); + assert_eq!( + GuildFeatures::from_str("VERIFIED").unwrap(), + GuildFeatures::Verified + ); + assert_eq!( + GuildFeatures::from_str("VIP_REGIONS").unwrap(), + GuildFeatures::VipRegions + ); + assert_eq!( + GuildFeatures::from_str("VOICE_CHANNEL_EFFECTS").unwrap(), + GuildFeatures::VoiceChannelEffects + ); + assert_eq!( + GuildFeatures::from_str("WELCOME_SCREEN_ENABLED").unwrap(), + GuildFeatures::WelcomeScreenEnabled + ); + assert_eq!( + GuildFeatures::from_str("ALIASABLE_NAMES").unwrap(), + GuildFeatures::AliasableNames + ); + assert_eq!( + GuildFeatures::from_str("ALLOW_INVALID_CHANNEL_NAME").unwrap(), + GuildFeatures::AllowInvalidChannelName + ); + assert_eq!( + GuildFeatures::from_str("ALLOW_UNNAMED_CHANNELS").unwrap(), + GuildFeatures::AllowUnnamedChannels + ); + assert_eq!( + GuildFeatures::from_str("CROSS_CHANNEL_REPLIES").unwrap(), + GuildFeatures::CrossChannelReplies + ); + assert_eq!( + GuildFeatures::from_str("IRC_LIKE_CATEGORY_NAMES").unwrap(), + GuildFeatures::IrcLikeCategoryNames + ); + assert_eq!( + GuildFeatures::from_str("INVITES_CLOSED").unwrap(), + GuildFeatures::InvitesClosed + ); + assert_eq!( + GuildFeatures::from_str("INVALID").unwrap_err().to_string(), + Error::Guild(GuildError::InvalidGuildFeature).to_string() + ); + } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn test_to_str() { + assert_eq!(GuildFeatures::ActivitiesAlpha.to_str(), "ACTIVITIES_ALPHA"); + assert_eq!( + GuildFeatures::ActivitiesEmployee.to_str(), + "ACTIVITIES_EMPLOYEE" + ); + assert_eq!( + GuildFeatures::ActivitiesInternalDev.to_str(), + "ACTIVITIES_INTERNAL_DEV" + ); + assert_eq!(GuildFeatures::AnimatedBanner.to_str(), "ANIMATED_BANNER"); + assert_eq!(GuildFeatures::AnimatedIcon.to_str(), "ANIMATED_ICON"); + assert_eq!( + GuildFeatures::ApplicationCommandPermissionsV2.to_str(), + "APPLICATION_COMMAND_PERMISSIONS_V2" + ); + assert_eq!(GuildFeatures::AutoModeration.to_str(), "AUTO_MODERATION"); + assert_eq!( + GuildFeatures::AutoModTriggerKeywordFilter.to_str(), + "AUTO_MOD_TRIGGER_KEYWORD_FILTER" + ); + assert_eq!( + GuildFeatures::AutoModTriggerMLSpamFilter.to_str(), + "AUTO_MOD_TRIGGER_ML_SPAM_FILTER" + ); + assert_eq!( + GuildFeatures::AutoModTriggerSpamLinkFilter.to_str(), + "AUTO_MOD_TRIGGER_SPAM_LINK_FILTER" + ); + assert_eq!( + GuildFeatures::AutoModTriggerUserProfile.to_str(), + "AUTO_MOD_TRIGGER_USER_PROFILE" + ); + assert_eq!(GuildFeatures::Banner.to_str(), "BANNER"); + assert_eq!(GuildFeatures::Bfg.to_str(), "BFG"); + assert_eq!( + GuildFeatures::BoostingTiersExperimentMediumGuild.to_str(), + "BOOSTING_TIERS_EXPERIMENT_MEDIUM_GUILD" + ); + assert_eq!( + GuildFeatures::BoostingTiersExperimentSmallGuild.to_str(), + "BOOSTING_TIERS_EXPERIMENT_SMALL_GUILD" + ); + assert_eq!( + GuildFeatures::BotDeveloperEarlyAccess.to_str(), + "BOT_DEVELOPER_EARLY_ACCESS" + ); + assert_eq!(GuildFeatures::BurstReactions.to_str(), "BURST_REACTIONS"); + assert_eq!(GuildFeatures::CommunityCanary.to_str(), "COMMUNITY_CANARY"); + assert_eq!( + GuildFeatures::CommunityExpLargeGated.to_str(), + "COMMUNITY_EXP_LARGE_GATED" + ); + assert_eq!( + GuildFeatures::CommunityExpLargeUngated.to_str(), + "COMMUNITY_EXP_LARGE_UNGATED" + ); + assert_eq!( + GuildFeatures::CommunityExpMedium.to_str(), + "COMMUNITY_EXP_MEDIUM" + ); + assert_eq!( + GuildFeatures::ChannelEmojisGenerated.to_str(), + "CHANNEL_EMOJIS_GENERATED" + ); + assert_eq!( + GuildFeatures::ChannelHighlights.to_str(), + "CHANNEL_HIGHLIGHTS" + ); + assert_eq!( + GuildFeatures::ChannelHighlightsDisabled.to_str(), + "CHANNEL_HIGHLIGHTS_DISABLED" + ); + assert_eq!(GuildFeatures::ClydeEnabled.to_str(), "CLYDE_ENABLED"); + assert_eq!( + GuildFeatures::ClydeExperimentEnabled.to_str(), + "CLYDE_EXPERIMENT_ENABLED" + ); + assert_eq!(GuildFeatures::ClydeDisabled.to_str(), "CLYDE_DISABLED"); + assert_eq!(GuildFeatures::Community.to_str(), "COMMUNITY"); + assert_eq!( + GuildFeatures::CreatorAcceptedNewTerms.to_str(), + "CREATOR_ACCEPTED_NEW_TERMS" + ); + assert_eq!( + GuildFeatures::CreatorMonetizable.to_str(), + "CREATOR_MONETIZABLE" + ); + assert_eq!( + GuildFeatures::CreatorMonetizableDisabled.to_str(), + "CREATOR_MONETIZABLE_DISABLED" + ); + assert_eq!( + GuildFeatures::CreatorMonetizablePendingNewOwnerOnboarding.to_str(), + "CREATOR_MONETIZABLE_PENDING_NEW_OWNER_ONBOARDING" + ); + assert_eq!( + GuildFeatures::CreatorMonetizableProvisional.to_str(), + "CREATOR_MONETIZABLE_PROVISIONAL" + ); + assert_eq!( + GuildFeatures::CreatorMonetizableRestricted.to_str(), + "CREATOR_MONETIZABLE_RESTRICTED" + ); + assert_eq!( + GuildFeatures::CreatorMonetizableWhiteglove.to_str(), + "CREATOR_MONETIZABLE_WHITEGLOVE" + ); + assert_eq!( + GuildFeatures::CreatorMonetizableApplicationAllowlist.to_str(), + "CREATOR_MONETIZABLE_APPLICATION_ALLOWLIST" + ); + assert_eq!(GuildFeatures::CreateStorePage.to_str(), "CREATE_STORE_PAGE"); + assert_eq!( + GuildFeatures::DeveloperSupportServer.to_str(), + "DEVELOPER_SUPPORT_SERVER" + ); + assert_eq!( + GuildFeatures::DiscoverableDisabled.to_str(), + "DISCOVERABLE_DISABLED" + ); + assert_eq!(GuildFeatures::Discoverable.to_str(), "DISCOVERABLE"); + assert_eq!( + GuildFeatures::EnabledDiscoverableBefore.to_str(), + "ENABLED_DISCOVERABLE_BEFORE" + ); + assert_eq!( + GuildFeatures::ExposedToActivitiesWTPExperiment.to_str(), + "EXPOSED_TO_ACTIVITIES_WTP_EXPERIMENT" + ); + assert_eq!(GuildFeatures::GuestsEnabled.to_str(), "GUESTS_ENABLED"); + assert_eq!( + GuildFeatures::GuildAutomodDefaultList.to_str(), + "GUILD_AUTOMOD_DEFAULT_LIST" + ); + assert_eq!( + GuildFeatures::GuildCommunicationDisabledGuilds.to_str(), + "GUILD_COMMUNICATION_DISABLED_GUILDS" + ); + assert_eq!( + GuildFeatures::GuildHomeDeprecationOverride.to_str(), + "GUILD_HOME_DEPRECATION_OVERRIDE" + ); + assert_eq!( + GuildFeatures::GuildHomeOverride.to_str(), + "GUILD_HOME_OVERRIDE" + ); + assert_eq!(GuildFeatures::GuildHomeTest.to_str(), "GUILD_HOME_TEST"); + assert_eq!( + GuildFeatures::GuildMemberVerificationExperiment.to_str(), + "GUILD_MEMBER_VERIFICATION_EXPERIMENT" + ); + assert_eq!(GuildFeatures::GuildOnboarding.to_str(), "GUILD_ONBOARDING"); + assert_eq!( + GuildFeatures::GuildOnboardingAdminOnly.to_str(), + "GUILD_ONBOARDING_ADMIN_ONLY" + ); + assert_eq!( + GuildFeatures::GuildOnboardingEverEnabled.to_str(), + "GUILD_ONBOARDING_EVER_ENABLED" + ); + assert_eq!( + GuildFeatures::GuildOnboardingHasPrompts.to_str(), + "GUILD_ONBOARDING_HAS_PROMPTS" + ); + assert_eq!( + GuildFeatures::GuildRoleSubscription.to_str(), + "GUILD_ROLE_SUBSCRIPTION" + ); + assert_eq!( + GuildFeatures::GuildRoleSubscriptionPurchaseFeedbackLoop.to_str(), + "GUILD_ROLE_SUBSCRIPTION_PURCHASE_FEEDBACK_LOOP" + ); + assert_eq!( + GuildFeatures::GuildRoleSubscriptionTrials.to_str(), + "GUILD_ROLE_SUBSCRIPTION_TRIALS" + ); + assert_eq!( + GuildFeatures::GuildServerGuide.to_str(), + "GUILD_SERVER_GUIDE" + ); + assert_eq!( + GuildFeatures::GuildWebPageVanityURL.to_str(), + "GUILD_WEB_PAGE_VANITY_URL" + ); + assert_eq!( + GuildFeatures::HadEarlyActivitiesAccess.to_str(), + "HAD_EARLY_ACTIVITIES_ACCESS" + ); + assert_eq!( + GuildFeatures::HasDirectoryEntry.to_str(), + "HAS_DIRECTORY_ENTRY" + ); + assert_eq!( + GuildFeatures::HideFromExperimentUi.to_str(), + "HIDE_FROM_EXPERIMENT_UI" + ); + assert_eq!(GuildFeatures::Hub.to_str(), "HUB"); + assert_eq!( + GuildFeatures::IncreasedThreadLimit.to_str(), + "INCREASED_THREAD_LIMIT" + ); + assert_eq!( + GuildFeatures::InternalEmployeeOnly.to_str(), + "INTERNAL_EMPLOYEE_ONLY" + ); + assert_eq!(GuildFeatures::InviteSplash.to_str(), "INVITE_SPLASH"); + assert_eq!(GuildFeatures::InvitesDisabled.to_str(), "INVITES_DISABLED"); + assert_eq!(GuildFeatures::LinkedToHub.to_str(), "LINKED_TO_HUB"); + assert_eq!( + GuildFeatures::MarketplacesConnectionRoles.to_str(), + "MARKETPLACES_CONNECTION_ROLES" + ); + assert_eq!(GuildFeatures::MemberProfiles.to_str(), "MEMBER_PROFILES"); + assert_eq!( + GuildFeatures::MemberVerificationGateEnabled.to_str(), + "MEMBER_VERIFICATION_GATE_ENABLED" + ); + assert_eq!( + GuildFeatures::MemberVerificationManualApproval.to_str(), + "MEMBER_VERIFICATION_MANUAL_APPROVAL" + ); + assert_eq!( + GuildFeatures::MobileWebRoleSubscriptionPurchasePage.to_str(), + "MOBILE_WEB_ROLE_SUBSCRIPTION_PURCHASE_PAGE" + ); + assert_eq!( + GuildFeatures::MonetizationEnabled.to_str(), + "MONETIZATION_ENABLED" + ); + assert_eq!(GuildFeatures::MoreEmoji.to_str(), "MORE_EMOJI"); + assert_eq!(GuildFeatures::MoreStickers.to_str(), "MORE_STICKERS"); + assert_eq!(GuildFeatures::News.to_str(), "NEWS"); + assert_eq!( + GuildFeatures::NewThreadPermissions.to_str(), + "NEW_THREAD_PERMISSIONS" + ); + assert_eq!(GuildFeatures::Partnered.to_str(), "PARTNERED"); + assert_eq!( + GuildFeatures::PremiumTier3Override.to_str(), + "PREMIUM_TIER_3_OVERRIDE" + ); + assert_eq!(GuildFeatures::PreviewEnabled.to_str(), "PREVIEW_ENABLED"); + assert_eq!( + GuildFeatures::RaidAlertsDisabled.to_str(), + "RAID_ALERTS_DISABLED" + ); + assert_eq!(GuildFeatures::RelayEnabled.to_str(), "RELAY_ENABLED"); + assert_eq!( + GuildFeatures::RestrictSpamRiskGuild.to_str(), + "RESTRICT_SPAM_RISK_GUILD" + ); + assert_eq!(GuildFeatures::RoleIcons.to_str(), "ROLE_ICONS"); + assert_eq!( + GuildFeatures::RoleSubscriptionsAvailableForPurchase.to_str(), + "ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE" + ); + assert_eq!( + GuildFeatures::RoleSubscriptionsEnabled.to_str(), + "ROLE_SUBSCRIPTIONS_ENABLED" + ); + assert_eq!( + GuildFeatures::RoleSubscriptionsEnabledForPurchase.to_str(), + "ROLE_SUBSCRIPTIONS_ENABLED_FOR_PURCHASE" + ); + assert_eq!(GuildFeatures::Shard.to_str(), "SHARD"); + assert_eq!( + GuildFeatures::SharedCanvasFriendsAndFamilyTest.to_str(), + "SHARED_CANVAS_FRIENDS_AND_FAMILY_TEST" + ); + assert_eq!(GuildFeatures::Soundboard.to_str(), "SOUNDBOARD"); + assert_eq!( + GuildFeatures::SummariesEnabled.to_str(), + "SUMMARIES_ENABLED" + ); + assert_eq!( + GuildFeatures::SummariesEnabledGa.to_str(), + "SUMMARIES_ENABLED_GA" + ); + assert_eq!( + GuildFeatures::SummariesDisabledByUser.to_str(), + "SUMMARIES_DISABLED_BY_USER" + ); + assert_eq!( + GuildFeatures::SummariesEnabledByUser.to_str(), + "SUMMARIES_ENABLED_BY_USER" + ); + assert_eq!( + GuildFeatures::TextInStageEnabled.to_str(), + "TEXT_IN_STAGE_ENABLED" + ); + assert_eq!( + GuildFeatures::TextInVoiceEnabled.to_str(), + "TEXT_IN_VOICE_ENABLED" + ); + assert_eq!( + GuildFeatures::ThreadsEnabledTesting.to_str(), + "THREADS_ENABLED_TESTING" + ); + assert_eq!(GuildFeatures::ThreadsEnabled.to_str(), "THREADS_ENABLED"); + assert_eq!( + GuildFeatures::ThreadDefaultAutoArchiveDuration.to_str(), + "THREAD_DEFAULT_AUTO_ARCHIVE_DURATION" + ); + assert_eq!( + GuildFeatures::ThreadsOnlyChannel.to_str(), + "THREADS_ONLY_CHANNEL" + ); + assert_eq!( + GuildFeatures::TicketedEventsEnabled.to_str(), + "TICKETED_EVENTS_ENABLED" + ); + assert_eq!( + GuildFeatures::TicketingEnabled.to_str(), + "TICKETING_ENABLED" + ); + assert_eq!(GuildFeatures::VanityUrl.to_str(), "VANITY_URL"); + assert_eq!(GuildFeatures::Verified.to_str(), "VERIFIED"); + assert_eq!(GuildFeatures::VipRegions.to_str(), "VIP_REGIONS"); + assert_eq!( + GuildFeatures::VoiceChannelEffects.to_str(), + "VOICE_CHANNEL_EFFECTS" + ); + assert_eq!( + GuildFeatures::WelcomeScreenEnabled.to_str(), + "WELCOME_SCREEN_ENABLED" + ); + assert_eq!(GuildFeatures::AliasableNames.to_str(), "ALIASABLE_NAMES"); + assert_eq!( + GuildFeatures::AllowInvalidChannelName.to_str(), + "ALLOW_INVALID_CHANNEL_NAME" + ); + assert_eq!( + GuildFeatures::AllowUnnamedChannels.to_str(), + "ALLOW_UNNAMED_CHANNELS" + ); + assert_eq!( + GuildFeatures::CrossChannelReplies.to_str(), + "CROSS_CHANNEL_REPLIES" + ); + assert_eq!( + GuildFeatures::IrcLikeCategoryNames.to_str(), + "IRC_LIKE_CATEGORY_NAMES" + ); + assert_eq!(GuildFeatures::InvitesClosed.to_str(), "INVITES_CLOSED"); + } + } + + mod domains_configuration { + use chorus::types::types::domains_configuration::Domains; + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn display_domains() { + let domains = Domains { + cdn: "http://localhost:3020/cdn/".to_string(), + gateway: "ws://localhost:3020/".to_string(), + api_endpoint: "http://localhost:3020/".to_string(), + default_api_version: "9".to_string(), + }; + let fmt_domains = domains.to_string(); + assert!(fmt_domains.contains("CDN URL: http://localhost:3020/cdn/")); + assert!(fmt_domains.contains("Gateway URL: ws://localhost:3020/")); + assert!(fmt_domains.contains("API Endpoint: http://localhost:3020/")); + assert!(fmt_domains.contains("Default API Version: 9")); + } + } +} + +mod entities { + use std::sync::{Arc, RwLock}; + + use chorus::types::{ApplicationFlags, ConfigEntity, Emoji, User}; + use serde_json::json; + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn application() { + let application = chorus::types::Application::default(); + assert!(application.name == *""); + assert!(application.verify_key == *""); + assert_ne!( + application.owner.read().unwrap().clone(), + Arc::new(RwLock::new(User::default())) + .read() + .unwrap() + .clone() + ); + let flags = ApplicationFlags::from_bits(0).unwrap(); + assert!(application.flags() == flags); + } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn config() { + let mut config_entity = ConfigEntity { + key: "ExampleKey".to_string(), + value: Some(json!(1)), + }; + config_entity.as_int().unwrap(); + assert!(config_entity.as_bool().is_none()); + assert!(config_entity.as_string().is_none()); + config_entity.value = Some(json!(true)); + config_entity.as_bool().unwrap(); + assert!(config_entity.as_int().is_none()); + assert!(config_entity.as_string().is_none()); + config_entity.value = Some(json!("Hello")); + config_entity.as_string().unwrap(); + assert!(config_entity.as_bool().is_none()); + assert!(config_entity.as_int().is_none()); + } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn emoji() { + let emoji = Emoji::default(); + let another_emoji = Emoji::default(); + assert_ne!(emoji.id, another_emoji.id); + assert_ne!(emoji, another_emoji); + } + + mod guild { + use std::hash::{Hash, Hasher}; + + use chorus::types::{Guild, GuildInvite}; + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn guild_hash() { + let id: u64 = 1; + let mut guild1 = Guild::default(); + let mut guild2 = Guild::default(); + guild1.id = id.into(); + guild2.id = id.into(); + let mut hasher1 = std::collections::hash_map::DefaultHasher::new(); + guild1.hash(&mut hasher1); + + let mut hasher2 = std::collections::hash_map::DefaultHasher::new(); + guild2.hash(&mut hasher2); + + assert_eq!(hasher1.finish(), hasher2.finish()); + } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn guild_invite_hash() { + let id: u64 = 1; + let mut invite1 = GuildInvite::default(); + let mut invite2 = GuildInvite::default(); + invite1.channel_id = id.into(); + invite2.channel_id = id.into(); + invite1.guild_id = id.into(); + invite2.guild_id = id.into(); + let mut hasher1 = std::collections::hash_map::DefaultHasher::new(); + invite1.hash(&mut hasher1); + + let mut hasher2 = std::collections::hash_map::DefaultHasher::new(); + invite2.hash(&mut hasher2); + + assert_eq!(hasher1.finish(), hasher2.finish()); + } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn guild_partial_eq() { + let id: u64 = 1; + let mut guild1 = Guild::default(); + let mut guild2 = Guild::default(); + guild1.id = id.into(); + guild2.id = id.into(); + + assert_eq!(guild1, guild2); + } + } + + mod relationship { + use chorus::types::{IntoShared, Relationship, User}; + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn relationship_partial_eq() { + let user = User::default(); + // These 2 users are different, because they do not have the same Snowflake "id". + let user_2 = User::default(); + let relationship_1 = Relationship { + id: 32_u64.into(), + relationship_type: chorus::types::RelationshipType::Friends, + nickname: Some("Xenia".to_string()), + user: user.into_public_user().into_shared(), + since: None, + }; + + let relationship_2 = Relationship { + id: 32_u64.into(), + relationship_type: chorus::types::RelationshipType::Friends, + nickname: Some("Xenia".to_string()), + user: user_2.into_public_user().into_shared(), + since: None, + }; + + // This should succeed, even though the two users' IDs are different. This is because + // `User` is only `PartialEq`, and the actual user object is not checked, since the + // `RwLock` would have to be locked. + assert_eq!(relationship_1, relationship_2); + } + } + + mod message { + use chorus::types::{Message, Snowflake}; + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn message_partial_eq() { + let id: Snowflake = 1_u64.into(); + let mut message1 = Message::default(); + let mut message2 = Message::default(); + message1.id = id; + message1.channel_id = id; + message2.id = id; + message2.channel_id = id; + + assert_eq!(message1, message2); + } + } +} diff --git a/tests/user.rs b/tests/user.rs new file mode 100644 index 0000000..bf7938b --- /dev/null +++ b/tests/user.rs @@ -0,0 +1,18 @@ +use chorus::types::{PublicUser, Snowflake, User}; + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), test)] +fn to_public_user() { + let mut user = User::default(); + let mut public_user = PublicUser { + username: Some("".to_string()), + discriminator: Some("".to_string()), + ..Default::default() + }; + let id: Snowflake = 1_u64.into(); + user.id = id; + public_user.id = id; + + let from_user = user.into_public_user(); + assert_eq!(public_user, from_user); +}