QoL Updates (#452)

- Make register and login not take an instance by ownership
- Add README information to `lib.rs`
- Add more derives to ChorusUser and Instance
- Impl From reqwest::Error for ChorusError
- Add support for .well-known #449 
- Remove "limited: bool" as an argument for `Instance::new` in favour of
dynamic instance limit checking #450
This commit is contained in:
Flori 2023-12-03 22:31:21 +01:00 committed by GitHub
commit 266a3d1dd3
30 changed files with 454 additions and 82 deletions

View File

@ -45,34 +45,34 @@ jobs:
cargo build --verbose --all-features cargo build --verbose --all-features
cargo test --verbose --all-features cargo test --verbose --all-features
fi fi
wasm-safari: # wasm-safari:
runs-on: macos-latest # runs-on: macos-latest
steps: # steps:
- uses: actions/checkout@v4 # - uses: actions/checkout@v4
- name: Clone spacebar server # - name: Clone spacebar server
run: | # run: |
git clone https://github.com/bitfl0wer/server.git # git clone https://github.com/bitfl0wer/server.git
- uses: actions/setup-node@v3 # - uses: actions/setup-node@v3
with: # with:
node-version: 18 # node-version: 18
cache: 'npm' # cache: 'npm'
cache-dependency-path: server/package-lock.json # cache-dependency-path: server/package-lock.json
- name: Prepare and start Spacebar server # - name: Prepare and start Spacebar server
run: | # run: |
npm install # npm install
npm run setup # npm run setup
npm run start & # npm run start &
working-directory: ./server # working-directory: ./server
- uses: Swatinem/rust-cache@v2 # - uses: Swatinem/rust-cache@v2
with: # with:
cache-all-crates: "true" # cache-all-crates: "true"
prefix-key: "macos" # prefix-key: "macos-safari"
- name: Run WASM tests with Safari, Firefox, Chrome # - name: Run WASM tests with Safari, Firefox, Chrome
run: | # run: |
rustup target add wasm32-unknown-unknown # 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 # 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 # 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: wasm-gecko:
runs-on: macos-latest runs-on: macos-latest
steps: steps:

View File

@ -3,7 +3,6 @@
[![Discord]][Discord-invite] [![Discord]][Discord-invite]
[![Build][build-shield]][build-url] [![Build][build-shield]][build-url]
[![Coverage][coverage-shield]][coverage-url] [![Coverage][coverage-shield]][coverage-url]
[![Contributors][contributors-shield]][contributors-url]
<img src="https://img.shields.io/static/v1?label=Status&message=Alpha&color=blue"> <img src="https://img.shields.io/static/v1?label=Status&message=Alpha&color=blue">
</br> </br>
@ -16,12 +15,12 @@
<p align="center"> <p align="center">
<br /> <br />
<a href="https://github.com/polyphony-chat/chorus"><strong>Explore the docs »</strong></a> <a href="https://docs.rs/chorus/latest/chorus/"><strong>Explore the docs »</strong></a>
<br /> <br />
<br /> <br />
<a href="https://github.com/polyphony-chat/chorus/issues">Report Bug</a> <a href="https://github.com/polyphony-chat/chorus/issues">Report Bug</a>
· ·
<a href="https://github.com/polyphony-chat/chorus/issues">Request Feature</a> <a href="https://crates.io/crates/chorus">crates.io</a>
· ·
<a href="https://discord.gg/8tKSC8wzDq">Join Discord</a> <a href="https://discord.gg/8tKSC8wzDq">Join Discord</a>
</p> </p>
@ -29,20 +28,23 @@
</div> </div>
Chorus is a Rust library that allows developers to interact with multiple Spacebar-compatible APIs and Gateways (Including Chorus is a Rust library which poses as an API wrapper for [Spacebar Chat](https://github.com/spacebarchat/)
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. 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 ## A Tour of Chorus
Chorus combines all the required functionalities of a user-centric Spacebar library into one package. The library Chorus combines all the required functionalities of a user-centric Spacebar library into one package.
handles a lot of things for you, such as rate limiting, authentication, and more. This means that you can focus on The library handles various aspects on your behalf, such as rate limiting, authentication and maintaining
building your application, instead of worrying about the underlying implementation details. 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: To get started with Chorus, import it into your project by adding the following to your `Cargo.toml` file:
```toml ```toml
[dependencies] [dependencies]
chorus = "0" chorus = "0.12.0"
``` ```
### Establishing a Connection ### Establishing a Connection

View File

@ -8,7 +8,7 @@ async fn main() {
"wss://example.com/".to_string(), "wss://example.com/".to_string(),
"https://example.com/cdn".to_string(), "https://example.com/cdn".to_string(),
); );
let instance = Instance::new(bundle, true) let instance = Instance::new(bundle)
.await .await
.expect("Failed to connect to the Spacebar server"); .expect("Failed to connect to the Spacebar server");
dbg!(instance.instance_info); dbg!(instance.instance_info);

View File

@ -9,7 +9,7 @@ async fn main() {
"wss://example.com/".to_string(), "wss://example.com/".to_string(),
"https://example.com/cdn".to_string(), "https://example.com/cdn".to_string(),
); );
let instance = Instance::new(bundle, true) let mut instance = Instance::new(bundle)
.await .await
.expect("Failed to connect to the Spacebar server"); .expect("Failed to connect to the Spacebar server");
// Assume, you already have an account created on this instance. Registering an account works // Assume, you already have an account created on this instance. Registering an account works

View File

@ -14,7 +14,7 @@ impl Instance {
/// ///
/// # Reference /// # Reference
/// See <https://docs.spacebar.chat/routes/#post-/auth/login/> /// See <https://docs.spacebar.chat/routes/#post-/auth/login/>
pub async fn login_account(mut self, login_schema: LoginSchema) -> ChorusResult<ChorusUser> { pub async fn login_account(&mut self, login_schema: LoginSchema) -> ChorusResult<ChorusUser> {
let endpoint_url = self.urls.api.clone() + "/auth/login"; let endpoint_url = self.urls.api.clone() + "/auth/login";
let chorus_request = ChorusRequest { let chorus_request = ChorusRequest {
request: Client::new() request: Client::new()

View File

@ -19,7 +19,7 @@ impl Instance {
/// # Reference /// # Reference
/// See <https://docs.spacebar.chat/routes/#post-/auth/register/> /// See <https://docs.spacebar.chat/routes/#post-/auth/register/>
pub async fn register_account( pub async fn register_account(
mut self, &mut self,
register_schema: RegisterSchema, register_schema: RegisterSchema,
) -> ChorusResult<ChorusUser> { ) -> ChorusResult<ChorusUser> {
let endpoint_url = self.urls.api.clone() + "/auth/register"; 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(); self.limits_information.as_mut().unwrap().ratelimits = shell.limits.unwrap();
} }
let user_object = self.get_user(token.clone(), None).await.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 mut identify = GatewayIdentifyPayload::common();
let gateway: GatewayHandle = Gateway::spawn(self.urls.wss.clone()).await.unwrap(); let gateway: GatewayHandle = Gateway::spawn(self.urls.wss.clone()).await.unwrap();
identify.token = token.clone(); identify.token = token.clone();

View File

@ -44,6 +44,18 @@ custom_error! {
InvalidArguments{error: String} = "Invalid arguments were provided. Error: {error}" InvalidArguments{error: String} = "Invalid arguments were provided. Error: {error}"
} }
impl From<reqwest::Error> 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! { custom_error! {
#[derive(PartialEq, Eq)] #[derive(PartialEq, Eq)]
pub ObserverError pub ObserverError

View File

@ -12,44 +12,85 @@ use crate::errors::ChorusResult;
use crate::gateway::{Gateway, GatewayHandle}; use crate::gateway::{Gateway, GatewayHandle};
use crate::ratelimiter::ChorusRequest; use crate::ratelimiter::ChorusRequest;
use crate::types::types::subconfigs::limits::rates::RateLimits; 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; 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. /// 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. /// If `limits_information` is `None`, then the instance will not be rate limited.
pub struct Instance { pub struct Instance {
pub urls: UrlBundle, pub urls: UrlBundle,
pub instance_info: GeneralConfiguration, pub instance_info: GeneralConfiguration,
pub limits_information: Option<LimitsInformation>, pub limits_information: Option<LimitsInformation>,
#[serde(skip)]
pub client: Client, 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<H: std::hash::Hasher>(&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 struct LimitsInformation {
pub ratelimits: HashMap<LimitType, Limit>, pub ratelimits: HashMap<LimitType, Limit>,
pub configuration: RateLimits, pub configuration: RateLimits,
} }
impl std::hash::Hash for LimitsInformation {
fn hash<H: std::hash::Hasher>(&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 { 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<Instance> { pub async fn new(urls: UrlBundle) -> ChorusResult<Instance> {
let limits_information; let is_limited: Option<LimitsConfiguration> = Instance::is_limited(&urls.api).await?;
if limited { let limit_information;
let limits_configuration = ChorusRequest::get_limits_config(&urls.api).await?.rate;
let limits = ChorusRequest::limits_config_to_hashmap(&limits_configuration); if let Some(limits_configuration) = is_limited {
limits_information = Some(LimitsInformation { let limits = ChorusRequest::limits_config_to_hashmap(&limits_configuration.rate);
limit_information = Some(LimitsInformation {
ratelimits: limits, ratelimits: limits,
configuration: limits_configuration, configuration: limits_configuration.rate,
}); });
} else { } else {
limits_information = None; limit_information = None
} }
let mut instance = Instance { let mut instance = Instance {
urls: urls.clone(), urls: urls.clone(),
// Will be overwritten in the next step // Will be overwritten in the next step
instance_info: GeneralConfiguration::default(), instance_info: GeneralConfiguration::default(),
limits_information, limits_information: limit_information,
client: Client::new(), client: Client::new(),
}; };
instance.instance_info = match instance.general_configuration_schema().await { instance.instance_info = match instance.general_configuration_schema().await {
@ -61,12 +102,39 @@ impl Instance {
}; };
Ok(instance) Ok(instance)
} }
pub(crate) fn clone_limits_if_some(&self) -> Option<HashMap<LimitType, Limit>> { pub(crate) fn clone_limits_if_some(&self) -> Option<HashMap<LimitType, Limit>> {
if self.limits_information.is_some() { if self.limits_information.is_some() {
return Some(self.limits_information.as_ref().unwrap().ratelimits.clone()); return Some(self.limits_information.as_ref().unwrap().ratelimits.clone());
} }
None None
} }
/// Creates a new [`Instance`] by trying to get the [relevant instance urls](UrlBundle) from a root url.
/// Shorthand for `Instance::new(UrlBundle::from_root_domain(root_domain).await?)`.
///
/// If `limited` is `true`, then Chorus will track and enforce rate limits for this instance.
pub async fn from_root_url(root_url: &str) -> ChorusResult<Instance> {
let urls = UrlBundle::from_root_url(root_url).await?;
Instance::new(urls).await
}
pub async fn is_limited(api_url: &str) -> ChorusResult<Option<LimitsConfiguration>> {
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::<LimitsConfiguration>().await {
Ok(limits) => Ok(Some(limits)),
Err(_) => Ok(None),
}
}
} }
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
@ -93,6 +161,16 @@ pub struct ChorusUser {
pub gateway: GatewayHandle, 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 { impl ChorusUser {
pub fn token(&self) -> String { pub fn token(&self) -> String {
self.token.clone() self.token.clone()

View File

@ -1,7 +1,94 @@
//! A library for interacting with one or multiple Spacebar-compatible APIs and Gateways. /*!
//! Chorus combines all the required functionalities of a user-centric Spacebar library into one package.
//! # About The library handles various aspects on your behalf, such as rate limiting, authentication and maintaining
//!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. 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:
```rs
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:
```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.
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 --<chrome/firefox/safari> --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( #![doc(
html_logo_url = "https://raw.githubusercontent.com/polyphony-chat/design/main/branding/polyphony-chorus-round-8bit.png" html_logo_url = "https://raw.githubusercontent.com/polyphony-chat/design/main/branding/polyphony-chorus-round-8bit.png"
)] )]
@ -14,11 +101,23 @@
clippy::new_without_default, clippy::new_without_default,
clippy::useless_conversion clippy::useless_conversion
)] )]
#![warn(
clippy::todo,
clippy::unimplemented,
clippy::dbg_macro,
clippy::print_stdout,
clippy::print_stderr
)]
#[cfg(all(feature = "rt", feature = "rt_multi_thread"))] #[cfg(all(feature = "rt", feature = "rt_multi_thread"))]
compile_error!("feature \"rt\" and feature \"rt_multi_thread\" cannot be enabled at the same time"); compile_error!("feature \"rt\" and feature \"rt_multi_thread\" cannot be enabled at the same time");
use errors::ChorusResult;
use serde::{Deserialize, Serialize};
use types::types::domains_configuration::WellKnownResponse;
use url::{ParseError, Url}; use url::{ParseError, Url};
use crate::errors::ChorusError;
#[cfg(feature = "client")] #[cfg(feature = "client")]
pub mod api; pub mod api;
pub mod errors; pub mod errors;
@ -32,7 +131,7 @@ pub mod types;
#[cfg(feature = "client")] #[cfg(feature = "client")]
pub mod voice; 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. /// A URLBundle bundles together the API-, Gateway- and CDN-URLs of a Spacebar instance.
/// ///
/// # Notes /// # Notes
@ -80,7 +179,7 @@ impl UrlBundle {
let url_fmt = format!("http://{}", url); let url_fmt = format!("http://{}", url);
return UrlBundle::parse_url(url_fmt); 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. // if the last character of the string is a slash, remove it.
let mut url_string = url.to_string(); let mut url_string = url.to_string();
@ -89,6 +188,63 @@ impl UrlBundle {
} }
url_string url_string
} }
/// 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`
/// - 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_url(url: &str) -> ChorusResult<UrlBundle> {
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::<WellKnownResponse>().await?.api;
UrlBundle::from_api_url(&body).await
} else {
if let Ok(response_slash_api) =
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", parsed)).await
{
Ok(response_api)
} else {
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() } )
}
}
}
async fn from_api_url(url: &str) -> ChorusResult<UrlBundle> {
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::<types::types::domains_configuration::Domains>()
.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(),
})
}
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -349,7 +349,7 @@ impl ChorusRequest {
/// ///
/// # Reference /// # Reference
/// See <https://docs.spacebar.chat/routes/#get-/policies/instance/limits/> /// See <https://docs.spacebar.chat/routes/#get-/policies/instance/limits/>
pub(crate) async fn get_limits_config(url_api: &str) -> ChorusResult<LimitsConfiguration> { pub async fn get_limits_config(url_api: &str) -> ChorusResult<LimitsConfiguration> {
let request = Client::new() let request = Client::new()
.get(format!("{}/policies/instance/limits/", url_api)) .get(format!("{}/policies/instance/limits/", url_api))
.send() .send()

View File

@ -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 <https://docs.spacebar.chat/setup/server/wellknown/> 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
)
}
}

