diff --git a/src/api/channels/messages.rs b/src/api/channels/messages.rs index d05f640..ffa3f06 100644 --- a/src/api/channels/messages.rs +++ b/src/api/channels/messages.rs @@ -5,7 +5,6 @@ pub mod messages { use serde_json::to_string; use crate::api::types::{Message, PartialDiscordFileAttachment, User}; - use crate::errors::InstanceServerError; use crate::limit::LimitedRequester; impl Message { diff --git a/src/api/guilds/guilds.rs b/src/api/guilds/guilds.rs new file mode 100644 index 0000000..23249b9 --- /dev/null +++ b/src/api/guilds/guilds.rs @@ -0,0 +1,169 @@ +use serde_json::from_str; +use serde_json::to_string; + +use crate::api::schemas; +use crate::api::types; +use crate::errors::InstanceServerError; + +impl<'a> types::Guild { + /// Creates a new guild with the given parameters. + /// + /// # Arguments + /// + /// * `user` - A mutable reference to the user creating the guild. + /// * `instance` - A mutable reference to the instance where the guild will be created. + /// * `guild_create_schema` - A reference to the schema containing the guild creation parameters. + /// + /// # Returns + /// + /// A `Result` containing the ID of the newly created guild, or an error if the request fails. + /// + /// # Errors + /// + /// Returns an `InstanceServerError` if the request fails. + /// + /// # Examples + /// + /// ```rs + /// let guild_create_schema = chorus::api::schemas::GuildCreateSchema::new(insert args here); + /// + /// let result = Guild::create(&mut user, &mut instance, &guild_create_schema).await; + /// + /// match result { + /// Ok(guild_id) => println!("Created guild with ID {}", guild_id), + /// Err(e) => println!("Failed to create guild: {}", e), + /// } + /// ``` + pub async fn create( + user: &mut types::User<'a>, + url_api: &str, + guild_create_schema: schemas::GuildCreateSchema, + ) -> Result { + let url = format!("{}/guilds/", url_api); + let limits_user = user.limits.get_as_mut(); + let limits_instance = &mut user.belongs_to.limits; + let request = reqwest::Client::new() + .post(url.clone()) + .bearer_auth(user.token.clone()) + .body(to_string(&guild_create_schema).unwrap()); + let mut requester = crate::limit::LimitedRequester::new().await; + let result = match requester + .send_request( + request, + crate::api::limits::LimitType::Guild, + limits_instance, + limits_user, + ) + .await + { + Ok(result) => result, + Err(e) => return Err(e), + }; + let id: types::GuildCreateResponse = from_str(&result.text().await.unwrap()).unwrap(); + Ok(id.id) + } + + /// Deletes a guild. + /// + /// # Arguments + /// + /// * `user` - A mutable reference to a `User` instance. + /// * `instance` - A mutable reference to an `Instance` instance. + /// * `guild_id` - A `String` representing the ID of the guild to delete. + /// + /// # Returns + /// + /// An `Option` containing an `InstanceServerError` if an error occurred during the request, otherwise `None`. + /// + /// # Example + /// + /// ```rs + /// let mut user = User::new(); + /// let mut instance = Instance::new(); + /// let guild_id = String::from("1234567890"); + /// + /// match Guild::delete(&mut user, &mut instance, guild_id) { + /// Some(e) => println!("Error deleting guild: {:?}", e), + /// None => println!("Guild deleted successfully"), + /// } + /// ``` + pub async fn delete( + user: &mut types::User<'a>, + url_api: &str, + guild_id: String, + ) -> Option { + let url = format!("{}/guilds/{}/delete/", url_api, guild_id); + let limits_user = user.limits.get_as_mut(); + let limits_instance = &mut user.belongs_to.limits; + let request = reqwest::Client::new() + .post(url.clone()) + .bearer_auth(user.token.clone()); + let mut requester = crate::limit::LimitedRequester::new().await; + let result = requester + .send_request( + request, + crate::api::limits::LimitType::Guild, + limits_instance, + limits_user, + ) + .await; + if result.is_err() { + Some(result.err().unwrap()) + } else { + None + } + } +} + +#[cfg(test)] +mod test { + use crate::api::schemas; + use crate::api::types; + use crate::instance::Instance; + + #[tokio::test] + async fn guild_creation_deletion() { + let mut instance = Instance::new( + crate::URLBundle { + api: "http://localhost:3001/api".to_string(), + wss: "ws://localhost:3001/".to_string(), + cdn: "http://localhost:3001".to_string(), + }, + crate::limit::LimitedRequester::new().await, + ) + .await + .unwrap(); + let login_schema: schemas::LoginSchema = schemas::LoginSchema::new( + schemas::AuthUsername::new("user@test.xyz".to_string()).unwrap(), + "transrights".to_string(), + None, + None, + None, + None, + ) + .unwrap(); + let mut user = instance.login_account(&login_schema).await.unwrap(); + + let guild_create_schema = schemas::GuildCreateSchema { + name: Some("test".to_string()), + region: None, + icon: None, + channels: None, + guild_template_code: None, + system_channel_id: None, + rules_channel_id: None, + }; + + let guild = + types::Guild::create(&mut user, "http://localhost:3001/api", guild_create_schema) + .await + .unwrap(); + + println!("{}", guild); + + match types::Guild::delete(&mut user, "http://localhost:3001/api", guild).await { + None => assert!(true), + Some(_) => assert!(false), + } + } +} diff --git a/src/api/guilds/mod.rs b/src/api/guilds/mod.rs new file mode 100644 index 0000000..242a03e --- /dev/null +++ b/src/api/guilds/mod.rs @@ -0,0 +1,3 @@ +pub mod guilds; + +use guilds::*; diff --git a/src/api/mod.rs b/src/api/mod.rs index eb12765..37abc50 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,11 +1,13 @@ pub mod auth; pub mod channels; +pub mod guilds; pub mod policies; pub mod schemas; pub mod types; pub mod users; pub use channels::messages::*; +pub use guilds::*; pub use policies::instance::instance::*; pub use policies::instance::limits::*; pub use schemas::*; diff --git a/src/api/policies/instance/limits.rs b/src/api/policies/instance/limits.rs index 2818135..1c96883 100644 --- a/src/api/policies/instance/limits.rs +++ b/src/api/policies/instance/limits.rs @@ -1,5 +1,5 @@ pub mod limits { - use std::{collections::HashMap}; + use std::collections::HashMap; use reqwest::Client; use serde::{Deserialize, Serialize}; @@ -20,6 +20,23 @@ pub mod limits { 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 { diff --git a/src/api/schemas.rs b/src/api/schemas.rs index 670a828..1fd0e76 100644 --- a/src/api/schemas.rs +++ b/src/api/schemas.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::errors::FieldFormatError; -use super::Embed; +use super::{Channel, Embed}; /** A struct that represents a well-formed email address. @@ -287,6 +287,18 @@ impl MessageSendSchema { } } +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct GuildCreateSchema { + pub name: Option, + pub region: Option, + pub icon: Option, + pub channels: Option>, + pub guild_template_code: Option, + pub system_channel_id: Option, + pub rules_channel_id: Option, +} + // I know that some of these tests are... really really basic and unneccessary, but sometimes, I // just feel like writing tests, so there you go :) -@bitfl0wer #[cfg(test)] diff --git a/src/api/types.rs b/src/api/types.rs index 36e718c..30e57a7 100644 --- a/src/api/types.rs +++ b/src/api/types.rs @@ -1841,3 +1841,8 @@ pub struct Webhook { pub source_guild: Option, pub id: String, } + +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct GuildCreateResponse { + pub id: String, +} diff --git a/src/errors.rs b/src/errors.rs index 76d16bd..d449559 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -17,8 +17,11 @@ custom_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}", - RateLimited = "Ratelimited.", + RateLimited{bucket:String} = "Ratelimited on Bucket {bucket}", MultipartCreationError{error: String} = "Got an error whilst creating the form: {}", + TokenExpired = "Token expired, invalid or not found.", + NoPermission = "You do not have the permissions needed to perform this action.", + NotFound{error: String} = "The provided resource hasn't been found: {}", } custom_error! { diff --git a/src/limit.rs b/src/limit.rs index 82f5732..cc058b6 100644 --- a/src/limit.rs +++ b/src/limit.rs @@ -57,7 +57,8 @@ impl LimitedRequester { ## Errors - This method will panic, if: + This method will error, if: + - The request does not return a success status code (200-299) - The supplied [`RequestBuilder`](reqwest::RequestBuilder) contains invalid or incomplete information - There has been an error with processing (unwrapping) the [`Response`](`reqwest::Response`) @@ -72,13 +73,23 @@ impl LimitedRequester { user_rate_limits: &mut Limits, ) -> Result { if self.can_send_request(limit_type, instance_rate_limits, user_rate_limits) { - let built_request = request - .build() - .unwrap_or_else(|e| panic!("Error while building the Request for sending: {}", e)); + let built_request = match request.build() { + Ok(request) => request, + Err(e) => { + return Err(InstanceServerError::RequestErrorError { + url: "".to_string(), + error: e.to_string(), + }) + } + }; let result = self.http.execute(built_request).await; let response = match result { Ok(is_response) => is_response, - Err(e) => panic!("An error occured while processing the response: {}", e), + Err(e) => { + return Err(InstanceServerError::ReceivedErrorCodeError { + error_code: e.to_string(), + }) + } }; self.update_limits( &response, @@ -86,13 +97,27 @@ impl LimitedRequester { instance_rate_limits, user_rate_limits, ); - Ok(response) + if !response.status().is_success() { + match response.status().as_u16() { + 401 => return Err(InstanceServerError::TokenExpired), + 403 => return Err(InstanceServerError::TokenExpired), + _ => { + return Err(InstanceServerError::ReceivedErrorCodeError { + error_code: response.status().as_str().to_string(), + }) + } + } + } else { + Ok(response) + } } else { self.requests.push_back(TypedRequest { request, limit_type, }); - Err(InstanceServerError::RateLimited) + Err(InstanceServerError::RateLimited { + bucket: limit_type.to_string(), + }) } }