From 72b954af00826906ddfc2f68bda69371f33f39b4 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 22 Nov 2023 18:38:23 +0100 Subject: [PATCH 01/28] Token login test --- tests/auth.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/auth.rs b/tests/auth.rs index 086c8ba..2b593c1 100644 --- a/tests/auth.rs +++ b/tests/auth.rs @@ -20,3 +20,36 @@ async fn test_registration() { bundle.instance.clone().register_account(reg).await.unwrap(); common::teardown(bundle).await; } + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +async fn test_login_with_token() { + let mut bundle = common::setup().await; + + let token = &bundle.user.token; + let other_user = bundle + .instance + .login_with_token(token.clone()) + .await + .unwrap(); + assert_eq!( + bundle.user.object.read().unwrap().id, + other_user.object.read().unwrap().id + ); + assert_eq!(bundle.user.token, other_user.token); + + common::teardown(bundle).await; +} + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +async fn test_login_with_invalid_token() { + let mut bundle = common::setup().await; + + let token = "invalid token lalalalala".to_string(); + let other_user = bundle.instance.login_with_token(token.clone()).await; + + assert!(other_user.is_err()); + + common::teardown(bundle).await; +} From 7954e386434e91b8b89a55fedad5ef45968f1081 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 22 Nov 2023 18:48:37 +0100 Subject: [PATCH 02/28] Change: register/login no longer require ownership Register/login used to require ownership of `instance`. This wasn't really necessary and has been changed. --- src/api/auth/login.rs | 2 +- src/api/auth/register.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api/auth/login.rs b/src/api/auth/login.rs index 1d9fc8a..ff99be8 100644 --- a/src/api/auth/login.rs +++ b/src/api/auth/login.rs @@ -14,7 +14,7 @@ impl Instance { /// /// # Reference /// See - pub async fn login_account(mut self, login_schema: LoginSchema) -> ChorusResult { + pub async fn login_account(&mut self, login_schema: LoginSchema) -> ChorusResult { let endpoint_url = self.urls.api.clone() + "/auth/login"; let chorus_request = ChorusRequest { request: Client::new() diff --git a/src/api/auth/register.rs b/src/api/auth/register.rs index 2ea7d57..aa0b483 100644 --- a/src/api/auth/register.rs +++ b/src/api/auth/register.rs @@ -19,7 +19,7 @@ impl Instance { /// # Reference /// See pub async fn register_account( - mut self, + &mut self, register_schema: RegisterSchema, ) -> ChorusResult { let endpoint_url = self.urls.api.clone() + "/auth/register"; @@ -43,7 +43,7 @@ impl Instance { self.limits_information.as_mut().unwrap().ratelimits = shell.limits.unwrap(); } let user_object = self.get_user(token.clone(), None).await.unwrap(); - let settings = ChorusUser::get_settings(&token, &self.urls.api.clone(), &mut self).await?; + let settings = ChorusUser::get_settings(&token, &self.urls.api.clone(), self).await?; let mut identify = GatewayIdentifyPayload::common(); let gateway: GatewayHandle = Gateway::spawn(self.urls.wss.clone()).await.unwrap(); identify.token = token.clone(); From 05fd02a717343bcecc0a420f308b7df1ee8679b0 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 22 Nov 2023 18:48:44 +0100 Subject: [PATCH 03/28] Add tests for coverage --- examples/login.rs | 2 +- tests/auth.rs | 53 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/examples/login.rs b/examples/login.rs index b06eade..6b56a47 100644 --- a/examples/login.rs +++ b/examples/login.rs @@ -9,7 +9,7 @@ async fn main() { "wss://example.com/".to_string(), "https://example.com/cdn".to_string(), ); - let instance = Instance::new(bundle, true) + let mut instance = Instance::new(bundle, true) .await .expect("Failed to connect to the Spacebar server"); // Assume, you already have an account created on this instance. Registering an account works diff --git a/tests/auth.rs b/tests/auth.rs index 2b593c1..538e5cc 100644 --- a/tests/auth.rs +++ b/tests/auth.rs @@ -1,4 +1,6 @@ -use chorus::types::RegisterSchema; +use std::borrow::BorrowMut; + +use chorus::types::{LoginSchema, RegisterSchema}; // PRETTYFYME: Move common wasm setup to common.rs #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; @@ -10,14 +12,59 @@ mod common; #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_registration() { - let bundle = common::setup().await; + let mut bundle = common::setup().await; let reg = RegisterSchema { username: "Hiiii".into(), date_of_birth: Some("2000-01-01".to_string()), consent: true, ..Default::default() }; - bundle.instance.clone().register_account(reg).await.unwrap(); + bundle.instance.register_account(reg).await.unwrap(); + common::teardown(bundle).await; +} + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +async fn test_login() { + let mut bundle = common::setup().await; + let reg = RegisterSchema { + username: "Hiiii".into(), + email: Some("testuser1@integrationtesting.xyz".into()), + password: Some("Correct-Horse-Battery-Staple1".into()), + date_of_birth: Some("2000-01-01".to_string()), + consent: true, + ..Default::default() + }; + bundle.instance.register_account(reg).await.unwrap(); + let login = LoginSchema { + login: "testuser1@integrationtesting.xyz".into(), + password: "Correct-Horse-Battery-Staple1".into(), + ..Default::default() + }; + bundle.instance.login_account(login).await.unwrap(); + common::teardown(bundle).await; +} + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +async fn test_wrong_login() { + let mut bundle = common::setup().await; + let reg = RegisterSchema { + username: "Hiiii".into(), + email: Some("testuser2@integrationtesting.xyz".into()), + password: Some("Correct-Horse-Battery-Staple1".into()), + date_of_birth: Some("2000-01-01".to_string()), + consent: true, + ..Default::default() + }; + bundle.instance.register_account(reg).await.unwrap(); + let login = LoginSchema { + login: "testuser2@integrationtesting.xyz".into(), + password: "Correct-Horse-Battery-Staple2".into(), + ..Default::default() + }; + let res = bundle.instance.login_account(login).await; + assert!(res.is_err()); common::teardown(bundle).await; } From 9d581441e82244e8dd213fb66cf2f24a2b22df09 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 22 Nov 2023 20:54:51 +0100 Subject: [PATCH 04/28] Remove "prettyfyme" -> solved --- tests/auth.rs | 1 - tests/channels.rs | 1 - tests/gateway.rs | 1 - tests/guilds.rs | 1 - tests/instance.rs | 1 - tests/invites.rs | 1 - tests/members.rs | 1 - tests/messages.rs | 1 - tests/relationships.rs | 1 - tests/roles.rs | 1 - 10 files changed, 10 deletions(-) diff --git a/tests/auth.rs b/tests/auth.rs index 538e5cc..c105841 100644 --- a/tests/auth.rs +++ b/tests/auth.rs @@ -1,7 +1,6 @@ use std::borrow::BorrowMut; use chorus::types::{LoginSchema, RegisterSchema}; -// PRETTYFYME: Move common wasm setup to common.rs #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] diff --git a/tests/channels.rs b/tests/channels.rs index 864165a..d9842c6 100644 --- a/tests/channels.rs +++ b/tests/channels.rs @@ -4,7 +4,6 @@ use chorus::types::{ }; mod common; -// PRETTYFYME: Move common wasm setup to common.rs #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; diff --git a/tests/gateway.rs b/tests/gateway.rs index 68a203a..5bf5865 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -5,7 +5,6 @@ use std::sync::{Arc, RwLock}; use chorus::errors::GatewayError; use chorus::gateway::*; use chorus::types::{self, ChannelModifySchema, RoleCreateModifySchema, RoleObject}; -// PRETTYFYME: Move common wasm setup to common.rs #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] diff --git a/tests/guilds.rs b/tests/guilds.rs index 360113d..ab955de 100644 --- a/tests/guilds.rs +++ b/tests/guilds.rs @@ -3,7 +3,6 @@ use chorus::types::{ }; mod common; -// PRETTYFYME: Move common wasm setup to common.rs #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] diff --git a/tests/instance.rs b/tests/instance.rs index 56f4d6d..f1243a5 100644 --- a/tests/instance.rs +++ b/tests/instance.rs @@ -1,5 +1,4 @@ mod common; -// PRETTYFYME: Move common wasm setup to common.rs #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] diff --git a/tests/invites.rs b/tests/invites.rs index d830ee8..ae1b9ab 100644 --- a/tests/invites.rs +++ b/tests/invites.rs @@ -1,6 +1,5 @@ mod common; use chorus::types::CreateChannelInviteSchema; -// PRETTYFYME: Move common wasm setup to common.rs #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] diff --git a/tests/members.rs b/tests/members.rs index c9072ef..9b415c3 100644 --- a/tests/members.rs +++ b/tests/members.rs @@ -1,5 +1,4 @@ use chorus::{errors::ChorusResult, types::GuildMember}; -// PRETTYFYME: Move common wasm setup to common.rs #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] diff --git a/tests/messages.rs b/tests/messages.rs index fc59a42..7ff7598 100644 --- a/tests/messages.rs +++ b/tests/messages.rs @@ -2,7 +2,6 @@ use std::fs::File; use std::io::{BufReader, Read}; use chorus::types::{self, Guild, Message, MessageSearchQuery}; -// PRETTYFYME: Move common wasm setup to common.rs #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] diff --git a/tests/relationships.rs b/tests/relationships.rs index 156f6eb..c8ee9cc 100644 --- a/tests/relationships.rs +++ b/tests/relationships.rs @@ -1,5 +1,4 @@ use chorus::types::{self, Relationship, RelationshipType}; -// PRETTYFYME: Move common wasm setup to common.rs #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] diff --git a/tests/roles.rs b/tests/roles.rs index ca58582..8dda704 100644 --- a/tests/roles.rs +++ b/tests/roles.rs @@ -1,5 +1,4 @@ use chorus::types::{self, RoleCreateModifySchema, RoleObject}; -// PRETTYFYME: Move common wasm setup to common.rs #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] From 9336c7ac9b47c6b908e61c28c9bb2a5967b86819 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Fri, 24 Nov 2023 21:37:30 +0100 Subject: [PATCH 05/28] Change text passages for better clarity --- README.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 89d849f..0106942 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ [![Discord]][Discord-invite] [![Build][build-shield]][build-url] [![Coverage][coverage-shield]][coverage-url] -[![Contributors][contributors-shield]][contributors-url]
@@ -16,12 +15,12 @@