View File

@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use crate::types::utils::Snowflake; use crate::types::utils::Snowflake;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct GeneralConfiguration { pub struct GeneralConfiguration {
pub instance_name: String, pub instance_name: String,

View File

@ -1,6 +1,7 @@
pub mod api_configuration; pub mod api_configuration;
pub mod cdn_configuration; pub mod cdn_configuration;
pub mod defaults_configuration; pub mod defaults_configuration;
pub mod domains_configuration;
pub mod email_configuration; pub mod email_configuration;
pub mod endpoint_configuration; pub mod endpoint_configuration;
pub mod external_tokens_configuration; pub mod external_tokens_configuration;

View File

@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use crate::types::config::types::subconfigs::limits::ratelimits::RateLimitOptions; 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 struct AuthRateLimit {
pub login: RateLimitOptions, pub login: RateLimitOptions,
pub register: RateLimitOptions, pub register: RateLimitOptions,

View File

@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
pub mod auth; pub mod auth;
pub mod route; pub mod route;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct RateLimitOptions { pub struct RateLimitOptions {
pub bot: Option<u64>, pub bot: Option<u64>,

View File

@ -4,7 +4,7 @@ use crate::types::config::types::subconfigs::limits::ratelimits::{
auth::AuthRateLimit, RateLimitOptions, auth::AuthRateLimit, RateLimitOptions,
}; };
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub struct RouteRateLimit { pub struct RouteRateLimit {
pub guild: RateLimitOptions, pub guild: RateLimitOptions,
pub webhook: RateLimitOptions, pub webhook: RateLimitOptions,

View File

@ -7,7 +7,7 @@ use crate::types::{
LimitType, LimitType,
}; };
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub struct RateLimits { pub struct RateLimits {
pub enabled: bool, pub enabled: bool,
pub ip: RateLimitOptions, pub ip: RateLimitOptions,

View File

@ -25,7 +25,7 @@ pub enum LimitType {
/// A struct that represents the current ratelimits, either instance-wide or user-wide. /// A struct that represents the current ratelimits, either instance-wide or user-wide.
/// See <https://discord.com/developers/docs/topics/rate-limits#rate-limits> for more information. /// See <https://discord.com/developers/docs/topics/rate-limits#rate-limits> for more information.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct Limit { pub struct Limit {
pub bucket: LimitType, pub bucket: LimitType,
pub limit: u64, pub limit: u64,

View File

@ -1,5 +1,4 @@
use chorus::types::RegisterSchema; use chorus::types::{LoginSchema, RegisterSchema};
// PRETTYFYME: Move common wasm setup to common.rs
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
@ -10,13 +9,91 @@ mod common;
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn test_registration() { async fn test_registration() {
let bundle = common::setup().await; let mut bundle = common::setup().await;
let reg = RegisterSchema { let reg = RegisterSchema {
username: "Hiiii".into(), username: "Hiiii".into(),
date_of_birth: Some("2000-01-01".to_string()), date_of_birth: Some("2000-01-01".to_string()),
consent: true, consent: true,
..Default::default() ..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;
}
#[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; common::teardown(bundle).await;
} }

View File

@ -4,7 +4,6 @@ use chorus::types::{
}; };
mod common; mod common;
// PRETTYFYME: Move common wasm setup to common.rs
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*; use wasm_bindgen_test::*;

View File

@ -57,7 +57,7 @@ pub(crate) async fn setup() -> TestBundle {
"ws://localhost:3001".to_string(), "ws://localhost:3001".to_string(),
"http://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. // Requires the existance of the below user.
let reg = RegisterSchema { let reg = RegisterSchema {
username: "integrationtestuser".into(), username: "integrationtestuser".into(),

View File

@ -5,7 +5,6 @@ use std::sync::{Arc, RwLock};
use chorus::errors::GatewayError; use chorus::errors::GatewayError;
use chorus::gateway::*; use chorus::gateway::*;
use chorus::types::{self, ChannelModifySchema, RoleCreateModifySchema, RoleObject}; use chorus::types::{self, ChannelModifySchema, RoleCreateModifySchema, RoleObject};
// PRETTYFYME: Move common wasm setup to common.rs
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]

View File

@ -3,7 +3,6 @@ use chorus::types::{
}; };
mod common; mod common;
// PRETTYFYME: Move common wasm setup to common.rs
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]

View File

@ -1,5 +1,4 @@
mod common; mod common;
// PRETTYFYME: Move common wasm setup to common.rs
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]

View File

@ -1,6 +1,5 @@
mod common; mod common;
use chorus::types::CreateChannelInviteSchema; use chorus::types::CreateChannelInviteSchema;
// PRETTYFYME: Move common wasm setup to common.rs
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]

View File

@ -1,5 +1,4 @@
use chorus::{errors::ChorusResult, types::GuildMember}; use chorus::{errors::ChorusResult, types::GuildMember};
// PRETTYFYME: Move common wasm setup to common.rs
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]

View File

@ -2,7 +2,6 @@ use std::fs::File;
use std::io::{BufReader, Read}; use std::io::{BufReader, Read};
use chorus::types::{self, Guild, Message, MessageSearchQuery}; use chorus::types::{self, Guild, Message, MessageSearchQuery};
// PRETTYFYME: Move common wasm setup to common.rs
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]

View File

@ -1,5 +1,4 @@
use chorus::types::{self, Relationship, RelationshipType}; use chorus::types::{self, Relationship, RelationshipType};
// PRETTYFYME: Move common wasm setup to common.rs
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]

View File

@ -1,5 +1,4 @@
use chorus::types::{self, RoleCreateModifySchema, RoleObject}; use chorus::types::{self, RoleCreateModifySchema, RoleObject};
// PRETTYFYME: Move common wasm setup to common.rs
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]

26
tests/urlbundle.rs Normal file
View File

@ -0,0 +1,26 @@
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")]
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_url(url.as_str()).await.unwrap();
let url = url::Url::parse("http://localhost:3001/api/").unwrap();
UrlBundle::from_root_url(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();
}