From 6c9df9508e1b6305873badc8731efa84479c98bf Mon Sep 17 00:00:00 2001 From: Flori Weber Date: Fri, 23 Jun 2023 12:18:22 +0200 Subject: [PATCH 01/23] Fix stupid multi line comments --- src/api/channels/reactions.rs | 170 ++++++++++++++-------------------- 1 file changed, 70 insertions(+), 100 deletions(-) diff --git a/src/api/channels/reactions.rs b/src/api/channels/reactions.rs index c445a8b..c164246 100644 --- a/src/api/channels/reactions.rs +++ b/src/api/channels/reactions.rs @@ -16,20 +16,15 @@ pub struct ReactionMeta { } impl ReactionMeta { - /** - Deletes all reactions for a message. - This endpoint requires the `MANAGE_MESSAGES` permission to be present on the current user. - - # Arguments - * `user` - A mutable reference to a [`UserMeta`] instance. - - # Returns - A `Result` [`()`] [`crate::errors::ChorusLibError`] if something went wrong. - Fires a `Message Reaction Remove All` Gateway event. - - # Reference - See [https://discord.com/developers/docs/resources/channel#delete-all-reactions](https://discord.com/developers/docs/resources/channel#delete-all-reactions) - */ + /// Deletes all reactions for a message. + /// This endpoint requires the `MANAGE_MESSAGES` permission to be present on the current user. + /// # Arguments + /// * `user` - A mutable reference to a [`UserMeta`] instance. + /// # Returns + /// A `Result` [`()`] [`crate::errors::ChorusLibError`] if something went wrong. + /// Fires a `Message Reaction Remove All` Gateway event. + /// # Reference + /// See [https://discord.com/developers/docs/resources/channel#delete-all-reactions](https://discord.com/developers/docs/resources/channel#delete-all-reactions) pub async fn delete_all(&self, user: &mut UserMeta) -> ChorusResult<()> { let url = format!( "{}/channels/{}/messages/{}/reactions/", @@ -41,21 +36,16 @@ impl ReactionMeta { handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await } - /** - Gets a list of users that reacted with a specific emoji to a message. - - # Arguments - * `emoji` - A string slice containing the emoji to search for. The emoji must be URL Encoded or - the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the - format name:id with the emoji name and emoji id. - * `user` - A mutable reference to a [`UserMeta`] instance. - - # Returns - A Result that is [`Err(crate::errors::ChorusLibError)`] if something went wrong. - - # Reference - See [https://discord.com/developers/docs/resources/channel#get-reactions](https://discord.com/developers/docs/resources/channel#get-reactions) - */ + /// Gets a list of users that reacted with a specific emoji to a message. + /// # Arguments + /// * `emoji` - A string slice containing the emoji to search for. The emoji must be URL Encoded or + /// the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the + /// format name:id with the emoji name and emoji id. + /// * `user` - A mutable reference to a [`UserMeta`] instance. + /// # Returns + /// A Result that is [`Err(crate::errors::ChorusLibError)`] if something went wrong. + /// # Reference + /// See [https://discord.com/developers/docs/resources/channel#get-reactions](https://discord.com/developers/docs/resources/channel#get-reactions) pub async fn get(&self, emoji: &str, user: &mut UserMeta) -> ChorusResult<()> { let url = format!( "{}/channels/{}/messages/{}/reactions/{}/", @@ -68,23 +58,18 @@ impl ReactionMeta { handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await } - /** - Deletes all the reactions for a given `emoji` on a message. This endpoint requires the - MANAGE_MESSAGES permission to be present on the current user. - - # Arguments - * `emoji` - A string slice containing the emoji to delete. The `emoji` must be URL Encoded or - the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the - format name:id with the emoji name and emoji id. - * `user` - A mutable reference to a [`UserMeta`] instance. - - # Returns - A Result that is [`Err(crate::errors::ChorusLibError)`] if something went wrong. - Fires a `Message Reaction Remove Emoji` Gateway event. - - # Reference - See [https://discord.com/developers/docs/resources/channel#delete-all-reactions-for-emoji](https://discord.com/developers/docs/resources/channel#delete-all-reactions-for-emoji) - */ + /// Deletes all the reactions for a given `emoji` on a message. This endpoint requires the + /// MANAGE_MESSAGES permission to be present on the current user. + /// # Arguments + /// * `emoji` - A string slice containing the emoji to delete. The `emoji` must be URL Encoded or + /// the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the + /// format name:id with the emoji name and emoji id. + /// * `user` - A mutable reference to a [`UserMeta`] instance. + /// # Returns + /// A Result that is [`Err(crate::errors::ChorusLibError)`] if something went wrong. + /// Fires a `Message Reaction Remove Emoji` Gateway event. + /// # Reference + /// See [https://discord.com/developers/docs/resources/channel#delete-all-reactions-for-emoji](https://discord.com/developers/docs/resources/channel#delete-all-reactions-for-emoji) pub async fn delete_emoji(&self, emoji: &str, user: &mut UserMeta) -> ChorusResult<()> { let url = format!( "{}/channels/{}/messages/{}/reactions/{}/", @@ -97,25 +82,21 @@ impl ReactionMeta { handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await } - /** - Create a reaction for the message. - - This endpoint requires the READ_MESSAGE_HISTORY permission - to be present on the current user. Additionally, if nobody else has reacted to the message using - this emoji, this endpoint requires the ADD_REACTIONS permission to be present on the current - user. - # Arguments - * `emoji` - A string slice containing the emoji to delete. The `emoji` must be URL Encoded or - the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the - format name:id with the emoji name and emoji id. - * `user` - A mutable reference to a [`UserMeta`] instance. - - # Returns - A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`]. - - # Reference - See [https://discord.com/developers/docs/resources/channel#create-reaction](https://discord.com/developers/docs/resources/channel#create-reaction) - */ + /// Create a reaction for the message. + /// This endpoint requires the READ_MESSAGE_HISTORY permission + /// to be present on the current user. Additionally, if nobody else has reacted to the message using + /// this emoji, this endpoint requires the ADD_REACTIONS permission to be present on the current + /// user. + /// # Arguments + /// * `emoji` - A string slice containing the emoji to delete. The `emoji` must be URL Encoded or + /// the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the + /// format name:id with the emoji name and emoji id. + /// * `user` - A mutable reference to a [`UserMeta`] instance. + /// # Returns + /// A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`]. + /// # Reference + /// See [https://discord.com/developers/docs/resources/channel#create-reaction](https://discord.com/developers/docs/resources/channel#create-reaction) + /// pub async fn create(&self, emoji: &str, user: &mut UserMeta) -> ChorusResult<()> { let url = format!( "{}/channels/{}/messages/{}/reactions/{}/@me/", @@ -128,22 +109,17 @@ impl ReactionMeta { handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await } - /** - Delete a reaction the current user has made for the message. - - # Arguments - * `emoji` - A string slice containing the emoji to delete. The `emoji` must be URL Encoded or - the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the - format name:id with the emoji name and emoji id. - * `user` - A mutable reference to a [`UserMeta`] instance. - - # Returns - A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`]. - Fires a `Message Reaction Remove` Gateway event. - - # Reference - See [https://discord.com/developers/docs/resources/channel#delete-own-reaction](https://discord.com/developers/docs/resources/channel#delete-own-reaction) - */ + /// Delete a reaction the current user has made for the message. + /// # Arguments + /// * `emoji` - A string slice containing the emoji to delete. The `emoji` must be URL Encoded or + /// the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the + /// format name:id with the emoji name and emoji id. + /// * `user` - A mutable reference to a [`UserMeta`] instance. + /// # Returns + /// A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`]. + /// Fires a `Message Reaction Remove` Gateway event. + /// # Reference + /// See [https://discord.com/developers/docs/resources/channel#delete-own-reaction](https://discord.com/developers/docs/resources/channel#delete-own-reaction) pub async fn remove(&self, emoji: &str, user: &mut UserMeta) -> ChorusResult<()> { let url = format!( "{}/channels/{}/messages/{}/reactions/{}/@me/", @@ -156,25 +132,19 @@ impl ReactionMeta { handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await } - /** - Delete a user's reaction to a message. - - This endpoint requires the MANAGE_MESSAGES permission to be present on the current user. - - # Arguments - * `user_id` - ID of the user whose reaction is to be deleted. - * `emoji` - A string slice containing the emoji to delete. The `emoji` must be URL Encoded or - the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the - format name:id with the emoji name and emoji id. - * `user` - A mutable reference to a [`UserMeta`] instance. - - # Returns - A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`]. - Fires a Message Reaction Remove Gateway event. - - # Reference - See [https://discord.com/developers/docs/resources/channel#delete-own-reaction](https://discord.com/developers/docs/resources/channel#delete-own-reaction) - */ + /// Delete a user's reaction to a message. + /// This endpoint requires the MANAGE_MESSAGES permission to be present on the current user. + /// # Arguments + /// * `user_id` - ID of the user whose reaction is to be deleted. + /// * `emoji` - A string slice containing the emoji to delete. The `emoji` must be URL Encoded or + /// the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the + /// format name:id with the emoji name and emoji id. + /// * `user` - A mutable reference to a [`UserMeta`] instance. + /// # Returns + /// A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`]. + /// Fires a Message Reaction Remove Gateway event. + /// # Reference + /// See [https://discord.com/developers/docs/resources/channel#delete-own-reaction](https://discord.com/developers/docs/resources/channel#delete-own-reaction) pub async fn delete_user( &self, user_id: Snowflake, From c25795ab4df4e768bd807819a3b73c530a8335f1 Mon Sep 17 00:00:00 2001 From: Flori Weber Date: Fri, 23 Jun 2023 12:54:08 +0200 Subject: [PATCH 02/23] Make ReactionMeta::get() return Vec --- src/api/channels/reactions.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/api/channels/reactions.rs b/src/api/channels/reactions.rs index c164246..96a830a 100644 --- a/src/api/channels/reactions.rs +++ b/src/api/channels/reactions.rs @@ -1,10 +1,10 @@ use reqwest::Client; use crate::{ - api::handle_request_as_result, + api::{deserialize_response, handle_request_as_result}, errors::ChorusResult, instance::UserMeta, - types::{self, Snowflake}, + types::{self, PublicUser, Snowflake}, }; /** @@ -46,7 +46,7 @@ impl ReactionMeta { /// A Result that is [`Err(crate::errors::ChorusLibError)`] if something went wrong. /// # Reference /// See [https://discord.com/developers/docs/resources/channel#get-reactions](https://discord.com/developers/docs/resources/channel#get-reactions) - pub async fn get(&self, emoji: &str, user: &mut UserMeta) -> ChorusResult<()> { + pub async fn get(&self, emoji: &str, user: &mut UserMeta) -> ChorusResult> { let url = format!( "{}/channels/{}/messages/{}/reactions/{}/", user.belongs_to.borrow().urls.api, @@ -55,7 +55,12 @@ impl ReactionMeta { emoji ); let request = Client::new().get(url).bearer_auth(user.token()); - handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await + deserialize_response::>( + request, + user, + crate::api::limits::LimitType::Channel, + ) + .await } /// Deletes all the reactions for a given `emoji` on a message. This endpoint requires the From ecf54111a64a522fc9909b2ada771f5e2d28e8d5 Mon Sep 17 00:00:00 2001 From: Flori Weber Date: Fri, 23 Jun 2023 12:54:15 +0200 Subject: [PATCH 03/23] cargo fix --- tests/common/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 900e31e..d4699cb 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,5 +1,4 @@ use chorus::{ - errors::ChorusResult, instance::{Instance, UserMeta}, types::{ Channel, ChannelCreateSchema, Guild, GuildCreateSchema, RegisterSchema, From c0d484efdc605176c60e3be0e0084df47a05541c Mon Sep 17 00:00:00 2001 From: Vincent Junge Date: Sat, 24 Jun 2023 08:52:45 +0200 Subject: [PATCH 04/23] fixed tokio features --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index cece3e2..3590341 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ backend = ["poem", "sqlx"] client = [] [dependencies] -tokio = {version = "1.28.1", features = ["rt", "macros", "rt-multi-thread", "full"]} +tokio = {version = "1.28.1"} serde = {version = "1.0.163", features = ["derive"]} serde_json = {version= "1.0.96", features = ["raw_value"]} serde-aux = "4.2.0" @@ -36,5 +36,6 @@ thiserror = "1.0.40" jsonwebtoken = "8.3.0" [dev-dependencies] +tokio = {version = "1.28.1", features = ["full"]} lazy_static = "1.4.0" rusty-hook = "0.11.2" \ No newline at end of file From 8b0f41fad3c8cc0dcb77a2fe21ca4b0bcacfc75a Mon Sep 17 00:00:00 2001 From: Vincent Junge Date: Sun, 25 Jun 2023 11:33:50 +0200 Subject: [PATCH 05/23] remove client side validation --- src/api/auth/login.rs | 19 ++-- src/api/auth/register.rs | 16 +-- src/gateway.rs | 6 +- src/types/schema/auth.rs | 226 +-------------------------------------- src/types/schema/mod.rs | 73 ------------- tests/auth.rs | 12 +-- tests/common/mod.rs | 12 +-- tests/relationships.rs | 42 ++++---- 8 files changed, 52 insertions(+), 354 deletions(-) diff --git a/src/api/auth/login.rs b/src/api/auth/login.rs index 9670d15..b9c4332 100644 --- a/src/api/auth/login.rs +++ b/src/api/auth/login.rs @@ -26,16 +26,12 @@ impl Instance { self, &mut cloned_limits, ) - .await; - if response.is_err() { - return Err(ChorusLibError::NoResponse); - } + .await?; - let response_unwrap = response.unwrap(); - let status = response_unwrap.status(); - let response_text_string = response_unwrap.text().await.unwrap(); + let status = response.status(); + let response_text = response.text().await.unwrap(); if status.is_client_error() { - let json: ErrorResponse = serde_json::from_str(&response_text_string).unwrap(); + let json: ErrorResponse = serde_json::from_str(&response_text).unwrap(); let error_type = json.errors.errors.iter().next().unwrap().0.to_owned(); let mut error = "".to_string(); for (_, value) in json.errors.errors.iter() { @@ -47,11 +43,8 @@ impl Instance { } let cloned_limits = self.limits.clone(); - let login_result: LoginResult = from_str(&response_text_string).unwrap(); - let object = self - .get_user(login_result.token.clone(), None) - .await - .unwrap(); + let login_result: LoginResult = from_str(&response_text).unwrap(); + let object = self.get_user(login_result.token.clone(), None).await?; let user = UserMeta::new( Rc::new(RefCell::new(self.clone())), login_result.token, diff --git a/src/api/auth/register.rs b/src/api/auth/register.rs index b22d6ce..6d9d2ea 100644 --- a/src/api/auth/register.rs +++ b/src/api/auth/register.rs @@ -39,15 +39,11 @@ impl Instance { self, &mut cloned_limits, ) - .await; - if response.is_err() { - return Err(ChorusLibError::NoResponse); - } + .await?; - let response_unwrap = response.unwrap(); - let status = response_unwrap.status(); - let response_unwrap_text = response_unwrap.text().await.unwrap(); - let token = from_str::(&response_unwrap_text).unwrap(); + let status = response.status(); + let response_text = response.text().await.unwrap(); + let token = from_str::(&response_text).unwrap(); let token = token.token; if status.is_client_error() { let json: ErrorResponse = serde_json::from_str(&token).unwrap(); @@ -61,9 +57,7 @@ impl Instance { return Err(ChorusLibError::InvalidFormBodyError { error_type, error }); } let user_object = self.get_user(token.clone(), None).await.unwrap(); - let settings = UserMeta::get_settings(&token, &self.urls.api.clone(), self) - .await - .unwrap(); + let settings = UserMeta::get_settings(&token, &self.urls.api.clone(), self).await?; let user = UserMeta::new( Rc::new(RefCell::new(self.clone())), token.clone(), diff --git a/src/gateway.rs b/src/gateway.rs index 2f90217..ca57544 100644 --- a/src/gateway.rs +++ b/src/gateway.rs @@ -1879,7 +1879,7 @@ mod example { #[derive(Debug)] struct Consumer { - name: String, + _name: String, events_received: AtomicI32, } @@ -1900,13 +1900,13 @@ mod example { }; let consumer = Arc::new(Consumer { - name: "first".into(), + _name: "first".into(), events_received: 0.into(), }); event.subscribe(consumer.clone()); let second_consumer = Arc::new(Consumer { - name: "second".into(), + _name: "second".into(), events_received: 0.into(), }); event.subscribe(second_consumer.clone()); diff --git a/src/types/schema/auth.rs b/src/types/schema/auth.rs index 8b8a601..3fe0604 100644 --- a/src/types/schema/auth.rs +++ b/src/types/schema/auth.rs @@ -1,122 +1,8 @@ -use regex::Regex; use serde::{Deserialize, Serialize}; -use crate::errors::FieldFormatError; - -/** -A struct that represents a well-formed email address. - */ -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct AuthEmail { - pub email: String, -} - -impl AuthEmail { - /** - Returns a new [`Result`]. - ## Arguments - The email address you want to validate. - ## Errors - You will receive a [`FieldFormatError`], if: - - The email address is not in a valid format. - - */ - pub fn new(email: String) -> Result { - let regex = Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap(); - if !regex.is_match(email.as_str()) { - return Err(FieldFormatError::EmailError); - } - Ok(AuthEmail { email }) - } -} - -/** -A struct that represents a well-formed username. -## Arguments -Please use new() to create a new instance of this struct. -## Errors -You will receive a [`FieldFormatError`], if: -- The username is not between 2 and 32 characters. - */ -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct AuthUsername { - pub username: String, -} - -impl AuthUsername { - /** - Returns a new [`Result`]. - ## Arguments - The username you want to validate. - ## Errors - You will receive a [`FieldFormatError`], if: - - The username is not between 2 and 32 characters. - */ - pub fn new(username: String) -> Result { - if username.len() < 2 || username.len() > 32 { - Err(FieldFormatError::UsernameError) - } else { - Ok(AuthUsername { username }) - } - } -} - -/** -A struct that represents a well-formed password. -## Arguments -Please use new() to create a new instance of this struct. -## Errors -You will receive a [`FieldFormatError`], if: -- The password is not between 1 and 72 characters. - */ -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct AuthPassword { - pub password: String, -} - -impl AuthPassword { - /** - Returns a new [`Result`]. - ## Arguments - The password you want to validate. - ## Errors - You will receive a [`FieldFormatError`], if: - - The password is not between 1 and 72 characters. - */ - pub fn new(password: String) -> Result { - if password.is_empty() || password.len() > 72 { - Err(FieldFormatError::PasswordError) - } else { - Ok(AuthPassword { password }) - } - } -} - -/** -A struct that represents a well-formed register request. -## Arguments -Please use new() to create a new instance of this struct. -## Errors -You will receive a [`FieldFormatError`], if: -- The username is not between 2 and 32 characters. -- The password is not between 1 and 72 characters. - */ -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub struct RegisterSchema { - username: String, - password: Option, - consent: bool, - email: Option, - fingerprint: Option, - invite: Option, - date_of_birth: Option, - gift_code_sku_id: Option, - captcha_key: Option, - promotional_email_opt_in: Option, -} - -pub struct RegisterSchemaOptions { pub username: String, pub password: Option, pub consent: bool, @@ -129,83 +15,14 @@ pub struct RegisterSchemaOptions { pub promotional_email_opt_in: Option, } -impl RegisterSchema { - pub fn builder(username: impl Into, consent: bool) -> RegisterSchemaOptions { - RegisterSchemaOptions { - username: username.into(), - password: None, - consent, - email: None, - fingerprint: None, - invite: None, - date_of_birth: None, - gift_code_sku_id: None, - captcha_key: None, - promotional_email_opt_in: None, - } - } -} - -impl RegisterSchemaOptions { - /** - Create a new [`RegisterSchema`]. - ## Arguments - All but "String::username" and "bool::consent" are optional. - - ## Errors - You will receive a [`FieldFormatError`], if: - - The username is less than 2 or more than 32 characters in length - - You supply a `password` which is less than 1 or more than 72 characters in length. - - These constraints have been defined [in the Spacebar-API](https://docs.spacebar.chat/routes/) - */ - pub fn build(self) -> Result { - let username = AuthUsername::new(self.username)?.username; - - let email = if let Some(email) = self.email { - Some(AuthEmail::new(email)?.email) - } else { - None - }; - - let password = if let Some(password) = self.password { - Some(AuthPassword::new(password)?.password) - } else { - None - }; - - if !self.consent { - return Err(FieldFormatError::ConsentError); - } - - Ok(RegisterSchema { - username, - password, - consent: self.consent, - email, - fingerprint: self.fingerprint, - invite: self.invite, - date_of_birth: self.date_of_birth, - gift_code_sku_id: self.gift_code_sku_id, - captcha_key: self.captcha_key, - promotional_email_opt_in: self.promotional_email_opt_in, - }) - } -} - -/** -A struct that represents a well-formed login request. -## Arguments -Please use new() to create a new instance of this struct. -## Errors -You will receive a [`FieldFormatError`], if: -- The username is not between 2 and 32 characters. -- The password is not between 1 and 72 characters. - */ #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub struct LoginSchema { + /// For Discord, usernames must be between 2 and 32 characters, + /// but other servers may have different limits. pub login: String, + /// For Discord, must be between 1 and 72 characters, + /// but other servers may have different limits. pub password: Option, pub undelete: Option, pub captcha_key: Option, @@ -213,39 +30,6 @@ pub struct LoginSchema { pub gift_code_sku_id: Option, } -impl LoginSchema { - /** - Returns a new [`Result`]. - ## Arguments - login: The username you want to login with. - password: The password you want to login with. - undelete: Honestly no idea what this is for. - captcha_key: The captcha key you want to login with. - login_source: The login source. - gift_code_sku_id: The gift code sku id. - ## Errors - You will receive a [`FieldFormatError`], if: - - The username is less than 2 or more than 32 characters in length - */ - pub fn new( - login: String, - password: Option, - undelete: Option, - captcha_key: Option, - login_source: Option, - gift_code_sku_id: Option, - ) -> Result { - Ok(LoginSchema { - login, - password, - undelete, - captcha_key, - login_source, - gift_code_sku_id, - }) - } -} - #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub struct TotpSchema { diff --git a/src/types/schema/mod.rs b/src/types/schema/mod.rs index 1069428..08dae05 100644 --- a/src/types/schema/mod.rs +++ b/src/types/schema/mod.rs @@ -15,76 +15,3 @@ mod message; mod relationship; mod role; mod user; - -#[cfg(test)] -mod schemas_tests { - use crate::errors::FieldFormatError; - - use super::*; - - #[test] - fn password_too_short() { - assert_eq!( - AuthPassword::new("".to_string()), - Err(FieldFormatError::PasswordError) - ); - } - - #[test] - fn password_too_long() { - let mut long_pw = String::new(); - for _ in 0..73 { - long_pw += "a"; - } - assert_eq!( - AuthPassword::new(long_pw), - Err(FieldFormatError::PasswordError) - ); - } - - #[test] - fn username_too_short() { - assert_eq!( - AuthUsername::new("T".to_string()), - Err(FieldFormatError::UsernameError) - ); - } - - #[test] - fn username_too_long() { - let mut long_un = String::new(); - for _ in 0..33 { - long_un += "a"; - } - assert_eq!( - AuthUsername::new(long_un), - Err(FieldFormatError::UsernameError) - ); - } - - #[test] - fn consent_false() { - assert_eq!( - RegisterSchema::builder("Test", false).build(), - Err(FieldFormatError::ConsentError) - ); - } - - #[test] - fn invalid_email() { - assert_eq!( - AuthEmail::new("p@p.p".to_string()), - Err(FieldFormatError::EmailError) - ) - } - - #[test] - fn valid_email() { - let reg = RegisterSchemaOptions { - email: Some("me@mail.de".to_string()), - ..RegisterSchema::builder("Testy", true) - } - .build(); - assert_ne!(reg, Err(FieldFormatError::EmailError)); - } -} diff --git a/tests/auth.rs b/tests/auth.rs index 6972ace..c26552f 100644 --- a/tests/auth.rs +++ b/tests/auth.rs @@ -1,16 +1,16 @@ -use chorus::types::{RegisterSchema, RegisterSchemaOptions}; +use chorus::types::RegisterSchema; mod common; #[tokio::test] async fn test_registration() { let mut bundle = common::setup().await; - let reg = RegisterSchemaOptions { + let reg = RegisterSchema { + username: "Hiiii".into(), date_of_birth: Some("2000-01-01".to_string()), - ..RegisterSchema::builder("Hiiii", true) - } - .build() - .unwrap(); + consent: true, + ..Default::default() + }; bundle.instance.register_account(®).await.unwrap(); common::teardown(bundle).await; } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index d4699cb..9a62585 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -2,7 +2,7 @@ use chorus::{ instance::{Instance, UserMeta}, types::{ Channel, ChannelCreateSchema, Guild, GuildCreateSchema, RegisterSchema, - RegisterSchemaOptions, RoleCreateModifySchema, RoleObject, + RoleCreateModifySchema, RoleObject, }, UrlBundle, }; @@ -26,12 +26,12 @@ pub async fn setup() -> TestBundle { ); let mut instance = Instance::new(urls.clone()).await.unwrap(); // Requires the existance of the below user. - let reg = RegisterSchemaOptions { + let reg = RegisterSchema { + username: "integrationtestuser".into(), + consent: true, date_of_birth: Some("2000-01-01".to_string()), - ..RegisterSchema::builder("integrationtestuser", true) - } - .build() - .unwrap(); + ..Default::default() + }; let guild_create_schema = GuildCreateSchema { name: Some("Test-Guild!".to_string()), region: None, diff --git a/tests/relationships.rs b/tests/relationships.rs index 81f3230..e23f0f3 100644 --- a/tests/relationships.rs +++ b/tests/relationships.rs @@ -1,15 +1,15 @@ -use chorus::types::{self, RegisterSchema, RegisterSchemaOptions, Relationship, RelationshipType}; +use chorus::types::{self, RegisterSchema, Relationship, RelationshipType}; mod common; #[tokio::test] async fn test_get_mutual_relationships() { - let register_schema = RegisterSchemaOptions { + let register_schema = RegisterSchema { + username: "integrationtestuser2".to_string(), + consent: true, date_of_birth: Some("2000-01-01".to_string()), - ..RegisterSchema::builder("integrationtestuser2", true) - } - .build() - .unwrap(); + ..Default::default() + }; let mut bundle = common::setup().await; let belongs_to = &mut bundle.instance; @@ -30,12 +30,12 @@ async fn test_get_mutual_relationships() { #[tokio::test] async fn test_get_relationships() { - let register_schema = RegisterSchemaOptions { + let register_schema = RegisterSchema { + username: "integrationtestuser2".to_string(), + consent: true, date_of_birth: Some("2000-01-01".to_string()), - ..RegisterSchema::builder("integrationtestuser2", true) - } - .build() - .unwrap(); + ..Default::default() + }; let mut bundle = common::setup().await; let belongs_to = &mut bundle.instance; @@ -53,12 +53,12 @@ async fn test_get_relationships() { #[tokio::test] async fn test_modify_relationship_friends() { - let register_schema = RegisterSchemaOptions { + let register_schema = RegisterSchema { + username: "integrationtestuser2".to_string(), + consent: true, date_of_birth: Some("2000-01-01".to_string()), - ..RegisterSchema::builder("integrationtestuser2", true) - } - .build() - .unwrap(); + ..Default::default() + }; let mut bundle = common::setup().await; let belongs_to = &mut bundle.instance; @@ -101,12 +101,12 @@ async fn test_modify_relationship_friends() { #[tokio::test] async fn test_modify_relationship_block() { - let register_schema = RegisterSchemaOptions { + let register_schema = RegisterSchema { + username: "integrationtestuser2".to_string(), + consent: true, date_of_birth: Some("2000-01-01".to_string()), - ..RegisterSchema::builder("integrationtestuser2", true) - } - .build() - .unwrap(); + ..Default::default() + }; let mut bundle = common::setup().await; let belongs_to = &mut bundle.instance; From 94a051631f04c3d63dbffb635b86e46eb3b596bf Mon Sep 17 00:00:00 2001 From: Vincent Junge Date: Sun, 25 Jun 2023 22:34:05 +0200 Subject: [PATCH 06/23] require password to log in --- src/types/schema/auth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/schema/auth.rs b/src/types/schema/auth.rs index 3fe0604..9a3b9d6 100644 --- a/src/types/schema/auth.rs +++ b/src/types/schema/auth.rs @@ -23,7 +23,7 @@ pub struct LoginSchema { pub login: String, /// For Discord, must be between 1 and 72 characters, /// but other servers may have different limits. - pub password: Option, + pub password: String, pub undelete: Option, pub captcha_key: Option, pub login_source: Option, From 3f6d0e2d9dc720fd81b05bcf4d302717a2424a28 Mon Sep 17 00:00:00 2001 From: kozabrada123 <59031733+kozabrada123@users.noreply.github.com> Date: Sat, 1 Jul 2023 19:29:50 +0200 Subject: [PATCH 07/23] Use log instead of prints --- Cargo.toml | 1 + src/gateway.rs | 184 ++++++++++++++++++++++++------------------------- 2 files changed, 93 insertions(+), 92 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3590341..d7fdf0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ poem = { version = "1.3.55", 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.40" jsonwebtoken = "8.3.0" +log = "0.4.19" [dev-dependencies] tokio = {version = "1.28.1", features = ["full"]} diff --git a/src/gateway.rs b/src/gateway.rs index ca57544..87e1589 100644 --- a/src/gateway.rs +++ b/src/gateway.rs @@ -8,6 +8,7 @@ use futures_util::stream::SplitSink; use futures_util::stream::SplitStream; use futures_util::SinkExt; use futures_util::StreamExt; +use log::{debug, info, trace, warn}; use native_tls::TlsConnector; use tokio::net::TcpStream; use tokio::sync::mpsc::error::TryRecvError; @@ -191,7 +192,7 @@ impl GatewayHandle { pub async fn send_identify(&self, to_send: types::GatewayIdentifyPayload) { let to_send_value = serde_json::to_value(&to_send).unwrap(); - println!("GW: Sending Identify.."); + trace!("GW: Sending Identify.."); self.send_json_event(GATEWAY_IDENTIFY, to_send_value).await; } @@ -200,7 +201,7 @@ impl GatewayHandle { pub async fn send_resume(&self, to_send: types::GatewayResume) { let to_send_value = serde_json::to_value(&to_send).unwrap(); - println!("GW: Sending Resume.."); + trace!("GW: Sending Resume.."); self.send_json_event(GATEWAY_RESUME, to_send_value).await; } @@ -209,7 +210,7 @@ impl GatewayHandle { pub async fn send_update_presence(&self, to_send: types::UpdatePresence) { let to_send_value = serde_json::to_value(&to_send).unwrap(); - println!("GW: Sending Update Presence.."); + trace!("GW: Sending Update Presence.."); self.send_json_event(GATEWAY_UPDATE_PRESENCE, to_send_value) .await; @@ -219,7 +220,7 @@ impl GatewayHandle { pub async fn send_request_guild_members(&self, to_send: types::GatewayRequestGuildMembers) { let to_send_value = serde_json::to_value(&to_send).unwrap(); - println!("GW: Sending Request Guild Members.."); + trace!("GW: Sending Request Guild Members.."); self.send_json_event(GATEWAY_REQUEST_GUILD_MEMBERS, to_send_value) .await; @@ -229,7 +230,7 @@ impl GatewayHandle { pub async fn send_update_voice_state(&self, to_send: types::UpdateVoiceState) { let to_send_value = serde_json::to_value(&to_send).unwrap(); - println!("GW: Sending Update Voice State.."); + trace!("GW: Sending Update Voice State.."); self.send_json_event(GATEWAY_UPDATE_VOICE_STATE, to_send_value) .await; @@ -239,7 +240,7 @@ impl GatewayHandle { pub async fn send_call_sync(&self, to_send: types::CallSync) { let to_send_value = serde_json::to_value(&to_send).unwrap(); - println!("GW: Sending Call Sync.."); + trace!("GW: Sending Call Sync.."); self.send_json_event(GATEWAY_CALL_SYNC, to_send_value).await; } @@ -248,7 +249,7 @@ impl GatewayHandle { pub async fn send_lazy_request(&self, to_send: types::LazyRequest) { let to_send_value = serde_json::to_value(&to_send).unwrap(); - println!("GW: Sending Lazy Request.."); + trace!("GW: Sending Lazy Request.."); self.send_json_event(GATEWAY_LAZY_REQUEST, to_send_value) .await; @@ -318,7 +319,7 @@ impl Gateway { }); } - println!("GW: Received Hello"); + info!("GW: Received Hello"); let gateway_hello: types::HelloData = serde_json::from_str(gateway_payload.event_data.unwrap().get()).unwrap(); @@ -367,7 +368,7 @@ impl Gateway { } // We couldn't receive the next message or it was an error, something is wrong with the websocket, close - println!("GW: Websocket is broken, stopping gateway"); + warn!("GW: Websocket is broken, stopping gateway"); break; } } @@ -401,7 +402,7 @@ impl Gateway { } if !msg.is_error() && !msg.is_payload() { - println!( + warn!( "Message unrecognised: {:?}, please open an issue on the chorus github", msg.message.to_string() ); @@ -410,12 +411,10 @@ impl Gateway { // To:do: handle errors in a good way, maybe observers like events? if msg.is_error() { - println!("GW: Received error, connection will close.."); + warn!("GW: Received error, connection will close.."); let _error = msg.error(); - {} - self.close().await; return; } @@ -428,7 +427,7 @@ impl Gateway { GATEWAY_DISPATCH => { let gateway_payload_t = gateway_payload.clone().event_name.unwrap(); - println!("GW: Received {}..", gateway_payload_t); + trace!("GW: Received {}..", gateway_payload_t); //println!("Event data dump: {}", gateway_payload.d.clone().unwrap().get()); @@ -443,7 +442,7 @@ impl Gateway { .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -459,7 +458,7 @@ impl Gateway { .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -481,7 +480,7 @@ impl Gateway { .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -495,7 +494,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -509,7 +508,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -523,7 +522,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -537,7 +536,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -551,7 +550,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -565,7 +564,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -579,7 +578,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -593,7 +592,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -607,7 +606,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -621,7 +620,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -635,7 +634,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -649,7 +648,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -663,7 +662,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -677,7 +676,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -691,7 +690,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -705,7 +704,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -719,7 +718,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -733,7 +732,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -747,7 +746,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -761,7 +760,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -775,7 +774,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -789,7 +788,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -803,7 +802,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -817,7 +816,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -831,7 +830,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -845,7 +844,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -859,7 +858,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -873,7 +872,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -887,7 +886,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -901,7 +900,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -915,7 +914,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -929,7 +928,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -943,7 +942,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -957,7 +956,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -971,7 +970,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -985,7 +984,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -999,7 +998,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1014,7 +1013,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1033,7 +1032,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1047,7 +1046,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1061,7 +1060,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1075,7 +1074,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1089,7 +1088,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1103,7 +1102,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1117,7 +1116,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1131,7 +1130,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1145,7 +1144,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1159,7 +1158,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1173,7 +1172,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1187,7 +1186,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1201,7 +1200,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1215,7 +1214,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1229,7 +1228,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1243,7 +1242,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1257,7 +1256,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1271,7 +1270,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1285,7 +1284,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1299,7 +1298,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1313,7 +1312,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1327,7 +1326,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1341,7 +1340,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1353,7 +1352,7 @@ impl Gateway { let result: Result, serde_json::Error> = serde_json::from_str(gateway_payload.event_data.unwrap().get()); if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1373,7 +1372,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1387,7 +1386,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1401,7 +1400,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1415,7 +1414,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1429,7 +1428,7 @@ impl Gateway { Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) .await; if result.is_err() { - println!( + warn!( "Failed to parse gateway event {} ({})", gateway_payload_t, result.err().unwrap() @@ -1438,14 +1437,14 @@ impl Gateway { } } _ => { - println!("Received unrecognized gateway event ({})! Please open an issue on the chorus github so we can implement it", &gateway_payload_t); + warn!("Received unrecognized gateway event ({})! Please open an issue on the chorus github so we can implement it", &gateway_payload_t); } } } // We received a heartbeat from the server // "Discord may send the app a Heartbeat (opcode 1) event, in which case the app should send a Heartbeat event immediately." GATEWAY_HEARTBEAT => { - println!("GW: Received Heartbeat // Heartbeat Request"); + trace!("GW: Received Heartbeat // Heartbeat Request"); // Tell the heartbeat handler it should send a heartbeat right away @@ -1469,10 +1468,10 @@ impl Gateway { // Starts our heartbeat // We should have already handled this in gateway init GATEWAY_HELLO => { - panic!("Received hello when it was unexpected"); + warn!("Received hello when it was unexpected"); } GATEWAY_HEARTBEAT_ACK => { - println!("GW: Received Heartbeat ACK"); + trace!("GW: Received Heartbeat ACK"); // Tell the heartbeat handler we received an ack @@ -1500,7 +1499,7 @@ impl Gateway { Err::<(), GatewayError>(error).unwrap(); } _ => { - println!("Received unrecognized gateway op code ({})! Please open an issue on the chorus github so we can implement it", gateway_payload.op_code); + warn!("Received unrecognized gateway op code ({})! Please open an issue on the chorus github so we can implement it", gateway_payload.op_code); } } @@ -1588,6 +1587,7 @@ impl HeartbeatHandler { loop { let should_shutdown = kill_receive.try_recv().is_ok(); if should_shutdown { + trace!("GW: Closing heartbeat task"); break; } @@ -1627,11 +1627,11 @@ impl HeartbeatHandler { && last_heartbeat_timestamp.elapsed().as_millis() > HEARTBEAT_ACK_TIMEOUT { should_send = true; - println!("GW: Timed out waiting for a heartbeat ack, resending"); + info!("GW: Timed out waiting for a heartbeat ack, resending"); } if should_send { - println!("GW: Sending Heartbeat.."); + trace!("GW: Sending Heartbeat.."); let heartbeat = types::GatewayHeartbeat { op: GATEWAY_HEARTBEAT, @@ -1645,7 +1645,7 @@ impl HeartbeatHandler { let send_result = websocket_tx.lock().await.send(msg).await; if send_result.is_err() { // We couldn't send, the websocket is broken - println!("GW: Couldnt send heartbeat, websocket seems broken"); + warn!("GW: Couldnt send heartbeat, websocket seems broken"); break; } From 69b7c2445cb96a6b2830032cf79c0c1090d05a65 Mon Sep 17 00:00:00 2001 From: Flori <39242991+bitfl0wer@users.noreply.github.com> Date: Sun, 9 Jul 2023 18:38:02 +0200 Subject: [PATCH 08/23] Ratelimiter overhaul (#144) * Rename limits and limit to have better names * Remove empty lines * Remove handle_request (moved to requestlimiter) * Start working on new ratelimiter * Make limits Option, add "limited?" to constructor * Add missing logic to send_request * Rename Limits * Create Ratelimits and Limit Struct * Define Limit * Import Ratelimits * Define get_rate_limits * Remove unused import * + check_rate_limits & limits_config_to_ratelimits * Remove Absolute Limits These limits are not meant to be tracked anyways. * add ratelimits is_exhausted * Add error handling and send request checking * change limits to option ratelimits * Add strum * Change Ratelimits to Hashmap * Remove ratelimits in favor of hashmap * Change code from struct to hashmap * start working on update rate limits * Remove wrong import * Rename ChorusLibError to ChorusError * Documented the chorus errors * Made error documentation docstring * Make ReceivedErrorCodeError have error string * Remove unneeded import * Match changes in errors.rs * Improve update_rate_limits and can_send_request * add ratelimits.to_hash_map() * use instances' client instead of new client * add LimitsConfiguration to instance * improve update_limits, change a method name * Fix un-updated errors * Get LimitConfiguration in a sane way * Move common.rs into ratelimiter::ChorusRequest * Delete common.rs * Make instance.rs use overhauled errors * Refactor to use new Rate limiting implementation * Refactor to use new Rate limiting implementation * Refactor to use new Rate limiting implementation * Refactor to use new Rate limiting implementation * Refactor to use new Rate limiting implementation * Refactor to use new Rate limiting implementation * update ratelimiter implementation across all files * Fix remaining errors post-refactor * Changed Enum case to be correct * Use result * Re-add missing body to request * Remove unneeded late initalization * Change visibility from pub to pub(crate) I feel like these core methods don't need to be exposed as public API. * Remove unnecessary import * Fix clippy warnings * Add docstring * Change Error names across all files * Update Cargo.toml Strum is not needed * Update ratelimits.rs * Update ratelimits.rs * Bug/discord instance info unavailable (#146) * Change text to be more ambigous * Use default Configuration instead of erroring out * Emit warning log if instance config cant be gotten * Remove import * Update src/instance.rs Co-authored-by: SpecificProtagonist * Add missing closing bracket * Put limits and limits_configuration as one struct * Derive Hash * remove import * rename limits and limits_configuration * Save clone call * Change LimitsConfiguration to RateLimits `LimitsConfiguration` is in no way related to whether the instance has API rate limits enabled or not. Therefore, it has been replaced with what it should have been all along. * Add ensure_limit_in_map(), add `window` to `Limit` * Remove unneeded var * Remove import * Clean up unneeded things Dead code warnings have been supressed, but flagged as FIXME so they don't get forgotten. Anyone using tools like TODO Tree in VSCode can still see that they are there, however, they will not be shown as warnings anymore * Remove nested submodule `limit` * Add doc comments * Add more doc comments * Add some log messages to some methods --------- Co-authored-by: SpecificProtagonist --- Cargo.toml | 2 +- src/api/auth/login.rs | 54 +- src/api/auth/register.rs | 51 +- src/api/channels/channels.rs | 102 ++-- src/api/channels/messages.rs | 36 +- src/api/channels/permissions.rs | 35 +- src/api/channels/reactions.rs | 52 +- src/api/common.rs | 72 --- src/api/guilds/guilds.rs | 161 ++---- src/api/guilds/member.rs | 33 +- src/api/guilds/roles.rs | 78 +-- src/api/mod.rs | 4 +- src/api/policies/instance/instance.rs | 15 +- src/api/policies/instance/limits.rs | 499 ------------------ src/api/policies/instance/mod.rs | 4 +- src/api/policies/instance/ratelimits.rs | 35 ++ src/api/policies/mod.rs | 2 +- src/api/users/relationships.rs | 82 +-- src/api/users/users.rs | 85 ++- src/errors.rs | 81 +-- src/gateway.rs | 41 +- src/instance.rs | 88 ++- src/lib.rs | 2 +- src/limit.rs | 304 ----------- src/ratelimiter.rs | 462 ++++++++++++++++ .../config/types/general_configuration.rs | 6 +- .../config/types/subconfigs/limits/rates.rs | 24 +- src/types/entities/user.rs | 5 +- src/types/utils/rights.rs | 1 + src/types/utils/snowflake.rs | 2 +- tests/common/mod.rs | 2 +- tests/relationships.rs | 18 +- 32 files changed, 1049 insertions(+), 1389 deletions(-) delete mode 100644 src/api/common.rs delete mode 100644 src/api/policies/instance/limits.rs create mode 100644 src/api/policies/instance/ratelimits.rs delete mode 100644 src/limit.rs create mode 100644 src/ratelimiter.rs diff --git a/Cargo.toml b/Cargo.toml index d7fdf0c..5cffce1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,4 +39,4 @@ log = "0.4.19" [dev-dependencies] tokio = {version = "1.28.1", features = ["full"]} lazy_static = "1.4.0" -rusty-hook = "0.11.2" \ No newline at end of file +rusty-hook = "0.11.2" diff --git a/src/api/auth/login.rs b/src/api/auth/login.rs index b9c4332..baae4ae 100644 --- a/src/api/auth/login.rs +++ b/src/api/auth/login.rs @@ -2,57 +2,41 @@ use std::cell::RefCell; use std::rc::Rc; use reqwest::Client; -use serde_json::{from_str, json}; +use serde_json::to_string; -use crate::api::limits::LimitType; -use crate::errors::{ChorusLibError, ChorusResult}; +use crate::api::LimitType; +use crate::errors::ChorusResult; use crate::instance::{Instance, UserMeta}; -use crate::limit::LimitedRequester; -use crate::types::{ErrorResponse, LoginResult, LoginSchema}; +use crate::ratelimiter::ChorusRequest; +use crate::types::{LoginResult, LoginSchema}; impl Instance { pub async fn login_account(&mut self, login_schema: &LoginSchema) -> ChorusResult { - let json_schema = json!(login_schema); - let client = Client::new(); let endpoint_url = self.urls.api.clone() + "/auth/login"; - let request_builder = client.post(endpoint_url).body(json_schema.to_string()); + let chorus_request = ChorusRequest { + request: Client::new() + .post(endpoint_url) + .body(to_string(login_schema).unwrap()), + limit_type: LimitType::AuthLogin, + }; // We do not have a user yet, and the UserRateLimits will not be affected by a login // request (since login is an instance wide limit), which is why we are just cloning the // instances' limits to pass them on as user_rate_limits later. - let mut cloned_limits = self.limits.clone(); - let response = LimitedRequester::send_request( - request_builder, - LimitType::AuthRegister, - self, - &mut cloned_limits, - ) - .await?; - - let status = response.status(); - let response_text = response.text().await.unwrap(); - if status.is_client_error() { - let json: ErrorResponse = serde_json::from_str(&response_text).unwrap(); - let error_type = json.errors.errors.iter().next().unwrap().0.to_owned(); - let mut error = "".to_string(); - for (_, value) in json.errors.errors.iter() { - for error_item in value._errors.iter() { - error += &(error_item.message.to_string() + " (" + &error_item.code + ")"); - } - } - return Err(ChorusLibError::InvalidFormBodyError { error_type, error }); - } - - let cloned_limits = self.limits.clone(); - let login_result: LoginResult = from_str(&response_text).unwrap(); + let mut shell = UserMeta::shell(Rc::new(RefCell::new(self.clone())), "None".to_string()); + let login_result = chorus_request + .deserialize_response::(&mut shell) + .await?; let object = self.get_user(login_result.token.clone(), None).await?; + if self.limits_information.is_some() { + self.limits_information.as_mut().unwrap().ratelimits = shell.limits.clone().unwrap(); + } let user = UserMeta::new( Rc::new(RefCell::new(self.clone())), login_result.token, - cloned_limits, + self.clone_limits_if_some(), login_result.settings, object, ); - Ok(user) } } diff --git a/src/api/auth/register.rs b/src/api/auth/register.rs index 6d9d2ea..a6849cc 100644 --- a/src/api/auth/register.rs +++ b/src/api/auth/register.rs @@ -1,14 +1,14 @@ use std::{cell::RefCell, rc::Rc}; use reqwest::Client; -use serde_json::{from_str, json}; +use serde_json::to_string; use crate::{ - api::limits::LimitType, - errors::{ChorusLibError, ChorusResult}, + api::policies::instance::LimitType, + errors::ChorusResult, instance::{Instance, Token, UserMeta}, - limit::LimitedRequester, - types::{ErrorResponse, RegisterSchema}, + ratelimiter::ChorusRequest, + types::RegisterSchema, }; impl Instance { @@ -25,43 +25,30 @@ impl Instance { &mut self, register_schema: &RegisterSchema, ) -> ChorusResult { - let json_schema = json!(register_schema); - let client = Client::new(); let endpoint_url = self.urls.api.clone() + "/auth/register"; - let request_builder = client.post(endpoint_url).body(json_schema.to_string()); + let chorus_request = ChorusRequest { + request: Client::new() + .post(endpoint_url) + .body(to_string(register_schema).unwrap()), + limit_type: LimitType::AuthRegister, + }; // We do not have a user yet, and the UserRateLimits will not be affected by a login // request (since register is an instance wide limit), which is why we are just cloning // the instances' limits to pass them on as user_rate_limits later. - let mut cloned_limits = self.limits.clone(); - let response = LimitedRequester::send_request( - request_builder, - LimitType::AuthRegister, - self, - &mut cloned_limits, - ) - .await?; - - let status = response.status(); - let response_text = response.text().await.unwrap(); - let token = from_str::(&response_text).unwrap(); - let token = token.token; - if status.is_client_error() { - let json: ErrorResponse = serde_json::from_str(&token).unwrap(); - let error_type = json.errors.errors.iter().next().unwrap().0.to_owned(); - let mut error = "".to_string(); - for (_, value) in json.errors.errors.iter() { - for error_item in value._errors.iter() { - error += &(error_item.message.to_string() + " (" + &error_item.code + ")"); - } - } - return Err(ChorusLibError::InvalidFormBodyError { error_type, error }); + let mut shell = UserMeta::shell(Rc::new(RefCell::new(self.clone())), "None".to_string()); + let token = chorus_request + .deserialize_response::(&mut shell) + .await? + .token; + if self.limits_information.is_some() { + self.limits_information.as_mut().unwrap().ratelimits = shell.limits.unwrap(); } let user_object = self.get_user(token.clone(), None).await.unwrap(); let settings = UserMeta::get_settings(&token, &self.urls.api.clone(), self).await?; let user = UserMeta::new( Rc::new(RefCell::new(self.clone())), token.clone(), - cloned_limits, + self.clone_limits_if_some(), settings, user_object, ); diff --git a/src/api/channels/channels.rs b/src/api/channels/channels.rs index cbe481c..82662e3 100644 --- a/src/api/channels/channels.rs +++ b/src/api/channels/channels.rs @@ -2,32 +2,23 @@ use reqwest::Client; use serde_json::to_string; use crate::{ - api::common, - errors::{ChorusLibError, ChorusResult}, + api::LimitType, + errors::{ChorusError, ChorusResult}, instance::UserMeta, + ratelimiter::ChorusRequest, types::{Channel, ChannelModifySchema, GetChannelMessagesSchema, Message, Snowflake}, }; impl Channel { pub async fn get(user: &mut UserMeta, channel_id: Snowflake) -> ChorusResult { - let url = user.belongs_to.borrow_mut().urls.api.clone(); - let request = Client::new() - .get(format!("{}/channels/{}/", url, channel_id)) - .bearer_auth(user.token()); - - let result = common::deserialize_response::( - request, - user, - crate::api::limits::LimitType::Channel, - ) - .await; - if result.is_err() { - return Err(ChorusLibError::RequestErrorError { - url: format!("{}/channels/{}/", url, channel_id), - error: result.err().unwrap().to_string(), - }); - } - Ok(result.unwrap()) + let url = user.belongs_to.borrow().urls.api.clone(); + let chorus_request = ChorusRequest { + request: Client::new() + .get(format!("{}/channels/{}/", url, channel_id)) + .bearer_auth(user.token()), + limit_type: LimitType::Channel(channel_id), + }; + chorus_request.deserialize_response::(user).await } /// Deletes a channel. @@ -44,15 +35,17 @@ impl Channel { /// /// A `Result` that contains a `ChorusLibError` if an error occurred during the request, or `()` if the request was successful. pub async fn delete(self, user: &mut UserMeta) -> ChorusResult<()> { - let request = Client::new() - .delete(format!( - "{}/channels/{}/", - user.belongs_to.borrow_mut().urls.api, - self.id - )) - .bearer_auth(user.token()); - common::handle_request_as_result(request, user, crate::api::limits::LimitType::Channel) - .await + let chorus_request = ChorusRequest { + request: Client::new() + .delete(format!( + "{}/channels/{}/", + user.belongs_to.borrow().urls.api, + self.id + )) + .bearer_auth(user.token()), + limit_type: LimitType::Channel(self.id), + }; + chorus_request.handle_request_as_result(user).await } /// Modifies a channel. @@ -75,20 +68,18 @@ impl Channel { channel_id: Snowflake, user: &mut UserMeta, ) -> ChorusResult<()> { - let request = Client::new() - .patch(format!( - "{}/channels/{}/", - user.belongs_to.borrow().urls.api, - channel_id - )) - .bearer_auth(user.token()) - .body(to_string(&modify_data).unwrap()); - let new_channel = common::deserialize_response::( - request, - user, - crate::api::limits::LimitType::Channel, - ) - .await?; + let chorus_request = ChorusRequest { + request: Client::new() + .patch(format!( + "{}/channels/{}/", + user.belongs_to.borrow().urls.api, + channel_id + )) + .bearer_auth(user.token()) + .body(to_string(&modify_data).unwrap()), + limit_type: LimitType::Channel(channel_id), + }; + let new_channel = chorus_request.deserialize_response::(user).await?; let _ = std::mem::replace(self, new_channel); Ok(()) } @@ -97,16 +88,21 @@ impl Channel { range: GetChannelMessagesSchema, channel_id: Snowflake, user: &mut UserMeta, - ) -> Result, ChorusLibError> { - let request = Client::new() - .get(format!( - "{}/channels/{}/messages", - user.belongs_to.borrow().urls.api, - channel_id - )) - .bearer_auth(user.token()) - .query(&range); + ) -> Result, ChorusError> { + let chorus_request = ChorusRequest { + request: Client::new() + .get(format!( + "{}/channels/{}/messages", + user.belongs_to.borrow().urls.api, + channel_id + )) + .bearer_auth(user.token()) + .query(&range), + limit_type: Default::default(), + }; - common::deserialize_response::>(request, user, Default::default()).await + chorus_request + .deserialize_response::>(user) + .await } } diff --git a/src/api/channels/messages.rs b/src/api/channels/messages.rs index c61756a..dbffaa8 100644 --- a/src/api/channels/messages.rs +++ b/src/api/channels/messages.rs @@ -3,8 +3,9 @@ use http::HeaderMap; use reqwest::{multipart, Client}; use serde_json::to_string; -use crate::api::deserialize_response; +use crate::api::LimitType; use crate::instance::UserMeta; +use crate::ratelimiter::ChorusRequest; use crate::types::{Message, MessageSendSchema, PartialDiscordFileAttachment, Snowflake}; impl Message { @@ -24,16 +25,18 @@ impl Message { channel_id: Snowflake, message: &mut MessageSendSchema, files: Option>, - ) -> Result { + ) -> Result { let url_api = user.belongs_to.borrow().urls.api.clone(); if files.is_none() { - let request = Client::new() - .post(format!("{}/channels/{}/messages/", url_api, channel_id)) - .bearer_auth(user.token()) - .body(to_string(message).unwrap()); - deserialize_response::(request, user, crate::api::limits::LimitType::Channel) - .await + let chorus_request = ChorusRequest { + request: Client::new() + .post(format!("{}/channels/{}/messages/", url_api, channel_id)) + .bearer_auth(user.token()) + .body(to_string(message).unwrap()), + limit_type: LimitType::Channel(channel_id), + }; + 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); @@ -62,13 +65,14 @@ impl Message { form = form.part(part_name, part); } - let request = Client::new() - .post(format!("{}/channels/{}/messages/", url_api, channel_id)) - .bearer_auth(user.token()) - .multipart(form); - - deserialize_response::(request, user, crate::api::limits::LimitType::Channel) - .await + let chorus_request = ChorusRequest { + request: Client::new() + .post(format!("{}/channels/{}/messages/", url_api, channel_id)) + .bearer_auth(user.token()) + .multipart(form), + limit_type: LimitType::Channel(channel_id), + }; + chorus_request.deserialize_response::(user).await } } } @@ -91,7 +95,7 @@ impl UserMeta { message: &mut MessageSendSchema, channel_id: Snowflake, files: Option>, - ) -> Result { + ) -> Result { Message::send(self, channel_id, message, files).await } } diff --git a/src/api/channels/permissions.rs b/src/api/channels/permissions.rs index 0958821..bc666ff 100644 --- a/src/api/channels/permissions.rs +++ b/src/api/channels/permissions.rs @@ -2,9 +2,10 @@ use reqwest::Client; use serde_json::to_string; use crate::{ - api::handle_request_as_result, - errors::{ChorusLibError, ChorusResult}, + api::LimitType, + errors::{ChorusError, ChorusResult}, instance::UserMeta, + ratelimiter::ChorusRequest, types::{self, PermissionOverwrite, Snowflake}, }; @@ -25,24 +26,25 @@ impl types::Channel { channel_id: Snowflake, overwrite: PermissionOverwrite, ) -> ChorusResult<()> { - let url = { - format!( - "{}/channels/{}/permissions/{}", - user.belongs_to.borrow_mut().urls.api, - channel_id, - overwrite.id - ) - }; + let url = format!( + "{}/channels/{}/permissions/{}", + user.belongs_to.borrow_mut().urls.api, + channel_id, + overwrite.id + ); let body = match to_string(&overwrite) { Ok(string) => string, Err(e) => { - return Err(ChorusLibError::FormCreationError { + return Err(ChorusError::FormCreation { error: e.to_string(), }); } }; - let request = Client::new().put(url).bearer_auth(user.token()).body(body); - handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await + let chorus_request = ChorusRequest { + request: Client::new().put(url).bearer_auth(user.token()).body(body), + limit_type: LimitType::Channel(channel_id), + }; + chorus_request.handle_request_as_result(user).await } /// Deletes a permission overwrite for a channel. @@ -67,7 +69,10 @@ impl types::Channel { channel_id, overwrite_id ); - let request = Client::new().delete(url).bearer_auth(user.token()); - handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await + let chorus_request = ChorusRequest { + request: Client::new().delete(url).bearer_auth(user.token()), + limit_type: LimitType::Channel(channel_id), + }; + chorus_request.handle_request_as_result(user).await } } diff --git a/src/api/channels/reactions.rs b/src/api/channels/reactions.rs index 96a830a..35dbb94 100644 --- a/src/api/channels/reactions.rs +++ b/src/api/channels/reactions.rs @@ -1,9 +1,10 @@ use reqwest::Client; use crate::{ - api::{deserialize_response, handle_request_as_result}, + api::LimitType, errors::ChorusResult, instance::UserMeta, + ratelimiter::ChorusRequest, types::{self, PublicUser, Snowflake}, }; @@ -32,8 +33,11 @@ impl ReactionMeta { self.channel_id, self.message_id ); - let request = Client::new().delete(url).bearer_auth(user.token()); - handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await + let chorus_request = ChorusRequest { + request: Client::new().delete(url).bearer_auth(user.token()), + limit_type: LimitType::Channel(self.channel_id), + }; + chorus_request.handle_request_as_result(user).await } /// Gets a list of users that reacted with a specific emoji to a message. @@ -54,13 +58,13 @@ impl ReactionMeta { self.message_id, emoji ); - let request = Client::new().get(url).bearer_auth(user.token()); - deserialize_response::>( - request, - user, - crate::api::limits::LimitType::Channel, - ) - .await + let chorus_request = ChorusRequest { + request: Client::new().get(url).bearer_auth(user.token()), + limit_type: LimitType::Channel(self.channel_id), + }; + chorus_request + .deserialize_response::>(user) + .await } /// Deletes all the reactions for a given `emoji` on a message. This endpoint requires the @@ -83,8 +87,11 @@ impl ReactionMeta { self.message_id, emoji ); - let request = Client::new().delete(url).bearer_auth(user.token()); - handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await + let chorus_request = ChorusRequest { + request: Client::new().delete(url).bearer_auth(user.token()), + limit_type: LimitType::Channel(self.channel_id), + }; + chorus_request.handle_request_as_result(user).await } /// Create a reaction for the message. @@ -110,8 +117,11 @@ impl ReactionMeta { self.message_id, emoji ); - let request = Client::new().put(url).bearer_auth(user.token()); - handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await + let chorus_request = ChorusRequest { + request: Client::new().put(url).bearer_auth(user.token()), + limit_type: LimitType::Channel(self.channel_id), + }; + chorus_request.handle_request_as_result(user).await } /// Delete a reaction the current user has made for the message. @@ -133,8 +143,11 @@ impl ReactionMeta { self.message_id, emoji ); - let request = Client::new().delete(url).bearer_auth(user.token()); - handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await + let chorus_request = ChorusRequest { + request: Client::new().delete(url).bearer_auth(user.token()), + limit_type: LimitType::Channel(self.channel_id), + }; + chorus_request.handle_request_as_result(user).await } /// Delete a user's reaction to a message. @@ -164,7 +177,10 @@ impl ReactionMeta { emoji, user_id ); - let request = Client::new().delete(url).bearer_auth(user.token()); - handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await + let chorus_request = ChorusRequest { + request: Client::new().delete(url).bearer_auth(user.token()), + limit_type: LimitType::Channel(self.channel_id), + }; + chorus_request.handle_request_as_result(user).await } } diff --git a/src/api/common.rs b/src/api/common.rs deleted file mode 100644 index 86a972e..0000000 --- a/src/api/common.rs +++ /dev/null @@ -1,72 +0,0 @@ -use reqwest::RequestBuilder; -use serde::Deserialize; -use serde_json::from_str; - -use crate::{ - errors::{ChorusLibError, ChorusResult}, - instance::UserMeta, - limit::LimitedRequester, -}; - -use super::limits::LimitType; - -/// Sends a request to wherever it needs to go and performs some basic error handling. -pub async fn handle_request( - request: RequestBuilder, - user: &mut UserMeta, - limit_type: LimitType, -) -> Result { - LimitedRequester::send_request( - request, - limit_type, - &mut user.belongs_to.borrow_mut(), - &mut user.limits, - ) - .await -} - -/// Sends a request to wherever it needs to go. Returns [`Ok(())`] on success and -/// [`Err(ChorusLibError)`] on failure. -pub async fn handle_request_as_result( - request: RequestBuilder, - user: &mut UserMeta, - limit_type: LimitType, -) -> ChorusResult<()> { - match handle_request(request, user, limit_type).await { - Ok(_) => Ok(()), - Err(e) => Err(ChorusLibError::InvalidResponseError { - error: e.to_string(), - }), - } -} - -pub async fn deserialize_response Deserialize<'a>>( - request: RequestBuilder, - user: &mut UserMeta, - limit_type: LimitType, -) -> ChorusResult { - let response = handle_request(request, user, limit_type).await.unwrap(); - let response_text = match response.text().await { - Ok(string) => string, - Err(e) => { - return Err(ChorusLibError::InvalidResponseError { - error: format!( - "Error while trying to process the HTTP response into a String: {}", - e - ), - }); - } - }; - let object = match from_str::(&response_text) { - Ok(object) => object, - Err(e) => { - return Err(ChorusLibError::InvalidResponseError { - error: format!( - "Error while trying to deserialize the JSON response into T: {}", - e - ), - }) - } - }; - Ok(object) -} diff --git a/src/api/guilds/guilds.rs b/src/api/guilds/guilds.rs index ac00df1..3654698 100644 --- a/src/api/guilds/guilds.rs +++ b/src/api/guilds/guilds.rs @@ -2,15 +2,11 @@ use reqwest::Client; use serde_json::from_str; use serde_json::to_string; -use crate::api::deserialize_response; -use crate::api::handle_request; -use crate::api::handle_request_as_result; -use crate::api::limits::Limits; -use crate::errors::ChorusLibError; +use crate::api::LimitType; +use crate::errors::ChorusError; use crate::errors::ChorusResult; -use crate::instance::Instance; use crate::instance::UserMeta; -use crate::limit::LimitedRequester; +use crate::ratelimiter::ChorusRequest; use crate::types::Snowflake; use crate::types::{Channel, ChannelCreateSchema, Guild, GuildCreateSchema}; @@ -36,11 +32,14 @@ impl Guild { guild_create_schema: GuildCreateSchema, ) -> ChorusResult { let url = format!("{}/guilds/", user.belongs_to.borrow().urls.api); - let request = reqwest::Client::new() - .post(url.clone()) - .bearer_auth(user.token.clone()) - .body(to_string(&guild_create_schema).unwrap()); - deserialize_response::(request, user, crate::api::limits::LimitType::Guild).await + let chorus_request = ChorusRequest { + request: Client::new() + .post(url.clone()) + .bearer_auth(user.token.clone()) + .body(to_string(&guild_create_schema).unwrap()), + limit_type: LimitType::Global, + }; + chorus_request.deserialize_response::(user).await } /// Deletes a guild. @@ -73,10 +72,13 @@ impl Guild { user.belongs_to.borrow().urls.api, guild_id ); - let request = reqwest::Client::new() - .post(url.clone()) - .bearer_auth(user.token.clone()); - handle_request_as_result(request, user, crate::api::limits::LimitType::Guild).await + let chorus_request = ChorusRequest { + request: Client::new() + .post(url.clone()) + .bearer_auth(user.token.clone()), + limit_type: LimitType::Global, + }; + chorus_request.handle_request_as_result(user).await } /// Sends a request to create a new channel in the guild. @@ -97,14 +99,7 @@ impl Guild { user: &mut UserMeta, schema: ChannelCreateSchema, ) -> ChorusResult { - Channel::_create( - &user.token, - self.id, - schema, - &mut user.limits, - &mut user.belongs_to.borrow_mut(), - ) - .await + Channel::create(user, self.id, schema).await } /// Returns a `Result` containing a vector of `Channel` structs if the request was successful, or an `ChorusLibError` if there was an error. @@ -117,20 +112,21 @@ impl Guild { /// * `limits_instance` - A mutable reference to a `Limits` struct containing the instance's rate limits. /// pub async fn channels(&self, user: &mut UserMeta) -> ChorusResult> { - let request = Client::new() - .get(format!( - "{}/guilds/{}/channels/", - user.belongs_to.borrow().urls.api, - self.id - )) - .bearer_auth(user.token()); - let result = handle_request(request, user, crate::api::limits::LimitType::Channel) - .await - .unwrap(); + let chorus_request = ChorusRequest { + request: Client::new() + .get(format!( + "{}/guilds/{}/channels/", + user.belongs_to.borrow().urls.api, + self.id + )) + .bearer_auth(user.token()), + limit_type: LimitType::Channel(self.id), + }; + let result = chorus_request.send_request(user).await?; let stringed_response = match result.text().await { Ok(value) => value, Err(e) => { - return Err(ChorusLibError::InvalidResponseError { + return Err(ChorusError::InvalidResponse { error: e.to_string(), }); } @@ -138,7 +134,7 @@ impl Guild { let _: Vec = match from_str(&stringed_response) { Ok(result) => return Ok(result), Err(e) => { - return Err(ChorusLibError::InvalidResponseError { + return Err(ChorusError::InvalidResponse { error: e.to_string(), }); } @@ -155,35 +151,19 @@ impl Guild { /// * `limits_user` - A mutable reference to a `Limits` struct containing the user's rate limits. /// * `limits_instance` - A mutable reference to a `Limits` struct containing the instance's rate limits. /// - pub async fn get(user: &mut UserMeta, guild_id: Snowflake) -> ChorusResult { - let mut belongs_to = user.belongs_to.borrow_mut(); - Guild::_get(guild_id, &user.token, &mut user.limits, &mut belongs_to).await - } - - /// For internal use. Does the same as the public get method, but does not require a second, mutable - /// borrow of `UserMeta::belongs_to`, when used in conjunction with other methods, which borrow `UserMeta::belongs_to`. - async fn _get( - guild_id: Snowflake, - token: &str, - limits_user: &mut Limits, - instance: &mut Instance, - ) -> ChorusResult { - let request = Client::new() - .get(format!("{}/guilds/{}/", instance.urls.api, guild_id)) - .bearer_auth(token); - let response = match LimitedRequester::send_request( - request, - crate::api::limits::LimitType::Guild, - instance, - limits_user, - ) - .await - { - Ok(response) => response, - Err(e) => return Err(e), + pub async fn get(guild_id: Snowflake, user: &mut UserMeta) -> ChorusResult { + let chorus_request = ChorusRequest { + request: Client::new() + .get(format!( + "{}/guilds/{}/", + user.belongs_to.borrow().urls.api, + guild_id + )) + .bearer_auth(user.token()), + limit_type: LimitType::Guild(guild_id), }; - let guild: Guild = from_str(&response.text().await.unwrap()).unwrap(); - Ok(guild) + let response = chorus_request.deserialize_response::(user).await?; + Ok(response) } } @@ -207,48 +187,17 @@ impl Channel { guild_id: Snowflake, schema: ChannelCreateSchema, ) -> ChorusResult { - let mut belongs_to = user.belongs_to.borrow_mut(); - Channel::_create( - &user.token, - guild_id, - schema, - &mut user.limits, - &mut belongs_to, - ) - .await - } - - async fn _create( - token: &str, - guild_id: Snowflake, - schema: ChannelCreateSchema, - limits_user: &mut Limits, - instance: &mut Instance, - ) -> ChorusResult { - let request = Client::new() - .post(format!( - "{}/guilds/{}/channels/", - instance.urls.api, guild_id - )) - .bearer_auth(token) - .body(to_string(&schema).unwrap()); - let result = match LimitedRequester::send_request( - request, - crate::api::limits::LimitType::Guild, - instance, - limits_user, - ) - .await - { - Ok(result) => result, - Err(e) => return Err(e), + let chorus_request = ChorusRequest { + request: Client::new() + .post(format!( + "{}/guilds/{}/channels/", + user.belongs_to.borrow().urls.api, + guild_id + )) + .bearer_auth(user.token()) + .body(to_string(&schema).unwrap()), + limit_type: LimitType::Guild(guild_id), }; - match from_str::(&result.text().await.unwrap()) { - Ok(object) => Ok(object), - Err(e) => Err(ChorusLibError::RequestErrorError { - url: format!("{}/guilds/{}/channels/", instance.urls.api, guild_id), - error: e.to_string(), - }), - } + chorus_request.deserialize_response::(user).await } } diff --git a/src/api/guilds/member.rs b/src/api/guilds/member.rs index e110cfa..5fa99cd 100644 --- a/src/api/guilds/member.rs +++ b/src/api/guilds/member.rs @@ -1,9 +1,10 @@ use reqwest::Client; use crate::{ - api::{deserialize_response, handle_request_as_result}, + api::LimitType, errors::ChorusResult, instance::UserMeta, + ratelimiter::ChorusRequest, types::{self, Snowflake}, }; @@ -30,13 +31,13 @@ impl types::GuildMember { guild_id, member_id ); - let request = Client::new().get(url).bearer_auth(user.token()); - deserialize_response::( - request, - user, - crate::api::limits::LimitType::Guild, - ) - .await + let chorus_request = ChorusRequest { + request: Client::new().get(url).bearer_auth(user.token()), + limit_type: LimitType::Guild(guild_id), + }; + chorus_request + .deserialize_response::(user) + .await } /// Adds a role to a guild member. @@ -64,8 +65,11 @@ impl types::GuildMember { member_id, role_id ); - let request = Client::new().put(url).bearer_auth(user.token()); - handle_request_as_result(request, user, crate::api::limits::LimitType::Guild).await + let chorus_request = ChorusRequest { + request: Client::new().put(url).bearer_auth(user.token()), + limit_type: LimitType::Guild(guild_id), + }; + chorus_request.handle_request_as_result(user).await } /// Removes a role from a guild member. @@ -85,7 +89,7 @@ impl types::GuildMember { guild_id: Snowflake, member_id: Snowflake, role_id: Snowflake, - ) -> Result<(), crate::errors::ChorusLibError> { + ) -> Result<(), crate::errors::ChorusError> { let url = format!( "{}/guilds/{}/members/{}/roles/{}/", user.belongs_to.borrow().urls.api, @@ -93,7 +97,10 @@ impl types::GuildMember { member_id, role_id ); - let request = Client::new().delete(url).bearer_auth(user.token()); - handle_request_as_result(request, user, crate::api::limits::LimitType::Guild).await + let chorus_request = ChorusRequest { + request: Client::new().delete(url).bearer_auth(user.token()), + limit_type: LimitType::Guild(guild_id), + }; + chorus_request.handle_request_as_result(user).await } } diff --git a/src/api/guilds/roles.rs b/src/api/guilds/roles.rs index 80dddad..1d1bc68 100644 --- a/src/api/guilds/roles.rs +++ b/src/api/guilds/roles.rs @@ -2,9 +2,10 @@ use reqwest::Client; use serde_json::to_string; use crate::{ - api::deserialize_response, - errors::{ChorusLibError, ChorusResult}, + api::LimitType, + errors::{ChorusError, ChorusResult}, instance::UserMeta, + ratelimiter::ChorusRequest, types::{self, RoleCreateModifySchema, RoleObject, Snowflake}, }; @@ -32,14 +33,14 @@ impl types::RoleObject { user.belongs_to.borrow().urls.api, guild_id ); - let request = Client::new().get(url).bearer_auth(user.token()); - let roles = deserialize_response::>( - request, - user, - crate::api::limits::LimitType::Guild, - ) - .await - .unwrap(); + let chorus_request = ChorusRequest { + request: Client::new().get(url).bearer_auth(user.token()), + limit_type: LimitType::Guild(guild_id), + }; + let roles = chorus_request + .deserialize_response::>(user) + .await + .unwrap(); if roles.is_empty() { return Ok(None); } @@ -72,8 +73,13 @@ impl types::RoleObject { guild_id, role_id ); - let request = Client::new().get(url).bearer_auth(user.token()); - deserialize_response(request, user, crate::api::limits::LimitType::Guild).await + let chorus_request = ChorusRequest { + request: Client::new().get(url).bearer_auth(user.token()), + limit_type: LimitType::Guild(guild_id), + }; + chorus_request + .deserialize_response::(user) + .await } /// Creates a new role for a given guild. @@ -102,12 +108,17 @@ impl types::RoleObject { guild_id ); let body = to_string::(&role_create_schema).map_err(|e| { - ChorusLibError::FormCreationError { + ChorusError::FormCreation { error: e.to_string(), } })?; - let request = Client::new().post(url).bearer_auth(user.token()).body(body); - deserialize_response(request, user, crate::api::limits::LimitType::Guild).await + let chorus_request = ChorusRequest { + request: Client::new().post(url).bearer_auth(user.token()).body(body), + limit_type: LimitType::Guild(guild_id), + }; + chorus_request + .deserialize_response::(user) + .await } /// Updates the position of a role in the guild's hierarchy. @@ -135,16 +146,19 @@ impl types::RoleObject { user.belongs_to.borrow().urls.api, guild_id ); - let body = to_string(&role_position_update_schema).map_err(|e| { - ChorusLibError::FormCreationError { + let body = + to_string(&role_position_update_schema).map_err(|e| ChorusError::FormCreation { error: e.to_string(), - } - })?; - let request = Client::new() - .patch(url) - .bearer_auth(user.token()) - .body(body); - deserialize_response::(request, user, crate::api::limits::LimitType::Guild) + })?; + let chorus_request = ChorusRequest { + request: Client::new() + .patch(url) + .bearer_auth(user.token()) + .body(body), + limit_type: LimitType::Guild(guild_id), + }; + chorus_request + .deserialize_response::(user) .await } @@ -177,15 +191,19 @@ impl types::RoleObject { role_id ); let body = to_string::(&role_create_schema).map_err(|e| { - ChorusLibError::FormCreationError { + ChorusError::FormCreation { error: e.to_string(), } })?; - let request = Client::new() - .patch(url) - .bearer_auth(user.token()) - .body(body); - deserialize_response::(request, user, crate::api::limits::LimitType::Guild) + let chorus_request = ChorusRequest { + request: Client::new() + .patch(url) + .bearer_auth(user.token()) + .body(body), + limit_type: LimitType::Guild(guild_id), + }; + chorus_request + .deserialize_response::(user) .await } } diff --git a/src/api/mod.rs b/src/api/mod.rs index 56abb5f..0c02b98 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,12 +1,10 @@ pub use channels::messages::*; -pub use common::*; pub use guilds::*; pub use policies::instance::instance::*; -pub use policies::instance::limits::*; +pub use policies::instance::ratelimits::*; pub mod auth; pub mod channels; -pub mod common; pub mod guilds; pub mod policies; pub mod users; diff --git a/src/api/policies/instance/instance.rs b/src/api/policies/instance/instance.rs index 0ea3699..75f832c 100644 --- a/src/api/policies/instance/instance.rs +++ b/src/api/policies/instance/instance.rs @@ -1,7 +1,6 @@ -use reqwest::Client; use serde_json::from_str; -use crate::errors::{ChorusLibError, ChorusResult}; +use crate::errors::{ChorusError, ChorusResult}; use crate::instance::Instance; use crate::types::GeneralConfiguration; @@ -10,21 +9,21 @@ impl Instance { /// # Errors /// [`ChorusLibError`] - If the request fails. pub async fn general_configuration_schema(&self) -> ChorusResult { - let client = Client::new(); let endpoint_url = self.urls.api.clone() + "/policies/instance/"; - let request = match client.get(&endpoint_url).send().await { + let request = match self.client.get(&endpoint_url).send().await { Ok(result) => result, Err(e) => { - return Err(ChorusLibError::RequestErrorError { + return Err(ChorusError::RequestFailed { url: endpoint_url, - error: e.to_string(), + error: e, }); } }; if !request.status().as_str().starts_with('2') { - return Err(ChorusLibError::ReceivedErrorCodeError { - error_code: request.status().to_string(), + return Err(ChorusError::ReceivedErrorCode { + error_code: request.status().as_u16(), + error: request.text().await.unwrap(), }); } diff --git a/src/api/policies/instance/limits.rs b/src/api/policies/instance/limits.rs deleted file mode 100644 index 3c06d29..0000000 --- a/src/api/policies/instance/limits.rs +++ /dev/null @@ -1,499 +0,0 @@ -pub mod limits { - use std::collections::HashMap; - - use reqwest::Client; - use serde::{Deserialize, Serialize}; - use serde_json::from_str; - - #[derive(Clone, Copy, Eq, Hash, PartialEq, Debug, Default)] - pub enum LimitType { - AuthRegister, - AuthLogin, - AbsoluteMessage, - AbsoluteRegister, - #[default] - Global, - Ip, - Channel, - Error, - Guild, - Webhook, - } - - impl ToString for LimitType { - fn to_string(&self) -> String { - match self { - LimitType::AuthRegister => "AuthRegister".to_string(), - LimitType::AuthLogin => "AuthLogin".to_string(), - LimitType::AbsoluteMessage => "AbsoluteMessage".to_string(), - LimitType::AbsoluteRegister => "AbsoluteRegister".to_string(), - LimitType::Global => "Global".to_string(), - LimitType::Ip => "Ip".to_string(), - LimitType::Channel => "Channel".to_string(), - LimitType::Error => "Error".to_string(), - LimitType::Guild => "Guild".to_string(), - LimitType::Webhook => "Webhook".to_string(), - } - } - } - - #[derive(Debug, Deserialize, Serialize)] - #[allow(non_snake_case)] - pub struct User { - pub maxGuilds: u64, - pub maxUsername: u64, - pub maxFriends: u64, - } - - #[derive(Debug, Deserialize, Serialize)] - #[allow(non_snake_case)] - pub struct Guild { - pub maxRoles: u64, - pub maxEmojis: u64, - pub maxMembers: u64, - pub maxChannels: u64, - pub maxChannelsInCategory: u64, - } - - #[derive(Debug, Deserialize, Serialize)] - #[allow(non_snake_case)] - pub struct Message { - pub maxCharacters: u64, - pub maxTTSCharacters: u64, - pub maxReactions: u64, - pub maxAttachmentSize: u64, - pub maxBulkDelete: u64, - pub maxEmbedDownloadSize: u64, - } - - #[derive(Debug, Deserialize, Serialize)] - #[allow(non_snake_case)] - pub struct Channel { - pub maxPins: u64, - pub maxTopic: u64, - pub maxWebhooks: u64, - } - - #[derive(Debug, Deserialize, Serialize)] - pub struct Rate { - pub enabled: bool, - pub ip: Window, - pub global: Window, - pub error: Window, - pub routes: Routes, - } - - #[derive(Debug, Deserialize, Serialize)] - pub struct Window { - pub count: u64, - pub window: u64, - } - - #[derive(Debug, Deserialize, Serialize)] - pub struct Routes { - pub guild: Window, - pub webhook: Window, - pub channel: Window, - pub auth: AuthRoutes, - } - - #[derive(Debug, Deserialize, Serialize)] - #[allow(non_snake_case)] - pub struct AuthRoutes { - pub login: Window, - pub register: Window, - } - - #[derive(Debug, Deserialize, Serialize)] - #[allow(non_snake_case)] - pub struct AbsoluteRate { - pub register: AbsoluteWindow, - pub sendMessage: AbsoluteWindow, - } - - #[derive(Debug, Deserialize, Serialize)] - pub struct AbsoluteWindow { - pub limit: u64, - pub window: u64, - pub enabled: bool, - } - - #[derive(Debug, Deserialize, Serialize)] - #[allow(non_snake_case)] - pub struct Config { - pub user: User, - pub guild: Guild, - pub message: Message, - pub channel: Channel, - pub rate: Rate, - pub absoluteRate: AbsoluteRate, - } - - #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] - pub struct Limit { - pub bucket: LimitType, - pub limit: u64, - pub remaining: u64, - pub reset: u64, - } - - impl std::fmt::Display for Limit { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "Bucket: {:?}, Limit: {}, Remaining: {}, Reset: {}", - self.bucket, self.limit, self.remaining, self.reset - ) - } - } - - impl Limit { - pub fn add_remaining(&mut self, remaining: i64) { - if remaining < 0 { - if (self.remaining as i64 + remaining) <= 0 { - self.remaining = 0; - return; - } - self.remaining -= remaining.unsigned_abs(); - return; - } - self.remaining += remaining.unsigned_abs(); - } - } - - pub struct LimitsMutRef<'a> { - pub limit_absolute_messages: &'a mut Limit, - pub limit_absolute_register: &'a mut Limit, - pub limit_auth_login: &'a mut Limit, - pub limit_auth_register: &'a mut Limit, - pub limit_ip: &'a mut Limit, - pub limit_global: &'a mut Limit, - pub limit_error: &'a mut Limit, - pub limit_guild: &'a mut Limit, - pub limit_webhook: &'a mut Limit, - pub limit_channel: &'a mut Limit, - } - - impl LimitsMutRef<'_> { - pub fn combine_mut_ref<'a>( - instance_rate_limits: &'a mut Limits, - user_rate_limits: &'a mut Limits, - ) -> LimitsMutRef<'a> { - LimitsMutRef { - limit_absolute_messages: &mut instance_rate_limits.limit_absolute_messages, - limit_absolute_register: &mut instance_rate_limits.limit_absolute_register, - limit_auth_login: &mut instance_rate_limits.limit_auth_login, - limit_auth_register: &mut instance_rate_limits.limit_auth_register, - limit_channel: &mut user_rate_limits.limit_channel, - limit_error: &mut user_rate_limits.limit_error, - limit_global: &mut instance_rate_limits.limit_global, - limit_guild: &mut user_rate_limits.limit_guild, - limit_ip: &mut instance_rate_limits.limit_ip, - limit_webhook: &mut user_rate_limits.limit_webhook, - } - } - - pub fn get_limit_ref(&self, limit_type: &LimitType) -> &Limit { - match limit_type { - LimitType::AbsoluteMessage => self.limit_absolute_messages, - LimitType::AbsoluteRegister => self.limit_absolute_register, - LimitType::AuthLogin => self.limit_auth_login, - LimitType::AuthRegister => self.limit_auth_register, - LimitType::Channel => self.limit_channel, - LimitType::Error => self.limit_error, - LimitType::Global => self.limit_global, - LimitType::Guild => self.limit_guild, - LimitType::Ip => self.limit_ip, - LimitType::Webhook => self.limit_webhook, - } - } - - pub fn get_limit_mut_ref(&mut self, limit_type: &LimitType) -> &mut Limit { - match limit_type { - LimitType::AbsoluteMessage => self.limit_absolute_messages, - LimitType::AbsoluteRegister => self.limit_absolute_register, - LimitType::AuthLogin => self.limit_auth_login, - LimitType::AuthRegister => self.limit_auth_register, - LimitType::Channel => self.limit_channel, - LimitType::Error => self.limit_error, - LimitType::Global => self.limit_global, - LimitType::Guild => self.limit_guild, - LimitType::Ip => self.limit_ip, - LimitType::Webhook => self.limit_webhook, - } - } - } - - #[derive(Debug, Clone, Default)] - pub struct Limits { - pub limit_absolute_messages: Limit, - pub limit_absolute_register: Limit, - pub limit_auth_login: Limit, - pub limit_auth_register: Limit, - pub limit_ip: Limit, - pub limit_global: Limit, - pub limit_error: Limit, - pub limit_guild: Limit, - pub limit_webhook: Limit, - pub limit_channel: Limit, - } - - impl Limits { - pub fn combine(instance_rate_limits: &Limits, user_rate_limits: &Limits) -> Limits { - Limits { - limit_absolute_messages: instance_rate_limits.limit_absolute_messages, - limit_absolute_register: instance_rate_limits.limit_absolute_register, - limit_auth_login: instance_rate_limits.limit_auth_login, - limit_auth_register: instance_rate_limits.limit_auth_register, - limit_channel: user_rate_limits.limit_channel, - limit_error: user_rate_limits.limit_error, - limit_global: instance_rate_limits.limit_global, - limit_guild: user_rate_limits.limit_guild, - limit_ip: instance_rate_limits.limit_ip, - limit_webhook: user_rate_limits.limit_webhook, - } - } - - pub fn get_limit_ref(&self, limit_type: &LimitType) -> &Limit { - match limit_type { - LimitType::AbsoluteMessage => &self.limit_absolute_messages, - LimitType::AbsoluteRegister => &self.limit_absolute_register, - LimitType::AuthLogin => &self.limit_auth_login, - LimitType::AuthRegister => &self.limit_auth_register, - LimitType::Channel => &self.limit_channel, - LimitType::Error => &self.limit_error, - LimitType::Global => &self.limit_global, - LimitType::Guild => &self.limit_guild, - LimitType::Ip => &self.limit_ip, - LimitType::Webhook => &self.limit_webhook, - } - } - - pub fn get_limit_mut_ref(&mut self, limit_type: &LimitType) -> &mut Limit { - match limit_type { - LimitType::AbsoluteMessage => &mut self.limit_absolute_messages, - LimitType::AbsoluteRegister => &mut self.limit_absolute_register, - LimitType::AuthLogin => &mut self.limit_auth_login, - LimitType::AuthRegister => &mut self.limit_auth_register, - LimitType::Channel => &mut self.limit_channel, - LimitType::Error => &mut self.limit_error, - LimitType::Global => &mut self.limit_global, - LimitType::Guild => &mut self.limit_guild, - LimitType::Ip => &mut self.limit_ip, - LimitType::Webhook => &mut self.limit_webhook, - } - } - - pub fn to_hash_map(&self) -> HashMap { - let mut map: HashMap = HashMap::new(); - map.insert(LimitType::AbsoluteMessage, self.limit_absolute_messages); - map.insert(LimitType::AbsoluteRegister, self.limit_absolute_register); - map.insert(LimitType::AuthLogin, self.limit_auth_login); - map.insert(LimitType::AuthRegister, self.limit_auth_register); - map.insert(LimitType::Ip, self.limit_ip); - map.insert(LimitType::Global, self.limit_global); - map.insert(LimitType::Error, self.limit_error); - map.insert(LimitType::Guild, self.limit_guild); - map.insert(LimitType::Webhook, self.limit_webhook); - map.insert(LimitType::Channel, self.limit_channel); - map - } - - pub fn get_as_mut(&mut self) -> &mut Limits { - self - } - - /// check_limits uses the API to get the current request limits of the instance. - /// It returns a `Limits` struct containing all the limits. - /// If the rate limit is disabled, then the limit is set to `u64::MAX`. - /// # Errors - /// This function will panic if the request fails or if the response body cannot be parsed. - /// TODO: Change this to return a Result and handle the errors properly. - pub async fn check_limits(api_url: String) -> Limits { - let client = Client::new(); - let url_parsed = crate::UrlBundle::parse_url(api_url) + "/policies/instance/limits"; - let result = client - .get(url_parsed) - .send() - .await - .unwrap_or_else(|e| panic!("An error occured while performing the request: {}", e)) - .text() - .await - .unwrap_or_else(|e| { - panic!( - "An error occured while parsing the request body string: {}", - e - ) - }); - let config: Config = from_str(&result).unwrap(); - // If config.rate.enabled is false, then add return a Limits struct with all limits set to u64::MAX - let mut limits: Limits; - if !config.rate.enabled { - limits = Limits { - limit_absolute_messages: Limit { - bucket: LimitType::AbsoluteMessage, - limit: u64::MAX, - remaining: u64::MAX, - reset: u64::MAX, - }, - limit_absolute_register: Limit { - bucket: LimitType::AbsoluteRegister, - limit: u64::MAX, - remaining: u64::MAX, - reset: u64::MAX, - }, - limit_auth_login: Limit { - bucket: LimitType::AuthLogin, - limit: u64::MAX, - remaining: u64::MAX, - reset: u64::MAX, - }, - limit_auth_register: Limit { - bucket: LimitType::AuthRegister, - limit: u64::MAX, - remaining: u64::MAX, - reset: u64::MAX, - }, - limit_ip: Limit { - bucket: LimitType::Ip, - limit: u64::MAX, - remaining: u64::MAX, - reset: u64::MAX, - }, - limit_global: Limit { - bucket: LimitType::Global, - limit: u64::MAX, - remaining: u64::MAX, - reset: u64::MAX, - }, - limit_error: Limit { - bucket: LimitType::Error, - limit: u64::MAX, - remaining: u64::MAX, - reset: u64::MAX, - }, - limit_guild: Limit { - bucket: LimitType::Guild, - limit: u64::MAX, - remaining: u64::MAX, - reset: u64::MAX, - }, - limit_webhook: Limit { - bucket: LimitType::Webhook, - limit: u64::MAX, - remaining: u64::MAX, - reset: u64::MAX, - }, - limit_channel: Limit { - bucket: LimitType::Channel, - limit: u64::MAX, - remaining: u64::MAX, - reset: u64::MAX, - }, - }; - } else { - limits = Limits { - limit_absolute_messages: Limit { - bucket: LimitType::AbsoluteMessage, - limit: config.absoluteRate.sendMessage.limit, - remaining: config.absoluteRate.sendMessage.limit, - reset: config.absoluteRate.sendMessage.window, - }, - limit_absolute_register: Limit { - bucket: LimitType::AbsoluteRegister, - limit: config.absoluteRate.register.limit, - remaining: config.absoluteRate.register.limit, - reset: config.absoluteRate.register.window, - }, - limit_auth_login: Limit { - bucket: LimitType::AuthLogin, - limit: config.rate.routes.auth.login.count, - remaining: config.rate.routes.auth.login.count, - reset: config.rate.routes.auth.login.window, - }, - limit_auth_register: Limit { - bucket: LimitType::AuthRegister, - limit: config.rate.routes.auth.register.count, - remaining: config.rate.routes.auth.register.count, - reset: config.rate.routes.auth.register.window, - }, - limit_ip: Limit { - bucket: LimitType::Ip, - limit: config.rate.ip.count, - remaining: config.rate.ip.count, - reset: config.rate.ip.window, - }, - limit_global: Limit { - bucket: LimitType::Global, - limit: config.rate.global.count, - remaining: config.rate.global.count, - reset: config.rate.global.window, - }, - limit_error: Limit { - bucket: LimitType::Error, - limit: config.rate.error.count, - remaining: config.rate.error.count, - reset: config.rate.error.window, - }, - limit_guild: Limit { - bucket: LimitType::Guild, - limit: config.rate.routes.guild.count, - remaining: config.rate.routes.guild.count, - reset: config.rate.routes.guild.window, - }, - limit_webhook: Limit { - bucket: LimitType::Webhook, - limit: config.rate.routes.webhook.count, - remaining: config.rate.routes.webhook.count, - reset: config.rate.routes.webhook.window, - }, - limit_channel: Limit { - bucket: LimitType::Channel, - limit: config.rate.routes.channel.count, - remaining: config.rate.routes.channel.count, - reset: config.rate.routes.channel.window, - }, - }; - } - - if !config.absoluteRate.register.enabled { - limits.limit_absolute_register = Limit { - bucket: LimitType::AbsoluteRegister, - limit: u64::MAX, - remaining: u64::MAX, - reset: u64::MAX, - }; - } - - if !config.absoluteRate.sendMessage.enabled { - limits.limit_absolute_messages = Limit { - bucket: LimitType::AbsoluteMessage, - limit: u64::MAX, - remaining: u64::MAX, - reset: u64::MAX, - }; - } - - limits - } - } -} - -#[cfg(test)] -mod instance_limits { - use crate::api::limits::{Limit, LimitType}; - - #[test] - fn limit_below_zero() { - let mut limit = Limit { - bucket: LimitType::AbsoluteMessage, - limit: 0, - remaining: 1, - reset: 0, - }; - limit.add_remaining(-2); - assert_eq!(0_u64, limit.remaining); - limit.add_remaining(-2123123); - assert_eq!(0_u64, limit.remaining); - } -} diff --git a/src/api/policies/instance/mod.rs b/src/api/policies/instance/mod.rs index 7be9605..0a1f245 100644 --- a/src/api/policies/instance/mod.rs +++ b/src/api/policies/instance/mod.rs @@ -1,5 +1,5 @@ pub use instance::*; -pub use limits::*; +pub use ratelimits::*; pub mod instance; -pub mod limits; +pub mod ratelimits; diff --git a/src/api/policies/instance/ratelimits.rs b/src/api/policies/instance/ratelimits.rs new file mode 100644 index 0000000..125af32 --- /dev/null +++ b/src/api/policies/instance/ratelimits.rs @@ -0,0 +1,35 @@ +use std::hash::Hash; + +use crate::types::Snowflake; + +/// The different types of ratelimits that can be applied to a request. Includes "Baseline"-variants +/// for when the Snowflake is not yet known. +/// See for more information. +#[derive(Clone, Copy, Eq, PartialEq, Debug, Default, Hash)] +pub enum LimitType { + AuthRegister, + AuthLogin, + #[default] + Global, + Ip, + Channel(Snowflake), + ChannelBaseline, + Error, + Guild(Snowflake), + GuildBaseline, + Webhook(Snowflake), + WebhookBaseline, +} + +/// A struct that represents the current ratelimits, either instance-wide or user-wide. +/// Unlike [`RateLimits`], this struct shows the current ratelimits, not the rate limit +/// configuration for the instance. +/// See for more information. +#[derive(Debug, Clone)] +pub struct Limit { + pub bucket: LimitType, + pub limit: u64, + pub remaining: u64, + pub reset: u64, + pub window: u64, +} diff --git a/src/api/policies/mod.rs b/src/api/policies/mod.rs index 3e25d8c..d0c29f1 100644 --- a/src/api/policies/mod.rs +++ b/src/api/policies/mod.rs @@ -1,3 +1,3 @@ -pub use instance::limits::*; +pub use instance::ratelimits::*; pub mod instance; diff --git a/src/api/users/relationships.rs b/src/api/users/relationships.rs index 36cabd2..39c75d8 100644 --- a/src/api/users/relationships.rs +++ b/src/api/users/relationships.rs @@ -2,9 +2,10 @@ use reqwest::Client; use serde_json::to_string; use crate::{ - api::{deserialize_response, handle_request_as_result}, + api::LimitType, errors::ChorusResult, instance::UserMeta, + ratelimiter::ChorusRequest, types::{self, CreateUserRelationshipSchema, RelationshipType, Snowflake}, }; @@ -26,13 +27,13 @@ impl UserMeta { self.belongs_to.borrow().urls.api, user_id ); - let request = Client::new().get(url).bearer_auth(self.token()); - deserialize_response::>( - request, - self, - crate::api::limits::LimitType::Global, - ) - .await + let chorus_request = ChorusRequest { + request: Client::new().get(url).bearer_auth(self.token()), + limit_type: LimitType::Global, + }; + chorus_request + .deserialize_response::>(self) + .await } /// Retrieves the authenticated user's relationships. @@ -44,13 +45,13 @@ impl UserMeta { "{}/users/@me/relationships/", self.belongs_to.borrow().urls.api ); - let request = Client::new().get(url).bearer_auth(self.token()); - deserialize_response::>( - request, - self, - crate::api::limits::LimitType::Global, - ) - .await + let chorus_request = ChorusRequest { + request: Client::new().get(url).bearer_auth(self.token()), + limit_type: LimitType::Global, + }; + chorus_request + .deserialize_response::>(self) + .await } /// Sends a friend request to a user. @@ -70,8 +71,11 @@ impl UserMeta { self.belongs_to.borrow().urls.api ); let body = to_string(&schema).unwrap(); - let request = Client::new().post(url).bearer_auth(self.token()).body(body); - handle_request_as_result(request, self, crate::api::limits::LimitType::Global).await + let chorus_request = ChorusRequest { + request: Client::new().post(url).bearer_auth(self.token()).body(body), + limit_type: LimitType::Global, + }; + chorus_request.handle_request_as_result(self).await } /// Modifies the relationship between the authenticated user and the specified user. @@ -96,10 +100,13 @@ impl UserMeta { let api_url = self.belongs_to.borrow().urls.api.clone(); match relationship_type { RelationshipType::None => { - let request = Client::new() - .delete(format!("{}/users/@me/relationships/{}/", api_url, user_id)) - .bearer_auth(self.token()); - handle_request_as_result(request, self, crate::api::limits::LimitType::Global).await + let chorus_request = ChorusRequest { + request: Client::new() + .delete(format!("{}/users/@me/relationships/{}/", api_url, user_id)) + .bearer_auth(self.token()), + limit_type: LimitType::Global, + }; + chorus_request.handle_request_as_result(self).await } RelationshipType::Friends | RelationshipType::Incoming | RelationshipType::Outgoing => { let body = CreateUserRelationshipSchema { @@ -107,11 +114,14 @@ impl UserMeta { from_friend_suggestion: None, friend_token: None, }; - let request = Client::new() - .put(format!("{}/users/@me/relationships/{}/", api_url, user_id)) - .bearer_auth(self.token()) - .body(to_string(&body).unwrap()); - handle_request_as_result(request, self, crate::api::limits::LimitType::Global).await + let chorus_request = ChorusRequest { + request: Client::new() + .put(format!("{}/users/@me/relationships/{}/", api_url, user_id)) + .bearer_auth(self.token()) + .body(to_string(&body).unwrap()), + limit_type: LimitType::Global, + }; + chorus_request.handle_request_as_result(self).await } RelationshipType::Blocked => { let body = CreateUserRelationshipSchema { @@ -119,11 +129,14 @@ impl UserMeta { from_friend_suggestion: None, friend_token: None, }; - let request = Client::new() - .put(format!("{}/users/@me/relationships/{}/", api_url, user_id)) - .bearer_auth(self.token()) - .body(to_string(&body).unwrap()); - handle_request_as_result(request, self, crate::api::limits::LimitType::Global).await + let chorus_request = ChorusRequest { + request: Client::new() + .put(format!("{}/users/@me/relationships/{}/", api_url, user_id)) + .bearer_auth(self.token()) + .body(to_string(&body).unwrap()), + limit_type: LimitType::Global, + }; + chorus_request.handle_request_as_result(self).await } RelationshipType::Suggestion | RelationshipType::Implicit => Ok(()), } @@ -143,7 +156,10 @@ impl UserMeta { self.belongs_to.borrow().urls.api, user_id ); - let request = Client::new().delete(url).bearer_auth(self.token()); - handle_request_as_result(request, self, crate::api::limits::LimitType::Global).await + let chorus_request = ChorusRequest { + request: Client::new().delete(url).bearer_auth(self.token()), + limit_type: LimitType::Global, + }; + chorus_request.handle_request_as_result(self).await } } diff --git a/src/api/users/users.rs b/src/api/users/users.rs index cd777dc..bd46a36 100644 --- a/src/api/users/users.rs +++ b/src/api/users/users.rs @@ -1,11 +1,13 @@ +use std::{cell::RefCell, rc::Rc}; + use reqwest::Client; use serde_json::to_string; use crate::{ - api::{deserialize_response, handle_request_as_result}, - errors::{ChorusLibError, ChorusResult}, + api::LimitType, + errors::{ChorusError, ChorusResult}, instance::{Instance, UserMeta}, - limit::LimitedRequester, + ratelimiter::ChorusRequest, types::{User, UserModifySchema, UserSettings}, }; @@ -48,16 +50,20 @@ impl UserMeta { || modify_schema.email.is_some() || modify_schema.code.is_some() { - return Err(ChorusLibError::PasswordRequiredError); + return Err(ChorusError::PasswordRequired); } let request = Client::new() .patch(format!("{}/users/@me/", self.belongs_to.borrow().urls.api)) .body(to_string(&modify_schema).unwrap()) .bearer_auth(self.token()); - let user_updated = - deserialize_response::(request, self, crate::api::limits::LimitType::Ip) - .await - .unwrap(); + let chorus_request = ChorusRequest { + request, + limit_type: LimitType::default(), + }; + let user_updated = chorus_request + .deserialize_response::(self) + .await + .unwrap(); let _ = std::mem::replace(&mut self.object, user_updated.clone()); Ok(user_updated) } @@ -78,43 +84,28 @@ impl UserMeta { self.belongs_to.borrow().urls.api )) .bearer_auth(self.token()); - handle_request_as_result(request, &mut self, crate::api::limits::LimitType::Ip).await + let chorus_request = ChorusRequest { + request, + limit_type: LimitType::default(), + }; + chorus_request.handle_request_as_result(&mut self).await } } impl User { pub async fn get(user: &mut UserMeta, id: Option<&String>) -> ChorusResult { - let mut belongs_to = user.belongs_to.borrow_mut(); - User::_get( - &user.token(), - &format!("{}", belongs_to.urls.api), - &mut belongs_to, - id, - ) - .await - } - - async fn _get( - token: &str, - url_api: &str, - instance: &mut Instance, - id: Option<&String>, - ) -> ChorusResult { + let url_api = user.belongs_to.borrow().urls.api.clone(); let url = if id.is_none() { format!("{}/users/@me/", url_api) } else { format!("{}/users/{}", url_api, id.unwrap()) }; - let request = reqwest::Client::new().get(url).bearer_auth(token); - let mut cloned_limits = instance.limits.clone(); - match LimitedRequester::send_request( + let request = reqwest::Client::new().get(url).bearer_auth(user.token()); + let chorus_request = ChorusRequest { request, - crate::api::limits::LimitType::Ip, - instance, - &mut cloned_limits, - ) - .await - { + limit_type: LimitType::Global, + }; + match chorus_request.send_request(user).await { Ok(result) => { let result_text = result.text().await.unwrap(); Ok(serde_json::from_str::(&result_text).unwrap()) @@ -131,18 +122,20 @@ impl User { let request: reqwest::RequestBuilder = Client::new() .get(format!("{}/users/@me/settings/", url_api)) .bearer_auth(token); - let mut cloned_limits = instance.limits.clone(); - match LimitedRequester::send_request( + let mut user = UserMeta::shell(Rc::new(RefCell::new(instance.clone())), token.clone()); + let chorus_request = ChorusRequest { request, - crate::api::limits::LimitType::Ip, - instance, - &mut cloned_limits, - ) - .await - { + limit_type: LimitType::Global, + }; + let result = match chorus_request.send_request(&mut user).await { Ok(result) => Ok(serde_json::from_str(&result.text().await.unwrap()).unwrap()), Err(e) => Err(e), + }; + if instance.limits_information.is_some() { + instance.limits_information.as_mut().unwrap().ratelimits = + user.belongs_to.borrow().clone_limits_if_some().unwrap(); } + result } } @@ -158,6 +151,12 @@ impl Instance { This function is a wrapper around [`User::get`]. */ pub async fn get_user(&mut self, token: String, id: Option<&String>) -> ChorusResult { - User::_get(&token, &self.urls.api.clone(), self, id).await + let mut user = UserMeta::shell(Rc::new(RefCell::new(self.clone())), token); + let result = User::get(&mut user, id).await; + if self.limits_information.is_some() { + self.limits_information.as_mut().unwrap().ratelimits = + user.belongs_to.borrow().clone_limits_if_some().unwrap(); + } + result } } diff --git a/src/errors.rs b/src/errors.rs index d8bda9c..bf08772 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,39 +1,50 @@ use custom_error::custom_error; +use reqwest::Error; custom_error! { #[derive(PartialEq, Eq)] - pub FieldFormatError - PasswordError = "Password must be between 1 and 72 characters.", - UsernameError = "Username must be between 2 and 32 characters.", - ConsentError = "Consent must be 'true' to register.", - EmailError = "The provided email address is in an invalid format.", + pub RegistrationError + Consent = "Consent must be 'true' to register.", } -pub type ChorusResult = std::result::Result; +pub type ChorusResult = std::result::Result; custom_error! { - #[derive(PartialEq, Eq)] - pub ChorusLibError + pub ChorusError + /// Server did not respond. NoResponse = "Did not receive a response from the Server.", - RequestErrorError{url:String, error:String} = "An error occured while trying to GET from {url}: {error}", - ReceivedErrorCodeError{error_code:String} = "Received the following error code while requesting from the route: {error_code}", - CantGetInfoError{error:String} = "Something seems to be wrong with the instance. Cannot get information about the instance: {error}", - InvalidFormBodyError{error_type: String, error:String} = "The server responded with: {error_type}: {error}", + /// Reqwest returned an Error instead of a Response object. + RequestFailed{url:String, error: Error} = "An error occured while trying to GET from {url}: {error}", + /// Response received, however, it was not of the successful responses type. Used when no other, special case applies. + ReceivedErrorCode{error_code: u16, error: String} = "Received the following error code while requesting from the route: {error_code}", + /// Used when there is likely something wrong with the instance, the request was directed to. + CantGetInformation{error:String} = "Something seems to be wrong with the instance. Cannot get information about the instance: {error}", + /// The requests form body was malformed/invalid. + InvalidFormBody{error_type: String, error:String} = "The server responded with: {error_type}: {error}", + /// The request has not been processed by the server due to a relevant rate limit bucket being exhausted. RateLimited{bucket:String} = "Ratelimited on Bucket {bucket}", - MultipartCreationError{error: String} = "Got an error whilst creating the form: {error}", - FormCreationError{error: String} = "Got an error whilst creating the form: {error}", + /// The multipart form could not be created. + MultipartCreation{error: String} = "Got an error whilst creating the form: {error}", + /// The regular form could not be created. + FormCreation{error: String} = "Got an error whilst creating the form: {error}", + /// The token is invalid. TokenExpired = "Token expired, invalid or not found.", + /// No permission NoPermission = "You do not have the permissions needed to perform this action.", + /// Resource not found NotFound{error: String} = "The provided resource hasn't been found: {error}", - PasswordRequiredError = "You need to provide your current password to authenticate for this action.", - InvalidResponseError{error: String} = "The response is malformed and cannot be processed. Error: {error}", - InvalidArgumentsError{error: String} = "Invalid arguments were provided. Error: {error}" + /// Used when you, for example, try to change your spacebar account password without providing your old password for verification. + PasswordRequired = "You need to provide your current password to authenticate for this action.", + /// Malformed or unexpected response. + InvalidResponse{error: String} = "The response is malformed and cannot be processed. Error: {error}", + /// Invalid, insufficient or too many arguments provided. + InvalidArguments{error: String} = "Invalid arguments were provided. Error: {error}" } custom_error! { #[derive(PartialEq, Eq)] pub ObserverError - AlreadySubscribedError = "Each event can only be subscribed to once." + AlreadySubscribed = "Each event can only be subscribed to once." } custom_error! { @@ -45,25 +56,25 @@ custom_error! { #[derive(PartialEq, Eq)] pub GatewayError // Errors we have received from the gateway - UnknownError = "We're not sure what went wrong. Try reconnecting?", - UnknownOpcodeError = "You sent an invalid Gateway opcode or an invalid payload for an opcode", - DecodeError = "Gateway server couldn't decode payload", - NotAuthenticatedError = "You sent a payload prior to identifying", - AuthenticationFailedError = "The account token sent with your identify payload is invalid", - AlreadyAuthenticatedError = "You've already identified, no need to reauthenticate", - InvalidSequenceNumberError = "The sequence number sent when resuming the session was invalid. Reconnect and start a new session", - RateLimitedError = "You are being rate limited!", - SessionTimedOutError = "Your session timed out. Reconnect and start a new one", - InvalidShardError = "You sent us an invalid shard when identifying", - ShardingRequiredError = "The session would have handled too many guilds - you are required to shard your connection in order to connect", - InvalidAPIVersionError = "You sent an invalid Gateway version", - InvalidIntentsError = "You sent an invalid intent", - DisallowedIntentsError = "You sent a disallowed intent. You may have tried to specify an intent that you have not enabled or are not approved for", + Unknown = "We're not sure what went wrong. Try reconnecting?", + UnknownOpcode = "You sent an invalid Gateway opcode or an invalid payload for an opcode", + Decode = "Gateway server couldn't decode payload", + NotAuthenticated = "You sent a payload prior to identifying", + AuthenticationFailed = "The account token sent with your identify payload is invalid", + AlreadyAuthenticated = "You've already identified, no need to reauthenticate", + InvalidSequenceNumber = "The sequence number sent when resuming the session was invalid. Reconnect and start a new session", + RateLimited = "You are being rate limited!", + SessionTimedOut = "Your session timed out. Reconnect and start a new one", + InvalidShard = "You sent us an invalid shard when identifying", + ShardingRequired = "The session would have handled too many guilds - you are required to shard your connection in order to connect", + InvalidAPIVersion = "You sent an invalid Gateway version", + InvalidIntents = "You sent an invalid intent", + DisallowedIntents = "You sent a disallowed intent. You may have tried to specify an intent that you have not enabled or are not approved for", // Errors when initiating a gateway connection - CannotConnectError{error: String} = "Cannot connect due to a tungstenite error: {error}", - NonHelloOnInitiateError{opcode: u8} = "Received non hello on initial gateway connection ({opcode}), something is definitely wrong", + CannotConnect{error: String} = "Cannot connect due to a tungstenite error: {error}", + NonHelloOnInitiate{opcode: u8} = "Received non hello on initial gateway connection ({opcode}), something is definitely wrong", // Other misc errors - UnexpectedOpcodeReceivedError{opcode: u8} = "Received an opcode we weren't expecting to receive: {opcode}", + UnexpectedOpcodeReceived{opcode: u8} = "Received an opcode we weren't expecting to receive: {opcode}", } diff --git a/src/gateway.rs b/src/gateway.rs index 87e1589..ed96aac 100644 --- a/src/gateway.rs +++ b/src/gateway.rs @@ -8,7 +8,7 @@ use futures_util::stream::SplitSink; use futures_util::stream::SplitStream; use futures_util::SinkExt; use futures_util::StreamExt; -use log::{debug, info, trace, warn}; +use log::{info, trace, warn}; use native_tls::TlsConnector; use tokio::net::TcpStream; use tokio::sync::mpsc::error::TryRecvError; @@ -95,25 +95,21 @@ impl GatewayMessage { let processed_content = content.to_lowercase().replace('.', ""); match processed_content.as_str() { - "unknown error" | "4000" => Some(GatewayError::UnknownError), - "unknown opcode" | "4001" => Some(GatewayError::UnknownOpcodeError), - "decode error" | "error while decoding payload" | "4002" => { - Some(GatewayError::DecodeError) - } - "not authenticated" | "4003" => Some(GatewayError::NotAuthenticatedError), - "authentication failed" | "4004" => Some(GatewayError::AuthenticationFailedError), - "already authenticated" | "4005" => Some(GatewayError::AlreadyAuthenticatedError), - "invalid seq" | "4007" => Some(GatewayError::InvalidSequenceNumberError), - "rate limited" | "4008" => Some(GatewayError::RateLimitedError), - "session timed out" | "4009" => Some(GatewayError::SessionTimedOutError), - "invalid shard" | "4010" => Some(GatewayError::InvalidShardError), - "sharding required" | "4011" => Some(GatewayError::ShardingRequiredError), - "invalid api version" | "4012" => Some(GatewayError::InvalidAPIVersionError), - "invalid intent(s)" | "invalid intent" | "4013" => { - Some(GatewayError::InvalidIntentsError) - } + "unknown error" | "4000" => Some(GatewayError::Unknown), + "unknown opcode" | "4001" => Some(GatewayError::UnknownOpcode), + "decode error" | "error while decoding payload" | "4002" => Some(GatewayError::Decode), + "not authenticated" | "4003" => Some(GatewayError::NotAuthenticated), + "authentication failed" | "4004" => Some(GatewayError::AuthenticationFailed), + "already authenticated" | "4005" => Some(GatewayError::AlreadyAuthenticated), + "invalid seq" | "4007" => Some(GatewayError::InvalidSequenceNumber), + "rate limited" | "4008" => Some(GatewayError::RateLimited), + "session timed out" | "4009" => Some(GatewayError::SessionTimedOut), + "invalid shard" | "4010" => Some(GatewayError::InvalidShard), + "sharding required" | "4011" => Some(GatewayError::ShardingRequired), + "invalid api version" | "4012" => Some(GatewayError::InvalidAPIVersion), + "invalid intent(s)" | "invalid intent" | "4013" => Some(GatewayError::InvalidIntents), "disallowed intent(s)" | "disallowed intents" | "4014" => { - Some(GatewayError::DisallowedIntentsError) + Some(GatewayError::DisallowedIntents) } _ => None, } @@ -294,7 +290,7 @@ impl Gateway { { Ok(websocket_stream) => websocket_stream, Err(e) => { - return Err(GatewayError::CannotConnectError { + return Err(GatewayError::CannotConnect { error: e.to_string(), }) } @@ -314,7 +310,7 @@ impl Gateway { serde_json::from_str(msg.to_text().unwrap()).unwrap(); if gateway_payload.op_code != GATEWAY_HELLO { - return Err(GatewayError::NonHelloOnInitiateError { + return Err(GatewayError::NonHelloOnInitiate { opcode: gateway_payload.op_code, }); } @@ -1493,7 +1489,7 @@ impl Gateway { | GATEWAY_REQUEST_GUILD_MEMBERS | GATEWAY_CALL_SYNC | GATEWAY_LAZY_REQUEST => { - let error = GatewayError::UnexpectedOpcodeReceivedError { + let error = GatewayError::UnexpectedOpcodeReceived { opcode: gateway_payload.op_code, }; Err::<(), GatewayError>(error).unwrap(); @@ -1521,6 +1517,7 @@ impl Gateway { } /// Handles sending heartbeats to the gateway in another thread +#[allow(dead_code)] // FIXME: Remove this, once HeartbeatHandler is used struct HeartbeatHandler { /// The heartbeat interval in milliseconds pub heartbeat_interval: u128, diff --git a/src/instance.rs b/src/instance.rs index 2477217..15c513c 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -1,26 +1,36 @@ use std::cell::RefCell; +use std::collections::HashMap; use std::fmt; use std::rc::Rc; use reqwest::Client; use serde::{Deserialize, Serialize}; -use crate::api::limits::Limits; -use crate::errors::{ChorusLibError, ChorusResult, FieldFormatError}; +use crate::api::{Limit, LimitType}; +use crate::errors::ChorusResult; +use crate::ratelimiter::ChorusRequest; +use crate::types::types::subconfigs::limits::rates::RateLimits; use crate::types::{GeneralConfiguration, User, UserSettings}; use crate::UrlBundle; #[derive(Debug, Clone)] /** 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, pub instance_info: GeneralConfiguration, - pub limits: Limits, + pub limits_information: Option, pub client: Client, } +#[derive(Debug, Clone)] +pub struct LimitsInformation { + pub ratelimits: HashMap, + pub configuration: RateLimits, +} + impl Instance { /// Creates a new [`Instance`]. /// # Arguments @@ -28,24 +38,43 @@ impl Instance { /// * `requester` - The [`LimitedRequester`] that will be used to make requests to the Spacebar server. /// # Errors /// * [`InstanceError`] - If the instance cannot be created. - pub async fn new(urls: UrlBundle) -> ChorusResult { + pub async fn new(urls: UrlBundle, limited: bool) -> ChorusResult { + let limits_information; + if limited { + let limits_configuration = + Some(ChorusRequest::get_limits_config(&urls.api).await?.rate); + let limits = Some(ChorusRequest::limits_config_to_hashmap( + limits_configuration.as_ref().unwrap(), + )); + limits_information = Some(LimitsInformation { + ratelimits: limits.unwrap(), + configuration: limits_configuration.unwrap(), + }); + } else { + limits_information = None; + } let mut instance = Instance { urls: urls.clone(), // Will be overwritten in the next step instance_info: GeneralConfiguration::default(), - limits: Limits::check_limits(urls.api).await, + limits_information, client: Client::new(), }; instance.instance_info = match instance.general_configuration_schema().await { Ok(schema) => schema, Err(e) => { - return Err(ChorusLibError::CantGetInfoError { - error: e.to_string(), - }); + log::warn!("Could not get instance configuration schema: {}", e); + GeneralConfiguration::default() } }; 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 + } } #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -59,30 +88,11 @@ impl fmt::Display for Token { } } -#[derive(Debug, PartialEq, Eq)] -pub struct Username { - pub username: String, -} - -impl Username { - /// Creates a new [`Username`]. - /// # Arguments - /// * `username` - The username that will be used to create the [`Username`]. - /// # Errors - /// * [`UsernameFormatError`] - If the username is not between 2 and 32 characters. - pub fn new(username: String) -> Result { - if username.len() < 2 || username.len() > 32 { - return Err(FieldFormatError::UsernameError); - } - Ok(Username { username }) - } -} - #[derive(Debug)] pub struct UserMeta { pub belongs_to: Rc>, pub token: String, - pub limits: Limits, + pub limits: Option>, pub settings: UserSettings, pub object: User, } @@ -99,7 +109,7 @@ impl UserMeta { pub fn new( belongs_to: Rc>, token: String, - limits: Limits, + limits: Option>, settings: UserSettings, object: User, ) -> UserMeta { @@ -111,4 +121,24 @@ impl UserMeta { object, } } + + /// Creates a new 'shell' of a user. The user does not exist as an object, and exists so that you have + /// a UserMeta object to make Rate Limited requests with. This is useful in scenarios like + /// registering or logging in to the Instance, where you do not yet have a User object, but still + /// need to make a RateLimited request. + pub(crate) fn shell(instance: Rc>, token: String) -> UserMeta { + let settings = UserSettings::default(); + let object = User::default(); + UserMeta { + belongs_to: instance.clone(), + token, + limits: instance + .borrow() + .limits_information + .as_ref() + .map(|info| info.ratelimits.clone()), + settings, + object, + } + } } diff --git a/src/lib.rs b/src/lib.rs index bfda613..77425b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ pub mod gateway; #[cfg(feature = "client")] pub mod instance; #[cfg(feature = "client")] -pub mod limit; +pub mod ratelimiter; pub mod types; #[cfg(feature = "client")] pub mod voice; diff --git a/src/limit.rs b/src/limit.rs deleted file mode 100644 index 4415e0c..0000000 --- a/src/limit.rs +++ /dev/null @@ -1,304 +0,0 @@ -use reqwest::{RequestBuilder, Response}; - -use crate::{ - api::limits::{Limit, LimitType, Limits, LimitsMutRef}, - errors::{ChorusLibError, ChorusResult}, - instance::Instance, -}; - -#[derive(Debug)] -pub struct LimitedRequester; - -impl LimitedRequester { - /// Checks if a request can be sent without hitting API rate limits and sends it, if true. - /// Will automatically update the rate limits of the LimitedRequester the request has been - /// sent with. - /// - /// # Arguments - /// - /// * `request`: A `RequestBuilder` that contains a request ready to be sent. Unfinished or - /// invalid requests will result in the method panicing. - /// * `limit_type`: Because this library does not yet implement a way to check for which rate - /// limit will be used when the request gets send, you will have to specify this manually using - /// a `LimitType` enum. - /// - /// # Returns - /// - /// * `Response`: The `Response` gotten from sending the request to the server. This will be - /// returned if the Request was built and send successfully. Is wrapped in an `Option`. - /// * `None`: `None` will be returned if the rate limit has been hit, and the request could - /// therefore not have been sent. - /// - /// # Errors - /// - /// This method will error if: - /// - /// * The request does not return a success status code (200-299) - /// * The supplied `RequestBuilder` contains invalid or incomplete information - /// * There has been an error with processing (unwrapping) the `Response` - /// * The call to `update_limits` yielded errors. Read the methods' Errors section for more - /// information. - pub async fn send_request( - request: RequestBuilder, - limit_type: LimitType, - instance: &mut Instance, - user_rate_limits: &mut Limits, - ) -> ChorusResult { - if LimitedRequester::can_send_request(limit_type, &instance.limits, user_rate_limits) { - let built_request = match request.build() { - Ok(request) => request, - Err(e) => { - return Err(ChorusLibError::RequestErrorError { - url: "".to_string(), - error: e.to_string(), - }); - } - }; - let result = instance.client.execute(built_request).await; - let response = match result { - Ok(is_response) => is_response, - Err(e) => { - return Err(ChorusLibError::ReceivedErrorCodeError { - error_code: e.to_string(), - }); - } - }; - LimitedRequester::update_limits( - &response, - limit_type, - &mut instance.limits, - user_rate_limits, - ); - if !response.status().is_success() { - match response.status().as_u16() { - 401 => Err(ChorusLibError::TokenExpired), - 403 => Err(ChorusLibError::TokenExpired), - _ => Err(ChorusLibError::ReceivedErrorCodeError { - error_code: response.status().as_str().to_string(), - }), - } - } else { - Ok(response) - } - } else { - Err(ChorusLibError::RateLimited { - bucket: limit_type.to_string(), - }) - } - } - - fn update_limit_entry(entry: &mut Limit, reset: u64, remaining: u64, limit: u64) { - if reset != entry.reset { - entry.reset = reset; - entry.remaining = limit; - entry.limit = limit; - } else { - entry.remaining = remaining; - entry.limit = limit; - } - } - - fn can_send_request( - limit_type: LimitType, - instance_rate_limits: &Limits, - user_rate_limits: &Limits, - ) -> bool { - // Check if all of the limits in this vec have at least one remaining request - - let rate_limits = Limits::combine(instance_rate_limits, user_rate_limits); - - let constant_limits: Vec<&LimitType> = [ - &LimitType::Error, - &LimitType::Global, - &LimitType::Ip, - &limit_type, - ] - .to_vec(); - for limit in constant_limits.iter() { - match rate_limits.to_hash_map().get(limit) { - Some(limit) => { - if limit.remaining == 0 { - return false; - } - // AbsoluteRegister and AuthRegister can cancel each other out. - if limit.bucket == LimitType::AbsoluteRegister - && rate_limits - .to_hash_map() - .get(&LimitType::AuthRegister) - .unwrap() - .remaining - == 0 - { - return false; - } - if limit.bucket == LimitType::AuthRegister - && rate_limits - .to_hash_map() - .get(&LimitType::AbsoluteRegister) - .unwrap() - .remaining - == 0 - { - return false; - } - } - None => return false, - } - } - true - } - - fn update_limits( - response: &Response, - limit_type: LimitType, - instance_rate_limits: &mut Limits, - user_rate_limits: &mut Limits, - ) { - let mut rate_limits = LimitsMutRef::combine_mut_ref(instance_rate_limits, user_rate_limits); - - let remaining = match response.headers().get("X-RateLimit-Remaining") { - Some(remaining) => remaining.to_str().unwrap().parse::().unwrap(), - None => rate_limits.get_limit_mut_ref(&limit_type).remaining - 1, - }; - let limit = match response.headers().get("X-RateLimit-Limit") { - Some(limit) => limit.to_str().unwrap().parse::().unwrap(), - None => rate_limits.get_limit_mut_ref(&limit_type).limit, - }; - let reset = match response.headers().get("X-RateLimit-Reset") { - Some(reset) => reset.to_str().unwrap().parse::().unwrap(), - None => rate_limits.get_limit_mut_ref(&limit_type).reset, - }; - - let status = response.status(); - let status_str = status.as_str(); - - if status_str.starts_with('4') { - rate_limits - .get_limit_mut_ref(&LimitType::Error) - .add_remaining(-1); - } - - rate_limits - .get_limit_mut_ref(&LimitType::Global) - .add_remaining(-1); - - rate_limits - .get_limit_mut_ref(&LimitType::Ip) - .add_remaining(-1); - - match limit_type { - LimitType::Error => { - let entry = rate_limits.get_limit_mut_ref(&LimitType::Error); - LimitedRequester::update_limit_entry(entry, reset, remaining, limit); - } - LimitType::Global => { - let entry = rate_limits.get_limit_mut_ref(&LimitType::Global); - LimitedRequester::update_limit_entry(entry, reset, remaining, limit); - } - LimitType::Ip => { - let entry = rate_limits.get_limit_mut_ref(&LimitType::Ip); - LimitedRequester::update_limit_entry(entry, reset, remaining, limit); - } - LimitType::AuthLogin => { - let entry = rate_limits.get_limit_mut_ref(&LimitType::AuthLogin); - LimitedRequester::update_limit_entry(entry, reset, remaining, limit); - } - LimitType::AbsoluteRegister => { - let entry = rate_limits.get_limit_mut_ref(&LimitType::AbsoluteRegister); - LimitedRequester::update_limit_entry(entry, reset, remaining, limit); - // AbsoluteRegister and AuthRegister both need to be updated, if a Register event - // happens. - rate_limits - .get_limit_mut_ref(&LimitType::AuthRegister) - .remaining -= 1; - } - LimitType::AuthRegister => { - let entry = rate_limits.get_limit_mut_ref(&LimitType::AuthRegister); - LimitedRequester::update_limit_entry(entry, reset, remaining, limit); - // AbsoluteRegister and AuthRegister both need to be updated, if a Register event - // happens. - rate_limits - .get_limit_mut_ref(&LimitType::AbsoluteRegister) - .remaining -= 1; - } - LimitType::AbsoluteMessage => { - let entry = rate_limits.get_limit_mut_ref(&LimitType::AbsoluteMessage); - LimitedRequester::update_limit_entry(entry, reset, remaining, limit); - } - LimitType::Channel => { - let entry = rate_limits.get_limit_mut_ref(&LimitType::Channel); - LimitedRequester::update_limit_entry(entry, reset, remaining, limit); - } - LimitType::Guild => { - let entry = rate_limits.get_limit_mut_ref(&LimitType::Guild); - LimitedRequester::update_limit_entry(entry, reset, remaining, limit); - } - LimitType::Webhook => { - let entry = rate_limits.get_limit_mut_ref(&LimitType::Webhook); - LimitedRequester::update_limit_entry(entry, reset, remaining, limit); - } - } - } -} - -#[cfg(test)] -mod rate_limit { - use serde_json::from_str; - - use crate::{api::limits::Config, UrlBundle}; - - use super::*; - - #[tokio::test] - async fn run_into_limit() { - let urls = UrlBundle::new( - String::from("http://localhost:3001/api/"), - String::from("wss://localhost:3001/"), - String::from("http://localhost:3001/cdn"), - ); - let mut request: Option> = None; - let mut instance = Instance::new(urls.clone()).await.unwrap(); - let mut user_rate_limits = Limits::check_limits(urls.api.clone()).await; - - for _ in 0..=50 { - let request_path = urls.api.clone() + "/some/random/nonexisting/path"; - let request_builder = instance.client.get(request_path); - request = Some( - LimitedRequester::send_request( - request_builder, - LimitType::Channel, - &mut instance, - &mut user_rate_limits, - ) - .await, - ); - } - assert!(matches!(request, Some(Err(_)))); - } - - #[tokio::test] - async fn test_send_request() { - let urls = UrlBundle::new( - String::from("http://localhost:3001/api/"), - String::from("wss://localhost:3001/"), - String::from("http://localhost:3001/cdn"), - ); - let mut instance = Instance::new(urls.clone()).await.unwrap(); - let mut user_rate_limits = Limits::check_limits(urls.api.clone()).await; - let _requester = LimitedRequester; - let request_path = urls.api.clone() + "/policies/instance/limits"; - let request_builder = instance.client.get(request_path); - let request = LimitedRequester::send_request( - request_builder, - LimitType::Channel, - &mut instance, - &mut user_rate_limits, - ) - .await; - let result = match request { - Ok(result) => result, - Err(_) => panic!("Request failed"), - }; - let _config: Config = from_str(result.text().await.unwrap().as_str()).unwrap(); - } -} diff --git a/src/ratelimiter.rs b/src/ratelimiter.rs new file mode 100644 index 0000000..629c3d7 --- /dev/null +++ b/src/ratelimiter.rs @@ -0,0 +1,462 @@ +use std::collections::HashMap; + +use log; +use reqwest::{Client, RequestBuilder, Response}; +use serde::Deserialize; +use serde_json::from_str; + +use crate::{ + api::{Limit, LimitType}, + errors::{ChorusError, ChorusResult}, + instance::UserMeta, + types::{types::subconfigs::limits::rates::RateLimits, LimitsConfiguration}, +}; + +/// Chorus' request struct. This struct is used to send rate-limited requests to the Spacebar server. +/// See for more information. +pub struct ChorusRequest { + pub request: RequestBuilder, + pub limit_type: LimitType, +} + +impl ChorusRequest { + /// Sends a [`ChorusRequest`]. Checks if the user is rate limited, and if not, sends the request. + /// If the user is not rate limited and the instance has rate limits enabled, it will update the + /// rate limits. + #[allow(clippy::await_holding_refcell_ref)] + pub(crate) async fn send_request(self, user: &mut UserMeta) -> ChorusResult { + if !ChorusRequest::can_send_request(user, &self.limit_type) { + log::info!("Rate limit hit. Bucket: {:?}", self.limit_type); + return Err(ChorusError::RateLimited { + bucket: format!("{:?}", self.limit_type), + }); + } + let belongs_to = user.belongs_to.borrow(); + let result = match belongs_to + .client + .execute(self.request.build().unwrap()) + .await + { + Ok(result) => result, + Err(error) => { + log::warn!("Request failed: {:?}", error); + return Err(ChorusError::RequestFailed { + url: error.url().unwrap().to_string(), + error, + }); + } + }; + drop(belongs_to); + if !result.status().is_success() { + if result.status().as_u16() == 429 { + log::warn!("Rate limit hit unexpectedly. Bucket: {:?}. Setting the instances' remaining global limit to 0 to have cooldown.", self.limit_type); + user.belongs_to + .borrow_mut() + .limits_information + .as_mut() + .unwrap() + .ratelimits + .get_mut(&LimitType::Global) + .unwrap() + .remaining = 0; + return Err(ChorusError::RateLimited { + bucket: format!("{:?}", self.limit_type), + }); + } + log::warn!("Request failed: {:?}", result); + return Err(ChorusRequest::interpret_error(result).await); + } + ChorusRequest::update_rate_limits(user, &self.limit_type, !result.status().is_success()); + Ok(result) + } + + fn can_send_request(user: &mut UserMeta, limit_type: &LimitType) -> bool { + log::trace!("Checking if user or instance is rate-limited..."); + let mut belongs_to = user.belongs_to.borrow_mut(); + if belongs_to.limits_information.is_none() { + log::trace!("Instance indicates no rate limits are configured. Continuing."); + return true; + } + let instance_dictated_limits = [ + &LimitType::AuthLogin, + &LimitType::AuthRegister, + &LimitType::Global, + &LimitType::Ip, + ]; + let limits = match instance_dictated_limits.contains(&limit_type) { + true => { + log::trace!( + "Limit type {:?} is dictated by the instance. Continuing.", + limit_type + ); + belongs_to + .limits_information + .as_mut() + .unwrap() + .ratelimits + .clone() + } + false => { + log::trace!( + "Limit type {:?} is dictated by the user. Continuing.", + limit_type + ); + ChorusRequest::ensure_limit_in_map( + &belongs_to + .limits_information + .as_ref() + .unwrap() + .configuration, + user.limits.as_mut().unwrap(), + limit_type, + ); + user.limits.as_mut().unwrap().clone() + } + }; + let global = belongs_to + .limits_information + .as_ref() + .unwrap() + .ratelimits + .get(&LimitType::Global) + .unwrap(); + let ip = belongs_to + .limits_information + .as_ref() + .unwrap() + .ratelimits + .get(&LimitType::Ip) + .unwrap(); + let limit_type_limit = limits.get(limit_type).unwrap(); + global.remaining > 0 && ip.remaining > 0 && limit_type_limit.remaining > 0 + } + + fn ensure_limit_in_map( + rate_limits_config: &RateLimits, + map: &mut HashMap, + limit_type: &LimitType, + ) { + log::trace!("Ensuring limit type {:?} is in the map.", limit_type); + let time: u64 = chrono::Utc::now().timestamp() as u64; + match limit_type { + LimitType::Channel(snowflake) => { + if map.get(&LimitType::Channel(*snowflake)).is_some() { + log::trace!( + "Limit type {:?} is already in the map. Returning.", + limit_type + ); + return; + } + log::trace!("Limit type {:?} is not in the map. Adding it.", limit_type); + let channel_limit = &rate_limits_config.routes.channel; + map.insert( + LimitType::Channel(*snowflake), + Limit { + bucket: LimitType::Channel(*snowflake), + limit: channel_limit.count, + remaining: channel_limit.count, + reset: channel_limit.window + time, + window: channel_limit.window, + }, + ); + } + LimitType::Guild(snowflake) => { + if map.get(&LimitType::Guild(*snowflake)).is_some() { + return; + } + let guild_limit = &rate_limits_config.routes.guild; + map.insert( + LimitType::Guild(*snowflake), + Limit { + bucket: LimitType::Guild(*snowflake), + limit: guild_limit.count, + remaining: guild_limit.count, + reset: guild_limit.window + time, + window: guild_limit.window, + }, + ); + } + LimitType::Webhook(snowflake) => { + if map.get(&LimitType::Webhook(*snowflake)).is_some() { + return; + } + let webhook_limit = &rate_limits_config.routes.webhook; + map.insert( + LimitType::Webhook(*snowflake), + Limit { + bucket: LimitType::Webhook(*snowflake), + limit: webhook_limit.count, + remaining: webhook_limit.count, + reset: webhook_limit.window + time, + window: webhook_limit.window, + }, + ); + } + other_limit => { + if map.get(other_limit).is_some() { + return; + } + let limits_map = ChorusRequest::limits_config_to_hashmap(rate_limits_config); + map.insert( + *other_limit, + Limit { + bucket: *other_limit, + limit: limits_map.get(other_limit).as_ref().unwrap().limit, + remaining: limits_map.get(other_limit).as_ref().unwrap().remaining, + reset: limits_map.get(other_limit).as_ref().unwrap().reset, + window: limits_map.get(other_limit).as_ref().unwrap().window, + }, + ); + } + } + } + + async fn interpret_error(response: reqwest::Response) -> ChorusError { + match response.status().as_u16() { + 401..=403 | 407 => ChorusError::NoPermission, + 404 => ChorusError::NotFound { + error: response.text().await.unwrap(), + }, + 405 | 408 | 409 => ChorusError::ReceivedErrorCode { error_code: response.status().as_u16(), error: response.text().await.unwrap() }, + 411..=421 | 426 | 428 | 431 => ChorusError::InvalidArguments { + error: response.text().await.unwrap(), + }, + 429 => panic!("Illegal state: Rate limit exception should have been caught before this function call."), + 451 => ChorusError::NoResponse, + 500..=599 => ChorusError::ReceivedErrorCode { error_code: response.status().as_u16(), error: response.text().await.unwrap() }, + _ => ChorusError::ReceivedErrorCode { error_code: response.status().as_u16(), error: response.text().await.unwrap()}, + } + } + + /// Updates the rate limits of the user. The following steps are performed: + /// 1. If the current unix timestamp is greater than the reset timestamp, the reset timestamp is + /// set to the current unix timestamp + the rate limit window. The remaining rate limit is + /// reset to the rate limit limit. + /// 2. The remaining rate limit is decreased by 1. + fn update_rate_limits(user: &mut UserMeta, limit_type: &LimitType, response_was_err: bool) { + let instance_dictated_limits = [ + &LimitType::AuthLogin, + &LimitType::AuthRegister, + &LimitType::Global, + &LimitType::Ip, + ]; + // modify this to store something to look up the value with later, instead of storing a reference to the actual data itself. + let mut relevant_limits = Vec::new(); + if instance_dictated_limits.contains(&limit_type) { + relevant_limits.push((LimitOrigin::Instance, *limit_type)); + } else { + relevant_limits.push((LimitOrigin::User, *limit_type)); + } + relevant_limits.push((LimitOrigin::Instance, LimitType::Global)); + relevant_limits.push((LimitOrigin::Instance, LimitType::Ip)); + if response_was_err { + relevant_limits.push((LimitOrigin::User, LimitType::Error)); + } + let time: u64 = chrono::Utc::now().timestamp() as u64; + for relevant_limit in relevant_limits.iter() { + let mut belongs_to = user.belongs_to.borrow_mut(); + let limit = match relevant_limit.0 { + LimitOrigin::Instance => { + log::trace!( + "Updating instance rate limit. Bucket: {:?}", + relevant_limit.1 + ); + belongs_to + .limits_information + .as_mut() + .unwrap() + .ratelimits + .get_mut(&relevant_limit.1) + .unwrap() + } + LimitOrigin::User => { + log::trace!("Updating user rate limit. Bucket: {:?}", relevant_limit.1); + user.limits + .as_mut() + .unwrap() + .get_mut(&relevant_limit.1) + .unwrap() + } + }; + if time > limit.reset { + // Spacebar does not yet return rate limit information in its response headers. We + // therefore have to guess the next rate limit window. This is not ideal. Oh well! + log::trace!("Rate limit replenished. Bucket: {:?}", limit.bucket); + limit.reset += limit.window; + limit.remaining = limit.limit; + } + limit.remaining -= 1; + } + } + + pub(crate) async fn get_limits_config(url_api: &str) -> ChorusResult { + let request = Client::new() + .get(format!("{}/policies/instance/limits/", url_api)) + .send() + .await; + let request = match request { + Ok(request) => request, + Err(e) => { + return Err(ChorusError::RequestFailed { + url: url_api.to_string(), + error: e, + }) + } + }; + let limits_configuration = match request.status().as_u16() { + 200 => from_str::(&request.text().await.unwrap()).unwrap(), + 429 => { + return Err(ChorusError::RateLimited { + bucket: format!("{:?}", LimitType::Ip), + }) + } + 404 => return Err(ChorusError::NotFound { error: "Route \"/policies/instance/limits/\" not found. Are you perhaps trying to request the Limits configuration from an unsupported server?".to_string() }), + 400..=u16::MAX => { + return Err(ChorusError::ReceivedErrorCode { error_code: request.status().as_u16(), error: request.text().await.unwrap() }) + } + _ => { + return Err(ChorusError::InvalidResponse { + error: request.text().await.unwrap(), + }) + } + }; + + Ok(limits_configuration) + } + + pub(crate) fn limits_config_to_hashmap( + limits_configuration: &RateLimits, + ) -> HashMap { + let config = limits_configuration.clone(); + let routes = config.routes; + let mut map: HashMap = HashMap::new(); + let time: u64 = chrono::Utc::now().timestamp() as u64; + map.insert( + LimitType::AuthLogin, + Limit { + bucket: LimitType::AuthLogin, + limit: routes.auth.login.count, + remaining: routes.auth.login.count, + reset: routes.auth.login.window + time, + window: routes.auth.login.window, + }, + ); + map.insert( + LimitType::AuthRegister, + Limit { + bucket: LimitType::AuthRegister, + limit: routes.auth.register.count, + remaining: routes.auth.register.count, + reset: routes.auth.register.window + time, + window: routes.auth.register.window, + }, + ); + map.insert( + LimitType::ChannelBaseline, + Limit { + bucket: LimitType::ChannelBaseline, + limit: routes.channel.count, + remaining: routes.channel.count, + reset: routes.channel.window + time, + window: routes.channel.window, + }, + ); + map.insert( + LimitType::Error, + Limit { + bucket: LimitType::Error, + limit: config.error.count, + remaining: config.error.count, + reset: config.error.window + time, + window: config.error.window, + }, + ); + map.insert( + LimitType::Global, + Limit { + bucket: LimitType::Global, + limit: config.global.count, + remaining: config.global.count, + reset: config.global.window + time, + window: config.global.window, + }, + ); + map.insert( + LimitType::Ip, + Limit { + bucket: LimitType::Ip, + limit: config.ip.count, + remaining: config.ip.count, + reset: config.ip.window + time, + window: config.ip.window, + }, + ); + map.insert( + LimitType::GuildBaseline, + Limit { + bucket: LimitType::GuildBaseline, + limit: routes.guild.count, + remaining: routes.guild.count, + reset: routes.guild.window + time, + window: routes.guild.window, + }, + ); + map.insert( + LimitType::WebhookBaseline, + Limit { + bucket: LimitType::WebhookBaseline, + limit: routes.webhook.count, + remaining: routes.webhook.count, + reset: routes.webhook.window + time, + window: routes.webhook.window, + }, + ); + map + } + + /// Sends a [`ChorusRequest`] and returns a [`ChorusResult`] that contains nothing if the request + /// was successful, or a [`ChorusError`] if the request failed. + pub(crate) async fn handle_request_as_result(self, user: &mut UserMeta) -> ChorusResult<()> { + match self.send_request(user).await { + Ok(_) => Ok(()), + Err(e) => Err(e), + } + } + + /// Sends a [`ChorusRequest`] and returns a [`ChorusResult`] that contains a [`T`] if the request + /// was successful, or a [`ChorusError`] if the request failed. + pub(crate) async fn deserialize_response Deserialize<'a>>( + self, + user: &mut UserMeta, + ) -> ChorusResult { + let response = self.send_request(user).await?; + let response_text = match response.text().await { + Ok(string) => string, + Err(e) => { + return Err(ChorusError::InvalidResponse { + error: format!( + "Error while trying to process the HTTP response into a String: {}", + e + ), + }); + } + }; + let object = match from_str::(&response_text) { + Ok(object) => object, + Err(e) => { + return Err(ChorusError::InvalidResponse { + error: format!( + "Error while trying to deserialize the JSON response into T: {}", + e + ), + }) + } + }; + Ok(object) + } +} + +enum LimitOrigin { + Instance, + User, +} diff --git a/src/types/config/types/general_configuration.rs b/src/types/config/types/general_configuration.rs index 07444b0..13b3aa8 100644 --- a/src/types/config/types/general_configuration.rs +++ b/src/types/config/types/general_configuration.rs @@ -18,10 +18,8 @@ pub struct GeneralConfiguration { impl Default for GeneralConfiguration { fn default() -> Self { Self { - instance_name: String::from("Spacebar Instance"), - instance_description: Some(String::from( - "This is a Spacebar instance made in the pre-release days", - )), + instance_name: String::from("Spacebar-compatible Instance"), + instance_description: Some(String::from("This is a spacebar-compatible instance.")), front_page: None, tos_page: None, correspondence_email: None, diff --git a/src/types/config/types/subconfigs/limits/rates.rs b/src/types/config/types/subconfigs/limits/rates.rs index 9d0cab1..ce1ea60 100644 --- a/src/types/config/types/subconfigs/limits/rates.rs +++ b/src/types/config/types/subconfigs/limits/rates.rs @@ -1,7 +1,12 @@ +use std::collections::HashMap; + use serde::{Deserialize, Serialize}; -use crate::types::config::types::subconfigs::limits::ratelimits::{ - route::RouteRateLimit, RateLimitOptions, +use crate::{ + api::LimitType, + types::config::types::subconfigs::limits::ratelimits::{ + route::RouteRateLimit, RateLimitOptions, + }, }; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -39,3 +44,18 @@ impl Default for RateLimits { } } } + +impl RateLimits { + pub fn to_hash_map(&self) -> HashMap { + let mut map = HashMap::new(); + map.insert(LimitType::AuthLogin, self.routes.auth.login.clone()); + map.insert(LimitType::AuthRegister, self.routes.auth.register.clone()); + map.insert(LimitType::ChannelBaseline, self.routes.channel.clone()); + map.insert(LimitType::Error, self.error.clone()); + map.insert(LimitType::Global, self.global.clone()); + map.insert(LimitType::Ip, self.ip.clone()); + map.insert(LimitType::WebhookBaseline, self.routes.webhook.clone()); + map.insert(LimitType::GuildBaseline, self.routes.guild.clone()); + map + } +} diff --git a/src/types/entities/user.rs b/src/types/entities/user.rs index d0059fc..b240fa2 100644 --- a/src/types/entities/user.rs +++ b/src/types/entities/user.rs @@ -1,9 +1,8 @@ +use crate::types::utils::Snowflake; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_aux::prelude::deserialize_option_number_from_string; -use crate::types::utils::Snowflake; - #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))] pub struct UserData { @@ -16,7 +15,6 @@ impl User { PublicUser::from(self) } } - #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] pub struct User { @@ -89,6 +87,7 @@ impl From for PublicUser { } } +#[allow(dead_code)] // FIXME: Remove this when we actually use this const CUSTOM_USER_FLAG_OFFSET: u64 = 1 << 32; bitflags::bitflags! { diff --git a/src/types/utils/rights.rs b/src/types/utils/rights.rs index 0198af6..fecf268 100644 --- a/src/types/utils/rights.rs +++ b/src/types/utils/rights.rs @@ -73,6 +73,7 @@ impl Rights { } } +#[allow(dead_code)] // FIXME: Remove this when we use this fn all_rights() -> Rights { Rights::OPERATOR | Rights::MANAGE_APPLICATIONS diff --git a/src/types/utils/snowflake.rs b/src/types/utils/snowflake.rs index 8502275..6176ea5 100644 --- a/src/types/utils/snowflake.rs +++ b/src/types/utils/snowflake.rs @@ -12,7 +12,7 @@ const EPOCH: i64 = 1420070400000; /// Unique identifier including a timestamp. /// See https://discord.com/developers/docs/reference#snowflakes -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "sqlx", derive(Type))] #[cfg_attr(feature = "sqlx", sqlx(transparent))] pub struct Snowflake(u64); diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 9a62585..0b48f89 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -24,7 +24,7 @@ pub async fn setup() -> TestBundle { "ws://localhost:3001".to_string(), "http://localhost:3001".to_string(), ); - let mut instance = Instance::new(urls.clone()).await.unwrap(); + let mut instance = Instance::new(urls.clone(), true).await.unwrap(); // Requires the existance of the below user. let reg = RegisterSchema { username: "integrationtestuser".into(), diff --git a/tests/relationships.rs b/tests/relationships.rs index e23f0f3..5578efd 100644 --- a/tests/relationships.rs +++ b/tests/relationships.rs @@ -19,7 +19,7 @@ async fn test_get_mutual_relationships() { username: user.object.username.clone(), discriminator: Some(user.object.discriminator.clone()), }; - other_user.send_friend_request(friend_request_schema).await; + let _ = other_user.send_friend_request(friend_request_schema).await; let relationships = user .get_mutual_relationships(other_user.object.id) .await @@ -45,7 +45,10 @@ async fn test_get_relationships() { username: user.object.username.clone(), discriminator: Some(user.object.discriminator.clone()), }; - other_user.send_friend_request(friend_request_schema).await; + 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); common::teardown(bundle).await @@ -64,7 +67,7 @@ async fn test_modify_relationship_friends() { let belongs_to = &mut bundle.instance; let user = &mut bundle.user; let mut other_user = belongs_to.register_account(®ister_schema).await.unwrap(); - other_user + let _ = other_user .modify_user_relationship(user.object.id, types::RelationshipType::Friends) .await; let relationships = user.get_relationships().await.unwrap(); @@ -79,7 +82,8 @@ async fn test_modify_relationship_friends() { relationships.get(0).unwrap().relationship_type, RelationshipType::Outgoing ); - user.modify_user_relationship(other_user.object.id, RelationshipType::Friends) + let _ = user + .modify_user_relationship(other_user.object.id, RelationshipType::Friends) .await; assert_eq!( other_user @@ -91,7 +95,7 @@ async fn test_modify_relationship_friends() { .relationship_type, RelationshipType::Friends ); - user.remove_relationship(other_user.object.id).await; + let _ = user.remove_relationship(other_user.object.id).await; assert_eq!( other_user.get_relationships().await.unwrap(), Vec::::new() @@ -112,7 +116,7 @@ async fn test_modify_relationship_block() { let belongs_to = &mut bundle.instance; let user = &mut bundle.user; let mut other_user = belongs_to.register_account(®ister_schema).await.unwrap(); - other_user + let _ = other_user .modify_user_relationship(user.object.id, types::RelationshipType::Blocked) .await; let relationships = user.get_relationships().await.unwrap(); @@ -123,7 +127,7 @@ async fn test_modify_relationship_block() { relationships.get(0).unwrap().relationship_type, RelationshipType::Blocked ); - other_user.remove_relationship(user.object.id).await; + let _ = other_user.remove_relationship(user.object.id).await; assert_eq!( other_user.get_relationships().await.unwrap(), Vec::::new() From 0d6d705e46e67986cecedcb77ee4d8f2351474f4 Mon Sep 17 00:00:00 2001 From: Zert3x <81202811+Zert3x@users.noreply.github.com> Date: Tue, 11 Jul 2023 13:20:27 -0400 Subject: [PATCH 09/23] Async Observer Trait (#147) --- Cargo.toml | 1 + examples/gateway_observers.rs | 4 +++- src/gateway.rs | 9 ++++++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5cffce1..a0db82c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ sqlx = { git = "https://github.com/zert3x/sqlx", branch="feature/skip", features thiserror = "1.0.40" jsonwebtoken = "8.3.0" log = "0.4.19" +async-trait = "0.1.71" [dev-dependencies] tokio = {version = "1.28.1", features = ["full"]} diff --git a/examples/gateway_observers.rs b/examples/gateway_observers.rs index d8762e0..1572aa9 100644 --- a/examples/gateway_observers.rs +++ b/examples/gateway_observers.rs @@ -1,3 +1,4 @@ +use async_trait::async_trait; use chorus::{ self, gateway::{Gateway, Observer}, @@ -15,9 +16,10 @@ pub struct ExampleObserver {} // This struct can observe GatewayReady events when subscribed, because it implements the trait Observer. // The Observer trait can be implemented for a struct for a given websocketevent to handle observing it // One struct can be an observer of multiple websocketevents, if needed +#[async_trait] impl Observer for ExampleObserver { // After we subscribe to an event this function is called every time we receive it - fn update(&self, _data: &GatewayReady) { + async fn update(&self, _data: &GatewayReady) { println!("Observed Ready!"); } } diff --git a/src/gateway.rs b/src/gateway.rs index ed96aac..3b3d46e 100644 --- a/src/gateway.rs +++ b/src/gateway.rs @@ -2,6 +2,7 @@ use crate::errors::GatewayError; use crate::gateway::events::Events; use crate::types; use crate::types::WebSocketEvent; +use async_trait::async_trait; use std::sync::Arc; use futures_util::stream::SplitSink; @@ -1667,8 +1668,9 @@ struct HeartbeatThreadCommunication { /// an Observable. The Observer is notified when the Observable's data changes. /// In this case, the Observable is a [`GatewayEvent`], which is a wrapper around a WebSocketEvent. /// Note that `Debug` is used to tell `Observer`s apart when unsubscribing. +#[async_trait] pub trait Observer: Sync + Send + std::fmt::Debug { - fn update(&self, data: &T); + async fn update(&self, data: &T); } /// GatewayEvent is a wrapper around a WebSocketEvent. It is used to notify the observers of a @@ -1703,7 +1705,7 @@ impl GatewayEvent { /// Notifies the observers of the GatewayEvent. async fn notify(&self, new_event_data: T) { for observer in &self.observers { - observer.update(&new_event_data); + observer.update(&new_event_data).await; } } } @@ -1880,8 +1882,9 @@ mod example { events_received: AtomicI32, } + #[async_trait] impl Observer for Consumer { - fn update(&self, _data: &types::GatewayResume) { + async fn update(&self, _data: &types::GatewayResume) { self.events_received.fetch_add(1, Relaxed); } } From db0b1131ed8d24af8821fac63f17f01177520483 Mon Sep 17 00:00:00 2001 From: SpecificProtagonist Date: Tue, 11 Jul 2023 20:11:13 +0200 Subject: [PATCH 10/23] Deduplicate gateway dispatch event handling (#148) deduplicate gateway dispatch event handling --- src/gateway.rs | 1142 ++++++------------------------------------------ 1 file changed, 123 insertions(+), 1019 deletions(-) diff --git a/src/gateway.rs b/src/gateway.rs index 3b3d46e..840c4b0 100644 --- a/src/gateway.rs +++ b/src/gateway.rs @@ -406,7 +406,7 @@ impl Gateway { return; } - // To:do: handle errors in a good way, maybe observers like events? + // Todo: handle errors in a good way, maybe observers like events? if msg.is_error() { warn!("GW: Received error, connection will close.."); @@ -422,1021 +422,127 @@ impl Gateway { match gateway_payload.op_code { // An event was dispatched, we need to look at the gateway event name t GATEWAY_DISPATCH => { - let gateway_payload_t = gateway_payload.clone().event_name.unwrap(); + let Some(event_name) = gateway_payload.event_name else { + warn!("Gateway dispatch op without event_name"); + return + }; - trace!("GW: Received {}..", gateway_payload_t); + trace!("Gateway: Received {event_name}"); - //println!("Event data dump: {}", gateway_payload.d.clone().unwrap().get()); + macro_rules! handle { + ($($name:literal => $($path:ident).+),*) => { + match event_name.as_str() { + $($name => { + let event = &mut self.events.lock().await.$($path).+; + + let result = + Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) + .await; + + if let Err(err) = result { + warn!("Failed to parse gateway event {event_name} ({err})"); + } + },)* + "RESUMED" => (), + "SESSIONS_REPLACE" => { + let result: Result, serde_json::Error> = + serde_json::from_str(gateway_payload.event_data.unwrap().get()); + match result { + Err(err) => { + warn!( + "Failed to parse gateway event {} ({})", + event_name, + err + ); + return; + } + Ok(sessions) => { + self.events.lock().await.session.replace.notify( + types::SessionsReplace {sessions} + ).await; + } + } + }, + _ => { + warn!("Received unrecognized gateway event ({event_name})! Please open an issue on the chorus github so we can implement it"); + } + } + }; + } // See https://discord.com/developers/docs/topics/gateway-events#receive-events // "Some" of these are undocumented - match gateway_payload_t.as_str() { - "READY" => { - let event = &mut self.events.lock().await.session.ready; - - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "READY_SUPPLEMENTAL" => { - let event = &mut self.events.lock().await.session.ready_supplemental; - - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "RESUMED" => {} - "APPLICATION_COMMAND_PERMISSIONS_UPDATE" => { - let event = &mut self - .events - .lock() - .await - .application - .command_permissions_update; - - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "AUTO_MODERATION_RULE_CREATE" => { - let event = &mut self.events.lock().await.auto_moderation.rule_create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "AUTO_MODERATION_RULE_UPDATE" => { - let event = &mut self.events.lock().await.auto_moderation.rule_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "AUTO_MODERATION_RULE_DELETE" => { - let event = &mut self.events.lock().await.auto_moderation.rule_delete; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "AUTO_MODERATION_ACTION_EXECUTION" => { - let event = &mut self.events.lock().await.auto_moderation.action_execution; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "CHANNEL_CREATE" => { - let event = &mut self.events.lock().await.channel.create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "CHANNEL_UPDATE" => { - let event = &mut self.events.lock().await.channel.update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "CHANNEL_UNREAD_UPDATE" => { - let event = &mut self.events.lock().await.channel.unread_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "CHANNEL_DELETE" => { - let event = &mut self.events.lock().await.channel.delete; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "CHANNEL_PINS_UPDATE" => { - let event = &mut self.events.lock().await.channel.pins_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "CALL_CREATE" => { - let event = &mut self.events.lock().await.call.create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "CALL_UPDATE" => { - let event = &mut self.events.lock().await.call.update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "CALL_DELETE" => { - let event = &mut self.events.lock().await.call.delete; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "THREAD_CREATE" => { - let event = &mut self.events.lock().await.thread.create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "THREAD_UPDATE" => { - let event = &mut self.events.lock().await.thread.update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "THREAD_DELETE" => { - let event = &mut self.events.lock().await.thread.delete; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "THREAD_LIST_SYNC" => { - let event = &mut self.events.lock().await.thread.list_sync; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "THREAD_MEMBER_UPDATE" => { - let event = &mut self.events.lock().await.thread.member_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "THREAD_MEMBERS_UPDATE" => { - let event = &mut self.events.lock().await.thread.members_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_CREATE" => { - let event = &mut self.events.lock().await.guild.create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_UPDATE" => { - let event = &mut self.events.lock().await.guild.update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_DELETE" => { - let event = &mut self.events.lock().await.guild.delete; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_AUDIT_LOG_ENTRY_CREATE" => { - let event = &mut self.events.lock().await.guild.audit_log_entry_create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_BAN_ADD" => { - let event = &mut self.events.lock().await.guild.ban_add; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_BAN_REMOVE" => { - let event = &mut self.events.lock().await.guild.ban_remove; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_EMOJIS_UPDATE" => { - let event = &mut self.events.lock().await.guild.emojis_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_STICKERS_UPDATE" => { - let event = &mut self.events.lock().await.guild.stickers_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_INTEGRATIONS_UPDATE" => { - let event = &mut self.events.lock().await.guild.integrations_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_MEMBER_ADD" => { - let event = &mut self.events.lock().await.guild.member_add; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_MEMBER_REMOVE" => { - let event = &mut self.events.lock().await.guild.member_remove; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_MEMBER_UPDATE" => { - let event = &mut self.events.lock().await.guild.member_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_MEMBERS_CHUNK" => { - let event = &mut self.events.lock().await.guild.members_chunk; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_ROLE_CREATE" => { - let event = &mut self.events.lock().await.guild.role_create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_ROLE_UPDATE" => { - let event = &mut self.events.lock().await.guild.role_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_ROLE_DELETE" => { - let event = &mut self.events.lock().await.guild.role_delete; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_SCHEDULED_EVENT_CREATE" => { - let event = &mut self.events.lock().await.guild.role_scheduled_event_create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_SCHEDULED_EVENT_UPDATE" => { - let event = &mut self.events.lock().await.guild.role_scheduled_event_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_SCHEDULED_EVENT_DELETE" => { - let event = &mut self.events.lock().await.guild.role_scheduled_event_delete; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_SCHEDULED_EVENT_USER_ADD" => { - let event = - &mut self.events.lock().await.guild.role_scheduled_event_user_add; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "GUILD_SCHEDULED_EVENT_USER_REMOVE" => { - let event = &mut self - .events - .lock() - .await - .guild - .role_scheduled_event_user_remove; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "PASSIVE_UPDATE_V1" => { - let event = &mut self.events.lock().await.guild.passive_update_v1; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "INTEGRATION_CREATE" => { - let event = &mut self.events.lock().await.integration.create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "INTEGRATION_UPDATE" => { - let event = &mut self.events.lock().await.integration.update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "INTEGRATION_DELETE" => { - let event = &mut self.events.lock().await.integration.delete; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "INTERACTION_CREATE" => { - let event = &mut self.events.lock().await.interaction.create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "INVITE_CREATE" => { - let event = &mut self.events.lock().await.invite.create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "INVITE_DELETE" => { - let event = &mut self.events.lock().await.invite.delete; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "MESSAGE_CREATE" => { - let event = &mut self.events.lock().await.message.create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "MESSAGE_UPDATE" => { - let event = &mut self.events.lock().await.message.update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "MESSAGE_DELETE" => { - let event = &mut self.events.lock().await.message.delete; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "MESSAGE_DELETE_BULK" => { - let event = &mut self.events.lock().await.message.delete_bulk; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "MESSAGE_REACTION_ADD" => { - let event = &mut self.events.lock().await.message.reaction_add; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "MESSAGE_REACTION_REMOVE" => { - let event = &mut self.events.lock().await.message.reaction_remove; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "MESSAGE_REACTION_REMOVE_ALL" => { - let event = &mut self.events.lock().await.message.reaction_remove_all; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "MESSAGE_REACTION_REMOVE_EMOJI" => { - let event = &mut self.events.lock().await.message.reaction_remove_emoji; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "MESSAGE_ACK" => { - let event = &mut self.events.lock().await.message.ack; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "PRESENCE_UPDATE" => { - let event = &mut self.events.lock().await.user.presence_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "RELATIONSHIP_ADD" => { - let event = &mut self.events.lock().await.relationship.add; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "RELATIONSHIP_REMOVE" => { - let event = &mut self.events.lock().await.relationship.remove; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "STAGE_INSTANCE_CREATE" => { - let event = &mut self.events.lock().await.stage_instance.create; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "STAGE_INSTANCE_UPDATE" => { - let event = &mut self.events.lock().await.stage_instance.update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "STAGE_INSTANCE_DELETE" => { - let event = &mut self.events.lock().await.stage_instance.delete; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "SESSIONS_REPLACE" => { - let result: Result, serde_json::Error> = - serde_json::from_str(gateway_payload.event_data.unwrap().get()); - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - - let data = types::SessionsReplace { - sessions: result.unwrap(), - }; - - self.events.lock().await.session.replace.notify(data).await; - } - "USER_UPDATE" => { - let event = &mut self.events.lock().await.user.update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "USER_GUILD_SETTINGS_UPDATE" => { - let event = &mut self.events.lock().await.user.guild_settings_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "VOICE_STATE_UPDATE" => { - let event = &mut self.events.lock().await.voice.state_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "VOICE_SERVER_UPDATE" => { - let event = &mut self.events.lock().await.voice.server_update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - "WEBHOOKS_UPDATE" => { - let event = &mut self.events.lock().await.webhooks.update; - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - if result.is_err() { - warn!( - "Failed to parse gateway event {} ({})", - gateway_payload_t, - result.err().unwrap() - ); - return; - } - } - _ => { - warn!("Received unrecognized gateway event ({})! Please open an issue on the chorus github so we can implement it", &gateway_payload_t); - } - } + handle!( + "READY" => session.ready, + "READY_SUPPLEMENTAL" => session.ready_supplemental, + "APPLICATION_COMMAND_PERMISSIONS_UPDATE" => application.command_permissions_update, + "AUTO_MODERATION_RULE_CREATE" =>auto_moderation.rule_create, + "AUTO_MODERATION_RULE_UPDATE" =>auto_moderation.rule_update, + "AUTO_MODERATION_RULE_DELETE" => auto_moderation.rule_delete, + "AUTO_MODERATION_ACTION_EXECUTION" => auto_moderation.action_execution, + "CHANNEL_CREATE" => channel.create, + "CHANNEL_UPDATE" => channel.update, + "CHANNEL_UNREAD_UPDATE" => channel.unread_update, + "CHANNEL_DELETE" => channel.delete, + "CHANNEL_PINS_UPDATE" => channel.pins_update, + "CALL_CREATE" => call.create, + "CALL_UPDATE" => call.update, + "CALL_DELETE" => call.delete, + "THREAD_CREATE" => thread.create, + "THREAD_UPDATE" => thread.update, + "THREAD_DELETE" => thread.delete, + "THREAD_LIST_SYNC" => thread.list_sync, + "THREAD_MEMBER_UPDATE" => thread.member_update, + "THREAD_MEMBERS_UPDATE" => thread.members_update, + "GUILD_CREATE" => guild.create, + "GUILD_UPDATE" => guild.update, + "GUILD_DELETE" => guild.delete, + "GUILD_AUDIT_LOG_ENTRY_CREATE" => guild.audit_log_entry_create, + "GUILD_BAN_ADD" => guild.ban_add, + "GUILD_BAN_REMOVE" => guild.ban_remove, + "GUILD_EMOJIS_UPDATE" => guild.emojis_update, + "GUILD_STICKERS_UPDATE" => guild.stickers_update, + "GUILD_INTEGRATIONS_UPDATE" => guild.integrations_update, + "GUILD_MEMBER_ADD" => guild.member_add, + "GUILD_MEMBER_REMOVE" => guild.member_remove, + "GUILD_MEMBER_UPDATE" => guild.member_update, + "GUILD_MEMBERS_CHUNK" => guild.members_chunk, + "GUILD_ROLE_CREATE" => guild.role_create, + "GUILD_ROLE_UPDATE" => guild.role_update, + "GUILD_ROLE_DELETE" => guild.role_delete, + "GUILD_SCHEDULED_EVENT_CREATE" => guild.role_scheduled_event_create, + "GUILD_SCHEDULED_EVENT_UPDATE" => guild.role_scheduled_event_update, + "GUILD_SCHEDULED_EVENT_DELETE" => guild.role_scheduled_event_delete, + "GUILD_SCHEDULED_EVENT_USER_ADD" => guild.role_scheduled_event_user_add, + "GUILD_SCHEDULED_EVENT_USER_REMOVE" => guild.role_scheduled_event_user_remove, + "PASSIVE_UPDATE_V1" => guild.passive_update_v1, + "INTEGRATION_CREATE" => integration.create, + "INTEGRATION_UPDATE" => integration.update, + "INTEGRATION_DELETE" => integration.delete, + "INTERACTION_CREATE" => interaction.create, + "INVITE_CREATE" => invite.create, + "INVITE_DELETE" => invite.delete, + "MESSAGE_CREATE" => message.create, + "MESSAGE_UPDATE" => message.update, + "MESSAGE_DELETE" => message.delete, + "MESSAGE_DELETE_BULK" => message.delete_bulk, + "MESSAGE_REACTION_ADD" => message.reaction_add, + "MESSAGE_REACTION_REMOVE" => message.reaction_remove, + "MESSAGE_REACTION_REMOVE_ALL" => message.reaction_remove_all, + "MESSAGE_REACTION_REMOVE_EMOJI" => message.reaction_remove_emoji, + "MESSAGE_ACK" => message.ack, + "PRESENCE_UPDATE" => user.presence_update, + "RELATIONSHIP_ADD" => relationship.add, + "RELATIONSHIP_REMOVE" => relationship.remove, + "STAGE_INSTANCE_CREATE" => stage_instance.create, + "STAGE_INSTANCE_UPDATE" => stage_instance.update, + "STAGE_INSTANCE_DELETE" => stage_instance.delete, + "USER_UPDATE" => user.update, + "USER_GUILD_SETTINGS_UPDATE" => user.guild_settings_update, + "VOICE_STATE_UPDATE" => voice.state_update, + "VOICE_SERVER_UPDATE" => voice.server_update, + "WEBHOOKS_UPDATE" => webhooks.update + ); } // We received a heartbeat from the server // "Discord may send the app a Heartbeat (opcode 1) event, in which case the app should send a Heartbeat event immediately." @@ -1501,9 +607,9 @@ impl Gateway { } // If we we received a seq number we should let it know - if gateway_payload.sequence_number.is_some() { + if let Some(seq_num) = gateway_payload.sequence_number { let heartbeat_communication = HeartbeatThreadCommunication { - sequence_number: Some(gateway_payload.sequence_number.unwrap()), + sequence_number: Some(seq_num), // Op code is irrelevant here op_code: None, }; @@ -1597,16 +703,14 @@ impl HeartbeatHandler { let received_communication: Result = receive.try_recv(); - if received_communication.is_ok() { - let communication = received_communication.unwrap(); - + if let Ok(communication) = received_communication { // If we received a seq number update, use that as the last seq number if communication.sequence_number.is_some() { - last_seq_number = Some(communication.sequence_number.unwrap()); + last_seq_number = communication.sequence_number; } - if communication.op_code.is_some() { - match communication.op_code.unwrap() { + if let Some(op_code) = communication.op_code { + match op_code { GATEWAY_HEARTBEAT => { // As per the api docs, if the server sends us a Heartbeat, that means we need to respond with a heartbeat immediately should_send = true; From 0cd1f8142199db8efb7f9840021ef2b65eb8979e Mon Sep 17 00:00:00 2001 From: kozabrada123 <59031733+kozabrada123@users.noreply.github.com> Date: Fri, 14 Jul 2023 09:58:12 +0000 Subject: [PATCH 11/23] Faster actions hopefully (#150) --- .github/workflows/build_and_test.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 4e98355..d0567fe 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -15,21 +15,23 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Install dependencies + - uses: actions/checkout@v3 + - name: Clone spacebar server run: | - sudo apt-get update - sudo apt-get install -y git python3 build-essential - curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash - - sudo apt-get install -y nodejs git clone https://github.com/bitfl0wer/server.git + - uses: actions/setup-node@v3 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: server/package-lock.json - name: Prepare and start Spacebar server run: | npm install npm run setup npm run start & working-directory: ./server + - uses: Swatinem/rust-cache@v2 - name: Build - run: cargo build --verbose + run: cargo build --release --verbose - name: Run tests run: cargo test --verbose From 1163b324609706dfc6f05d8abd0e91e1fb50d945 Mon Sep 17 00:00:00 2001 From: kozabrada123 <59031733+kozabrada123@users.noreply.github.com> Date: Fri, 14 Jul 2023 10:12:39 +0000 Subject: [PATCH 12/23] Revert actions build back to debug (#151) Cargo build --release is faster for builds, but test uses debug built libraries, so it rebuilds them --- .github/workflows/build_and_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index d0567fe..166d4c8 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -32,6 +32,6 @@ jobs: working-directory: ./server - uses: Swatinem/rust-cache@v2 - name: Build - run: cargo build --release --verbose + run: cargo build --verbose - name: Run tests run: cargo test --verbose From 6f3bd049635cdafa44c66a79aeb816e77cdbd41a Mon Sep 17 00:00:00 2001 From: Flori <39242991+bitfl0wer@users.noreply.github.com> Date: Fri, 14 Jul 2023 17:38:54 +0200 Subject: [PATCH 13/23] Enable saving and loading LimitsConfiguration (#152) Impl Serialize, Deserialize for LimitsConfiguration and children --- src/api/policies/instance/ratelimits.rs | 6 ++++-- src/instance.rs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/api/policies/instance/ratelimits.rs b/src/api/policies/instance/ratelimits.rs index 125af32..e32a835 100644 --- a/src/api/policies/instance/ratelimits.rs +++ b/src/api/policies/instance/ratelimits.rs @@ -1,11 +1,13 @@ use std::hash::Hash; +use serde::{Deserialize, Serialize}; + use crate::types::Snowflake; /// The different types of ratelimits that can be applied to a request. Includes "Baseline"-variants /// for when the Snowflake is not yet known. /// See for more information. -#[derive(Clone, Copy, Eq, PartialEq, Debug, Default, Hash)] +#[derive(Clone, Copy, Eq, PartialEq, Debug, Default, Hash, Serialize, Deserialize)] pub enum LimitType { AuthRegister, AuthLogin, @@ -25,7 +27,7 @@ pub enum LimitType { /// Unlike [`RateLimits`], this struct shows the current ratelimits, not the rate limit /// configuration for the instance. /// See for more information. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Limit { pub bucket: LimitType, pub limit: u64, diff --git a/src/instance.rs b/src/instance.rs index 15c513c..d22d935 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -25,7 +25,7 @@ pub struct Instance { pub client: Client, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct LimitsInformation { pub ratelimits: HashMap, pub configuration: RateLimits, From 8a09512e3dd10dffda5c6e4ccfbfb61145c238ed Mon Sep 17 00:00:00 2001 From: Flori <39242991+bitfl0wer@users.noreply.github.com> Date: Mon, 17 Jul 2023 12:36:33 +0200 Subject: [PATCH 14/23] Create rust-clippy.yml --- .github/workflows/rust-clippy.yml | 55 +++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 .github/workflows/rust-clippy.yml diff --git a/.github/workflows/rust-clippy.yml b/.github/workflows/rust-clippy.yml new file mode 100644 index 0000000..63e6b66 --- /dev/null +++ b/.github/workflows/rust-clippy.yml @@ -0,0 +1,55 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# rust-clippy is a tool that runs a bunch of lints to catch common +# mistakes in your Rust code and help improve your Rust code. +# More details at https://github.com/rust-lang/rust-clippy +# and https://rust-lang.github.io/rust-clippy/ + +name: rust-clippy analyze + +on: + push: + branches: [ "main", "preserve/*" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "main" ] + schedule: + - cron: '18 21 * * 6' + +jobs: + rust-clippy-analyze: + name: Run rust-clippy analyzing + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Install Rust toolchain + uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af #@v1 + with: + profile: minimal + toolchain: stable + components: clippy + override: true + + - name: Install required cargo + run: cargo install clippy-sarif sarif-fmt + + - name: Run rust-clippy + run: + cargo clippy + --all-features + --message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt + continue-on-error: true + + - name: Upload analysis results to GitHub + uses: github/codeql-action/upload-sarif@v1 + with: + sarif_file: rust-clippy-results.sarif + wait-for-processing: true From 3928f52d83aac955fc2797284b55a32115b665dc Mon Sep 17 00:00:00 2001 From: Flori <39242991+bitfl0wer@users.noreply.github.com> Date: Mon, 17 Jul 2023 12:36:45 +0200 Subject: [PATCH 15/23] Delete clippy.yml --- .github/workflows/clippy.yml | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 .github/workflows/clippy.yml diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml deleted file mode 100644 index ba12407..0000000 --- a/.github/workflows/clippy.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Clippy check - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - - -# Make sure CI fails on all warnings, including Clippy lints -env: - RUSTFLAGS: "-Dwarnings" - -jobs: - clippy_check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Run Clippy - run: cargo clippy --all-targets --all-features \ No newline at end of file From 378a05c2d77766db6b77fbd5234e05d50519bec6 Mon Sep 17 00:00:00 2001 From: Flori <39242991+bitfl0wer@users.noreply.github.com> Date: Mon, 17 Jul 2023 19:35:16 +0200 Subject: [PATCH 16/23] Update code-ql action to v2 --- .github/workflows/rust-clippy.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/rust-clippy.yml b/.github/workflows/rust-clippy.yml index 63e6b66..dbbba76 100644 --- a/.github/workflows/rust-clippy.yml +++ b/.github/workflows/rust-clippy.yml @@ -15,8 +15,6 @@ on: pull_request: # The branches below must be a subset of the branches above branches: [ "main" ] - schedule: - - cron: '18 21 * * 6' jobs: rust-clippy-analyze: @@ -49,7 +47,7 @@ jobs: continue-on-error: true - name: Upload analysis results to GitHub - uses: github/codeql-action/upload-sarif@v1 + uses: github/codeql-action/upload-sarif@v2 with: sarif_file: rust-clippy-results.sarif wait-for-processing: true From 1fdbe6cc0040bf9ea3f23949f7d681b7163453a5 Mon Sep 17 00:00:00 2001 From: Flori <39242991+bitfl0wer@users.noreply.github.com> Date: Mon, 17 Jul 2023 19:36:28 +0200 Subject: [PATCH 17/23] Join/Leave Guilds, (Group) DMs and minor improvements (#157) ## Summary: **Added:** - Schemas `PrivateChannelCreateSchema` `ChannelInviteCreateSchema`, `AddChannelRecipientSchema` recursively (including schemas which were needed to create these schemas) - Methods `create_private_channel`, `leave_guild`, `accept_invite`, `create_user_invite`, `create_guild_invite`, `add_channel_recipient`, `remove_channel_recipient` - Integration tests for the functionality covered by implementing #45 - Documentation in some places where I noticed it would be useful to have some - `create_user` method in `/src/tests`: Cuts down on test boilerplate needed to create an addition test user **Changed:** - `.gitignore` - Added `.DS_store` files to gitignore (some weird macos files), removed Cargo.lock, as Cargo.lock should be included for libraries - Added a lot of default trait derives like Clone, Copy, PartialEq, Eq, Ord, PartialOrd to structs and enums where I saw them missing - Added missing `teardown()` calls to the integration tests - Renamed integration test files in `/src/tests` dir to all be plural: `channel.rs` -> `channels.rs` - Made more fields on `User` type `Option<>` - All instances in `/src/tests` where a second test user was created using a RegistrationSchema and the register_user method were replaced with the new `create_user` method - README.md: Marked roadmap fields covered by #45 as implemented - Changed visibility of `/src/tests/common/mod.rs` methods from `pub` to `pub(crate)`. In hindsight, this was probably not required, haha **Removed:** - Unneeded import in`src/types/config/types/guild_configuration.rs` ## Commit log: * Add .DS_store, remove Cargo.lock * Create PrivateChannelCreateSchema * pub use users * add channels.rs * create channels.rs * Add Deserialize/Serialize derives * Implement create_private_channel * Add create_dm() test * Make optional fields on `User` `Option<>` * Check boxes for implemented features * Create users/guilds.rs * Remove unneeded import * Add UserMeta::leave_guild() * Create api/invites/mod.rs * Add debug print to send_request * Rename tests files * Create invites.rs * create invite.rs * Add documentation * Implement accept_invite * Sort fields on Channel alphabetically * Add invite mod * Add forgotten teardown() at test end * change visiblities, add create_user() * Implement `create_user_invite()` * start working on invite tests * Add allow flags * Fix bad url * Create CreateChannelInviteSchema and friends * remove non-implemented test code * add body to requests * Add Clone to UserMeta * More comprehensive error message when running into a deserialization error * Add arguments documentation to accept_invite * Add Eq derive to GuildFeaturesList * Add Eq derive to Emoji * Add Eq derive to GuildBan * Add create_accept_invite() test * Add Default derive to ChannelCreateSchema * Change create_guild_invite to return GuildInvite * Dates as chrono::Date(Utc); sort alphabetically * Add default derives wherever possible * Implement add_- and remove_channel_recipient * Create AddChannelRecipientSchema * replace otheruser regs with bundle.creeate_user() * Add (disabled) test remove_add_person_from_to_dm() --- .gitignore | 13 +- Cargo.lock | 2552 +++++++++++++++++ README.md | 6 +- src/api/channels/channels.rs | 51 + src/api/invites/mod.rs | 73 + src/api/mod.rs | 3 + src/api/users/channels.rs | 32 + src/api/users/guilds.rs | 30 + src/api/users/mod.rs | 4 + src/instance.rs | 2 +- src/ratelimiter.rs | 12 +- src/types/config/types/guild_configuration.rs | 4 +- src/types/entities/channel.rs | 86 +- src/types/entities/emoji.rs | 2 +- src/types/entities/guild.rs | 2 +- src/types/entities/invite.rs | 75 + src/types/entities/mod.rs | 2 + src/types/entities/user.rs | 6 +- src/types/schema/channel.rs | 62 +- src/types/schema/user.rs | 18 + src/types/utils/snowflake.rs | 2 +- tests/{channel.rs => channels.rs} | 80 +- tests/common/mod.rs | 23 +- tests/{guild.rs => guilds.rs} | 0 tests/invites.rs | 25 + tests/{member.rs => members.rs} | 0 tests/{message.rs => messages.rs} | 0 tests/relationships.rs | 42 +- 28 files changed, 3096 insertions(+), 111 deletions(-) create mode 100644 Cargo.lock create mode 100644 src/api/invites/mod.rs create mode 100644 src/api/users/channels.rs create mode 100644 src/api/users/guilds.rs create mode 100644 src/types/entities/invite.rs rename tests/{channel.rs => channels.rs} (56%) rename tests/{guild.rs => guilds.rs} (100%) create mode 100644 tests/invites.rs rename tests/{member.rs => members.rs} (100%) rename tests/{message.rs => messages.rs} (100%) diff --git a/.gitignore b/.gitignore index ef337e7..d3170e2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,19 +2,18 @@ # will have compiled files and executables /target/ -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - # These are backup files generated by rustfmt **/*.rs.bk - # Added by cargo /target -### +# IDE specific folders and configs .vscode/** -.idea/** \ No newline at end of file +.idea/** + +# macOS + +**/.DS_Store \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..eec51aa --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2552 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "async-trait" +version = "0.1.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "atoi" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c57d12312ff59c811c0643f4d80830505833c9ffaebd193d819392b265be8e" +dependencies = [ + "num-traits", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +dependencies = [ + "serde", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chorus" +version = "0.1.0" +dependencies = [ + "async-trait", + "base64 0.21.2", + "bitflags 2.3.3", + "chrono", + "custom_error", + "futures-util", + "hostname", + "http", + "jsonwebtoken", + "lazy_static", + "log", + "native-tls", + "openssl", + "poem", + "regex", + "reqwest", + "rusty-hook", + "serde", + "serde-aux", + "serde_json", + "serde_repr", + "serde_with", + "sqlx", + "thiserror", + "tokio", + "tokio-tungstenite", + "url", +] + +[[package]] +name = "chrono" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "time 0.1.45", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "ci_info" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24f638c70e8c5753795cc9a8c07c44da91554a09e4cf11a7326e8161b0a3c45e" +dependencies = [ + "envmnt", +] + +[[package]] +name = "const-oid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" + +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-bigint" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "custom_error" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f8a51dd197fa6ba5b4dc98a990a43cc13693c23eb0089ebb0fcc1f04152bca6" + +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.25", +] + +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "data-encoding" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" + +[[package]] +name = "der" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +dependencies = [ + "const-oid", + "crypto-bigint", + "pem-rfc7468", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "envmnt" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d328fc287c61314c4a61af7cfdcbd7e678e39778488c7cb13ec133ce0f4059" +dependencies = [ + "fsio", + "indexmap 1.9.3", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "flume" +version = "0.10.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +dependencies = [ + "futures-core", + "futures-sink", + "pin-project", + "spin 0.9.8", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fsio" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fd087255f739f4f1aeea69f11b72f8080e9c2e7645cd06955dad4a178a49e3" + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot 0.11.2", +] + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-core", + "futures-macro", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gimli" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" + +[[package]] +name = "h2" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 1.9.3", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +dependencies = [ + "ahash 0.8.3", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f" +dependencies = [ + "hashbrown 0.14.0", +] + +[[package]] +name = "headers" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +dependencies = [ + "base64 0.13.1", + "bitflags 1.3.2", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + +[[package]] +name = "ipnet" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" + +[[package]] +name = "ipnetwork" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f84f1612606f3753f205a4e9a2efd6fe5b4c573a6269b2cc6c3003d44a0d127" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken" +version = "8.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" +dependencies = [ + "base64 0.21.2", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + +[[package]] +name = "libsqlite3-sys" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nias" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab250442c86f1850815b5d268639dff018c0627022bc1940eb2d642ca1ce12f0" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "openssl" +version = "0.10.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.8", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.3.5", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b27ab7be369122c218afc2079489cdcb4b517c0a3fc386ff11e1fedfcc2b35" + +[[package]] +name = "pem" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +dependencies = [ + "base64 0.13.1", +] + +[[package]] +name = "pem-rfc7468" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01de5d978f34aa4b2296576379fcc416034702fd94117c56ffd8a1a767cefb30" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pin-project" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a78f66c04ccc83dd4486fd46c33896f4e17b24a7a3a6400dedc48ed0ddd72320" +dependencies = [ + "der", + "pkcs8", + "zeroize", +] + +[[package]] +name = "pkcs8" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" +dependencies = [ + "der", + "spki", + "zeroize", +] + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "poem" +version = "1.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a56df40b79ebdccf7986b337f9b0e51ac55cd5e9d21fb20b6aa7c7d49741854" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "headers", + "http", + "hyper", + "mime", + "parking_lot 0.12.1", + "percent-encoding", + "pin-project-lite", + "poem-derive", + "regex", + "rfc7239", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "thiserror", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "poem-derive" +version = "1.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1701f977a2d650a03df42c053686ea0efdb83554f34c7b026b89383c0a1b7846" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "reqwest" +version = "0.11.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +dependencies = [ + "base64 0.21.2", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "rfc7239" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "087317b3cf7eb481f13bd9025d729324b7cd068d6f470e2d76d049e191f5ba47" +dependencies = [ + "uncased", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rsa" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf22754c49613d2b3b119f0e5d46e34a2c628a937e3024b8762de4e7d8c710b" +dependencies = [ + "byteorder", + "digest", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "smallvec", + "subtle", + "zeroize", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.37.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rusty-hook" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96cee9be61be7e1cbadd851e58ed7449c29c620f00b23df937cb9cbc04ac21a3" +dependencies = [ + "ci_info", + "getopts", + "nias", + "toml", +] + +[[package]] +name = "ryu" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" + +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "security-framework" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-aux" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3dfe1b7eb6f9dcf011bd6fad169cdeaae75eda0d61b1a99a3f015b41b0cae39" +dependencies = [ + "chrono", + "serde", + "serde_json", +] + +[[package]] +name = "serde_derive" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "serde_json" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5062a995d481b2308b6064e9af76011f2921c35f97b0468811ed9f6cd91dfed" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d89a8107374290037607734c0b73a85db7ed80cae314b3c5791f192a496e731" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f02d8aa6e3c385bf084924f660ce2a3a6bd333ba55b35e8590b321f35d88513" +dependencies = [ + "base64 0.21.2", + "chrono", + "hex", + "indexmap 1.9.3", + "serde", + "serde_json", + "serde_with_macros", + "time 0.3.23", +] + +[[package]] +name = "serde_with_macros" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc7d5d3932fb12ce722ee5e64dd38c504efba37567f0c402f6ca728c3b8b070" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time 0.3.23", +] + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e" +dependencies = [ + "itertools", + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.6.3" +source = "git+https://github.com/zert3x/sqlx?branch=feature/skip#b417a65842442b177e2abb39f3940a7c95265d90" +dependencies = [ + "sqlx-core", + "sqlx-macros", +] + +[[package]] +name = "sqlx-core" +version = "0.6.3" +source = "git+https://github.com/zert3x/sqlx?branch=feature/skip#b417a65842442b177e2abb39f3940a7c95265d90" +dependencies = [ + "ahash 0.7.6", + "atoi", + "bitflags 1.3.2", + "byteorder", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "digest", + "dotenvy", + "either", + "event-listener", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "generic-array", + "hashlink", + "hex", + "indexmap 1.9.3", + "ipnetwork", + "itoa", + "libc", + "libsqlite3-sys", + "log", + "memchr", + "num-bigint", + "once_cell", + "paste", + "percent-encoding", + "rand", + "rsa", + "serde", + "serde_json", + "sha1", + "sha2", + "smallvec", + "sqlformat", + "sqlx-rt", + "stringprep", + "thiserror", + "tokio-stream", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.6.3" +source = "git+https://github.com/zert3x/sqlx?branch=feature/skip#b417a65842442b177e2abb39f3940a7c95265d90" +dependencies = [ + "dotenvy", + "either", + "heck", + "once_cell", + "proc-macro2", + "quote", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-rt", + "syn 1.0.109", + "url", +] + +[[package]] +name = "sqlx-rt" +version = "0.6.3" +source = "git+https://github.com/zert3x/sqlx?branch=feature/skip#b417a65842442b177e2abb39f3940a7c95265d90" +dependencies = [ + "native-tls", + "once_cell", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "stringprep" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +dependencies = [ + "autocfg", + "cfg-if", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys", +] + +[[package]] +name = "thiserror" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[package]] +name = "time-macros" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" +dependencies = [ + "autocfg", + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot 0.12.1", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec509ac96e9a0c43427c74f003127d953a265737636129424288d27cb5c4b12c" +dependencies = [ + "futures-util", + "log", + "native-tls", + "tokio", + "tokio-native-tls", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.19.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f8751d9c1b03c6500c387e96f81f815a4f8e72d142d2d4a9ffa6fedd51ddee7" +dependencies = [ + "indexmap 2.0.0", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "tungstenite" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15fba1a6d6bb030745759a9a2a588bfe8490fc8b4751a277db3a0be1c9ebbf67" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "native-tls", + "rand", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "uncased" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b9bc53168a4be7402ab86c3aad243a84dd7381d09be0eddc81280c1da95ca68" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.25", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winnow" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/README.md b/README.md index 9a75a54..9292a46 100644 --- a/README.md +++ b/README.md @@ -56,9 +56,9 @@ accepted, if it violates these guidelines or [our Code of Conduct](https://githu - [x] Channel creation - [x] Channel deletion - [x] [Channel management (name, description, icon, etc.)](https://github.com/polyphony-chat/chorus/issues/48) - - [ ] [Join and Leave Guilds](https://github.com/polyphony-chat/chorus/issues/45) - - [ ] [Start DMs](https://github.com/polyphony-chat/chorus/issues/45) - - [ ] [Group DM creation, deletion and member management](https://github.com/polyphony-chat/chorus/issues/89) + - [x] [Join and Leave Guilds](https://github.com/polyphony-chat/chorus/issues/45) + - [x] [Start DMs](https://github.com/polyphony-chat/chorus/issues/45) + - [x] [Group DM creation, deletion and member management](https://github.com/polyphony-chat/chorus/issues/89) - [ ] [Deleting messages](https://github.com/polyphony-chat/chorus/issues/91) - [ ] [Message threads](https://github.com/polyphony-chat/chorus/issues/90) - [x] [Reactions](https://github.com/polyphony-chat/chorus/issues/85) diff --git a/src/api/channels/channels.rs b/src/api/channels/channels.rs index 82662e3..de23b4a 100644 --- a/src/api/channels/channels.rs +++ b/src/api/channels/channels.rs @@ -1,6 +1,7 @@ use reqwest::Client; use serde_json::to_string; +use crate::types::AddChannelRecipientSchema; use crate::{ api::LimitType, errors::{ChorusError, ChorusResult}, @@ -105,4 +106,54 @@ impl Channel { .deserialize_response::>(user) .await } + + /// # Reference: + /// Read: + pub async fn add_channel_recipient( + &self, + recipient_id: Snowflake, + user: &mut UserMeta, + add_channel_recipient_schema: Option, + ) -> ChorusResult<()> { + let mut request = Client::new() + .put(format!( + "{}/channels/{}/recipients/{}/", + user.belongs_to.borrow().urls.api, + self.id, + recipient_id + )) + .bearer_auth(user.token()); + if let Some(schema) = add_channel_recipient_schema { + request = request.body(to_string(&schema).unwrap()); + } + ChorusRequest { + request, + limit_type: LimitType::Channel(self.id), + } + .handle_request_as_result(user) + .await + } + + /// # Reference: + /// Read: + pub async fn remove_channel_recipient( + &self, + recipient_id: Snowflake, + user: &mut UserMeta, + ) -> ChorusResult<()> { + let request = Client::new() + .delete(format!( + "{}/channels/{}/recipients/{}/", + user.belongs_to.borrow().urls.api, + self.id, + recipient_id + )) + .bearer_auth(user.token()); + ChorusRequest { + request, + limit_type: LimitType::Channel(self.id), + } + .handle_request_as_result(user) + .await + } } diff --git a/src/api/invites/mod.rs b/src/api/invites/mod.rs new file mode 100644 index 0000000..b766a5b --- /dev/null +++ b/src/api/invites/mod.rs @@ -0,0 +1,73 @@ +use reqwest::Client; +use serde_json::to_string; + +use crate::errors::ChorusResult; +use crate::instance::UserMeta; +use crate::ratelimiter::ChorusRequest; +use crate::types::{CreateChannelInviteSchema, GuildInvite, Invite, Snowflake}; + +impl UserMeta { + /// # Arguments + /// - invite_code: The invite code to accept the invite for. + /// - session_id: The session ID that is accepting the invite, required for guest invites. + /// + /// # Reference: + /// Read + pub async fn accept_invite( + &mut self, + invite_code: &str, + session_id: Option<&str>, + ) -> ChorusResult { + let mut request = ChorusRequest { + request: Client::new() + .post(format!( + "{}/invites/{}/", + self.belongs_to.borrow().urls.api, + invite_code + )) + .bearer_auth(self.token()), + limit_type: super::LimitType::Global, + }; + if session_id.is_some() { + request.request = request + .request + .body(to_string(session_id.unwrap()).unwrap()); + } + request.deserialize_response::(self).await + } + /// Note: Spacebar does not yet implement this endpoint. + pub async fn create_user_invite(&mut self, code: Option<&str>) -> ChorusResult { + ChorusRequest { + request: Client::new() + .post(format!( + "{}/users/@me/invites/", + self.belongs_to.borrow().urls.api + )) + .body(to_string(&code).unwrap()) + .bearer_auth(self.token()), + limit_type: super::LimitType::Global, + } + .deserialize_response::(self) + .await + } + + pub async fn create_guild_invite( + &mut self, + create_channel_invite_schema: CreateChannelInviteSchema, + channel_id: Snowflake, + ) -> ChorusResult { + ChorusRequest { + request: Client::new() + .post(format!( + "{}/channels/{}/invites/", + self.belongs_to.borrow().urls.api, + channel_id + )) + .bearer_auth(self.token()) + .body(to_string(&create_channel_invite_schema).unwrap()), + limit_type: super::LimitType::Channel(channel_id), + } + .deserialize_response::(self) + .await + } +} diff --git a/src/api/mod.rs b/src/api/mod.rs index 0c02b98..9fd954d 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,10 +1,13 @@ pub use channels::messages::*; pub use guilds::*; +pub use invites::*; pub use policies::instance::instance::*; pub use policies::instance::ratelimits::*; +pub use users::*; pub mod auth; pub mod channels; pub mod guilds; +pub mod invites; pub mod policies; pub mod users; diff --git a/src/api/users/channels.rs b/src/api/users/channels.rs new file mode 100644 index 0000000..806bd9f --- /dev/null +++ b/src/api/users/channels.rs @@ -0,0 +1,32 @@ +use reqwest::Client; +use serde_json::to_string; + +use crate::{ + api::LimitType, + errors::ChorusResult, + instance::UserMeta, + ratelimiter::ChorusRequest, + types::{Channel, PrivateChannelCreateSchema}, +}; + +impl UserMeta { + /// Creates a DM channel or group DM channel. + /// + /// # Reference: + /// Read + pub async fn create_private_channel( + &mut self, + create_private_channel_schema: PrivateChannelCreateSchema, + ) -> ChorusResult { + let url = format!("{}/users/@me/channels", self.belongs_to.borrow().urls.api); + ChorusRequest { + request: Client::new() + .post(url) + .bearer_auth(self.token()) + .body(to_string(&create_private_channel_schema).unwrap()), + limit_type: LimitType::Global, + } + .deserialize_response::(self) + .await + } +} diff --git a/src/api/users/guilds.rs b/src/api/users/guilds.rs new file mode 100644 index 0000000..735ed9e --- /dev/null +++ b/src/api/users/guilds.rs @@ -0,0 +1,30 @@ +use reqwest::Client; +use serde_json::to_string; + +use crate::errors::ChorusResult; +use crate::instance::UserMeta; +use crate::ratelimiter::ChorusRequest; +use crate::types::Snowflake; + +impl UserMeta { + /// # Arguments: + /// - lurking: Whether the user is lurking in the guild + /// + /// # Reference: + /// Read + pub async fn leave_guild(&mut self, guild_id: &Snowflake, lurking: bool) -> ChorusResult<()> { + ChorusRequest { + request: Client::new() + .delete(format!( + "{}/users/@me/guilds/{}/", + self.belongs_to.borrow().urls.api, + guild_id + )) + .bearer_auth(self.token()) + .body(to_string(&lurking).unwrap()), + limit_type: crate::api::LimitType::Guild(*guild_id), + } + .handle_request_as_result(self) + .await + } +} diff --git a/src/api/users/mod.rs b/src/api/users/mod.rs index 84ea0ed..ba789ba 100644 --- a/src/api/users/mod.rs +++ b/src/api/users/mod.rs @@ -1,5 +1,9 @@ +pub use channels::*; +pub use guilds::*; pub use relationships::*; pub use users::*; +pub mod channels; +pub mod guilds; pub mod relationships; pub mod users; diff --git a/src/instance.rs b/src/instance.rs index d22d935..0cd65f4 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -88,7 +88,7 @@ impl fmt::Display for Token { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct UserMeta { pub belongs_to: Rc>, pub token: String, diff --git a/src/ratelimiter.rs b/src/ratelimiter.rs index 629c3d7..9227737 100644 --- a/src/ratelimiter.rs +++ b/src/ratelimiter.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use log; +use log::{self, debug}; use reqwest::{Client, RequestBuilder, Response}; use serde::Deserialize; use serde_json::from_str; @@ -37,7 +37,10 @@ impl ChorusRequest { .execute(self.request.build().unwrap()) .await { - Ok(result) => result, + Ok(result) => { + debug!("Request successful: {:?}", result); + result + } Err(error) => { log::warn!("Request failed: {:?}", error); return Err(ChorusError::RequestFailed { @@ -430,6 +433,7 @@ impl ChorusRequest { user: &mut UserMeta, ) -> ChorusResult { let response = self.send_request(user).await?; + debug!("Got response: {:?}", response); let response_text = match response.text().await { Ok(string) => string, Err(e) => { @@ -446,8 +450,8 @@ impl ChorusRequest { Err(e) => { return Err(ChorusError::InvalidResponse { error: format!( - "Error while trying to deserialize the JSON response into T: {}", - e + "Error while trying to deserialize the JSON response into requested type T: {}. JSON Response: {}", + e, response_text ), }) } diff --git a/src/types/config/types/guild_configuration.rs b/src/types/config/types/guild_configuration.rs index a854460..96e6ea8 100644 --- a/src/types/config/types/guild_configuration.rs +++ b/src/types/config/types/guild_configuration.rs @@ -10,7 +10,7 @@ use sqlx::{ database::{HasArguments, HasValueRef}, encode::IsNull, error::BoxDynError, - Decode, Encode, MySql, + Decode, MySql, }; use crate::types::config::types::subconfigs::guild::{ @@ -139,7 +139,7 @@ pub enum GuildFeatures { InvitesClosed, } -#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, Eq)] pub struct GuildFeaturesList(Vec); impl Deref for GuildFeaturesList { diff --git a/src/types/entities/channel.rs b/src/types/entities/channel.rs index 5471e22..01e9752 100644 --- a/src/types/entities/channel.rs +++ b/src/types/entities/channel.rs @@ -11,58 +11,58 @@ use crate::types::{ #[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] pub struct Channel { - pub id: Snowflake, - pub created_at: Option>, - #[serde(rename = "type")] - pub channel_type: ChannelType, - pub guild_id: Option, - pub position: Option, - #[cfg(feature = "sqlx")] - pub permission_overwrites: Option>>, - #[cfg(not(feature = "sqlx"))] - pub permission_overwrites: Option>, - pub name: Option, - pub topic: Option, - pub nsfw: Option, - pub last_message_id: Option, - pub bitrate: Option, - pub user_limit: Option, - pub rate_limit_per_user: Option, - #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub recipients: Option>, - pub icon: Option, - pub owner_id: Option, pub application_id: Option, - pub managed: Option, - pub parent_id: Option, - pub last_pin_timestamp: Option, - pub rtc_region: Option, - pub video_quality_mode: Option, - pub message_count: Option, - pub member_count: Option, - #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub thread_metadata: Option, - #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub member: Option, - pub default_auto_archive_duration: Option, - pub permissions: Option, - pub flags: Option, - pub total_message_sent: Option, - #[cfg(feature = "sqlx")] - pub available_tags: Option>>, - #[cfg(not(feature = "sqlx"))] - pub available_tags: Option>, #[cfg(feature = "sqlx")] pub applied_tags: Option>>, #[cfg(not(feature = "sqlx"))] pub applied_tags: Option>, #[cfg(feature = "sqlx")] + pub available_tags: Option>>, + #[cfg(not(feature = "sqlx"))] + pub available_tags: Option>, + pub bitrate: Option, + #[serde(rename = "type")] + pub channel_type: ChannelType, + pub created_at: Option>, + pub default_auto_archive_duration: Option, + pub default_forum_layout: Option, + #[cfg(feature = "sqlx")] pub default_reaction_emoji: Option>, #[cfg(not(feature = "sqlx"))] pub default_reaction_emoji: Option, - pub default_thread_rate_limit_per_user: Option, pub default_sort_order: Option, - pub default_forum_layout: Option, + pub default_thread_rate_limit_per_user: Option, + pub flags: Option, + pub guild_id: Option, + pub icon: Option, + pub id: Snowflake, + pub last_message_id: Option, + pub last_pin_timestamp: Option, + pub managed: Option, + #[cfg_attr(feature = "sqlx", sqlx(skip))] + pub member: Option, + pub member_count: Option, + pub message_count: Option, + pub name: Option, + pub nsfw: Option, + pub owner_id: Option, + pub parent_id: Option, + #[cfg(feature = "sqlx")] + pub permission_overwrites: Option>>, + #[cfg(not(feature = "sqlx"))] + pub permission_overwrites: Option>, + pub permissions: Option, + pub position: Option, + pub rate_limit_per_user: Option, + #[cfg_attr(feature = "sqlx", sqlx(skip))] + pub recipients: Option>, + pub rtc_region: Option, + #[cfg_attr(feature = "sqlx", sqlx(skip))] + pub thread_metadata: Option, + pub topic: Option, + pub total_message_sent: Option, + pub user_limit: Option, + pub video_quality_mode: Option, } #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] @@ -74,7 +74,7 @@ pub struct Tag { pub emoji_name: Option, } -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd)] pub struct PermissionOverwrite { pub id: Snowflake, #[serde(rename = "type")] diff --git a/src/types/entities/emoji.rs b/src/types/entities/emoji.rs index dbe998b..a0e8d14 100644 --- a/src/types/entities/emoji.rs +++ b/src/types/entities/emoji.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::types::entities::User; use crate::types::Snowflake; -#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)] +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] pub struct Emoji { pub id: Option, diff --git a/src/types/entities/guild.rs b/src/types/entities/guild.rs index 857ed2a..67e7f44 100644 --- a/src/types/entities/guild.rs +++ b/src/types/entities/guild.rs @@ -91,7 +91,7 @@ pub struct Guild { } /// See https://docs.spacebar.chat/routes/#get-/guilds/-guild_id-/bans/-user- -#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] pub struct GuildBan { pub user_id: Snowflake, diff --git a/src/types/entities/invite.rs b/src/types/entities/invite.rs new file mode 100644 index 0000000..6d7b570 --- /dev/null +++ b/src/types/entities/invite.rs @@ -0,0 +1,75 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::types::{Snowflake, WelcomeScreenObject}; + +use super::guild::GuildScheduledEvent; +use super::{Application, Channel, GuildMember, User}; + +/// Represents a code that when used, adds a user to a guild or group DM channel, or creates a relationship between two users. +/// See +#[derive(Debug, Serialize, Deserialize)] +pub struct Invite { + pub approximate_member_count: Option, + pub approximate_presence_count: Option, + pub channel: Option, + pub code: String, + pub created_at: Option>, + pub expires_at: Option>, + pub flags: Option, + pub guild: Option, + pub guild_id: Option, + pub guild_scheduled_event: Option, + #[serde(rename = "type")] + pub invite_type: Option, + pub inviter: Option, + pub max_age: Option, + pub max_uses: Option, + pub stage_instance: Option, + pub target_application: Option, + pub target_type: Option, + pub target_user: Option, + pub temporary: Option, + pub uses: Option, +} + +/// The guild an invite is for. +/// See +#[derive(Debug, Serialize, Deserialize)] +pub struct InviteGuild { + pub id: Snowflake, + pub name: String, + pub icon: Option, + pub splash: Option, + pub verification_level: i32, + pub features: Vec, + pub vanity_url_code: Option, + pub description: Option, + pub banner: Option, + pub premium_subscription_count: Option, + #[serde(rename = "nsfw")] + #[serde(skip_serializing_if = "Option::is_none")] + pub nsfw_deprecated: Option, + pub nsfw_level: NSFWLevel, + pub welcome_screen: Option, +} + +/// See for an explanation on what +/// the levels mean. +#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum NSFWLevel { + Default = 0, + Explicit = 1, + Safe = 2, + AgeRestricted = 3, +} + +/// See +#[derive(Debug, Serialize, Deserialize)] +pub struct InviteStageInstance { + pub members: Vec, + pub participant_count: i32, + pub speaker_count: i32, + pub topic: String, +} diff --git a/src/types/entities/mod.rs b/src/types/entities/mod.rs index b63bb9a..6f1f58b 100644 --- a/src/types/entities/mod.rs +++ b/src/types/entities/mod.rs @@ -8,6 +8,7 @@ pub use emoji::*; pub use guild::*; pub use guild_member::*; pub use integration::*; +pub use invite::*; pub use message::*; pub use relationship::*; pub use role::*; @@ -31,6 +32,7 @@ mod emoji; mod guild; mod guild_member; mod integration; +mod invite; mod message; mod relationship; mod role; diff --git a/src/types/entities/user.rs b/src/types/entities/user.rs index b240fa2..469c45e 100644 --- a/src/types/entities/user.rs +++ b/src/types/entities/user.rs @@ -43,9 +43,9 @@ pub struct User { pub bio: Option, pub theme_colors: Option>, pub phone: Option, - pub nsfw_allowed: bool, - pub premium: bool, - pub purchased_flags: i32, + pub nsfw_allowed: Option, + pub premium: Option, + pub purchased_flags: Option, pub premium_usage_flags: Option, pub disabled: Option, } diff --git a/src/types/schema/channel.rs b/src/types/schema/channel.rs index 2a78142..27c78ee 100644 --- a/src/types/schema/channel.rs +++ b/src/types/schema/channel.rs @@ -1,8 +1,9 @@ +use bitflags::bitflags; use serde::{Deserialize, Serialize}; use crate::types::{entities::PermissionOverwrite, Snowflake}; -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Default, PartialEq, PartialOrd)] #[serde(rename_all = "snake_case")] pub struct ChannelCreateSchema { pub name: String, @@ -26,7 +27,7 @@ pub struct ChannelCreateSchema { pub video_quality_mode: Option, } -#[derive(Debug, Deserialize, Serialize, Clone, Default)] +#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialEq, PartialOrd)] #[serde(rename_all = "snake_case")] pub struct ChannelModifySchema { pub name: Option, @@ -48,7 +49,7 @@ pub struct ChannelModifySchema { pub video_quality_mode: Option, } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] pub struct GetChannelMessagesSchema { /// Between 1 and 100, defaults to 50. pub limit: Option, @@ -56,7 +57,7 @@ pub struct GetChannelMessagesSchema { pub anchor: ChannelMessagesAnchor, } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] #[serde(rename_all = "snake_case")] pub enum ChannelMessagesAnchor { Before(Snowflake), @@ -94,3 +95,56 @@ impl GetChannelMessagesSchema { } } } + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd)] +pub struct CreateChannelInviteSchema { + pub flags: Option, + pub max_age: Option, + pub max_uses: Option, + pub temporary: Option, + pub unique: Option, + pub validate: Option, + pub target_type: Option, + pub target_user_id: Option, + pub target_application_id: Option, +} + +impl Default for CreateChannelInviteSchema { + fn default() -> Self { + Self { + flags: None, + max_age: Some(86400), + max_uses: Some(0), + temporary: Some(false), + unique: Some(false), + validate: None, + target_type: None, + target_user_id: None, + target_application_id: None, + } + } +} + +bitflags! { + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)] + pub struct InviteFlags: u64 { + const GUEST = 1 << 0; + } +} + +#[derive(Debug, Deserialize, Serialize, Clone, Copy, Default, PartialOrd, Ord, PartialEq, Eq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum InviteType { + #[default] + Stream = 1, + EmbeddedApplication = 2, + RoleSubscriptions = 3, + CreatorPage = 4, +} + +/// See +#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialOrd, Ord, PartialEq, Eq)] +pub struct AddChannelRecipientSchema { + pub access_token: Option, + pub nick: Option, +} diff --git a/src/types/schema/user.rs b/src/types/schema/user.rs index ab85a16..c8cf5bb 100644 --- a/src/types/schema/user.rs +++ b/src/types/schema/user.rs @@ -1,5 +1,9 @@ +use std::collections::HashMap; + use serde::{Deserialize, Serialize}; +use crate::types::Snowflake; + #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] pub struct UserModifySchema { @@ -14,3 +18,17 @@ pub struct UserModifySchema { pub email: Option, pub discriminator: Option, } + +/// # Attributes: +/// - recipients: The users to include in the private channel +/// - access_tokens: The access tokens of users that have granted your app the `gdm.join` scope. Only usable for OAuth2 requests (which can only create group DMs). +/// - nicks: A mapping of user IDs to their respective nicknames. Only usable for OAuth2 requests (which can only create group DMs). +/// +/// # Reference: +/// Read: +#[derive(Debug, Deserialize, Serialize)] +pub struct PrivateChannelCreateSchema { + pub recipients: Option>, + pub access_tokens: Option>, + pub nicks: Option>, +} diff --git a/src/types/utils/snowflake.rs b/src/types/utils/snowflake.rs index 6176ea5..77514a1 100644 --- a/src/types/utils/snowflake.rs +++ b/src/types/utils/snowflake.rs @@ -12,7 +12,7 @@ const EPOCH: i64 = 1420070400000; /// Unique identifier including a timestamp. /// See https://discord.com/developers/docs/reference#snowflakes -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[cfg_attr(feature = "sqlx", derive(Type))] #[cfg_attr(feature = "sqlx", sqlx(transparent))] pub struct Snowflake(u64); diff --git a/tests/channel.rs b/tests/channels.rs similarity index 56% rename from tests/channel.rs rename to tests/channels.rs index 06a3330..31bc543 100644 --- a/tests/channel.rs +++ b/tests/channels.rs @@ -1,6 +1,6 @@ use chorus::types::{ self, Channel, GetChannelMessagesSchema, MessageSendSchema, PermissionFlags, - PermissionOverwrite, Snowflake, + PermissionOverwrite, PrivateChannelCreateSchema, RelationshipType, Snowflake, }; mod common; @@ -136,3 +136,81 @@ async fn get_channel_messages() { common::teardown(bundle).await } + +#[tokio::test] +async fn create_dm() { + let mut bundle = common::setup().await; + 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])), + access_tokens: None, + nicks: None, + }; + let dm_channel = user + .create_private_channel(private_channel_create_schema) + .await + .unwrap(); + assert!(dm_channel.recipients.is_some()); + assert_eq!( + dm_channel.recipients.as_ref().unwrap().get(0).unwrap().id, + other_user.object.id + ); + assert_eq!( + dm_channel.recipients.as_ref().unwrap().get(1).unwrap().id, + user.object.id + ); + common::teardown(bundle).await; +} + +// #[tokio::test] +// 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 user = &mut bundle.user; + let private_channel_create_schema = PrivateChannelCreateSchema { + recipients: Some(Vec::from([other_user.object.id, third_user.object.id])), + access_tokens: None, + nicks: None, + }; + let dm_channel = user + .create_private_channel(private_channel_create_schema) + .await + .unwrap(); // Creates the Channel and stores the response Channel object + dm_channel + .remove_channel_recipient(other_user.object.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) + .await + .unwrap(); + user.modify_user_relationship(other_user.object.id, RelationshipType::Friends) + .await + .unwrap(); + third_user + .modify_user_relationship(user.object.id, RelationshipType::Friends) + .await + .unwrap(); + user.modify_user_relationship(third_user.object.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) + .await + .unwrap(); + assert!(dm_channel.recipients.is_some()); + assert_eq!( + dm_channel.recipients.as_ref().unwrap().get(0).unwrap().id, + other_user.object.id + ); + assert_eq!( + dm_channel.recipients.as_ref().unwrap().get(1).unwrap().id, + user.object.id + ); +} diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 0b48f89..fc4a19c 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -7,8 +7,9 @@ use chorus::{ UrlBundle, }; +#[allow(dead_code)] #[derive(Debug)] -pub struct TestBundle { +pub(crate) struct TestBundle { pub urls: UrlBundle, pub user: UserMeta, pub instance: Instance, @@ -17,8 +18,24 @@ pub struct TestBundle { pub channel: Channel, } +impl TestBundle { + #[allow(unused)] + pub(crate) async fn create_user(&mut self, username: &str) -> UserMeta { + let register_schema = RegisterSchema { + username: username.to_string(), + consent: true, + date_of_birth: Some("2000-01-01".to_string()), + ..Default::default() + }; + self.instance + .register_account(®ister_schema) + .await + .unwrap() + } +} + // Set up a test by creating an Instance and a User. Reduces Test boilerplate. -pub async fn setup() -> TestBundle { +pub(crate) async fn setup() -> TestBundle { let urls = UrlBundle::new( "http://localhost:3001/api".to_string(), "ws://localhost:3001".to_string(), @@ -93,7 +110,7 @@ pub async fn setup() -> TestBundle { // Teardown method to clean up after a test. #[allow(dead_code)] -pub async fn teardown(mut bundle: TestBundle) { +pub(crate) async fn teardown(mut bundle: TestBundle) { Guild::delete(&mut bundle.user, bundle.guild.id) .await .unwrap(); diff --git a/tests/guild.rs b/tests/guilds.rs similarity index 100% rename from tests/guild.rs rename to tests/guilds.rs diff --git a/tests/invites.rs b/tests/invites.rs new file mode 100644 index 0000000..b6206b9 --- /dev/null +++ b/tests/invites.rs @@ -0,0 +1,25 @@ +use chorus::types::CreateChannelInviteSchema; + +mod common; + +#[tokio::test] +async fn create_accept_invite() { + let mut bundle = common::setup().await; + let channel = bundle.channel.clone(); + let mut user = bundle.user.clone(); + let create_channel_invite_schema = CreateChannelInviteSchema::default(); + let mut other_user = bundle.create_user("testuser1312").await; + assert!(chorus::types::Guild::get(bundle.guild.id, &mut other_user) + .await + .is_err()); + let invite = user + .create_guild_invite(create_channel_invite_schema, channel.id) + .await + .unwrap(); + + other_user.accept_invite(&invite.code, None).await.unwrap(); + assert!(chorus::types::Guild::get(bundle.guild.id, &mut other_user) + .await + .is_ok()); + common::teardown(bundle).await; +} diff --git a/tests/member.rs b/tests/members.rs similarity index 100% rename from tests/member.rs rename to tests/members.rs diff --git a/tests/message.rs b/tests/messages.rs similarity index 100% rename from tests/message.rs rename to tests/messages.rs diff --git a/tests/relationships.rs b/tests/relationships.rs index 5578efd..00ef9cf 100644 --- a/tests/relationships.rs +++ b/tests/relationships.rs @@ -1,20 +1,12 @@ -use chorus::types::{self, RegisterSchema, Relationship, RelationshipType}; +use chorus::types::{self, Relationship, RelationshipType}; mod common; #[tokio::test] async fn test_get_mutual_relationships() { - let register_schema = RegisterSchema { - username: "integrationtestuser2".to_string(), - consent: true, - date_of_birth: Some("2000-01-01".to_string()), - ..Default::default() - }; - let mut bundle = common::setup().await; - let belongs_to = &mut bundle.instance; + let mut other_user = bundle.create_user("integrationtestuser2").await; let user = &mut bundle.user; - let mut other_user = belongs_to.register_account(®ister_schema).await.unwrap(); let friend_request_schema = types::FriendRequestSendSchema { username: user.object.username.clone(), discriminator: Some(user.object.discriminator.clone()), @@ -30,17 +22,9 @@ async fn test_get_mutual_relationships() { #[tokio::test] async fn test_get_relationships() { - let register_schema = RegisterSchema { - username: "integrationtestuser2".to_string(), - consent: true, - date_of_birth: Some("2000-01-01".to_string()), - ..Default::default() - }; - let mut bundle = common::setup().await; - let belongs_to = &mut bundle.instance; + let mut other_user = bundle.create_user("integrationtestuser2").await; let user = &mut bundle.user; - let mut other_user = belongs_to.register_account(®ister_schema).await.unwrap(); let friend_request_schema = types::FriendRequestSendSchema { username: user.object.username.clone(), discriminator: Some(user.object.discriminator.clone()), @@ -56,17 +40,9 @@ async fn test_get_relationships() { #[tokio::test] async fn test_modify_relationship_friends() { - let register_schema = RegisterSchema { - username: "integrationtestuser2".to_string(), - consent: true, - date_of_birth: Some("2000-01-01".to_string()), - ..Default::default() - }; - let mut bundle = common::setup().await; - let belongs_to = &mut bundle.instance; + let mut other_user = bundle.create_user("integrationtestuser2").await; let user = &mut bundle.user; - let mut other_user = belongs_to.register_account(®ister_schema).await.unwrap(); let _ = other_user .modify_user_relationship(user.object.id, types::RelationshipType::Friends) .await; @@ -105,17 +81,9 @@ async fn test_modify_relationship_friends() { #[tokio::test] async fn test_modify_relationship_block() { - let register_schema = RegisterSchema { - username: "integrationtestuser2".to_string(), - consent: true, - date_of_birth: Some("2000-01-01".to_string()), - ..Default::default() - }; - let mut bundle = common::setup().await; - let belongs_to = &mut bundle.instance; + let mut other_user = bundle.create_user("integrationtestuser2").await; let user = &mut bundle.user; - let mut other_user = belongs_to.register_account(®ister_schema).await.unwrap(); let _ = other_user .modify_user_relationship(user.object.id, types::RelationshipType::Blocked) .await; From df08bc33ac7533f5c30e18c2351ee90e3b45eaf7 Mon Sep 17 00:00:00 2001 From: Flori <39242991+bitfl0wer@users.noreply.github.com> Date: Mon, 17 Jul 2023 20:06:57 +0200 Subject: [PATCH 18/23] Bump crate versions (#158) --- Cargo.lock | 80 +++++++++++++++++++++++++++--------------------------- Cargo.toml | 26 +++++++++--------- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eec51aa..c90b667 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,7 +77,7 @@ checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", ] [[package]] @@ -352,7 +352,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.25", + "syn 2.0.26", ] [[package]] @@ -363,7 +363,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.25", + "syn 2.0.26", ] [[package]] @@ -560,7 +560,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", ] [[package]] @@ -901,9 +901,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" @@ -1177,7 +1177,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", ] [[package]] @@ -1248,9 +1248,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b27ab7be369122c218afc2079489cdcb4b517c0a3fc386ff11e1fedfcc2b35" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "pem" @@ -1293,7 +1293,7 @@ checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", ] [[package]] @@ -1395,18 +1395,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.64" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.29" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" dependencies = [ "proc-macro2", ] @@ -1604,9 +1604,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "schannel" @@ -1619,9 +1619,9 @@ dependencies = [ [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" @@ -1674,14 +1674,14 @@ checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", ] [[package]] name = "serde_json" -version = "1.0.102" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5062a995d481b2308b6064e9af76011f2921c35f97b0468811ed9f6cd91dfed" +checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" dependencies = [ "itoa", "ryu", @@ -1696,7 +1696,7 @@ checksum = "1d89a8107374290037607734c0b73a85db7ed80cae314b3c5791f192a496e731" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", ] [[package]] @@ -1713,9 +1713,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f02d8aa6e3c385bf084924f660ce2a3a6bd333ba55b35e8590b321f35d88513" +checksum = "21e47d95bc83ed33b2ecf84f4187ad1ab9685d18ff28db000c99deac8ce180e3" dependencies = [ "base64 0.21.2", "chrono", @@ -1729,14 +1729,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc7d5d3932fb12ce722ee5e64dd38c504efba37567f0c402f6ca728c3b8b070" +checksum = "ea3cee93715c2e266b9338b7544da68a9f24e227722ba482bd1c024367c77c65" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", ] [[package]] @@ -1936,9 +1936,9 @@ dependencies = [ [[package]] name = "stringprep" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +checksum = "db3737bde7edce97102e0e2b15365bf7a20bfdb5f60f4f9e8d7004258a51a8da" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -1969,9 +1969,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.25" +version = "2.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" +checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" dependencies = [ "proc-macro2", "quote", @@ -2009,7 +2009,7 @@ checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", ] [[package]] @@ -2093,7 +2093,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", ] [[package]] @@ -2162,9 +2162,9 @@ checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" [[package]] name = "toml_edit" -version = "0.19.13" +version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f8751d9c1b03c6500c387e96f81f815a4f8e72d142d2d4a9ffa6fedd51ddee7" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ "indexmap 2.0.0", "toml_datetime", @@ -2197,7 +2197,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", ] [[package]] @@ -2267,9 +2267,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-normalization" @@ -2375,7 +2375,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", "wasm-bindgen-shared", ] @@ -2409,7 +2409,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/Cargo.toml b/Cargo.toml index a0db82c..2e51270 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,34 +10,34 @@ backend = ["poem", "sqlx"] client = [] [dependencies] -tokio = {version = "1.28.1"} -serde = {version = "1.0.163", features = ["derive"]} -serde_json = {version= "1.0.96", features = ["raw_value"]} +tokio = {version = "1.29.1"} +serde = {version = "1.0.171", features = ["derive"]} +serde_json = {version= "1.0.103", features = ["raw_value"]} serde-aux = "4.2.0" serde_with = "3.0.0" -serde_repr = "0.1.12" -reqwest = {version = "0.11.16", features = ["multipart"]} -url = "2.3.1" -chrono = {version = "0.4.24", features = ["serde"]} -regex = "1.7.3" +serde_repr = "0.1.14" +reqwest = {version = "0.11.18", features = ["multipart"]} +url = "2.4.0" +chrono = {version = "0.4.26", features = ["serde"]} +regex = "1.9.1" custom_error = "1.9.2" native-tls = "0.2.11" tokio-tungstenite = {version = "0.19.0", features = ["native-tls"]} futures-util = "0.3.28" http = "0.2.9" -openssl = "0.10.52" +openssl = "0.10.55" base64 = "0.21.2" hostname = "0.3.1" -bitflags = { version = "2.2.1", features = ["serde"] } +bitflags = { version = "2.3.3", features = ["serde"] } lazy_static = "1.4.0" -poem = { version = "1.3.55", optional = true } +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 } -thiserror = "1.0.40" +thiserror = "1.0.43" jsonwebtoken = "8.3.0" log = "0.4.19" async-trait = "0.1.71" [dev-dependencies] -tokio = {version = "1.28.1", features = ["full"]} +tokio = {version = "1.29.1", features = ["full"]} lazy_static = "1.4.0" rusty-hook = "0.11.2" From 85fb24cd8c969d34a867375a54742c278c3ddf4f Mon Sep 17 00:00:00 2001 From: Flori <39242991+bitfl0wer@users.noreply.github.com> Date: Mon, 17 Jul 2023 23:26:25 +0200 Subject: [PATCH 19/23] Fix: message file attachment duplication (#159) * make send() take ownership of MessageSendSchema * Remove message file attachment duplication * Remove superfluous docstrings --- src/api/channels/messages.rs | 41 +++++++----------------------------- tests/channels.rs | 3 +-- tests/messages.rs | 12 ++++------- 3 files changed, 13 insertions(+), 43 deletions(-) diff --git a/src/api/channels/messages.rs b/src/api/channels/messages.rs index dbffaa8..6beec7f 100644 --- a/src/api/channels/messages.rs +++ b/src/api/channels/messages.rs @@ -6,34 +6,22 @@ use serde_json::to_string; use crate::api::LimitType; use crate::instance::UserMeta; use crate::ratelimiter::ChorusRequest; -use crate::types::{Message, MessageSendSchema, PartialDiscordFileAttachment, Snowflake}; +use crate::types::{Message, MessageSendSchema, Snowflake}; impl Message { - /** - Sends a message to the Spacebar server. - # Arguments - * `url_api` - The URL of the Spacebar server's API. - * `message` - The [`Message`] that will be sent to the Spacebar server. - * `limits_user` - The [`Limits`] of the user. - * `limits_instance` - The [`Limits`] of the instance. - * `requester` - The [`LimitedRequester`] that will be used to make requests to the Spacebar server. - # Errors - * [`ChorusLibError`] - If the message cannot be sent. - */ pub async fn send( user: &mut UserMeta, channel_id: Snowflake, - message: &mut MessageSendSchema, - files: Option>, + mut message: MessageSendSchema, ) -> Result { let url_api = user.belongs_to.borrow().urls.api.clone(); - if files.is_none() { + if message.attachments.is_none() { let chorus_request = ChorusRequest { request: Client::new() .post(format!("{}/channels/{}/messages/", url_api, channel_id)) .bearer_auth(user.token()) - .body(to_string(message).unwrap()), + .body(to_string(&message).unwrap()), limit_type: LimitType::Channel(channel_id), }; chorus_request.deserialize_response::(user).await @@ -42,12 +30,12 @@ impl Message { attachment.get_mut(index).unwrap().set_id(index as i16); } let mut form = reqwest::multipart::Form::new(); - let payload_json = to_string(message).unwrap(); + let payload_json = to_string(&message).unwrap(); let payload_field = reqwest::multipart::Part::text(payload_json); form = form.part("payload_json", payload_field); - for (index, attachment) in files.unwrap().into_iter().enumerate() { + 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 part_name = format!("files[{}]", index); @@ -78,24 +66,11 @@ impl Message { } impl UserMeta { - /// Shorthand call for Message::send() - /** - Sends a message to the Spacebar server. - # Arguments - * `url_api` - The URL of the Spacebar server's API. - * `message` - The [`Message`] that will be sent to the Spacebar server. - * `limits_user` - The [`Limits`] of the user. - * `limits_instance` - The [`Limits`] of the instance. - * `requester` - The [`LimitedRequester`] that will be used to make requests to the Spacebar server. - # Errors - * [`ChorusLibError`] - If the message cannot be sent. - */ pub async fn send_message( &mut self, - message: &mut MessageSendSchema, + message: MessageSendSchema, channel_id: Snowflake, - files: Option>, ) -> Result { - Message::send(self, channel_id, message, files).await + Message::send(self, channel_id, message).await } } diff --git a/tests/channels.rs b/tests/channels.rs index 31bc543..e2838be 100644 --- a/tests/channels.rs +++ b/tests/channels.rs @@ -89,12 +89,11 @@ async fn get_channel_messages() { let _ = bundle .user .send_message( - &mut MessageSendSchema { + MessageSendSchema { content: Some("A Message!".to_string()), ..Default::default() }, bundle.channel.id, - None, ) .await .unwrap(); diff --git a/tests/messages.rs b/tests/messages.rs index 94f3734..af6ee5b 100644 --- a/tests/messages.rs +++ b/tests/messages.rs @@ -8,13 +8,13 @@ mod common; #[tokio::test] async fn send_message() { let mut bundle = common::setup().await; - let mut message = types::MessageSendSchema { + let message = types::MessageSendSchema { content: Some("A Message!".to_string()), ..Default::default() }; let _ = bundle .user - .send_message(&mut message, bundle.channel.id, None) + .send_message(message, bundle.channel.id) .await .unwrap(); common::teardown(bundle).await @@ -45,7 +45,7 @@ async fn send_message_attachment() { content: buffer, }; - let mut message = types::MessageSendSchema { + let message = types::MessageSendSchema { content: Some("trans rights now".to_string()), attachments: Some(vec![attachment.clone()]), ..Default::default() @@ -55,11 +55,7 @@ async fn send_message_attachment() { let _arg = Some(&vec_attach); bundle .user - .send_message( - &mut message, - bundle.channel.id, - Some(vec![attachment.clone()]), - ) + .send_message(message, bundle.channel.id) .await .unwrap(); common::teardown(bundle).await From 776efce4dcd7012a0555bd4c316d16c2b1c8d91e Mon Sep 17 00:00:00 2001 From: SpecificProtagonist Date: Fri, 21 Jul 2023 13:59:40 +0200 Subject: [PATCH 20/23] Fix gateway heartbeat blocking (#162) fix gateway heartbeat blocking --- Cargo.toml | 2 +- src/gateway.rs | 72 +++++++++++++++++++-------------------- src/types/events/hello.rs | 3 +- 3 files changed, 37 insertions(+), 40 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2e51270..4126146 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ backend = ["poem", "sqlx"] client = [] [dependencies] -tokio = {version = "1.29.1"} +tokio = {version = "1.29.1", features = ["macros"]} serde = {version = "1.0.171", features = ["derive"]} serde_json = {version= "1.0.103", features = ["raw_value"]} serde-aux = "4.2.0" diff --git a/src/gateway.rs b/src/gateway.rs index 840c4b0..9e4eb9c 100644 --- a/src/gateway.rs +++ b/src/gateway.rs @@ -4,6 +4,8 @@ use crate::types; use crate::types::WebSocketEvent; use async_trait::async_trait; use std::sync::Arc; +use std::time::Duration; +use tokio::time::sleep_until; use futures_util::stream::SplitSink; use futures_util::stream::SplitStream; @@ -12,7 +14,6 @@ use futures_util::StreamExt; use log::{info, trace, warn}; use native_tls::TlsConnector; use tokio::net::TcpStream; -use tokio::sync::mpsc::error::TryRecvError; use tokio::sync::mpsc::Sender; use tokio::sync::Mutex; use tokio::task; @@ -71,7 +72,7 @@ const GATEWAY_CALL_SYNC: u8 = 13; const GATEWAY_LAZY_REQUEST: u8 = 14; /// The amount of time we wait for a heartbeat ack before resending our heartbeat in ms -const HEARTBEAT_ACK_TIMEOUT: u128 = 2000; +const HEARTBEAT_ACK_TIMEOUT: u64 = 2000; /// Represents a messsage received from the gateway. This will be either a [GatewayReceivePayload], containing events, or a [GatewayError]. /// This struct is used internally when handling messages. @@ -327,7 +328,7 @@ impl Gateway { let mut gateway = Gateway { events: shared_events.clone(), heartbeat_handler: HeartbeatHandler::new( - gateway_hello.heartbeat_interval, + Duration::from_millis(gateway_hello.heartbeat_interval), shared_websocket_send.clone(), kill_send.subscribe(), ), @@ -626,8 +627,8 @@ impl Gateway { /// Handles sending heartbeats to the gateway in another thread #[allow(dead_code)] // FIXME: Remove this, once HeartbeatHandler is used struct HeartbeatHandler { - /// The heartbeat interval in milliseconds - pub heartbeat_interval: u128, + /// How ofter heartbeats need to be sent at a minimum + pub heartbeat_interval: Duration, /// The send channel for the heartbeat thread pub send: Sender, /// The handle of the thread @@ -636,7 +637,7 @@ struct HeartbeatHandler { impl HeartbeatHandler { pub fn new( - heartbeat_interval: u128, + heartbeat_interval: Duration, websocket_tx: Arc< Mutex< SplitSink< @@ -680,7 +681,7 @@ impl HeartbeatHandler { >, >, >, - heartbeat_interval: u128, + heartbeat_interval: Duration, mut receive: tokio::sync::mpsc::Receiver, mut kill_receive: tokio::sync::broadcast::Receiver<()>, ) { @@ -689,49 +690,46 @@ impl HeartbeatHandler { let mut last_seq_number: Option = None; loop { - let should_shutdown = kill_receive.try_recv().is_ok(); - if should_shutdown { + if kill_receive.try_recv().is_ok() { trace!("GW: Closing heartbeat task"); break; } - let mut should_send; + let timeout = if last_heartbeat_acknowledged { + heartbeat_interval + } else { + // If the server hasn't acknowledged our heartbeat we should resend it + Duration::from_millis(HEARTBEAT_ACK_TIMEOUT) + }; - let time_to_send = last_heartbeat_timestamp.elapsed().as_millis() >= heartbeat_interval; + let mut should_send = false; - should_send = time_to_send; - - let received_communication: Result = - receive.try_recv(); - if let Ok(communication) = received_communication { - // If we received a seq number update, use that as the last seq number - if communication.sequence_number.is_some() { - last_seq_number = communication.sequence_number; + tokio::select! { + () = sleep_until(last_heartbeat_timestamp + timeout) => { + should_send = true; } + Some(communication) = receive.recv() => { + // If we received a seq number update, use that as the last seq number + if communication.sequence_number.is_some() { + last_seq_number = communication.sequence_number; + } - if let Some(op_code) = communication.op_code { - match op_code { - GATEWAY_HEARTBEAT => { - // As per the api docs, if the server sends us a Heartbeat, that means we need to respond with a heartbeat immediately - should_send = true; + if let Some(op_code) = communication.op_code { + match op_code { + GATEWAY_HEARTBEAT => { + // As per the api docs, if the server sends us a Heartbeat, that means we need to respond with a heartbeat immediately + should_send = true; + } + GATEWAY_HEARTBEAT_ACK => { + // The server received our heartbeat + last_heartbeat_acknowledged = true; + } + _ => {} } - GATEWAY_HEARTBEAT_ACK => { - // The server received our heartbeat - last_heartbeat_acknowledged = true; - } - _ => {} } } } - // If the server hasn't acknowledged our heartbeat we should resend it - if !last_heartbeat_acknowledged - && last_heartbeat_timestamp.elapsed().as_millis() > HEARTBEAT_ACK_TIMEOUT - { - should_send = true; - info!("GW: Timed out waiting for a heartbeat ack, resending"); - } - if should_send { trace!("GW: Sending Heartbeat.."); diff --git a/src/types/events/hello.rs b/src/types/events/hello.rs index e370f9c..44f1e4f 100644 --- a/src/types/events/hello.rs +++ b/src/types/events/hello.rs @@ -14,8 +14,7 @@ impl WebSocketEvent for GatewayHello {} /// Contains info on how often the client should send heartbeats to the server; pub struct HelloData { /// How often a client should send heartbeats, in milliseconds - // u128 because std used u128s for milliseconds - pub heartbeat_interval: u128, + pub heartbeat_interval: u64, } impl WebSocketEvent for HelloData {} From ccf44a53757485de4f8242d105671047ead5e913 Mon Sep 17 00:00:00 2001 From: Flori <39242991+bitfl0wer@users.noreply.github.com> Date: Fri, 21 Jul 2023 15:35:31 +0200 Subject: [PATCH 21/23] Auto updating structs (#163) * Gateway fields don't need to be pub * Add store to Gateway * Add UpdateMessage trait * Proof of concept: impl UpdateMessage for Channel * Start working on auto updating structs * Send entity updates over watch channel * Add id to UpdateMessage * Create trait Updateable * Add documentation * add gateway test * Complete test * Impl UpdateMessage::update() for ChannelUpdate * Impl UpdateMessage::update() for ChannelUpdate Co-authored by: SpecificProtagonist * make channel::modify no longer mutate Channel * change modify call * remove unused imports * Allow dead code with TODO to remove it * fix channel::modify test * Update src/gateway.rs Co-authored-by: SpecificProtagonist --------- Co-authored-by: SpecificProtagonist --- src/api/channels/channels.rs | 8 ++-- src/gateway.rs | 73 ++++++++++++++++++++++++++++------- src/types/entities/channel.rs | 7 ++++ src/types/events/channel.rs | 11 ++++++ src/types/events/mod.rs | 24 ++++++++++++ tests/channels.rs | 7 ++-- tests/gateway.rs | 26 ++++++++++++- 7 files changed, 133 insertions(+), 23 deletions(-) diff --git a/src/api/channels/channels.rs b/src/api/channels/channels.rs index de23b4a..df8e290 100644 --- a/src/api/channels/channels.rs +++ b/src/api/channels/channels.rs @@ -64,11 +64,11 @@ impl Channel { /// /// A `Result` that contains a `Channel` object if the request was successful, or an `ChorusLibError` if an error occurred during the request. pub async fn modify( - &mut self, + &self, modify_data: ChannelModifySchema, channel_id: Snowflake, user: &mut UserMeta, - ) -> ChorusResult<()> { + ) -> ChorusResult { let chorus_request = ChorusRequest { request: Client::new() .patch(format!( @@ -80,9 +80,7 @@ impl Channel { .body(to_string(&modify_data).unwrap()), limit_type: LimitType::Channel(channel_id), }; - let new_channel = chorus_request.deserialize_response::(user).await?; - let _ = std::mem::replace(self, new_channel); - Ok(()) + chorus_request.deserialize_response::(user).await } pub async fn messages( diff --git a/src/gateway.rs b/src/gateway.rs index 9e4eb9c..a9a3749 100644 --- a/src/gateway.rs +++ b/src/gateway.rs @@ -1,10 +1,14 @@ use crate::errors::GatewayError; use crate::gateway::events::Events; -use crate::types; -use crate::types::WebSocketEvent; +use crate::types::{self, Channel, ChannelUpdate, Snowflake}; +use crate::types::{UpdateMessage, WebSocketEvent}; use async_trait::async_trait; +use std::any::Any; +use std::collections::HashMap; +use std::fmt::Debug; use std::sync::Arc; use std::time::Duration; +use tokio::sync::watch; use tokio::time::sleep_until; use futures_util::stream::SplitSink; @@ -163,6 +167,12 @@ pub struct GatewayHandle { pub handle: JoinHandle<()>, /// Tells gateway tasks to close kill_send: tokio::sync::broadcast::Sender<()>, + store: Arc>>>, +} + +/// An entity type which is supposed to be updateable via the Gateway. This is implemented for all such types chorus supports, implementing it for your own types is likely a mistake. +pub trait Updateable: 'static + Send + Sync { + fn id(&self) -> Snowflake; } impl GatewayHandle { @@ -186,6 +196,27 @@ impl GatewayHandle { .unwrap(); } + pub async fn observe(&self, object: T) -> watch::Receiver { + let mut store = self.store.lock().await; + if let Some(channel) = store.get(&object.id()) { + let (_, rx) = channel + .downcast_ref::<(watch::Sender, watch::Receiver)>() + .unwrap_or_else(|| { + panic!( + "Snowflake {} already exists in the store, but it is not of type T.", + object.id() + ) + }); + rx.clone() + } else { + let id = object.id(); + let channel = watch::channel(object); + let receiver = channel.1.clone(); + store.insert(id, Box::new(channel)); + receiver + } + } + /// Sends an identify event to the gateway pub async fn send_identify(&self, to_send: types::GatewayIdentifyPayload) { let to_send_value = serde_json::to_value(&to_send).unwrap(); @@ -263,9 +294,9 @@ impl GatewayHandle { } pub struct Gateway { - pub events: Arc>, + events: Arc>, heartbeat_handler: HeartbeatHandler, - pub websocket_send: Arc< + websocket_send: Arc< Mutex< SplitSink< WebSocketStream>, @@ -273,8 +304,9 @@ pub struct Gateway { >, >, >, - pub websocket_receive: SplitStream>>, + websocket_receive: SplitStream>>, kill_send: tokio::sync::broadcast::Sender<()>, + store: Arc>>>, } impl Gateway { @@ -325,6 +357,8 @@ impl Gateway { let events = Events::default(); let shared_events = Arc::new(Mutex::new(events)); + let store = Arc::new(Mutex::new(HashMap::new())); + let mut gateway = Gateway { events: shared_events.clone(), heartbeat_handler: HeartbeatHandler::new( @@ -335,6 +369,7 @@ impl Gateway { websocket_send: shared_websocket_send.clone(), websocket_receive, kill_send: kill_send.clone(), + store: store.clone(), }; // Now we can continuously check for messages in a different task, since we aren't going to receive another hello @@ -348,6 +383,7 @@ impl Gateway { websocket_send: shared_websocket_send.clone(), handle, kill_send: kill_send.clone(), + store, }) } @@ -379,6 +415,7 @@ impl Gateway { /// Deserializes and updates a dispatched event, when we already know its type; /// (Called for every event in handle_message) + #[allow(dead_code)] // TODO: Remove this allow annotation async fn handle_event<'a, T: WebSocketEvent + serde::Deserialize<'a>>( data: &'a str, event: &mut GatewayEvent, @@ -431,17 +468,25 @@ impl Gateway { trace!("Gateway: Received {event_name}"); macro_rules! handle { - ($($name:literal => $($path:ident).+),*) => { + ($($name:literal => $($path:ident).+ $( $message_type:ty: $update_type:ty)?),*) => { match event_name.as_str() { $($name => { let event = &mut self.events.lock().await.$($path).+; - - let result = - Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event) - .await; - - if let Err(err) = result { - warn!("Failed to parse gateway event {event_name} ({err})"); + match serde_json::from_str(gateway_payload.event_data.unwrap().get()) { + Err(err) => warn!("Failed to parse gateway event {event_name} ({err})"), + Ok(message) => { + $( + let message: $message_type = message; + if let Some(to_update) = self.store.lock().await.get(&message.id()) { + if let Some((tx, _)) = to_update.downcast_ref::<(watch::Sender<$update_type>, watch::Receiver<$update_type>)>() { + tx.send_modify(|object| message.update(object)); + } else { + warn!("Received {} for {}, but it has been observed to be a different type!", $name, message.id()) + } + } + )? + event.notify(message).await; + } } },)* "RESUMED" => (), @@ -482,7 +527,7 @@ impl Gateway { "AUTO_MODERATION_RULE_DELETE" => auto_moderation.rule_delete, "AUTO_MODERATION_ACTION_EXECUTION" => auto_moderation.action_execution, "CHANNEL_CREATE" => channel.create, - "CHANNEL_UPDATE" => channel.update, + "CHANNEL_UPDATE" => channel.update ChannelUpdate: Channel, "CHANNEL_UNREAD_UPDATE" => channel.unread_update, "CHANNEL_DELETE" => channel.delete, "CHANNEL_PINS_UPDATE" => channel.pins_update, diff --git a/src/types/entities/channel.rs b/src/types/entities/channel.rs index 01e9752..aac57b6 100644 --- a/src/types/entities/channel.rs +++ b/src/types/entities/channel.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_aux::prelude::deserialize_string_from_number; use serde_repr::{Deserialize_repr, Serialize_repr}; +use crate::gateway::Updateable; use crate::types::{ entities::{GuildMember, User}, utils::Snowflake, @@ -65,6 +66,12 @@ pub struct Channel { pub video_quality_mode: Option, } +impl Updateable for Channel { + fn id(&self) -> Snowflake { + self.id + } +} + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] pub struct Tag { pub id: Snowflake, diff --git a/src/types/events/channel.rs b/src/types/events/channel.rs index 99c7640..b595d57 100644 --- a/src/types/events/channel.rs +++ b/src/types/events/channel.rs @@ -3,6 +3,8 @@ use crate::types::{entities::Channel, Snowflake}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use super::UpdateMessage; + #[derive(Debug, Default, Deserialize, Serialize)] /// See https://discord.com/developers/docs/topics/gateway-events#channel-pins-update pub struct ChannelPinsUpdate { @@ -31,6 +33,15 @@ pub struct ChannelUpdate { impl WebSocketEvent for ChannelUpdate {} +impl UpdateMessage for ChannelUpdate { + fn update(&self, object_to_update: &mut Channel) { + *object_to_update = self.channel.clone(); + } + fn id(&self) -> Snowflake { + self.channel.id + } +} + #[derive(Debug, Default, Deserialize, Serialize, Clone)] /// Officially undocumented. /// Sends updates to client about a new message with its id diff --git a/src/types/events/mod.rs b/src/types/events/mod.rs index 6333544..42e3912 100644 --- a/src/types/events/mod.rs +++ b/src/types/events/mod.rs @@ -26,6 +26,10 @@ pub use user::*; pub use voice::*; pub use webhooks::*; +use crate::gateway::Updateable; + +use super::Snowflake; + mod application; mod auto_moderation; mod call; @@ -95,3 +99,23 @@ pub struct GatewayReceivePayload<'a> { } impl<'a> WebSocketEvent for GatewayReceivePayload<'a> {} + +/// An [`UpdateMessage`] represents a received Gateway Message which contains updated +/// information for an [`Updateable`] of Type T. +/// # Example: +/// ```rs +/// impl UpdateMessage for ChannelUpdate { +/// fn update(...) {...} +/// fn id(...) {...} +/// } +/// ``` +/// This would imply, that the [`WebSocketEvent`] "[`ChannelUpdate`]" contains new/updated information +/// about a [`Channel`]. The update method describes how this new information will be turned into +/// a [`Channel`] object. +pub(crate) trait UpdateMessage: Clone +where + T: Updateable, +{ + fn update(&self, object_to_update: &mut T); + fn id(&self) -> Snowflake; +} diff --git a/tests/channels.rs b/tests/channels.rs index e2838be..c8564d7 100644 --- a/tests/channels.rs +++ b/tests/channels.rs @@ -28,10 +28,11 @@ async fn delete_channel() { #[tokio::test] async fn modify_channel() { + const CHANNEL_NAME: &str = "beepboop"; let mut bundle = common::setup().await; let channel = &mut bundle.channel; let modify_data: types::ChannelModifySchema = types::ChannelModifySchema { - name: Some("beepboop".to_string()), + name: Some(CHANNEL_NAME.to_string()), channel_type: None, topic: None, icon: None, @@ -49,10 +50,10 @@ async fn modify_channel() { default_thread_rate_limit_per_user: None, video_quality_mode: None, }; - Channel::modify(channel, modify_data, channel.id, &mut bundle.user) + let modified_channel = Channel::modify(channel, modify_data, channel.id, &mut bundle.user) .await .unwrap(); - assert_eq!(channel.name, Some("beepboop".to_string())); + assert_eq!(modified_channel.name, Some(CHANNEL_NAME.to_string())); let permission_override = PermissionFlags::from_vec(Vec::from([ PermissionFlags::MANAGE_CHANNELS, diff --git a/tests/gateway.rs b/tests/gateway.rs index c6f46dd..199e14b 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -1,6 +1,7 @@ mod common; + use chorus::gateway::*; -use chorus::types; +use chorus::types::{self, Channel}; #[tokio::test] /// Tests establishing a connection (hello and heartbeats) on the local gateway; @@ -22,3 +23,26 @@ async fn test_gateway_authenticate() { gateway.send_identify(identify).await; } + +#[tokio::test] +async fn test_self_updating_structs() { + let mut bundle = common::setup().await; + let gateway = Gateway::new(bundle.urls.wss).await.unwrap(); + let mut identify = types::GatewayIdentifyPayload::common(); + identify.token = bundle.user.token.clone(); + gateway.send_identify(identify).await; + let channel_receiver = gateway.observe(bundle.channel.clone()).await; + let received_channel = channel_receiver.borrow(); + assert_eq!(*received_channel, bundle.channel); + drop(received_channel); + let channel = &mut bundle.channel; + let modify_data = types::ChannelModifySchema { + name: Some("beepboop".to_string()), + ..Default::default() + }; + Channel::modify(channel, modify_data, channel.id, &mut bundle.user) + .await + .unwrap(); + let received_channel = channel_receiver.borrow(); + assert_eq!(received_channel.name.as_ref().unwrap(), "beepboop"); +} From 9ebe72a7dc6a0bdd00073a3a6d17d981cc04529b Mon Sep 17 00:00:00 2001 From: SpecificProtagonist Date: Sat, 22 Jul 2023 11:20:31 +0200 Subject: [PATCH 22/23] derive Updateable (#167) --- Cargo.lock | 9 +++++++ Cargo.toml | 1 + chorus-macros/Cargo.lock | 46 +++++++++++++++++++++++++++++++++++ chorus-macros/Cargo.toml | 11 +++++++++ chorus-macros/src/lib.rs | 18 ++++++++++++++ src/types/entities/channel.rs | 9 ++----- 6 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 chorus-macros/Cargo.lock create mode 100644 chorus-macros/Cargo.toml create mode 100644 chorus-macros/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index c90b667..d193cf4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,6 +189,7 @@ dependencies = [ "async-trait", "base64 0.21.2", "bitflags 2.3.3", + "chorus-macros", "chrono", "custom_error", "futures-util", @@ -215,6 +216,14 @@ dependencies = [ "url", ] +[[package]] +name = "chorus-macros" +version = "0.1.0" +dependencies = [ + "quote", + "syn 2.0.26", +] + [[package]] name = "chrono" version = "0.4.26" diff --git a/Cargo.toml b/Cargo.toml index 4126146..8dbb172 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ thiserror = "1.0.43" jsonwebtoken = "8.3.0" log = "0.4.19" async-trait = "0.1.71" +chorus-macros = {path = "chorus-macros"} [dev-dependencies] tokio = {version = "1.29.1", features = ["full"]} diff --git a/chorus-macros/Cargo.lock b/chorus-macros/Cargo.lock new file mode 100644 index 0000000..4aa96d3 --- /dev/null +++ b/chorus-macros/Cargo.lock @@ -0,0 +1,46 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "chorus-macros" +version = "0.1.0" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" diff --git a/chorus-macros/Cargo.toml b/chorus-macros/Cargo.toml new file mode 100644 index 0000000..cffffe1 --- /dev/null +++ b/chorus-macros/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "chorus-macros" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +quote = "1" +syn = "2" diff --git a/chorus-macros/src/lib.rs b/chorus-macros/src/lib.rs new file mode 100644 index 0000000..c8d5e87 --- /dev/null +++ b/chorus-macros/src/lib.rs @@ -0,0 +1,18 @@ +use proc_macro::TokenStream; +use quote::quote; + +#[proc_macro_derive(Updateable)] +pub fn updateable_macro_derive(input: TokenStream) -> TokenStream { + let ast: syn::DeriveInput = syn::parse(input).unwrap(); + + let name = &ast.ident; + // No need for macro hygiene, we're only using this in chorus + quote! { + impl Updateable for #name { + fn id(&self) -> Snowflake { + self.id + } + } + } + .into() +} diff --git a/src/types/entities/channel.rs b/src/types/entities/channel.rs index aac57b6..154b83c 100644 --- a/src/types/entities/channel.rs +++ b/src/types/entities/channel.rs @@ -1,3 +1,4 @@ +use chorus_macros::Updateable; use chrono::Utc; use serde::{Deserialize, Serialize}; use serde_aux::prelude::deserialize_string_from_number; @@ -9,7 +10,7 @@ use crate::types::{ utils::Snowflake, }; -#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Updateable)] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] pub struct Channel { pub application_id: Option, @@ -66,12 +67,6 @@ pub struct Channel { pub video_quality_mode: Option, } -impl Updateable for Channel { - fn id(&self) -> Snowflake { - self.id - } -} - #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] pub struct Tag { pub id: Snowflake, From 07f283a2052c13e3dfd6a8fa1e21c49010d46747 Mon Sep 17 00:00:00 2001 From: Flori <39242991+bitfl0wer@users.noreply.github.com> Date: Mon, 24 Jul 2023 19:13:53 +0200 Subject: [PATCH 23/23] Improve auto updating structs usage (#168) * Add `GatewayHandle` to `UserMeta` * Make user::shell async due to gateway add --- src/api/auth/login.rs | 11 +++++++++-- src/api/auth/register.rs | 10 +++++++++- src/api/users/users.rs | 5 +++-- src/instance.rs | 17 +++++++++++++---- tests/common/mod.rs | 13 ++++++++++++- tests/gateway.rs | 22 ++++++++++------------ tests/invites.rs | 8 +++----- 7 files changed, 59 insertions(+), 27 deletions(-) diff --git a/src/api/auth/login.rs b/src/api/auth/login.rs index baae4ae..68604d9 100644 --- a/src/api/auth/login.rs +++ b/src/api/auth/login.rs @@ -6,9 +6,10 @@ use serde_json::to_string; use crate::api::LimitType; use crate::errors::ChorusResult; +use crate::gateway::Gateway; use crate::instance::{Instance, UserMeta}; use crate::ratelimiter::ChorusRequest; -use crate::types::{LoginResult, LoginSchema}; +use crate::types::{GatewayIdentifyPayload, LoginResult, LoginSchema}; impl Instance { pub async fn login_account(&mut self, login_schema: &LoginSchema) -> ChorusResult { @@ -22,7 +23,8 @@ impl Instance { // We do not have a user yet, and the UserRateLimits will not be affected by a login // request (since login is an instance wide limit), which is why we are just cloning the // instances' limits to pass them on as user_rate_limits later. - let mut shell = UserMeta::shell(Rc::new(RefCell::new(self.clone())), "None".to_string()); + let mut shell = + UserMeta::shell(Rc::new(RefCell::new(self.clone())), "None".to_string()).await; let login_result = chorus_request .deserialize_response::(&mut shell) .await?; @@ -30,12 +32,17 @@ impl Instance { if self.limits_information.is_some() { self.limits_information.as_mut().unwrap().ratelimits = shell.limits.clone().unwrap(); } + let mut identify = GatewayIdentifyPayload::common(); + let gateway = Gateway::new(self.urls.wss.clone()).await.unwrap(); + identify.token = login_result.token.clone(); + gateway.send_identify(identify).await; let user = UserMeta::new( Rc::new(RefCell::new(self.clone())), login_result.token, self.clone_limits_if_some(), login_result.settings, object, + gateway, ); Ok(user) } diff --git a/src/api/auth/register.rs b/src/api/auth/register.rs index a6849cc..e818d82 100644 --- a/src/api/auth/register.rs +++ b/src/api/auth/register.rs @@ -3,6 +3,8 @@ use std::{cell::RefCell, rc::Rc}; use reqwest::Client; use serde_json::to_string; +use crate::gateway::Gateway; +use crate::types::GatewayIdentifyPayload; use crate::{ api::policies::instance::LimitType, errors::ChorusResult, @@ -35,7 +37,8 @@ impl Instance { // We do not have a user yet, and the UserRateLimits will not be affected by a login // request (since register is an instance wide limit), which is why we are just cloning // the instances' limits to pass them on as user_rate_limits later. - let mut shell = UserMeta::shell(Rc::new(RefCell::new(self.clone())), "None".to_string()); + let mut shell = + UserMeta::shell(Rc::new(RefCell::new(self.clone())), "None".to_string()).await; let token = chorus_request .deserialize_response::(&mut shell) .await? @@ -45,12 +48,17 @@ impl Instance { } let user_object = self.get_user(token.clone(), None).await.unwrap(); let settings = UserMeta::get_settings(&token, &self.urls.api.clone(), self).await?; + let mut identify = GatewayIdentifyPayload::common(); + let gateway = Gateway::new(self.urls.wss.clone()).await.unwrap(); + identify.token = token.clone(); + gateway.send_identify(identify).await; let user = UserMeta::new( Rc::new(RefCell::new(self.clone())), token.clone(), self.clone_limits_if_some(), settings, user_object, + gateway, ); Ok(user) } diff --git a/src/api/users/users.rs b/src/api/users/users.rs index bd46a36..af6d2ce 100644 --- a/src/api/users/users.rs +++ b/src/api/users/users.rs @@ -122,7 +122,8 @@ impl User { let request: reqwest::RequestBuilder = Client::new() .get(format!("{}/users/@me/settings/", url_api)) .bearer_auth(token); - let mut user = UserMeta::shell(Rc::new(RefCell::new(instance.clone())), token.clone()); + let mut user = + UserMeta::shell(Rc::new(RefCell::new(instance.clone())), token.clone()).await; let chorus_request = ChorusRequest { request, limit_type: LimitType::Global, @@ -151,7 +152,7 @@ impl Instance { This function is a wrapper around [`User::get`]. */ pub async fn get_user(&mut self, token: String, id: Option<&String>) -> ChorusResult { - let mut user = UserMeta::shell(Rc::new(RefCell::new(self.clone())), token); + let mut user = UserMeta::shell(Rc::new(RefCell::new(self.clone())), token).await; let result = User::get(&mut user, id).await; if self.limits_information.is_some() { self.limits_information.as_mut().unwrap().ratelimits = diff --git a/src/instance.rs b/src/instance.rs index 0cd65f4..9795557 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize}; use crate::api::{Limit, LimitType}; use crate::errors::ChorusResult; +use crate::gateway::{Gateway, GatewayHandle}; use crate::ratelimiter::ChorusRequest; use crate::types::types::subconfigs::limits::rates::RateLimits; use crate::types::{GeneralConfiguration, User, UserSettings}; @@ -88,13 +89,14 @@ impl fmt::Display for Token { } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct UserMeta { pub belongs_to: Rc>, pub token: String, pub limits: Option>, pub settings: UserSettings, pub object: User, + pub gateway: GatewayHandle, } impl UserMeta { @@ -112,6 +114,7 @@ impl UserMeta { limits: Option>, settings: UserSettings, object: User, + gateway: GatewayHandle, ) -> UserMeta { UserMeta { belongs_to, @@ -119,19 +122,24 @@ impl UserMeta { limits, settings, object, + gateway, } } /// Creates a new 'shell' of a user. The user does not exist as an object, and exists so that you have /// a UserMeta object to make Rate Limited requests with. This is useful in scenarios like /// registering or logging in to the Instance, where you do not yet have a User object, but still - /// need to make a RateLimited request. - pub(crate) fn shell(instance: Rc>, token: String) -> UserMeta { + /// need to make a RateLimited request. To use the [`GatewayHandle`], you will have to identify + /// first. + pub(crate) async fn shell(instance: Rc>, token: String) -> UserMeta { let settings = UserSettings::default(); let object = User::default(); + let wss_url = instance.borrow().urls.wss.clone(); + // Dummy gateway object + let gateway = Gateway::new(wss_url).await.unwrap(); UserMeta { - belongs_to: instance.clone(), token, + belongs_to: instance.clone(), limits: instance .borrow() .limits_information @@ -139,6 +147,7 @@ impl UserMeta { .map(|info| info.ratelimits.clone()), settings, object, + gateway, } } } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index fc4a19c..747a8cd 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,3 +1,4 @@ +use chorus::gateway::Gateway; use chorus::{ instance::{Instance, UserMeta}, types::{ @@ -18,8 +19,8 @@ pub(crate) struct TestBundle { pub channel: Channel, } +#[allow(unused)] impl TestBundle { - #[allow(unused)] pub(crate) async fn create_user(&mut self, username: &str) -> UserMeta { let register_schema = RegisterSchema { username: username.to_string(), @@ -32,6 +33,16 @@ impl TestBundle { .await .unwrap() } + pub(crate) async fn clone_user_without_gateway(&self) -> UserMeta { + UserMeta { + belongs_to: self.user.belongs_to.clone(), + token: self.user.token.clone(), + limits: self.user.limits.clone(), + settings: self.user.settings.clone(), + object: self.user.object.clone(), + gateway: Gateway::new(self.instance.urls.wss.clone()).await.unwrap(), + } + } } // Set up a test by creating an Instance and a User. Reduces Test boilerplate. diff --git a/tests/gateway.rs b/tests/gateway.rs index 199e14b..21a2018 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -8,7 +8,8 @@ use chorus::types::{self, Channel}; async fn test_gateway_establish() { let bundle = common::setup().await; - Gateway::new(bundle.urls.wss).await.unwrap(); + Gateway::new(bundle.urls.wss.clone()).await.unwrap(); + common::teardown(bundle).await } #[tokio::test] @@ -16,25 +17,21 @@ async fn test_gateway_establish() { async fn test_gateway_authenticate() { let bundle = common::setup().await; - let gateway = Gateway::new(bundle.urls.wss).await.unwrap(); + let gateway = Gateway::new(bundle.urls.wss.clone()).await.unwrap(); let mut identify = types::GatewayIdentifyPayload::common(); - identify.token = bundle.user.token; + identify.token = bundle.user.token.clone(); gateway.send_identify(identify).await; + common::teardown(bundle).await } #[tokio::test] async fn test_self_updating_structs() { let mut bundle = common::setup().await; - let gateway = Gateway::new(bundle.urls.wss).await.unwrap(); - let mut identify = types::GatewayIdentifyPayload::common(); - identify.token = bundle.user.token.clone(); - gateway.send_identify(identify).await; - let channel_receiver = gateway.observe(bundle.channel.clone()).await; - let received_channel = channel_receiver.borrow(); - assert_eq!(*received_channel, bundle.channel); - drop(received_channel); + let channel_updater = bundle.user.gateway.observe(bundle.channel.clone()).await; + let received_channel = channel_updater.borrow().clone(); + assert_eq!(received_channel, bundle.channel); let channel = &mut bundle.channel; let modify_data = types::ChannelModifySchema { name: Some("beepboop".to_string()), @@ -43,6 +40,7 @@ async fn test_self_updating_structs() { Channel::modify(channel, modify_data, channel.id, &mut bundle.user) .await .unwrap(); - let received_channel = channel_receiver.borrow(); + let received_channel = channel_updater.borrow(); assert_eq!(received_channel.name.as_ref().unwrap(), "beepboop"); + common::teardown(bundle).await } diff --git a/tests/invites.rs b/tests/invites.rs index b6206b9..d19be61 100644 --- a/tests/invites.rs +++ b/tests/invites.rs @@ -1,14 +1,12 @@ -use chorus::types::CreateChannelInviteSchema; - mod common; - +use chorus::types::CreateChannelInviteSchema; #[tokio::test] async fn create_accept_invite() { let mut bundle = common::setup().await; let channel = bundle.channel.clone(); - let mut user = bundle.user.clone(); - let create_channel_invite_schema = CreateChannelInviteSchema::default(); let mut other_user = bundle.create_user("testuser1312").await; + let user = &mut bundle.user; + let create_channel_invite_schema = CreateChannelInviteSchema::default(); assert!(chorus::types::Guild::get(bundle.guild.id, &mut other_user) .await .is_err());