- Explore the docs » + Explore the docs »

Report Bug · - Request Feature + crates.io · Join Discord

@@ -29,20 +28,23 @@ -Chorus is a Rust library that allows developers to interact with multiple Spacebar-compatible APIs and Gateways (Including -Discord.com) simultaneously. The library provides a simple and efficient way to communicate with these services, making it easier for developers to build applications that rely on them. Chorus is open-source and welcomes contributions from the community. +Chorus is a Rust library which poses as an API wrapper for [Spacebar Chat](https://github.com/spacebarchat/) +and Discord. It is designed to be easy to use, and to be compatible with both Discord and Spacebar Chat. + +You can establish as many connections to as many servers as you want, and you can use them all at the same time. ## A Tour of Chorus -Chorus combines all the required functionalities of a user-centric Spacebar library into one package. The library -handles a lot of things for you, such as rate limiting, authentication, and more. This means that you can focus on -building your application, instead of worrying about the underlying implementation details. +Chorus combines all the required functionalities of a user-centric Spacebar library into one package. +The library handles various aspects on your behalf, such as rate limiting, authentication and maintaining +a WebSocket connection to the Gateway. This means that you can focus on building your application, +instead of worrying about the underlying implementation details. To get started with Chorus, import it into your project by adding the following to your `Cargo.toml` file: ```toml [dependencies] -chorus = "0" +chorus = "0.12.0" ``` ### Establishing a Connection From c9501e6fadebc91cba5cbca8b597b7136e23e65e Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Fri, 24 Nov 2023 22:21:57 +0100 Subject: [PATCH 06/28] Add README information into lib.rs --- src/lib.rs | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 91 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 47bbaab..e187e44 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,94 @@ -//! A library for interacting with one or multiple Spacebar-compatible APIs and Gateways. -//! -//! # About -//!Chorus is a Rust library that allows developers to interact with multiple Spacebar-compatible APIs and Gateways simultaneously. The library provides a simple and efficient way to communicate with these services, making it easier for developers to build applications that rely on them. Chorus is open-source and welcomes contributions from the community. +/*! +Chorus combines all the required functionalities of a user-centric Spacebar library into one package. +The library handles various aspects on your behalf, such as rate limiting, authentication and maintaining +a WebSocket connection to the Gateway. This means that you can focus on building your application, +instead of worrying about the underlying implementation details. + +### Establishing a Connection + +To connect to a Spacebar compatible server, you need to create an [`Instance`](https://docs.rs/chorus/latest/chorus/instance/struct.Instance.html) like this: + +``` +use chorus::instance::Instance; +use chorus::UrlBundle; + +#[tokio::main] +async fn main() { + let bundle = UrlBundle::new( + "https://example.com/api".to_string(), + "wss://example.com/".to_string(), + "https://example.com/cdn".to_string(), + ); + let instance = Instance::new(bundle, true) + .await + .expect("Failed to connect to the Spacebar server"); + // You can create as many instances of `Instance` as you want, but each `Instance` should likely be unique. + dbg!(instance.instance_info); + dbg!(instance.limits_information); +} +``` + +This Instance can now be used to log in, register and from there on, interact with the server in all sorts of ways. + +### Logging In + +Logging in correctly provides you with an instance of [`ChorusUser`](https://docs.rs/chorus/latest/chorus/instance/struct.ChorusUser.html), with which you can interact with the server and +manipulate the account. Assuming you already have an account on the server, you can log in like this: + +``` +use chorus::types::LoginSchema; +// Assume, you already have an account created on this instance. Registering an account works +// the same way, but you'd use the Register-specific Structs and methods instead. +let login_schema = LoginSchema { + login: "user@example.com".to_string(), + password: "Correct-Horse-Battery-Staple".to_string(), + ..Default::default() +}; +// Each user connects to the Gateway. The Gateway connection lives on a seperate thread. Depending on +// the runtime feature you choose, this can potentially take advantage of all of your computers' threads. +let user = instance + .login_account(login_schema) + .await + .expect("An error occurred during the login process"); +dbg!(user.belongs_to); +dbg!(&user.object.read().unwrap().username); +``` + +## Supported Platforms + +All major desktop operating systems (Windows, macOS (aarch64/x86_64), Linux (aarch64/x86_64)) are supported. +`wasm32-unknown-unknown` is a supported compilation target on versions `0.12.0` and up. This allows you to use +Chorus in your browser, or in any other environment that supports WebAssembly. + +We recommend checking out the examples directory, as well as the documentation for more information. + +## MSRV (Minimum Supported Rust Version) + +Rust **1.67.1**. This number might change at any point while Chorus is not yet at version 1.0.0. + +## Development Setup + +Make sure that you have at least Rust 1.67.1 installed. You can check your Rust version by running `cargo --version` +in your terminal. To compile for `wasm32-unknown-unknown`, you need to install the `wasm32-unknown-unknown` target. +You can do this by running `rustup target add wasm32-unknown-unknown`. + +### Testing + +In general, the tests will require you to run a local instance of the Spacebar server. You can find instructions on how +to do that [here](https://docs.spacebar.chat/setup/server/). You can find a pre-configured version of the server +[here](https://github.com/bitfl0wer/server). It is recommended to use the pre-configured version, as certain things +like "proxy connection checking" are already disabled on this version, which otherwise might break tests. + +### wasm + +To test for wasm, you will need to `cargo install wasm-pack`. You can then run +`wasm-pack test -- --headless -- --target wasm32-unknown-unknown --features="rt, client" --no-default-features` +to run the tests for wasm. + +## Versioning + +This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read the specification [here](https://semver.org/spec/v2.0.0.html). +!*/ #![doc( html_logo_url = "https://raw.githubusercontent.com/polyphony-chat/design/main/branding/polyphony-chorus-round-8bit.png" )] From 28637ef731c82954d87b3873028243c71b2cb848 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 2 Dec 2023 17:35:47 +0100 Subject: [PATCH 07/28] Add Default, Hash, etc. where needed --- src/instance.rs | 42 ++++++++++++++++++- src/lib.rs | 3 +- .../config/types/general_configuration.rs | 2 +- .../subconfigs/limits/ratelimits/auth.rs | 2 +- .../types/subconfigs/limits/ratelimits/mod.rs | 2 +- .../subconfigs/limits/ratelimits/route.rs | 2 +- .../config/types/subconfigs/limits/rates.rs | 2 +- src/types/entities/ratelimits.rs | 2 +- tests/auth.rs | 2 - 9 files changed, 48 insertions(+), 11 deletions(-) diff --git a/src/instance.rs b/src/instance.rs index 4ce4338..8e160b8 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -15,22 +15,60 @@ use crate::types::types::subconfigs::limits::rates::RateLimits; use crate::types::{GeneralConfiguration, Limit, LimitType, User, UserSettings}; use crate::UrlBundle; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, Serialize, Deserialize)] /// The [`Instance`]; what you will be using to perform all sorts of actions on the Spacebar server. /// If `limits_information` is `None`, then the instance will not be rate limited. pub struct Instance { pub urls: UrlBundle, pub instance_info: GeneralConfiguration, pub limits_information: Option, + #[serde(skip)] pub client: Client, } -#[derive(Debug, Clone, Serialize, Deserialize, Default)] +impl PartialEq for Instance { + fn eq(&self, other: &Self) -> bool { + self.urls == other.urls + && self.instance_info == other.instance_info + && self.limits_information == other.limits_information + } +} + +impl Eq for Instance {} + +impl std::hash::Hash for Instance { + fn hash(&self, state: &mut H) { + self.urls.hash(state); + self.instance_info.hash(state); + if let Some(inf) = &self.limits_information { + inf.hash(state); + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default, Eq)] pub struct LimitsInformation { pub ratelimits: HashMap, pub configuration: RateLimits, } +impl std::hash::Hash for LimitsInformation { + fn hash(&self, state: &mut H) { + for (k, v) in self.ratelimits.iter() { + k.hash(state); + v.hash(state); + } + self.configuration.hash(state); + } +} + +impl PartialEq for LimitsInformation { + fn eq(&self, other: &Self) -> bool { + self.ratelimits.iter().eq(other.ratelimits.iter()) + && self.configuration == other.configuration + } +} + impl Instance { /// Creates a new [`Instance`] from the [relevant instance urls](UrlBundle), where `limited` is whether or not to automatically use rate limits. pub async fn new(urls: UrlBundle, limited: bool) -> ChorusResult { diff --git a/src/lib.rs b/src/lib.rs index e187e44..278a002 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -104,6 +104,7 @@ This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read #[cfg(all(feature = "rt", feature = "rt_multi_thread"))] compile_error!("feature \"rt\" and feature \"rt_multi_thread\" cannot be enabled at the same time"); +use serde::{Serialize, Deserialize}; use url::{ParseError, Url}; #[cfg(feature = "client")] @@ -119,7 +120,7 @@ pub mod types; #[cfg(feature = "client")] pub mod voice; -#[derive(Clone, Default, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] /// A URLBundle bundles together the API-, Gateway- and CDN-URLs of a Spacebar instance. /// /// # Notes diff --git a/src/types/config/types/general_configuration.rs b/src/types/config/types/general_configuration.rs index 13b3aa8..450fd52 100644 --- a/src/types/config/types/general_configuration.rs +++ b/src/types/config/types/general_configuration.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use crate::types::utils::Snowflake; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] #[serde(rename_all = "camelCase")] pub struct GeneralConfiguration { pub instance_name: String, diff --git a/src/types/config/types/subconfigs/limits/ratelimits/auth.rs b/src/types/config/types/subconfigs/limits/ratelimits/auth.rs index f5abb0f..9815a5d 100644 --- a/src/types/config/types/subconfigs/limits/ratelimits/auth.rs +++ b/src/types/config/types/subconfigs/limits/ratelimits/auth.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use crate::types::config::types::subconfigs::limits::ratelimits::RateLimitOptions; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] pub struct AuthRateLimit { pub login: RateLimitOptions, pub register: RateLimitOptions, diff --git a/src/types/config/types/subconfigs/limits/ratelimits/mod.rs b/src/types/config/types/subconfigs/limits/ratelimits/mod.rs index 66a2b78..934b922 100644 --- a/src/types/config/types/subconfigs/limits/ratelimits/mod.rs +++ b/src/types/config/types/subconfigs/limits/ratelimits/mod.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; pub mod auth; pub mod route; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] #[serde(rename_all = "camelCase")] pub struct RateLimitOptions { pub bot: Option, diff --git a/src/types/config/types/subconfigs/limits/ratelimits/route.rs b/src/types/config/types/subconfigs/limits/ratelimits/route.rs index 5529af3..1aa0be2 100644 --- a/src/types/config/types/subconfigs/limits/ratelimits/route.rs +++ b/src/types/config/types/subconfigs/limits/ratelimits/route.rs @@ -4,7 +4,7 @@ use crate::types::config::types::subconfigs::limits::ratelimits::{ auth::AuthRateLimit, RateLimitOptions, }; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] pub struct RouteRateLimit { pub guild: RateLimitOptions, pub webhook: RateLimitOptions, diff --git a/src/types/config/types/subconfigs/limits/rates.rs b/src/types/config/types/subconfigs/limits/rates.rs index 8fdd183..642dcc8 100644 --- a/src/types/config/types/subconfigs/limits/rates.rs +++ b/src/types/config/types/subconfigs/limits/rates.rs @@ -7,7 +7,7 @@ use crate::types::{ LimitType, }; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] pub struct RateLimits { pub enabled: bool, pub ip: RateLimitOptions, diff --git a/src/types/entities/ratelimits.rs b/src/types/entities/ratelimits.rs index a95a2c6..f766a12 100644 --- a/src/types/entities/ratelimits.rs +++ b/src/types/entities/ratelimits.rs @@ -25,7 +25,7 @@ pub enum LimitType { /// A struct that represents the current ratelimits, either instance-wide or user-wide. /// See for more information. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct Limit { pub bucket: LimitType, pub limit: u64, diff --git a/tests/auth.rs b/tests/auth.rs index c105841..130bfb6 100644 --- a/tests/auth.rs +++ b/tests/auth.rs @@ -1,5 +1,3 @@ -use std::borrow::BorrowMut; - use chorus::types::{LoginSchema, RegisterSchema}; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; From 2a40f804c1edd2c1702ab296ce6b9ff49b8cfe2a Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 2 Dec 2023 17:36:36 +0100 Subject: [PATCH 08/28] fix import order --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 278a002..c3c5957 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -104,7 +104,7 @@ This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read #[cfg(all(feature = "rt", feature = "rt_multi_thread"))] compile_error!("feature \"rt\" and feature \"rt_multi_thread\" cannot be enabled at the same time"); -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; use url::{ParseError, Url}; #[cfg(feature = "client")] From 734cbf8d302393c2cd4bdee0a5dcafa641e29083 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 2 Dec 2023 17:44:24 +0100 Subject: [PATCH 09/28] Mark code blocks as no-run --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c3c5957..71e0553 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ instead of worrying about the underlying implementation details. To connect to a Spacebar compatible server, you need to create an [`Instance`](https://docs.rs/chorus/latest/chorus/instance/struct.Instance.html) like this: -``` +```rs use chorus::instance::Instance; use chorus::UrlBundle; @@ -35,7 +35,7 @@ This Instance can now be used to log in, register and from there on, interact wi Logging in correctly provides you with an instance of [`ChorusUser`](https://docs.rs/chorus/latest/chorus/instance/struct.ChorusUser.html), with which you can interact with the server and manipulate the account. Assuming you already have an account on the server, you can log in like this: -``` +```rs use chorus::types::LoginSchema; // Assume, you already have an account created on this instance. Registering an account works // the same way, but you'd use the Register-specific Structs and methods instead. From 367c0a8ca5f94fe40cca11da85f2205aba74a199 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 2 Dec 2023 20:30:04 +0100 Subject: [PATCH 10/28] impl PartialEq for ChorusUser --- src/instance.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/instance.rs b/src/instance.rs index 8e160b8..fc0bcda 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -131,6 +131,16 @@ pub struct ChorusUser { pub gateway: GatewayHandle, } +impl PartialEq for ChorusUser { + fn eq(&self, other: &Self) -> bool { + self.token == other.token + && self.limits == other.limits + && self.gateway.url == other.gateway.url + } +} + +impl Eq for ChorusUser {} + impl ChorusUser { pub fn token(&self) -> String { self.token.clone() From 4aa45b3841eb3d6856b4a3e9e3e9d214735b1afc Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 3 Dec 2023 12:49:07 +0100 Subject: [PATCH 11/28] impl From reqwest::Error for ChorusError --- src/errors.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/errors.rs b/src/errors.rs index 4099a6b..7494433 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,5 +1,6 @@ //! Contains all the errors that can be returned by the library. use custom_error::custom_error; +use url::Url; use crate::types::WebSocketEvent; @@ -44,6 +45,18 @@ custom_error! { InvalidArguments{error: String} = "Invalid arguments were provided. Error: {error}" } +impl From for ChorusError { + fn from(value: reqwest::Error) -> Self { + ChorusError::RequestFailed { + url: match value.url() { + Some(url) => url.to_string(), + None => "None".to_string(), + }, + error: value.to_string(), + } + } +} + custom_error! { #[derive(PartialEq, Eq)] pub ObserverError From a27a3626f4120ddaf9a2ad654380e61caad95795 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 3 Dec 2023 12:49:22 +0100 Subject: [PATCH 12/28] impl from_root_domain for UrlBundle --- src/lib.rs | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 71e0553..3333bf0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -104,9 +104,12 @@ This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read #[cfg(all(feature = "rt", feature = "rt_multi_thread"))] compile_error!("feature \"rt\" and feature \"rt_multi_thread\" cannot be enabled at the same time"); +use errors::ChorusResult; use serde::{Deserialize, Serialize}; use url::{ParseError, Url}; +use crate::errors::ChorusError; + #[cfg(feature = "client")] pub mod api; pub mod errors; @@ -168,7 +171,7 @@ impl UrlBundle { let url_fmt = format!("http://{}", url); return UrlBundle::parse_url(url_fmt); } - Err(_) => panic!("Invalid URL"), + Err(_) => panic!("Invalid URL"), // TODO: should not panic here }; // if the last character of the string is a slash, remove it. let mut url_string = url.to_string(); @@ -177,6 +180,52 @@ impl UrlBundle { } url_string } + + /// Performs a few HTTP requests to try and retrieve a `UrlBundle` from an instances' root domain. + /// The method tries to retrieve the `UrlBundle` via these three strategies, in order: + /// - GET: `$url/.well-known/spacebar` -> Retrieve UrlBundle via `$wellknownurl/api/policies/instance/domains` + /// - GET: `$url/api/policies/instance/domains` + /// - GET: `$url/policies/instance/domains` + /// + /// The URL stored at `.well-known/spacebar` is the instances' API endpoint. The API + /// stores the CDN and WSS URLs under the `$api/policies/instance/domains` endpoint. If all three + /// of the above approaches fail, it is very likely that the instance is misconfigured, unreachable, or that + /// a wrong URL was provided. + pub async fn from_root_domain(url: &str) -> ChorusResult { + let parsed = UrlBundle::parse_url(url.to_string()); + let client = reqwest::Client::new(); + let request_wellknown = client + .get(format!("{}/.well-known/spacebar", &parsed)) + .header(http::header::ACCEPT, "application/json") + .build()?; + let response_wellknown = client.execute(request_wellknown).await?; + if response_wellknown.status().is_success() { + let body = response_wellknown.json::().await?.api; + UrlBundle::from_api_url(&body).await + } else { + if let Ok(response_slash_api) = + UrlBundle::from_api_url(&format!("{}/api/policies/instance/domains", url)).await + { + return Ok(response_slash_api); + } + if let Ok(response_api) = + UrlBundle::from_api_url(&format!("{}/policies/instance/domains", url)).await + { + Ok(response_api) + } else { + Err(ChorusError::RequestFailed { url: url.to_string(), error: "Could not retrieve UrlBundle from url after trying 3 different approaches. Check the provided Url and if the instance is reachable.".to_string() } ) + } + } + } + + async fn from_api_url(url: &str) -> ChorusResult { + todo!() + } +} + +#[derive(Deserialize)] +struct WellKnownResponse { + pub(crate) api: String, } #[cfg(test)] From b6324173b5ce5f181258f9e5d126b0e589a1b954 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 3 Dec 2023 12:51:34 +0100 Subject: [PATCH 13/28] clean up imports --- src/errors.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/errors.rs b/src/errors.rs index 7494433..c20ac64 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,6 +1,5 @@ //! Contains all the errors that can be returned by the library. use custom_error::custom_error; -use url::Url; use crate::types::WebSocketEvent; From 8224acf8975277304978d7a594e1ba23b70a8f7d Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 3 Dec 2023 13:04:02 +0100 Subject: [PATCH 14/28] Create domains_config, create Domains struct --- .../config/types/domains_configuration.rs | 29 +++++++++++++++++++ src/types/config/types/mod.rs | 1 + 2 files changed, 30 insertions(+) create mode 100644 src/types/config/types/domains_configuration.rs diff --git a/src/types/config/types/domains_configuration.rs b/src/types/config/types/domains_configuration.rs new file mode 100644 index 0000000..297b827 --- /dev/null +++ b/src/types/config/types/domains_configuration.rs @@ -0,0 +1,29 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize, Eq, PartialEq, Hash, Clone, Debug)] +/// Represents the result of the `$rooturl/.well-known/spacebar` endpoint. +/// +/// See for more information. +pub struct WellKnownResponse { + pub api: String, +} + +#[derive(Deserialize, Serialize, Eq, PartialEq, Hash, Clone, Debug)] +#[serde(rename_all = "camelCase")] +/// Represents the result of the `$api/policies/instance/domains` endpoint. +pub struct Domains { + pub cdn: String, + pub gateway: String, + pub api_endpoint: String, + pub default_api_version: String, +} + +impl std::fmt::Display for Domains { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{{\n\tCDN URL: {},\n\tGateway URL: {},\n\tAPI Endpoint: {},\n\tDefault API Version: {}\n}}", + self.cdn, self.gateway, self.api_endpoint, self.default_api_version + ) + } +} diff --git a/src/types/config/types/mod.rs b/src/types/config/types/mod.rs index dce4eb0..6ea2c03 100644 --- a/src/types/config/types/mod.rs +++ b/src/types/config/types/mod.rs @@ -1,6 +1,7 @@ pub mod api_configuration; pub mod cdn_configuration; pub mod defaults_configuration; +pub mod domains_configuration; pub mod email_configuration; pub mod endpoint_configuration; pub mod external_tokens_configuration; From 5792c61d09a1f5f04b4302f3a4805e8fc13b48f4 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 3 Dec 2023 13:04:17 +0100 Subject: [PATCH 15/28] move WellKnownResponse to src/types/ --- src/lib.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3333bf0..8e38026 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,6 +106,7 @@ compile_error!("feature \"rt\" and feature \"rt_multi_thread\" cannot be enabled use errors::ChorusResult; use serde::{Deserialize, Serialize}; +use types::types::domains_configuration::WellKnownResponse; use url::{ParseError, Url}; use crate::errors::ChorusError; @@ -223,11 +224,6 @@ impl UrlBundle { } } -#[derive(Deserialize)] -struct WellKnownResponse { - pub(crate) api: String, -} - #[cfg(test)] mod lib { use super::*; From 0e1f52edba2ce8e9905c453587c61092e1f87c3c Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 3 Dec 2023 13:13:57 +0100 Subject: [PATCH 16/28] add warning lint for usage of todo!() --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 8e38026..d2db694 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -101,6 +101,7 @@ This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read clippy::new_without_default, clippy::useless_conversion )] +#![warn(clippy::todo, clippy::unimplemented)] #[cfg(all(feature = "rt", feature = "rt_multi_thread"))] compile_error!("feature \"rt\" and feature \"rt_multi_thread\" cannot be enabled at the same time"); From dc263dbd5ac42c885f7283c27d662346f08275cd Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 3 Dec 2023 13:16:34 +0100 Subject: [PATCH 17/28] impl from_api_url --- src/lib.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d2db694..093d297 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -215,13 +215,29 @@ impl UrlBundle { { Ok(response_api) } else { - Err(ChorusError::RequestFailed { url: url.to_string(), error: "Could not retrieve UrlBundle from url after trying 3 different approaches. Check the provided Url and if the instance is reachable.".to_string() } ) + Err(ChorusError::RequestFailed { url: url.to_string(), error: "Could not retrieve UrlBundle from url after trying 3 different approaches. Check the provided Url and make sure the instance is reachable.".to_string() } ) } } } async fn from_api_url(url: &str) -> ChorusResult { - todo!() + let client = reqwest::Client::new(); + let request = client + .get(url) + .header(http::header::ACCEPT, "application/json") + .build()?; + let response = client.execute(request).await?; + if let Ok(body) = response + .json::() + .await + { + Ok(UrlBundle::new(body.api_endpoint, body.gateway, body.cdn)) + } else { + Err(ChorusError::RequestFailed { + url: url.to_string(), + error: "Could not retrieve a UrlBundle from the given url. Check the provided url and make sure the instance is reachable.".to_string(), + }) + } } } From d28ccf209af4260d41caa2b83ec9200751ad0283 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 3 Dec 2023 13:28:50 +0100 Subject: [PATCH 18/28] check for dbg! println! eprintln! in production code --- src/lib.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 093d297..47fda75 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -101,7 +101,13 @@ This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read clippy::new_without_default, clippy::useless_conversion )] -#![warn(clippy::todo, clippy::unimplemented)] +#![warn( + clippy::todo, + clippy::unimplemented, + clippy::dbg_macro, + clippy::print_stdout, + clippy::print_stderr +)] #[cfg(all(feature = "rt", feature = "rt_multi_thread"))] compile_error!("feature \"rt\" and feature \"rt_multi_thread\" cannot be enabled at the same time"); @@ -206,16 +212,16 @@ impl UrlBundle { UrlBundle::from_api_url(&body).await } else { if let Ok(response_slash_api) = - UrlBundle::from_api_url(&format!("{}/api/policies/instance/domains", url)).await + UrlBundle::from_api_url(&format!("{}/api/policies/instance/domains", parsed)).await { return Ok(response_slash_api); } if let Ok(response_api) = - UrlBundle::from_api_url(&format!("{}/policies/instance/domains", url)).await + UrlBundle::from_api_url(&format!("{}/policies/instance/domains", parsed)).await { Ok(response_api) } else { - Err(ChorusError::RequestFailed { url: url.to_string(), error: "Could not retrieve UrlBundle from url after trying 3 different approaches. Check the provided Url and make sure the instance is reachable.".to_string() } ) + Err(ChorusError::RequestFailed { url: parsed.to_string(), error: "Could not retrieve UrlBundle from url after trying 3 different approaches. Check the provided Url and make sure the instance is reachable.".to_string() } ) } } } From bae3bc3aeff09ea554a49d778df881953ece7a54 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 3 Dec 2023 13:29:38 +0100 Subject: [PATCH 19/28] Write test to check basic functionality of UrlBundle::from_root_domain() --- tests/urlbundle.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 tests/urlbundle.rs diff --git a/tests/urlbundle.rs b/tests/urlbundle.rs new file mode 100644 index 0000000..296e86b --- /dev/null +++ b/tests/urlbundle.rs @@ -0,0 +1,12 @@ +use chorus::UrlBundle; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test_configure!(run_in_browser); + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +async fn test_parse_url() { + let url = url::Url::parse("http://localhost:3001/").unwrap(); + UrlBundle::from_root_domain(url.as_str()).await.unwrap(); +} From 906f2234f76cf54e3012d85b33b6348c663cdaeb Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 3 Dec 2023 13:33:24 +0100 Subject: [PATCH 20/28] Add test if an example well-known response gets parsed correctly. --- tests/urlbundle.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/urlbundle.rs b/tests/urlbundle.rs index 296e86b..94dcc6d 100644 --- a/tests/urlbundle.rs +++ b/tests/urlbundle.rs @@ -1,4 +1,6 @@ +use chorus::types::types::domains_configuration::WellKnownResponse; use chorus::UrlBundle; +use serde_json::json; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] @@ -7,6 +9,18 @@ wasm_bindgen_test_configure!(run_in_browser); #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_parse_url() { + // TODO: Currently only tests two of the three branches in UrlBundle::from_root_domain. let url = url::Url::parse("http://localhost:3001/").unwrap(); UrlBundle::from_root_domain(url.as_str()).await.unwrap(); + let url = url::Url::parse("http://localhost:3001/api/").unwrap(); + UrlBundle::from_root_domain(url.as_str()).await.unwrap(); +} + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +async fn test_parse_wellknown() { + let json = json!({ + "api": "http://localhost:3001/api/v9" + }); + let _well_known: WellKnownResponse = serde_json::from_value(json).unwrap(); } From 7c5d7f731c5fdfb6ea1b47ee3cb5e8720aee4c9e Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 3 Dec 2023 13:37:32 +0100 Subject: [PATCH 21/28] Add Instance::from_root_domain(), change documentation wording --- src/instance.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/instance.rs b/src/instance.rs index fc0bcda..6a3b9af 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -70,7 +70,7 @@ impl PartialEq for LimitsInformation { } impl Instance { - /// Creates a new [`Instance`] from the [relevant instance urls](UrlBundle), where `limited` is whether or not to automatically use rate limits. + /// Creates a new [`Instance`] from the [relevant instance urls](UrlBundle), where `limited` is whether Chorus will track and enforce rate limits for this instance. pub async fn new(urls: UrlBundle, limited: bool) -> ChorusResult { let limits_information; if limited { @@ -99,12 +99,22 @@ impl Instance { }; Ok(instance) } + pub(crate) fn clone_limits_if_some(&self) -> Option> { if self.limits_information.is_some() { return Some(self.limits_information.as_ref().unwrap().ratelimits.clone()); } None } + + /// Creates a new [`Instance`] by trying to get the [relevant instance urls](UrlBundle) from a root domain. + /// Shorthand for `Instance::new(UrlBundle::from_root_domain(root_domain).await?)`. + /// + /// If `limited` is `true`, then Chorus will track and enforce rate limits for this instance. + pub async fn from_root_domain(root_domain: &str, limited: bool) -> ChorusResult { + let urls = UrlBundle::from_root_domain(root_domain).await?; + Instance::new(urls, limited).await + } } #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] From 4ad1201032457aad308a1cff897ba1cb072ec176 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 3 Dec 2023 13:39:23 +0100 Subject: [PATCH 22/28] Rename from_root_domain to from_root_url --- src/instance.rs | 6 +++--- src/lib.rs | 4 ++-- tests/urlbundle.rs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/instance.rs b/src/instance.rs index 6a3b9af..90ee1fa 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -107,12 +107,12 @@ impl Instance { None } - /// Creates a new [`Instance`] by trying to get the [relevant instance urls](UrlBundle) from a root domain. + /// Creates a new [`Instance`] by trying to get the [relevant instance urls](UrlBundle) from a root url. /// Shorthand for `Instance::new(UrlBundle::from_root_domain(root_domain).await?)`. /// /// If `limited` is `true`, then Chorus will track and enforce rate limits for this instance. - pub async fn from_root_domain(root_domain: &str, limited: bool) -> ChorusResult { - let urls = UrlBundle::from_root_domain(root_domain).await?; + pub async fn from_root_url(root_url: &str, limited: bool) -> ChorusResult { + let urls = UrlBundle::from_root_url(root_url).await?; Instance::new(urls, limited).await } } diff --git a/src/lib.rs b/src/lib.rs index 47fda75..80374fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -189,7 +189,7 @@ impl UrlBundle { url_string } - /// Performs a few HTTP requests to try and retrieve a `UrlBundle` from an instances' root domain. + /// Performs a few HTTP requests to try and retrieve a `UrlBundle` from an instances' root url. /// The method tries to retrieve the `UrlBundle` via these three strategies, in order: /// - GET: `$url/.well-known/spacebar` -> Retrieve UrlBundle via `$wellknownurl/api/policies/instance/domains` /// - GET: `$url/api/policies/instance/domains` @@ -199,7 +199,7 @@ impl UrlBundle { /// stores the CDN and WSS URLs under the `$api/policies/instance/domains` endpoint. If all three /// of the above approaches fail, it is very likely that the instance is misconfigured, unreachable, or that /// a wrong URL was provided. - pub async fn from_root_domain(url: &str) -> ChorusResult { + pub async fn from_root_url(url: &str) -> ChorusResult { let parsed = UrlBundle::parse_url(url.to_string()); let client = reqwest::Client::new(); let request_wellknown = client diff --git a/tests/urlbundle.rs b/tests/urlbundle.rs index 94dcc6d..790229b 100644 --- a/tests/urlbundle.rs +++ b/tests/urlbundle.rs @@ -11,9 +11,9 @@ wasm_bindgen_test_configure!(run_in_browser); async fn test_parse_url() { // TODO: Currently only tests two of the three branches in UrlBundle::from_root_domain. let url = url::Url::parse("http://localhost:3001/").unwrap(); - UrlBundle::from_root_domain(url.as_str()).await.unwrap(); + UrlBundle::from_root_url(url.as_str()).await.unwrap(); let url = url::Url::parse("http://localhost:3001/api/").unwrap(); - UrlBundle::from_root_domain(url.as_str()).await.unwrap(); + UrlBundle::from_root_url(url.as_str()).await.unwrap(); } #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] From df7ee4d2fc1d8fa1381e849154e82f0e85c3270d Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 3 Dec 2023 14:18:14 +0100 Subject: [PATCH 23/28] Change macOS Safari test strategy to no-fail-fast --- .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 a6abe43..6751f05 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -72,7 +72,7 @@ jobs: rustup target add wasm32-unknown-unknown curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force - SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" + SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" --no-fail-fast wasm-gecko: runs-on: macos-latest steps: From 0c92ffb64d8022175f44e119f440797a0322a3ff Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 3 Dec 2023 15:06:09 +0100 Subject: [PATCH 24/28] Try giving macos-safari its own cache key to fix CI --- .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 6751f05..bb28584 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -66,7 +66,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-all-crates: "true" - prefix-key: "macos" + prefix-key: "macos-safari" - name: Run WASM tests with Safari, Firefox, Chrome run: | rustup target add wasm32-unknown-unknown From 0553ecb753f2b3d95d330110c4205175610ecbe8 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 3 Dec 2023 15:57:03 +0100 Subject: [PATCH 25/28] wasm-safari CI seems to be bugged - disabling for now --- .github/workflows/build_and_test.yml | 56 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index bb28584..0cf6fd9 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -45,34 +45,34 @@ jobs: cargo build --verbose --all-features cargo test --verbose --all-features fi - wasm-safari: - runs-on: macos-latest - steps: - - uses: actions/checkout@v4 - - name: Clone spacebar server - run: | - git clone https://github.com/bitfl0wer/server.git - - uses: actions/setup-node@v3 - with: - node-version: 18 - 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 - with: - cache-all-crates: "true" - prefix-key: "macos-safari" - - name: Run WASM tests with Safari, Firefox, Chrome - run: | - rustup target add wasm32-unknown-unknown - curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash - cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force - SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" --no-fail-fast + # wasm-safari: + # runs-on: macos-latest + # steps: + # - uses: actions/checkout@v4 + # - name: Clone spacebar server + # run: | + # git clone https://github.com/bitfl0wer/server.git + # - uses: actions/setup-node@v3 + # with: + # node-version: 18 + # 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 + # with: + # cache-all-crates: "true" + # prefix-key: "macos-safari" + # - name: Run WASM tests with Safari, Firefox, Chrome + # run: | + # rustup target add wasm32-unknown-unknown + # curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash + # cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force + # SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" --no-fail-fast wasm-gecko: runs-on: macos-latest steps: From 37125e7973b29dbbe92523860d79c8e0f0606e07 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 3 Dec 2023 21:34:44 +0100 Subject: [PATCH 26/28] Remove "limited" parameter --- examples/instance.rs | 2 +- examples/login.rs | 2 +- tests/common/mod.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/instance.rs b/examples/instance.rs index d2a042f..b8f8518 100644 --- a/examples/instance.rs +++ b/examples/instance.rs @@ -8,7 +8,7 @@ async fn main() { "wss://example.com/".to_string(), "https://example.com/cdn".to_string(), ); - let instance = Instance::new(bundle, true) + let instance = Instance::new(bundle) .await .expect("Failed to connect to the Spacebar server"); dbg!(instance.instance_info); diff --git a/examples/login.rs b/examples/login.rs index 6b56a47..18b5db4 100644 --- a/examples/login.rs +++ b/examples/login.rs @@ -9,7 +9,7 @@ async fn main() { "wss://example.com/".to_string(), "https://example.com/cdn".to_string(), ); - let mut instance = Instance::new(bundle, true) + let mut instance = Instance::new(bundle) .await .expect("Failed to connect to the Spacebar server"); // Assume, you already have an account created on this instance. Registering an account works diff --git a/tests/common/mod.rs b/tests/common/mod.rs index b533fd2..d6aaa34 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -57,7 +57,7 @@ pub(crate) async fn setup() -> TestBundle { "ws://localhost:3001".to_string(), "http://localhost:3001".to_string(), ); - let instance = Instance::new(urls.clone(), true).await.unwrap(); + let instance = Instance::new(urls.clone()).await.unwrap(); // Requires the existance of the below user. let reg = RegisterSchema { username: "integrationtestuser".into(), From dc1991a9cccdc255a5d686cbfd7d2d9b3df0de7a Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 3 Dec 2023 21:42:41 +0100 Subject: [PATCH 27/28] Make get_limits_config part of public api --- src/ratelimiter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ratelimiter.rs b/src/ratelimiter.rs index 88d4a02..f6a7c26 100644 --- a/src/ratelimiter.rs +++ b/src/ratelimiter.rs @@ -349,7 +349,7 @@ impl ChorusRequest { /// /// # Reference /// See - pub(crate) async fn get_limits_config(url_api: &str) -> ChorusResult { + pub async fn get_limits_config(url_api: &str) -> ChorusResult { let request = Client::new() .get(format!("{}/policies/instance/limits/", url_api)) .send() From 565670c5b76f1d38d27fe3a9068c4a13ffc42939 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 3 Dec 2023 21:44:08 +0100 Subject: [PATCH 28/28] Remove limited argument from Instance --- src/instance.rs | 44 ++++++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/src/instance.rs b/src/instance.rs index 90ee1fa..cd9eb76 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -12,7 +12,9 @@ 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, Limit, LimitType, User, UserSettings}; +use crate::types::{ + GeneralConfiguration, Limit, LimitType, LimitsConfiguration, User, UserSettings, +}; use crate::UrlBundle; #[derive(Debug, Clone, Default, Serialize, Deserialize)] @@ -71,23 +73,24 @@ impl PartialEq for LimitsInformation { impl Instance { /// Creates a new [`Instance`] from the [relevant instance urls](UrlBundle), where `limited` is whether Chorus will track and enforce rate limits for this instance. - pub async fn new(urls: UrlBundle, limited: bool) -> ChorusResult { - let limits_information; - if limited { - let limits_configuration = ChorusRequest::get_limits_config(&urls.api).await?.rate; - let limits = ChorusRequest::limits_config_to_hashmap(&limits_configuration); - limits_information = Some(LimitsInformation { + pub async fn new(urls: UrlBundle) -> ChorusResult { + let is_limited: Option = Instance::is_limited(&urls.api).await?; + let limit_information; + + if let Some(limits_configuration) = is_limited { + let limits = ChorusRequest::limits_config_to_hashmap(&limits_configuration.rate); + limit_information = Some(LimitsInformation { ratelimits: limits, - configuration: limits_configuration, + configuration: limits_configuration.rate, }); } else { - limits_information = None; + limit_information = None } let mut instance = Instance { urls: urls.clone(), // Will be overwritten in the next step instance_info: GeneralConfiguration::default(), - limits_information, + limits_information: limit_information, client: Client::new(), }; instance.instance_info = match instance.general_configuration_schema().await { @@ -111,9 +114,26 @@ impl Instance { /// Shorthand for `Instance::new(UrlBundle::from_root_domain(root_domain).await?)`. /// /// If `limited` is `true`, then Chorus will track and enforce rate limits for this instance. - pub async fn from_root_url(root_url: &str, limited: bool) -> ChorusResult { + pub async fn from_root_url(root_url: &str) -> ChorusResult { let urls = UrlBundle::from_root_url(root_url).await?; - Instance::new(urls, limited).await + Instance::new(urls).await + } + + pub async fn is_limited(api_url: &str) -> ChorusResult> { + let api_url = UrlBundle::parse_url(api_url.to_string()); + let client = Client::new(); + let request = client + .get(format!("{}/policies/instance/limits", &api_url)) + .header(http::header::ACCEPT, "application/json") + .build()?; + let resp = match client.execute(request).await { + Ok(response) => response, + Err(_) => return Ok(None), + }; + match resp.json::().await { + Ok(limits) => Ok(Some(limits)), + Err(_) => Ok(None), + } } }