Components and Composites (#175)

As described in #165 , Gateway induced changes of entity structs do not
get propagated to other structs holding the "same" object which got
updated. This is bad because it means that structs still hold
potentially outdated info.

My proposed solution is to create components and composites. A component
is an object with information which is supposed to be the same in at
least 2 structs, such as a Channel object ("channels" field in Guild
struct and lone Channel object for example). These components should be
shared instead of cloned, to make sure that an update to this shared
structs fields is reflected everywhere the struct is being used.

We can do this by using `Arc<Mutex<T>>`. Mutex can be the
std::sync::Mutex, as long as locks on the components themselves are not
being held across .await points. ~~This draft is not yet finished, but~~
all instances of components in composite entity structs have been
replaced with their `Arc<Mutex<T>>` counterparts already.
This commit is contained in:
Flori 2023-08-04 15:55:47 +02:00 committed by GitHub
commit 1aa8bcc6d2
28 changed files with 284 additions and 135 deletions

2
.gitignore vendored
View File

@ -1,6 +1,6 @@
# Generated by Cargo
# will have compiled files and executables
/target/
/**/target/
# These are backup files generated by rustfmt
**/*.rs.bk

View File

@ -11,7 +11,7 @@ client = []
[dependencies]
tokio = { version = "1.29.1", features = ["macros"] }
serde = {version = "1.0.171", features = ["derive"]}
serde = { version = "1.0.171", features = ["derive", "rc"] }
serde_json = { version = "1.0.103", features = ["raw_value"] }
serde-aux = "4.2.0"
serde_with = "3.0.0"
@ -31,7 +31,15 @@ hostname = "0.3.1"
bitflags = { version = "2.3.3", features = ["serde"] }
lazy_static = "1.4.0"
poem = { version = "1.3.56", optional = true }
sqlx = { git = "https://github.com/zert3x/sqlx", branch="feature/skip", features = ["mysql", "sqlite", "json", "chrono", "ipnetwork", "runtime-tokio-native-tls", "any"], optional = true }
sqlx = { git = "https://github.com/zert3x/sqlx", branch = "feature/skip", features = [
"mysql",
"sqlite",
"json",
"chrono",
"ipnetwork",
"runtime-tokio-native-tls",
"any",
], optional = true }
thiserror = "1.0.43"
jsonwebtoken = "8.3.0"
log = "0.4.19"

View File

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

View File

@ -1,3 +1,4 @@
use std::sync::{Arc, Mutex};
use std::{cell::RefCell, rc::Rc};
use reqwest::Client;
@ -51,8 +52,8 @@ impl Instance {
Rc::new(RefCell::new(self.clone())),
token.clone(),
self.clone_limits_if_some(),
settings,
user_object,
Arc::new(Mutex::new(settings)),
Arc::new(Mutex::new(user_object)),
gateway,
);
Ok(user)

View File

@ -1,3 +1,4 @@
use std::sync::{Arc, Mutex};
use std::{cell::RefCell, rc::Rc};
use reqwest::Client;
@ -59,7 +60,7 @@ impl UserMeta {
.deserialize_response::<User>(self)
.await
.unwrap();
let _ = std::mem::replace(&mut self.object, user_updated.clone());
self.object = Arc::new(Mutex::new(user_updated.clone()));
Ok(user_updated)
}

View File

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

View File

@ -1,3 +1,5 @@
use std::sync::{Arc, Mutex};
use bitflags::bitflags;
use serde::{Deserialize, Serialize};
use serde_json::Value;
@ -6,7 +8,7 @@ use serde_repr::{Deserialize_repr, Serialize_repr};
use crate::types::utils::Snowflake;
use crate::types::{Team, User};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
/// # Reference
/// See <https://discord.com/developers/docs/resources/application#application-resource>
@ -25,7 +27,7 @@ pub struct Application {
pub bot_require_code_grant: bool,
pub verify_key: String,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub owner: User,
pub owner: Arc<Mutex<User>>,
pub flags: u64,
#[cfg(feature = "sqlx")]
pub redirect_uris: Option<sqlx::types::Json<Vec<String>>>,
@ -47,7 +49,7 @@ pub struct Application {
#[cfg(feature = "sqlx")]
pub install_params: Option<sqlx::types::Json<InstallParams>>,
#[cfg(not(feature = "sqlx"))]
pub install_params: Option<InstallParams>,
pub install_params: Option<Arc<Mutex<InstallParams>>>,
pub terms_of_service_url: Option<String>,
pub privacy_policy_url: Option<String>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
@ -103,7 +105,7 @@ pub struct InstallParams {
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
/// # Reference
/// See <https://discord.com/developers/docs/resources/application#application-object-application-flags>
pub struct ApplicationFlags: u64 {
@ -132,7 +134,7 @@ bitflags! {
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
/// # Reference
/// See <https://discord.com/developers/docs/interactions/application-commands#application-command-object>
pub struct ApplicationCommand {
@ -140,10 +142,10 @@ pub struct ApplicationCommand {
pub application_id: Snowflake,
pub name: String,
pub description: String,
pub options: Vec<ApplicationCommandOption>,
pub options: Vec<Arc<Mutex<ApplicationCommandOption>>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
/// Reference
/// See <https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-structure>
pub struct ApplicationCommandOption {
@ -152,7 +154,7 @@ pub struct ApplicationCommandOption {
pub description: String,
pub required: bool,
pub choices: Vec<ApplicationCommandOptionChoice>,
pub options: Vec<ApplicationCommandOption>,
pub options: Arc<Mutex<Vec<ApplicationCommandOption>>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
@ -161,7 +163,7 @@ pub struct ApplicationCommandOptionChoice {
pub value: Value,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize_repr, Deserialize_repr)]
#[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[repr(i32)]
/// # Reference
@ -184,27 +186,27 @@ pub enum ApplicationCommandOptionType {
Attachment = 11,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApplicationCommandInteractionData {
pub id: Snowflake,
pub name: String,
pub options: Vec<ApplicationCommandInteractionDataOption>,
pub options: Vec<Arc<Mutex<ApplicationCommandInteractionDataOption>>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApplicationCommandInteractionDataOption {
pub name: String,
pub value: Value,
pub options: Vec<ApplicationCommandInteractionDataOption>,
pub options: Vec<Arc<Mutex<ApplicationCommandInteractionDataOption>>>,
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
/// See <https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-guild-application-command-permissions-structure>
pub struct GuildApplicationCommandPermissions {
pub id: Snowflake,
pub application_id: Snowflake,
pub guild_id: Snowflake,
pub permissions: Vec<ApplicationCommandPermission>,
pub permissions: Vec<Arc<Mutex<ApplicationCommandPermission>>>,
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
@ -217,7 +219,7 @@ pub struct ApplicationCommandPermission {
pub permission: bool,
}
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, PartialEq)]
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, PartialEq, Eq, Hash)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[repr(u8)]
/// See <https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-application-command-permission-type>

View File

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

View File

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

View File

@ -1,3 +1,5 @@
use std::sync::{Arc, Mutex};
use chorus_macros::Updateable;
use chrono::Utc;
use serde::{Deserialize, Serialize};
@ -10,7 +12,7 @@ use crate::types::{
utils::Snowflake,
};
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Updateable)]
#[derive(Default, Debug, Serialize, Deserialize, Clone, Updateable)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
/// Represents a guild of private channel
///
@ -46,7 +48,7 @@ pub struct Channel {
pub last_pin_timestamp: Option<String>,
pub managed: Option<bool>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub member: Option<ThreadMember>,
pub member: Option<Arc<Mutex<ThreadMember>>>,
pub member_count: Option<i32>,
pub message_count: Option<i32>,
pub name: Option<String>,
@ -56,12 +58,12 @@ pub struct Channel {
#[cfg(feature = "sqlx")]
pub permission_overwrites: Option<sqlx::types::Json<Vec<PermissionOverwrite>>>,
#[cfg(not(feature = "sqlx"))]
pub permission_overwrites: Option<Vec<PermissionOverwrite>>,
pub permission_overwrites: Option<Vec<Arc<Mutex<PermissionOverwrite>>>>,
pub permissions: Option<String>,
pub position: Option<i32>,
pub rate_limit_per_user: Option<i32>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub recipients: Option<Vec<User>>,
pub recipients: Option<Vec<Arc<Mutex<User>>>>,
pub rtc_region: Option<String>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub thread_metadata: Option<ThreadMetadata>,
@ -71,7 +73,41 @@ pub struct Channel {
pub video_quality_mode: Option<i32>,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
impl PartialEq for Channel {
fn eq(&self, other: &Self) -> bool {
self.application_id == other.application_id
&& self.bitrate == other.bitrate
&& self.channel_type == other.channel_type
&& self.created_at == other.created_at
&& self.default_auto_archive_duration == other.default_auto_archive_duration
&& self.default_forum_layout == other.default_forum_layout
&& self.default_sort_order == other.default_sort_order
&& self.default_thread_rate_limit_per_user == other.default_thread_rate_limit_per_user
&& self.flags == other.flags
&& self.guild_id == other.guild_id
&& self.icon == other.icon
&& self.id == other.id
&& self.last_message_id == other.last_message_id
&& self.last_pin_timestamp == other.last_pin_timestamp
&& self.managed == other.managed
&& self.member_count == other.member_count
&& self.message_count == other.message_count
&& self.name == other.name
&& self.nsfw == other.nsfw
&& self.owner_id == other.owner_id
&& self.parent_id == other.parent_id
&& self.permissions == other.permissions
&& self.position == other.position
&& self.rate_limit_per_user == other.rate_limit_per_user
&& self.rtc_region == other.rtc_region
&& self.topic == other.topic
&& self.total_message_sent == other.total_message_sent
&& self.user_limit == other.user_limit
&& self.video_quality_mode == other.video_quality_mode
}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
/// A tag that can be applied to a thread in a [ChannelType::GuildForum] or [ChannelType::GuildMedia] channel.
///
/// # Reference
@ -112,7 +148,7 @@ pub struct ThreadMetadata {
pub create_timestamp: Option<String>,
}
#[derive(Default, Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
#[derive(Default, Debug, Deserialize, Serialize, Clone)]
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/channel#thread-member-object>
pub struct ThreadMember {
@ -120,7 +156,7 @@ pub struct ThreadMember {
pub user_id: Option<Snowflake>,
pub join_timestamp: Option<String>,
pub flags: Option<u64>,
pub member: Option<GuildMember>,
pub member: Option<Arc<Mutex<GuildMember>>>,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
@ -134,7 +170,7 @@ pub struct DefaultReaction {
pub emoji_name: Option<String>,
}
#[derive(Default, Clone, Copy, Debug, Serialize_repr, Deserialize_repr, PartialEq, Eq)]
#[derive(Default, Clone, Copy, Debug, Serialize_repr, Deserialize_repr, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[repr(i32)]

View File

@ -1,9 +1,11 @@
use std::sync::{Arc, Mutex};
use serde::{Deserialize, Serialize};
use crate::types::entities::User;
use crate::types::Snowflake;
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)]
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/emoji#emoji-object>
@ -15,7 +17,7 @@ pub struct Emoji {
#[cfg(not(feature = "sqlx"))]
pub roles: Option<Vec<Snowflake>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub user: Option<User>,
pub user: Option<Arc<Mutex<User>>>,
pub require_colons: Option<bool>,
pub managed: Option<bool>,
pub animated: Option<bool>,

View File

@ -1,3 +1,5 @@
use std::sync::{Arc, Mutex};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
@ -10,7 +12,7 @@ use crate::types::{
};
/// See <https://discord.com/developers/docs/resources/guild>
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct Guild {
pub afk_channel_id: Option<Snowflake>,
@ -25,13 +27,13 @@ pub struct Guild {
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub bans: Option<Vec<GuildBan>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub channels: Option<Vec<Channel>>,
pub channels: Option<Vec<Arc<Mutex<Channel>>>>,
pub default_message_notifications: Option<i32>,
pub description: Option<String>,
pub discovery_splash: Option<String>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
#[serde(default)]
pub emojis: Vec<Emoji>,
pub emojis: Vec<Arc<Mutex<Emoji>>>,
pub explicit_content_filter: Option<i32>,
//#[cfg_attr(feature = "sqlx", sqlx(try_from = "String"))]
pub features: Option<GuildFeaturesList>,
@ -40,7 +42,7 @@ pub struct Guild {
pub icon_hash: Option<String>,
pub id: Snowflake,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub invites: Option<Vec<GuildInvite>>,
pub invites: Option<Vec<Arc<Mutex<GuildInvite>>>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub joined_at: Option<String>,
pub large: Option<bool>,
@ -66,7 +68,7 @@ pub struct Guild {
pub public_updates_channel_id: Option<Snowflake>,
pub region: Option<String>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub roles: Option<Vec<RoleObject>>,
pub roles: Option<Vec<Arc<Mutex<RoleObject>>>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub rules_channel: Option<String>,
pub rules_channel_id: Option<Snowflake>,
@ -79,13 +81,13 @@ pub struct Guild {
pub vanity_url_code: Option<String>,
pub verification_level: Option<i32>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub voice_states: Option<Vec<VoiceState>>,
pub voice_states: Option<Vec<Arc<Mutex<VoiceState>>>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub webhooks: Option<Vec<Webhook>>,
pub webhooks: Option<Vec<Arc<Mutex<Webhook>>>>,
#[cfg(feature = "sqlx")]
pub welcome_screen: Option<sqlx::types::Json<WelcomeScreenObject>>,
#[cfg(not(feature = "sqlx"))]
pub welcome_screen: Option<WelcomeScreenObject>,
pub welcome_screen: Option<Arc<Mutex<WelcomeScreenObject>>>,
pub widget_channel_id: Option<Snowflake>,
pub widget_enabled: Option<bool>,
}
@ -100,7 +102,7 @@ pub struct GuildBan {
}
/// See <https://docs.spacebar.chat/routes/#cmp--schemas-invite>
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct GuildInvite {
pub code: String,
@ -111,11 +113,11 @@ pub struct GuildInvite {
pub created_at: DateTime<Utc>,
pub expires_at: Option<DateTime<Utc>>,
pub guild_id: Snowflake,
pub guild: Option<Guild>,
pub guild: Option<Arc<Mutex<Guild>>>,
pub channel_id: Snowflake,
pub channel: Option<Channel>,
pub channel: Option<Arc<Mutex<Channel>>>,
pub inviter_id: Option<Snowflake>,
pub inviter: Option<User>,
pub inviter: Option<Arc<Mutex<User>>>,
pub target_user_id: Option<Snowflake>,
pub target_user: Option<String>,
pub target_user_type: Option<i32>,
@ -149,7 +151,7 @@ pub struct GuildScheduledEvent {
pub entity_type: GuildScheduledEventEntityType,
pub entity_id: Option<Snowflake>,
pub entity_metadata: Option<GuildScheduledEventEntityMetadata>,
pub creator: Option<User>,
pub creator: Option<Arc<Mutex<User>>>,
pub user_count: Option<u64>,
pub image: Option<String>,
}

View File

@ -1,14 +1,16 @@
use std::sync::{Arc, Mutex};
use serde::{Deserialize, Serialize};
use crate::types::{entities::PublicUser, Snowflake};
#[derive(Debug, Deserialize, Default, Serialize, Clone, PartialEq, Eq)]
#[derive(Debug, Deserialize, Default, Serialize, Clone)]
/// Represents a participating user in a guild.
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/guild#guild-member-object>
pub struct GuildMember {
pub user: Option<PublicUser>,
pub user: Option<Arc<Mutex<PublicUser>>>,
pub nick: Option<String>,
pub avatar: Option<String>,
pub roles: Vec<Snowflake>,

View File

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

View File

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

View File

@ -8,7 +8,7 @@ use crate::types::{
utils::Snowflake,
};
#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq)]
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
/// Represents a message sent in a channel.
///
@ -77,7 +77,7 @@ pub struct MessageReference {
pub fail_if_not_exists: Option<bool>,
}
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct MessageInteraction {
pub id: Snowflake,
#[serde(rename = "type")]
@ -112,7 +112,7 @@ pub struct ChannelMention {
name: String,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Embed {
title: Option<String>,
#[serde(rename = "type")]
@ -182,7 +182,7 @@ pub struct EmbedField {
inline: Option<bool>,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Reaction {
pub count: u32,
pub burst_count: u32,

View File

@ -1,3 +1,5 @@
use std::sync::{Arc, Mutex};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
@ -6,17 +8,26 @@ use crate::types::Snowflake;
use super::PublicUser;
#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialEq, Eq)]
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
/// See <https://discord-userdoccers.vercel.app/resources/user#relationship-structure>
pub struct Relationship {
pub id: Snowflake,
#[serde(rename = "type")]
pub relationship_type: RelationshipType,
pub nickname: Option<String>,
pub user: PublicUser,
pub user: Arc<Mutex<PublicUser>>,
pub since: Option<DateTime<Utc>>,
}
impl PartialEq for Relationship {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
&& self.relationship_type == other.relationship_type
&& self.since == other.since
&& self.nickname == other.nickname
}
}
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default, Eq, PartialEq)]
#[repr(u8)]
/// See <https://discord-userdoccers.vercel.app/resources/user#relationship-type>

View File

@ -34,7 +34,7 @@ pub struct RoleSubscriptionData {
pub is_renewal: bool,
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, Eq, PartialEq)]
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Hash)]
/// See <https://discord.com/developers/docs/topics/permissions#role-object-role-tags-structure>
pub struct RoleTags {
#[serde(default)]
@ -53,7 +53,7 @@ pub struct RoleTags {
}
bitflags! {
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[derive(Debug, Default, Clone, Hash, Serialize, Deserialize, PartialEq, Eq)]
/// Permissions limit what users of certain roles can do on a Guild to Guild basis.
///
/// # Reference:

View File

@ -1,8 +1,10 @@
use std::sync::{Arc, Mutex};
use serde::{Deserialize, Serialize};
use crate::types::{entities::User, utils::Snowflake};
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
/// Represents a sticker that can be sent in messages.
///
@ -22,7 +24,7 @@ pub struct Sticker {
pub available: Option<bool>,
pub guild_id: Option<Snowflake>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub user: Option<User>,
pub user: Option<Arc<Mutex<User>>>,
pub sort_value: Option<u8>,
}

View File

@ -1,9 +1,11 @@
use std::sync::{Arc, Mutex};
use serde::{Deserialize, Serialize};
use crate::types::entities::User;
use crate::types::Snowflake;
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
#[derive(Debug, Deserialize, Serialize, Clone)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct Team {
pub icon: Option<String>,
@ -14,10 +16,10 @@ pub struct Team {
pub owner_user_id: Snowflake,
}
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct TeamMember {
pub membership_state: u8,
pub permissions: Vec<String>,
pub team_id: Snowflake,
pub user: User,
pub user: Arc<Mutex<User>>,
}

View File

@ -1,3 +1,5 @@
use std::sync::{Arc, Mutex};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
@ -7,7 +9,7 @@ use crate::types::{
};
/// See <https://docs.spacebar.chat/routes/#cmp--schemas-template>
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct GuildTemplate {
pub code: String,
@ -16,13 +18,13 @@ pub struct GuildTemplate {
pub usage_count: Option<u64>,
pub creator_id: Snowflake,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub creator: User,
pub creator: Arc<Mutex<User>>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub source_guild_id: Snowflake,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub source_guild: Vec<Guild>,
pub source_guild: Vec<Arc<Mutex<Guild>>>,
// Unsure how a {recursive: Guild} looks like, might be a Vec?
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub serialized_source_guild: Vec<Guild>,
pub serialized_source_guild: Vec<Arc<Mutex<Guild>>>,
}

View File

@ -91,7 +91,7 @@ impl From<User> for PublicUser {
const CUSTOM_USER_FLAG_OFFSET: u64 = 1 << 32;
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
pub struct UserFlags: u64 {
const DISCORD_EMPLOYEE = 1 << 0;

View File

@ -1,3 +1,5 @@
use std::sync::{Arc, Mutex};
use chrono::{serde::ts_milliseconds_option, Utc};
use serde::{Deserialize, Serialize};
@ -28,7 +30,7 @@ pub enum UserTheme {
Light,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct UserSettings {
pub afk_timeout: u16,
@ -73,7 +75,7 @@ pub struct UserSettings {
#[cfg(not(feature = "sqlx"))]
pub restricted_guilds: Vec<String>,
pub show_current_game: bool,
pub status: UserStatus,
pub status: Arc<Mutex<UserStatus>>,
pub stream_notifications_enabled: bool,
pub theme: UserTheme,
pub timezone_offset: i16,
@ -109,7 +111,7 @@ impl Default for UserSettings {
render_reactions: true,
restricted_guilds: Default::default(),
show_current_game: true,
status: UserStatus::Online,
status: Arc::new(Mutex::new(UserStatus::Online)),
stream_notifications_enabled: false,
theme: UserTheme::Dark,
timezone_offset: 0,
@ -138,7 +140,7 @@ impl Default for FriendSourceFlags {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GuildFolder {
pub color: u32,
pub guild_ids: Vec<String>,
@ -149,5 +151,5 @@ pub struct GuildFolder {
#[derive(Debug, Serialize, Deserialize)]
pub struct LoginResult {
pub token: String,
pub settings: UserSettings,
pub settings: Arc<Mutex<UserSettings>>,
}

View File

@ -1,3 +1,5 @@
use std::sync::{Arc, Mutex};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
@ -7,14 +9,14 @@ use crate::types::{
};
/// See <https://docs.spacebar.chat/routes/#cmp--schemas-voicestate>
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct VoiceState {
pub guild_id: Option<Snowflake>,
pub guild: Option<Guild>,
pub channel_id: Option<Snowflake>,
pub user_id: Snowflake,
pub member: Option<GuildMember>,
pub member: Option<Arc<Mutex<GuildMember>>>,
pub session_id: Snowflake,
pub token: Option<String>,
pub deaf: bool,

View File

@ -1,3 +1,5 @@
use std::sync::{Arc, Mutex};
use serde::{Deserialize, Serialize};
use crate::types::{
@ -6,7 +8,7 @@ use crate::types::{
};
/// See <https://docs.spacebar.chat/routes/#cmp--schemas-webhook>
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct Webhook {
pub id: Snowflake,
@ -20,10 +22,10 @@ pub struct Webhook {
pub application_id: Snowflake,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub user: Option<User>,
pub user: Option<Arc<Mutex<User>>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub source_guild: Option<Guild>,
pub source_guild: Option<Arc<Mutex<Guild>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
}

View File

@ -59,8 +59,9 @@ async fn modify_channel() {
PermissionFlags::MANAGE_CHANNELS,
PermissionFlags::MANAGE_MESSAGES,
]));
let user_id: types::Snowflake = bundle.user.object.lock().unwrap().id;
let permission_override = PermissionOverwrite {
id: bundle.user.object.id,
id: user_id,
overwrite_type: "1".to_string(),
allow: permission_override,
deny: "0".to_string(),
@ -143,7 +144,7 @@ async fn create_dm() {
let other_user = bundle.create_user("integrationtestuser2").await;
let user = &mut bundle.user;
let private_channel_create_schema = PrivateChannelCreateSchema {
recipients: Some(Vec::from([other_user.object.id])),
recipients: Some(Vec::from([other_user.object.lock().unwrap().id])),
access_tokens: None,
nicks: None,
};
@ -153,26 +154,47 @@ async fn create_dm() {
.unwrap();
assert!(dm_channel.recipients.is_some());
assert_eq!(
dm_channel.recipients.as_ref().unwrap().get(0).unwrap().id,
other_user.object.id
dm_channel
.recipients
.as_ref()
.unwrap()
.get(0)
.unwrap()
.lock()
.unwrap()
.id
.clone(),
other_user.object.lock().unwrap().id
);
assert_eq!(
dm_channel.recipients.as_ref().unwrap().get(1).unwrap().id,
user.object.id
dm_channel
.recipients
.as_ref()
.unwrap()
.get(1)
.unwrap()
.lock()
.unwrap()
.id
.clone(),
user.object.lock().unwrap().id.clone()
);
common::teardown(bundle).await;
}
// #[tokio::test]
// This test currently is broken due to an issue with the Spacebar Server.
// TODO This test currently is broken due to an issue with the Spacebar Server.
#[allow(dead_code)]
async fn remove_add_person_from_to_dm() {
let mut bundle = common::setup().await;
let mut other_user = bundle.create_user("integrationtestuser2").await;
let mut third_user = bundle.create_user("integrationtestuser3").await;
let third_user_id = third_user.object.lock().unwrap().id;
let other_user_id = other_user.object.lock().unwrap().id;
let user_id = bundle.user.object.lock().unwrap().id;
let user = &mut bundle.user;
let private_channel_create_schema = PrivateChannelCreateSchema {
recipients: Some(Vec::from([other_user.object.id, third_user.object.id])),
recipients: Some(Vec::from([other_user_id, third_user_id])),
access_tokens: None,
nicks: None,
};
@ -181,36 +203,52 @@ async fn remove_add_person_from_to_dm() {
.await
.unwrap(); // Creates the Channel and stores the response Channel object
dm_channel
.remove_channel_recipient(other_user.object.id, user)
.remove_channel_recipient(other_user_id, user)
.await
.unwrap();
assert!(dm_channel.recipients.as_ref().unwrap().get(1).is_none());
other_user
.modify_user_relationship(user.object.id, RelationshipType::Friends)
.modify_user_relationship(user_id, RelationshipType::Friends)
.await
.unwrap();
user.modify_user_relationship(other_user.object.id, RelationshipType::Friends)
user.modify_user_relationship(other_user_id, RelationshipType::Friends)
.await
.unwrap();
third_user
.modify_user_relationship(user.object.id, RelationshipType::Friends)
.modify_user_relationship(user_id, RelationshipType::Friends)
.await
.unwrap();
user.modify_user_relationship(third_user.object.id, RelationshipType::Friends)
user.modify_user_relationship(third_user_id, RelationshipType::Friends)
.await
.unwrap();
// Users 1-2 and 1-3 are now friends
dm_channel
.add_channel_recipient(other_user.object.id, user, None)
.add_channel_recipient(other_user_id, user, None)
.await
.unwrap();
assert!(dm_channel.recipients.is_some());
assert_eq!(
dm_channel.recipients.as_ref().unwrap().get(0).unwrap().id,
other_user.object.id
dm_channel
.recipients
.as_ref()
.unwrap()
.get(0)
.unwrap()
.lock()
.unwrap()
.id,
other_user_id
);
assert_eq!(
dm_channel.recipients.as_ref().unwrap().get(1).unwrap().id,
user.object.id
dm_channel
.recipients
.as_ref()
.unwrap()
.get(1)
.unwrap()
.lock()
.unwrap()
.id,
user_id
);
}

View File

@ -7,7 +7,7 @@ async fn add_remove_role() -> ChorusResult<()> {
let mut bundle = common::setup().await;
let guild = bundle.guild.id;
let role = bundle.role.id;
let member_id = bundle.user.object.id;
let member_id = bundle.user.object.lock().unwrap().id;
GuildMember::add_role(&mut bundle.user, guild, member_id, role).await?;
let member = GuildMember::get(&mut bundle.user, guild, member_id)
.await

View File

@ -7,15 +7,18 @@ async fn test_get_mutual_relationships() {
let mut bundle = common::setup().await;
let mut other_user = bundle.create_user("integrationtestuser2").await;
let user = &mut bundle.user;
let username = user.object.lock().unwrap().username.clone();
let discriminator = user.object.lock().unwrap().discriminator.clone();
let other_user_id: types::Snowflake = other_user.object.lock().unwrap().id;
let friend_request_schema = types::FriendRequestSendSchema {
username: user.object.username.clone(),
discriminator: Some(user.object.discriminator.clone()),
username,
discriminator: Some(discriminator),
};
let _ = other_user.send_friend_request(friend_request_schema).await;
let relationships = user
.get_mutual_relationships(other_user.object.id)
other_user
.send_friend_request(friend_request_schema)
.await
.unwrap();
let relationships = user.get_mutual_relationships(other_user_id).await.unwrap();
println!("{:?}", relationships);
common::teardown(bundle).await
}
@ -25,16 +28,21 @@ async fn test_get_relationships() {
let mut bundle = common::setup().await;
let mut other_user = bundle.create_user("integrationtestuser2").await;
let user = &mut bundle.user;
let username = user.object.lock().unwrap().username.clone();
let discriminator = user.object.lock().unwrap().discriminator.clone();
let friend_request_schema = types::FriendRequestSendSchema {
username: user.object.username.clone(),
discriminator: Some(user.object.discriminator.clone()),
username,
discriminator: Some(discriminator),
};
other_user
.send_friend_request(friend_request_schema)
.await
.unwrap();
let relationships = user.get_relationships().await.unwrap();
assert_eq!(relationships.get(0).unwrap().id, other_user.object.id);
assert_eq!(
relationships.get(0).unwrap().id,
other_user.object.lock().unwrap().id
);
common::teardown(bundle).await
}
@ -43,23 +51,33 @@ async fn test_modify_relationship_friends() {
let mut bundle = common::setup().await;
let mut other_user = bundle.create_user("integrationtestuser2").await;
let user = &mut bundle.user;
let _ = other_user
.modify_user_relationship(user.object.id, types::RelationshipType::Friends)
.await;
let user_id: types::Snowflake = user.object.lock().unwrap().id;
let other_user_id: types::Snowflake = other_user.object.lock().unwrap().id;
other_user
.modify_user_relationship(user_id, types::RelationshipType::Friends)
.await
.unwrap();
let relationships = user.get_relationships().await.unwrap();
assert_eq!(relationships.get(0).unwrap().id, other_user.object.id);
assert_eq!(
relationships.get(0).unwrap().id,
other_user.object.lock().unwrap().id
);
assert_eq!(
relationships.get(0).unwrap().relationship_type,
RelationshipType::Incoming
);
let relationships = other_user.get_relationships().await.unwrap();
assert_eq!(relationships.get(0).unwrap().id, user.object.id);
assert_eq!(
relationships.get(0).unwrap().id,
user.object.lock().unwrap().id
);
assert_eq!(
relationships.get(0).unwrap().relationship_type,
RelationshipType::Outgoing
);
let _ = user
.modify_user_relationship(other_user.object.id, RelationshipType::Friends)
.modify_user_relationship(other_user_id, RelationshipType::Friends)
.await;
assert_eq!(
other_user
@ -71,7 +89,7 @@ async fn test_modify_relationship_friends() {
.relationship_type,
RelationshipType::Friends
);
let _ = user.remove_relationship(other_user.object.id).await;
let _ = user.remove_relationship(other_user_id).await;
assert_eq!(
other_user.get_relationships().await.unwrap(),
Vec::<Relationship>::new()
@ -84,18 +102,24 @@ async fn test_modify_relationship_block() {
let mut bundle = common::setup().await;
let mut other_user = bundle.create_user("integrationtestuser2").await;
let user = &mut bundle.user;
let _ = other_user
.modify_user_relationship(user.object.id, types::RelationshipType::Blocked)
.await;
let user_id: types::Snowflake = user.object.lock().unwrap().id;
other_user
.modify_user_relationship(user_id, types::RelationshipType::Blocked)
.await
.unwrap();
let relationships = user.get_relationships().await.unwrap();
assert_eq!(relationships, Vec::<Relationship>::new());
let relationships = other_user.get_relationships().await.unwrap();
assert_eq!(relationships.get(0).unwrap().id, user.object.id);
assert_eq!(
relationships.get(0).unwrap().id,
user.object.lock().unwrap().id
);
assert_eq!(
relationships.get(0).unwrap().relationship_type,
RelationshipType::Blocked
);
let _ = other_user.remove_relationship(user.object.id).await;
other_user.remove_relationship(user_id).await.unwrap();
assert_eq!(
other_user.get_relationships().await.unwrap(),
Vec::<Relationship>::new()