Merge branch 'dev' into user-routes

This commit is contained in:
kozabrada123 2024-08-29 09:25:45 +02:00 committed by GitHub
commit 81b1525ac0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 784 additions and 544 deletions

View File

@ -101,7 +101,7 @@ jobs:
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.92" --force cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.93" --force
GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway" GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway"
wasm-chrome: wasm-chrome:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -130,5 +130,5 @@ jobs:
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.92" --force cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.93" --force
CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway" CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway"

450
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
[package] [package]
name = "chorus" name = "chorus"
description = "A library for interacting with multiple Spacebar-compatible Instances at once." description = "A library for interacting with multiple Spacebar-compatible Instances at once."
version = "0.15.0" version = "0.16.0"
license = "MPL-2.0" license = "MPL-2.0"
edition = "2021" edition = "2021"
repository = "https://github.com/polyphony-chat/chorus" repository = "https://github.com/polyphony-chat/chorus"
@ -13,18 +13,19 @@ rust-version = "1.70.0"
[features] [features]
default = ["client", "rt-multi-thread"] default = ["client", "rt-multi-thread"]
backend = ["poem", "sqlx"] backend = ["poem", "sqlx", "sqlx-pg-uint"]
rt-multi-thread = ["tokio/rt-multi-thread"] rt-multi-thread = ["tokio/rt-multi-thread"]
rt = ["tokio/rt"] rt = ["tokio/rt"]
client = ["flate2"] client = ["flate2"]
voice = ["voice_udp", "voice_gateway"] voice = ["voice_udp", "voice_gateway"]
voice_udp = ["dep:discortp", "dep:crypto_secretbox"] voice_udp = ["dep:discortp", "dep:crypto_secretbox"]
voice_gateway = [] voice_gateway = []
sqlx-pg-uint = ["dep:sqlx-pg-uint", "sqlx-pg-uint/serde"]
[dependencies] [dependencies]
tokio = { version = "1.38.1", features = ["macros", "sync"] } tokio = { version = "1.39.3", features = ["macros", "sync"] }
serde = { version = "1.0.204", features = ["derive", "rc"] } serde = { version = "1.0.209", features = ["derive", "rc"] }
serde_json = { version = "1.0.120", features = ["raw_value"] } serde_json = { version = "1.0.127", features = ["raw_value"] }
serde-aux = "4.5.0" serde-aux = "4.5.0"
serde_with = "3.9.0" serde_with = "3.9.0"
serde_repr = "0.1.19" serde_repr = "0.1.19"
@ -35,7 +36,7 @@ reqwest = { features = [
], version = "=0.11.26", default-features = false } ], version = "=0.11.26", default-features = false }
url = "2.5.2" url = "2.5.2"
chrono = { version = "0.4.38", features = ["serde"] } chrono = { version = "0.4.38", features = ["serde"] }
regex = "1.10.5" regex = "1.10.6"
custom_error = "1.9.2" custom_error = "1.9.2"
futures-util = "0.3.30" futures-util = "0.3.30"
http = "0.2.12" http = "0.2.12"
@ -48,14 +49,13 @@ jsonwebtoken = "8.3.0"
log = "0.4.22" log = "0.4.22"
async-trait = "0.1.81" async-trait = "0.1.81"
chorus-macros = { path = "./chorus-macros", version = "0" } # Note: version here is used when releasing. This will use the latest release. Make sure to republish the crate when code in macros is changed! chorus-macros = { path = "./chorus-macros", version = "0" } # Note: version here is used when releasing. This will use the latest release. Make sure to republish the crate when code in macros is changed!
sqlx = { version = "0.8.0", features = [ sqlx = { version = "0.8.1", features = [
"mysql",
"sqlite",
"json", "json",
"chrono", "chrono",
"ipnetwork", "ipnetwork",
"runtime-tokio-rustls", "runtime-tokio-rustls",
"any", "postgres",
"bigdecimal",
], optional = true } ], optional = true }
discortp = { version = "0.5.0", optional = true, features = [ discortp = { version = "0.5.0", optional = true, features = [
"rtp", "rtp",
@ -64,9 +64,10 @@ discortp = { version = "0.5.0", optional = true, features = [
] } ] }
crypto_secretbox = { version = "0.1.1", optional = true } crypto_secretbox = { version = "0.1.1", optional = true }
rand = "0.8.5" rand = "0.8.5"
flate2 = { version = "1.0.30", optional = true } flate2 = { version = "1.0.33", optional = true }
webpki-roots = "0.26.3" webpki-roots = "0.26.3"
pubserve = { version = "1.1.0", features = ["async", "send"] } pubserve = { version = "1.1.0", features = ["async", "send"] }
sqlx-pg-uint = { version = "0.5.0", features = ["serde"], optional = true }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
rustls = "0.21.12" rustls = "0.21.12"
@ -79,13 +80,13 @@ getrandom = { version = "0.2.15" }
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2.15", features = ["js"] } getrandom = { version = "0.2.15", features = ["js"] }
ws_stream_wasm = "0.7.4" ws_stream_wasm = "0.7.4"
wasm-bindgen-futures = "0.4.42" wasm-bindgen-futures = "0.4.43"
wasmtimer = "0.2.0" wasmtimer = "0.2.0"
[dev-dependencies] [dev-dependencies]
lazy_static = "1.5.0" lazy_static = "1.5.0"
wasm-bindgen-test = "0.3.42" wasm-bindgen-test = "0.3.43"
wasm-bindgen = "0.2.92" wasm-bindgen = "0.2.93"
simple_logger = { version = "5.0.0", default-features = false } simple_logger = { version = "5.0.0", default-features = false }
[lints.rust] [lints.rust]

130
README.md
View File

@ -28,14 +28,15 @@
</div> </div>
Chorus is a Rust library which poses as an API wrapper for [Spacebar Chat](https://github.com/spacebarchat/) 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. Discord and our own Polyphony. Its high-level API is designed to be easy to use, while still providing the
flexibility one would expect from a library like this.
You can establish as many connections to as many servers as you want, and you can use them all at the same time. 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. Chorus combines all the required functionalities of an API wrapper for chat services into one modular library.
The library handles various aspects on your behalf, such as rate limiting, authentication and maintaining 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, a WebSocket connection to the Gateway. This means that you can focus on building your application,
instead of worrying about the underlying implementation details. instead of worrying about the underlying implementation details.
@ -44,19 +45,19 @@ To get started with Chorus, import it into your project by adding the following
```toml ```toml
[dependencies] [dependencies]
chorus = "0.15.0" chorus = "0.16.0"
``` ```
### Establishing a Connection ### 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: To connect to a Polyphony/Spacebar compatible server, you'll need to create an [`Instance`](https://docs.rs/chorus/latest/chorus/instance/struct.Instance.html) like this:
```rs ```rs
use chorus::instance::Instance; use chorus::instance::Instance;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let instance = Instance::new("https://example.com") let instance = Instance::new("https://example.com", None)
.await .await
.expect("Failed to connect to the Spacebar server"); .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. // You can create as many instances of `Instance` as you want, but each `Instance` should likely be unique.
@ -81,7 +82,7 @@ let login_schema = LoginSchema {
password: "Correct-Horse-Battery-Staple".to_string(), password: "Correct-Horse-Battery-Staple".to_string(),
..Default::default() ..Default::default()
}; };
// Each user connects to the Gateway. The Gateway connection lives on a separate thread. Depending on // Each user connects to the Gateway. Each users' Gateway connection lives on a separate thread. Depending on
// the runtime feature you choose, this can potentially take advantage of all of your computers' threads. // the runtime feature you choose, this can potentially take advantage of all of your computers' threads.
let user = instance let user = instance
.login_account(login_schema) .login_account(login_schema)
@ -148,98 +149,23 @@ This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read
See [CONTRIBUTING.md](./CONTRIBUTING.md). See [CONTRIBUTING.md](./CONTRIBUTING.md).
<details> [Rust]: https://img.shields.io/badge/Rust-orange?style=plastic&logo=rust
<summary>Progress Tracker/Roadmap</summary> [Rust-url]: https://www.rust-lang.org/
[build-shield]: https://img.shields.io/github/actions/workflow/status/polyphony-chat/chorus/build_and_test.yml?style=flat
### Core Functionality [build-url]: https://github.com/polyphony-chat/chorus/blob/main/.github/workflows/build_and_test.yml
- [x] Rate Limiter (hint: couldn't be fully tested due to [an Issue with the Spacebar Server](https://github.com/spacebarchat/server/issues/1022)) [clippy-shield]: https://img.shields.io/github/actions/workflow/status/polyphony-chat/chorus/clippy.yml?style=flat
- [x] [Login (the conventional way)](https://github.com/polyphony-chat/chorus/issues/1) [clippy-url]: https://github.com/polyphony-chat/chorus/blob/main/.github/workflows/clippy.yml
- [ ] [2FA](https://github.com/polyphony-chat/chorus/issues/40) [contributors-shield]: https://img.shields.io/github/contributors/polyphony-chat/chorus.svg?style=flat
- [x] [Registration](https://github.com/polyphony-chat/chorus/issues/1) [contributors-url]: https://github.com/polyphony-chat/chorus/graphs/contributors
[coverage-shield]: https://coveralls.io/repos/github/polyphony-chat/chorus/badge.svg?branch=main
### Messaging [coverage-url]: https://coveralls.io/github/polyphony-chat/chorus?branch=main
- [x] [Sending messages](https://github.com/polyphony-chat/chorus/issues/23) [forks-shield]: https://img.shields.io/github/forks/polyphony-chat/chorus.svg?style=flat
- [x] [Events (Message, User, Channel, etc.)](https://github.com/polyphony-chat/chorus/issues/51) [forks-url]: https://github.com/polyphony-chat/chorus/network/members
- [x] Channel creation [stars-shield]: https://img.shields.io/github/stars/polyphony-chat/chorus.svg?style=flat
- [x] Channel deletion [stars-url]: https://github.com/polyphony-chat/chorus/stargazers
- [x] [Channel management (name, description, icon, etc.)](https://github.com/polyphony-chat/chorus/issues/48) [issues-shield]: https://img.shields.io/github/issues/polyphony-chat/chorus.svg?style=flat
- [x] [Join and Leave Guilds](https://github.com/polyphony-chat/chorus/issues/45) [issues-url]: https://github.com/polyphony-chat/chorus/issues
- [x] [Start DMs](https://github.com/polyphony-chat/chorus/issues/45) [license-shield]: https://img.shields.io/github/license/polyphony-chat/chorus.svg?style=f;at
- [x] [Group DM creation, deletion and member management](https://github.com/polyphony-chat/chorus/issues/89) [license-url]: https://github.com/polyphony-chat/chorus/blob/master/LICENSE
- [ ] [Deleting messages](https://github.com/polyphony-chat/chorus/issues/91) [Discord]: https://dcbadge.vercel.app/api/server/m3FpcapGDD?style=flat
- [ ] [Message threads](https://github.com/polyphony-chat/chorus/issues/90) [Discord-invite]: https://discord.com/invite/m3FpcapGDD
- [x] [Reactions](https://github.com/polyphony-chat/chorus/issues/85)
- [ ] Message Search
- [ ] Message history
- [ ] Emoji
- [ ] Stickers
- [ ] [Forum channels](https://github.com/polyphony-chat/chorus/issues/90)
### User Management
- [ ] [User profile customization](https://github.com/polyphony-chat/chorus/issues/41)
- [x] Gettings users and user profiles
- [x] [Friend requests](https://github.com/polyphony-chat/chorus/issues/92)
- [x] [Blocking users](https://github.com/polyphony-chat/chorus/issues/92)
- [ ] User presence (online, offline, idle, etc.)
- [ ] User status (custom status, etc.)
- [x] Account deletion
### Additional Features
- [ ] Server discovery
- [ ] Server templates
### Voice and Video
- [ ] [Voice chat support](https://github.com/polyphony-chat/chorus/issues/49)
- [ ] [Video chat support](https://github.com/polyphony-chat/chorus/issues/49)
### Permissions and Roles
- [x] [Role management](https://github.com/polyphony-chat/chorus/issues/46) (creation, deletion, modification)
- [x] [Permission management](https://github.com/polyphony-chat/chorus/issues/46) (assigning and revoking permissions)
- [x] [Channel-specific permissions](https://github.com/polyphony-chat/chorus/issues/88)
- [x] Role-based access control
### Guild Management
- [x] Guild creation
- [x] Guild deletion
- [ ] [Guild settings (name, description, icon, etc.)](https://github.com/polyphony-chat/chorus/issues/43)
- [ ] Guild invites
### Moderation
- [ ] Channel moderation (slow mode, etc.)
- [ ] User sanctions (mute, kick, ban)
- [ ] Audit logs
### Embeds and Rich Content
- [x] Sending rich content in messages (links, images, videos)
- [ ] Customizing embed appearance (title, description, color, fields)
### Webhooks
- [ ] Webhook creation and management
- [ ] Handling incoming webhook events
### Documentation and Examples
- [ ] Comprehensive documentation
- [ ] Example usage and code snippets
- [ ] Tutorials and guides
[Rust]: https://img.shields.io/badge/Rust-orange?style=plastic&logo=rust
[Rust-url]: https://www.rust-lang.org/
[build-shield]: https://img.shields.io/github/actions/workflow/status/polyphony-chat/chorus/build_and_test.yml?style=flat
[build-url]: https://github.com/polyphony-chat/chorus/blob/main/.github/workflows/build_and_test.yml
[clippy-shield]: https://img.shields.io/github/actions/workflow/status/polyphony-chat/chorus/clippy.yml?style=flat
[clippy-url]: https://github.com/polyphony-chat/chorus/blob/main/.github/workflows/clippy.yml
[contributors-shield]: https://img.shields.io/github/contributors/polyphony-chat/chorus.svg?style=flat
[contributors-url]: https://github.com/polyphony-chat/chorus/graphs/contributors
[coverage-shield]: https://coveralls.io/repos/github/polyphony-chat/chorus/badge.svg?branch=main
[coverage-url]: https://coveralls.io/github/polyphony-chat/chorus?branch=main
[forks-shield]: https://img.shields.io/github/forks/polyphony-chat/chorus.svg?style=flat
[forks-url]: https://github.com/polyphony-chat/chorus/network/members
[stars-shield]: https://img.shields.io/github/stars/polyphony-chat/chorus.svg?style=flat
[stars-url]: https://github.com/polyphony-chat/chorus/stargazers
[issues-shield]: https://img.shields.io/github/issues/polyphony-chat/chorus.svg?style=flat
[issues-url]: https://github.com/polyphony-chat/chorus/issues
[license-shield]: https://img.shields.io/github/license/polyphony-chat/chorus.svg?style=f;at
[license-url]: https://github.com/polyphony-chat/chorus/blob/master/LICENSE
[Discord]: https://dcbadge.vercel.app/api/server/m3FpcapGDD?style=flat
[Discord-invite]: https://discord.com/invite/m3FpcapGDD
</details>

View File

@ -1,6 +1,6 @@
[package] [package]
name = "chorus-macros" name = "chorus-macros"
version = "0.4.1" version = "0.5.0"
edition = "2021" edition = "2021"
license = "MPL-2.0" license = "MPL-2.0"
description = "Macros for the chorus crate." description = "Macros for the chorus crate."

View File

@ -164,24 +164,23 @@ pub fn sqlx_bitflag_derive(input: TokenStream) -> TokenStream {
quote!{ quote!{
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
impl sqlx::Type<sqlx::Any> for #name { impl sqlx::Type<sqlx::Postgres> for #name {
fn type_info() -> sqlx::any::AnyTypeInfo { fn type_info() -> sqlx::postgres::PgTypeInfo {
<Vec<u8> as sqlx::Type<sqlx::Any>>::type_info() <sqlx_pg_uint::PgU64 as sqlx::Type<sqlx::Postgres>>::type_info()
} }
} }
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
impl<'q> sqlx::Encode<'q, sqlx::Any> for #name { impl<'q> sqlx::Encode<'q, sqlx::Postgres> for #name {
fn encode_by_ref(&self, buf: &mut <sqlx::Any as sqlx::Database>::ArgumentBuffer<'q>) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> { fn encode_by_ref(&self, buf: &mut <sqlx::Postgres as sqlx::Database>::ArgumentBuffer<'q>) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
<Vec<u8> as sqlx::Encode<sqlx::Any>>::encode_by_ref(&self.bits().to_be_bytes().into(), buf) <sqlx_pg_uint::PgU64 as sqlx::Encode<sqlx::Postgres>>::encode_by_ref(&self.bits().into(), buf)
} }
} }
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
impl<'q> sqlx::Decode<'q, sqlx::Any> for #name { impl<'q> sqlx::Decode<'q, sqlx::Postgres> for #name {
fn decode(value: <sqlx::Any as sqlx::Database>::ValueRef<'q>) -> Result<Self, sqlx::error::BoxDynError> { fn decode(value: <sqlx::Postgres as sqlx::Database>::ValueRef<'q>) -> Result<Self, sqlx::error::BoxDynError> {
let vec = <Vec<u8> as sqlx::Decode<sqlx::Any>>::decode(value)?; <sqlx_pg_uint::PgU64 as sqlx::Decode<sqlx::Postgres>>::decode(value).map(|v| Self::from_bits_truncate(v.to_uint()))
Ok(Self::from_bits(vec_u8_to_u64(vec)).unwrap())
} }
} }

View File

@ -52,7 +52,7 @@ impl Subscriber<GatewayReady> for ExampleObserver {
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
async fn main() { async fn main() {
let gateway_websocket_url = GATEWAY_URL.to_string(); let gateway_websocket_url = GATEWAY_URL;
// These options specify the encoding format, compression, etc // These options specify the encoding format, compression, etc
// //

View File

@ -25,7 +25,7 @@ use wasmtimer::tokio::sleep;
/// This example creates a simple gateway connection and a session with an Identify event /// This example creates a simple gateway connection and a session with an Identify event
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
async fn main() { async fn main() {
let gateway_websocket_url = GATEWAY_URL.to_string(); let gateway_websocket_url = GATEWAY_URL;
// These options specify the encoding format, compression, etc // These options specify the encoding format, compression, etc
// //
@ -34,7 +34,9 @@ async fn main() {
let options = GatewayOptions::default(); let options = GatewayOptions::default();
// Initiate the gateway connection, starting a listener in one thread and a heartbeat handler in another // Initiate the gateway connection, starting a listener in one thread and a heartbeat handler in another
let gateway = Gateway::spawn(gateway_websocket_url, options).await.unwrap(); let gateway = Gateway::spawn(gateway_websocket_url, options)
.await
.unwrap();
// At this point, we are connected to the server and are sending heartbeats, however we still haven't authenticated // At this point, we are connected to the server and are sending heartbeats, however we still haven't authenticated

View File

@ -6,7 +6,7 @@ use chorus::instance::Instance;
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
async fn main() { async fn main() {
let instance = Instance::new("https://example.com/") let instance = Instance::new("https://example.com/", None)
.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

@ -7,7 +7,7 @@ use chorus::types::LoginSchema;
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
async fn main() { async fn main() {
let mut instance = Instance::new("https://example.com/") let mut instance = Instance::new("https://example.com/", None)
.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

@ -30,13 +30,12 @@ impl Instance {
// We do not have a user yet, and the UserRateLimits will not be affected by a login // We do not have a user yet, and the UserRateLimits will not be affected by a login
// request (since login is an instance wide limit), which is why we are just cloning the // request (since login is an instance wide limit), which is why we are just cloning the
// instances' limits to pass them on as user_rate_limits later. // instances' limits to pass them on as user_rate_limits later.
let mut user = let mut user = ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None").await;
ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None".to_string()).await;
let login_result = chorus_request let login_result = chorus_request
.deserialize_response::<LoginResult>(&mut user) .deserialize_response::<LoginResult>(&mut user)
.await?; .await?;
user.set_token(login_result.token); user.set_token(&login_result.token);
user.settings = login_result.settings; user.settings = login_result.settings;
let object = User::get_current(&mut user).await?; let object = User::get_current(&mut user).await?;

View File

@ -22,9 +22,8 @@ pub mod register;
impl Instance { impl Instance {
/// Logs into an existing account on the spacebar server, using only a token. /// Logs into an existing account on the spacebar server, using only a token.
pub async fn login_with_token(&mut self, token: String) -> ChorusResult<ChorusUser> { pub async fn login_with_token(&mut self, token: &str) -> ChorusResult<ChorusUser> {
let mut user = let mut user = ChorusUser::shell(Arc::new(RwLock::new(self.clone())), token).await;
ChorusUser::shell(Arc::new(RwLock::new(self.clone())), token).await;
let object = User::get_current(&mut user).await?; let object = User::get_current(&mut user).await?;
let settings = User::get_settings(&mut user).await?; let settings = User::get_settings(&mut user).await?;

View File

@ -37,14 +37,14 @@ impl Instance {
// We do not have a user yet, and the UserRateLimits will not be affected by a login // We do not have a user yet, and the UserRateLimits will not be affected by a login
// request (since register is an instance wide limit), which is why we are just cloning // request (since register is an instance wide limit), which is why we are just cloning
// the instances' limits to pass them on as user_rate_limits later. // the instances' limits to pass them on as user_rate_limits later.
let mut user = let mut user = ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None").await;
ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None".to_string()).await;
let token = chorus_request let token = chorus_request
.deserialize_response::<Token>(&mut user) .deserialize_response::<Token>(&mut user)
.await? .await?
.token; .token;
user.set_token(token);
user.set_token(&token);
let object = User::get_current(&mut user).await?; let object = User::get_current(&mut user).await?;
let settings = User::get_settings(&mut user).await?; let settings = User::get_settings(&mut user).await?;

View File

@ -16,6 +16,7 @@ use crate::types::{
}; };
impl Message { impl Message {
#[allow(clippy::useless_conversion)]
/// Sends a message in the channel with the provided channel_id. /// Sends a message in the channel with the provided channel_id.
/// Returns the sent message. /// Returns the sent message.
/// ///
@ -40,7 +41,7 @@ impl Message {
chorus_request.deserialize_response::<Message>(user).await chorus_request.deserialize_response::<Message>(user).await
} else { } else {
for (index, attachment) in message.attachments.iter_mut().enumerate() { for (index, attachment) in message.attachments.iter_mut().enumerate() {
attachment.get_mut(index).unwrap().id = Some(index as i16); attachment.get_mut(index).unwrap().id = Some((index as u64).into());
} }
let mut form = reqwest::multipart::Form::new(); let mut form = reqwest::multipart::Form::new();
let payload_json = to_string(&message).unwrap(); let payload_json = to_string(&message).unwrap();
@ -111,7 +112,7 @@ impl Message {
let result = request.send_request(user).await?; let result = request.send_request(user).await?;
let result_json = result.json::<Value>().await.unwrap(); let result_json = result.json::<Value>().await.unwrap();
if !result_json.is_object() { if !result_json.is_object() {
return Err(search_error(result_json.to_string())); return Err(search_error(result_json.to_string().as_str()));
} }
let value_map = result_json.as_object().unwrap(); let value_map = result_json.as_object().unwrap();
if let Some(messages) = value_map.get("messages") { if let Some(messages) = value_map.get("messages") {
@ -122,7 +123,7 @@ impl Message {
} }
// The code below might be incorrect. We'll cross that bridge when we come to it // The code below might be incorrect. We'll cross that bridge when we come to it
if !value_map.contains_key("code") || !value_map.contains_key("retry_after") { if !value_map.contains_key("code") || !value_map.contains_key("retry_after") {
return Err(search_error(result_json.to_string())); return Err(search_error(result_json.to_string().as_str()));
} }
let code = value_map.get("code").unwrap().as_u64().unwrap(); let code = value_map.get("code").unwrap().as_u64().unwrap();
let retry_after = value_map.get("retry_after").unwrap().as_u64().unwrap(); let retry_after = value_map.get("retry_after").unwrap().as_u64().unwrap();
@ -481,7 +482,7 @@ impl Message {
} }
} }
fn search_error(result_text: String) -> ChorusError { fn search_error(result_text: &str) -> ChorusError {
ChorusError::InvalidResponse { ChorusError::InvalidResponse {
error: format!( error: format!(
"Got unexpected Response, or Response which is not valid JSON. Response: \n{}", "Got unexpected Response, or Response which is not valid JSON. Response: \n{}",

View File

@ -9,8 +9,10 @@ use futures_util::{
}; };
use tokio::net::TcpStream; use tokio::net::TcpStream;
use tokio_tungstenite::{ use tokio_tungstenite::{
connect_async_tls_with_config, tungstenite, Connector, MaybeTlsStream, WebSocketStream, connect_async_tls_with_config, connect_async_with_config, tungstenite, Connector,
MaybeTlsStream, WebSocketStream,
}; };
use url::Url;
use crate::gateway::{GatewayMessage, RawGatewayMessage}; use crate::gateway::{GatewayMessage, RawGatewayMessage};
@ -32,38 +34,60 @@ impl TungsteniteBackend {
pub async fn connect( pub async fn connect(
websocket_url: &str, websocket_url: &str,
) -> Result<(TungsteniteSink, TungsteniteStream), TungsteniteBackendError> { ) -> Result<(TungsteniteSink, TungsteniteStream), TungsteniteBackendError> {
let certs = webpki_roots::TLS_SERVER_ROOTS; let websocket_url_parsed =
let roots = rustls::RootCertStore { Url::parse(websocket_url).map_err(|_| TungsteniteBackendError::TungsteniteError {
roots: certs error: tungstenite::error::Error::Url(
.iter() tungstenite::error::UrlError::UnsupportedUrlScheme,
.map(|cert| { ),
rustls::OwnedTrustAnchor::from_subject_spki_name_constraints( })?;
cert.subject.to_vec(), if websocket_url_parsed.scheme() == "ws" {
cert.subject_public_key_info.to_vec(), let (websocket_stream, _) =
cert.name_constraints.as_ref().map(|der| der.to_vec()), match connect_async_with_config(websocket_url, None, false).await {
) Ok(websocket_stream) => websocket_stream,
}) Err(e) => return Err(TungsteniteBackendError::TungsteniteError { error: e }),
.collect(), };
};
let (websocket_stream, _) = match connect_async_tls_with_config(
websocket_url,
None,
false,
Some(Connector::Rustls(
rustls::ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(roots)
.with_no_client_auth()
.into(),
)),
)
.await
{
Ok(websocket_stream) => websocket_stream,
Err(e) => return Err(TungsteniteBackendError::TungsteniteError { error: e }),
};
Ok(websocket_stream.split()) Ok(websocket_stream.split())
} else if websocket_url_parsed.scheme() == "wss" {
let certs = webpki_roots::TLS_SERVER_ROOTS;
let roots = rustls::RootCertStore {
roots: certs
.iter()
.map(|cert| {
rustls::OwnedTrustAnchor::from_subject_spki_name_constraints(
cert.subject.to_vec(),
cert.subject_public_key_info.to_vec(),
cert.name_constraints.as_ref().map(|der| der.to_vec()),
)
})
.collect(),
};
let (websocket_stream, _) = match connect_async_tls_with_config(
websocket_url,
None,
false,
Some(Connector::Rustls(
rustls::ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(roots)
.with_no_client_auth()
.into(),
)),
)
.await
{
Ok(websocket_stream) => websocket_stream,
Err(e) => return Err(TungsteniteBackendError::TungsteniteError { error: e }),
};
Ok(websocket_stream.split())
} else {
Err(TungsteniteBackendError::TungsteniteError {
error: tungstenite::error::Error::Url(
tungstenite::error::UrlError::UnsupportedUrlScheme,
),
})
}
} }
} }

View File

@ -48,7 +48,7 @@ impl Gateway {
/// # Note /// # Note
/// The websocket url should begin with the prefix wss:// or ws:// (for unsecure connections) /// The websocket url should begin with the prefix wss:// or ws:// (for unsecure connections)
pub async fn spawn( pub async fn spawn(
websocket_url: String, websocket_url: &str,
options: GatewayOptions, options: GatewayOptions,
) -> Result<GatewayHandle, GatewayError> { ) -> Result<GatewayHandle, GatewayError> {
let url = options.add_to_url(websocket_url); let url = options.add_to_url(websocket_url);

View File

@ -10,9 +10,10 @@ use std::fmt::Debug;
use super::{events::Events, *}; use super::{events::Events, *};
use crate::types::{self, Composite, Shared}; use crate::types::{self, Composite, Shared};
/// Represents a handle to a Gateway connection. A Gateway connection will create observable /// Represents a handle to a Gateway connection.
/// [`GatewayEvents`](GatewayEvent), which you can subscribe to. Gateway events include all currently ///
/// implemented types with the trait [`WebSocketEvent`] /// A Gateway connection will create observable [`Events`], which you can subscribe to.
///
/// Using this handle you can also send Gateway Events directly. /// Using this handle you can also send Gateway Events directly.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct GatewayHandle { pub struct GatewayHandle {

View File

@ -25,8 +25,8 @@ impl GatewayOptions {
/// Adds the options to an existing gateway url /// Adds the options to an existing gateway url
/// ///
/// Returns the new url /// Returns the new url
pub(crate) fn add_to_url(&self, url: String) -> String { pub(crate) fn add_to_url(&self, url: &str) -> String {
let mut url = url; let mut url = url.to_string();
let mut parameters = Vec::with_capacity(2); let mut parameters = Vec::with_capacity(2);

View File

@ -69,8 +69,13 @@ impl Instance {
/// Creates a new [`Instance`] from the [relevant instance urls](UrlBundle). /// Creates a new [`Instance`] from the [relevant instance urls](UrlBundle).
/// ///
/// If `options` is `None`, the default [`GatewayOptions`] will be used.
///
/// To create an Instance from one singular url, use [`Instance::new()`]. /// To create an Instance from one singular url, use [`Instance::new()`].
pub async fn from_url_bundle(urls: UrlBundle) -> ChorusResult<Instance> { pub async fn from_url_bundle(
urls: UrlBundle,
options: Option<GatewayOptions>,
) -> ChorusResult<Instance> {
let is_limited: Option<LimitsConfiguration> = Instance::is_limited(&urls.api).await?; let is_limited: Option<LimitsConfiguration> = Instance::is_limited(&urls.api).await?;
let limit_information; let limit_information;
@ -89,7 +94,7 @@ impl Instance {
instance_info: GeneralConfiguration::default(), instance_info: GeneralConfiguration::default(),
limits_information: limit_information, limits_information: limit_information,
client: Client::new(), client: Client::new(),
gateway_options: GatewayOptions::default(), gateway_options: options.unwrap_or_default(),
}; };
instance.instance_info = match instance.general_configuration_schema().await { instance.instance_info = match instance.general_configuration_schema().await {
Ok(schema) => schema, Ok(schema) => schema,
@ -103,14 +108,16 @@ impl Instance {
/// Creates a new [`Instance`] by trying to get the [relevant instance urls](UrlBundle) from a root url. /// Creates a new [`Instance`] by trying to get the [relevant instance urls](UrlBundle) from a root url.
/// ///
/// If `options` is `None`, the default [`GatewayOptions`] will be used.
///
/// Shorthand for `Instance::from_url_bundle(UrlBundle::from_root_domain(root_domain).await?)`. /// Shorthand for `Instance::from_url_bundle(UrlBundle::from_root_domain(root_domain).await?)`.
pub async fn new(root_url: &str) -> ChorusResult<Instance> { pub async fn new(root_url: &str, options: Option<GatewayOptions>) -> ChorusResult<Instance> {
let urls = UrlBundle::from_root_url(root_url).await?; let urls = UrlBundle::from_root_url(root_url).await?;
Instance::from_url_bundle(urls).await Instance::from_url_bundle(urls, options).await
} }
pub async fn is_limited(api_url: &str) -> ChorusResult<Option<LimitsConfiguration>> { pub async fn is_limited(api_url: &str) -> ChorusResult<Option<LimitsConfiguration>> {
let api_url = UrlBundle::parse_url(api_url.to_string()); let api_url = UrlBundle::parse_url(api_url);
let client = Client::new(); let client = Client::new();
let request = client let request = client
.get(format!("{}/policies/instance/limits", &api_url)) .get(format!("{}/policies/instance/limits", &api_url))
@ -163,8 +170,8 @@ impl ChorusUser {
self.token.clone() self.token.clone()
} }
pub fn set_token(&mut self, token: String) { pub fn set_token(&mut self, token: &str) {
self.token = token; self.token = token.to_string();
} }
/// Creates a new [ChorusUser] from existing data. /// Creates a new [ChorusUser] from existing data.
@ -195,16 +202,15 @@ impl ChorusUser {
/// registering or logging in to the Instance, where you do not yet have a User object, but still /// registering or logging in to the Instance, where you do not yet have a User object, but still
/// need to make a RateLimited request. To use the [`GatewayHandle`], you will have to identify /// need to make a RateLimited request. To use the [`GatewayHandle`], you will have to identify
/// first. /// first.
pub(crate) async fn shell(instance: Shared<Instance>, token: String) -> ChorusUser { pub(crate) async fn shell(instance: Shared<Instance>, token: &str) -> ChorusUser {
let settings = Arc::new(RwLock::new(UserSettings::default())); let settings = Arc::new(RwLock::new(UserSettings::default()));
let object = Arc::new(RwLock::new(User::default())); let object = Arc::new(RwLock::new(User::default()));
let wss_url = instance.read().unwrap().urls.wss.clone(); let wss_url = &instance.read().unwrap().urls.wss.clone();
let gateway_options = instance.read().unwrap().gateway_options;
// Dummy gateway object // Dummy gateway object
let gateway = Gateway::spawn(wss_url, GatewayOptions::default()) let gateway = Gateway::spawn(wss_url, gateway_options).await.unwrap();
.await
.unwrap();
ChorusUser { ChorusUser {
token, token: token.to_string(),
belongs_to: instance.clone(), belongs_to: instance.clone(),
limits: instance limits: instance
.read() .read()

View File

@ -3,27 +3,29 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
/*! /*!
Chorus combines all the required functionalities of a user-centric Spacebar library into one package. Chorus is a Rust library which poses as an API wrapper for [Spacebar Chat](https://github.com/spacebarchat/),
Discord and our own Polyphony. Its high-level API is designed to be easy to use, while still providing the
flexibility one would expect from a library like this.
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 an API wrapper for chat services into one modular library.
The library handles various aspects on your behalf, such as rate limiting, authentication and maintaining 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, a WebSocket connection to the Gateway. This means that you can focus on building your application,
instead of worrying about the underlying implementation details. instead of worrying about the underlying implementation details.
### Establishing a Connection ### 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: To connect to a Polyphony/Spacebar compatible server, you'll need to create an [`Instance`](https://docs.rs/chorus/latest/chorus/instance/struct.Instance.html) like this:
```rs ```rs
use chorus::instance::Instance; use chorus::instance::Instance;
use chorus::UrlBundle;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let bundle = UrlBundle::new( let instance = Instance::new("https://example.com")
"https://example.com/api".to_string(),
"wss://example.com/".to_string(),
"https://example.com/cdn".to_string(),
);
let instance = Instance::new(bundle)
.await .await
.expect("Failed to connect to the Spacebar server"); .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. // You can create as many instances of `Instance` as you want, but each `Instance` should likely be unique.
@ -36,7 +38,7 @@ This Instance can now be used to log in, register and from there on, interact wi
### Logging In ### 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 Logging in correctly provides you with an instance of `ChorusUser`, 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: manipulate the account. Assuming you already have an account on the server, you can log in like this:
```rs ```rs
@ -48,7 +50,7 @@ let login_schema = LoginSchema {
password: "Correct-Horse-Battery-Staple".to_string(), password: "Correct-Horse-Battery-Staple".to_string(),
..Default::default() ..Default::default()
}; };
// Each user connects to the Gateway. The Gateway connection lives on a separate thread. Depending on // Each user connects to the Gateway. Each users' Gateway connection lives on a separate thread. Depending on
// the runtime feature you choose, this can potentially take advantage of all of your computers' threads. // the runtime feature you choose, this can potentially take advantage of all of your computers' threads.
let user = instance let user = instance
.login_account(login_schema) .login_account(login_schema)
@ -64,15 +66,33 @@ All major desktop operating systems (Windows, macOS (aarch64/x86_64), Linux (aar
`wasm32-unknown-unknown` is a supported compilation target on versions `0.12.0` and up. This allows you to use `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. 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. To compile for `wasm32-unknown-unknown`, execute the following command:
```sh
cargo build --target=wasm32-unknown-unknown --no-default-features
```
The following features are supported on `wasm32-unknown-unknown`:
| Feature | WASM Support |
| ----------------- | ------------ |
| `client` | |
| `rt` | |
| `rt-multi-thread` | |
| `backend` | |
| `voice` | |
| `voice_udp` | |
| `voice_gateway` | |
We recommend checking out the "examples" directory, as well as the documentation for more information.
## MSRV (Minimum Supported Rust Version) ## 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. Rust **1.70.0**. This number might change at any point while Chorus is not yet at version 1.0.0.
## Development Setup ## Development Setup
Make sure that you have at least Rust 1.67.1 installed. You can check your Rust version by running `cargo --version` Make sure that you have at least Rust 1.70.0 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. 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`. You can do this by running `rustup target add wasm32-unknown-unknown`.
@ -86,12 +106,16 @@ like "proxy connection checking" are already disabled on this version, which oth
### wasm ### wasm
To test for wasm, you will need to `cargo install wasm-pack`. You can then run 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` `wasm-pack test --<chrome/firefox/safari> --headless -- --target wasm32-unknown-unknown --features="rt, client, voice_gateway" --no-default-features`
to run the tests for wasm. to run the tests for wasm.
## Versioning ## 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). 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).
## Contributing
See [CONTRIBUTING.md](./CONTRIBUTING.md).
!*/ !*/
#![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"
@ -101,8 +125,7 @@ This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read
clippy::extra_unused_lifetimes, clippy::extra_unused_lifetimes,
clippy::from_over_into, clippy::from_over_into,
clippy::needless_borrow, clippy::needless_borrow,
clippy::new_without_default, clippy::new_without_default
clippy::useless_conversion
)] )]
#![warn( #![warn(
clippy::todo, clippy::todo,
@ -111,7 +134,8 @@ This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read
clippy::print_stdout, clippy::print_stdout,
clippy::print_stderr, clippy::print_stderr,
missing_debug_implementations, missing_debug_implementations,
missing_copy_implementations missing_copy_implementations,
clippy::useless_conversion
)] )]
#[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");
@ -139,6 +163,27 @@ pub mod types;
))] ))]
pub mod voice; pub mod voice;
#[cfg(not(feature = "sqlx"))]
pub type UInt128 = u128;
#[cfg(feature = "sqlx")]
pub type UInt128 = sqlx_pg_uint::PgU128;
#[cfg(not(feature = "sqlx"))]
pub type UInt64 = u64;
#[cfg(feature = "sqlx")]
pub type UInt64 = sqlx_pg_uint::PgU64;
#[cfg(not(feature = "sqlx"))]
pub type UInt32 = u32;
#[cfg(feature = "sqlx")]
pub type UInt32 = sqlx_pg_uint::PgU32;
#[cfg(not(feature = "sqlx"))]
pub type UInt16 = u16;
#[cfg(feature = "sqlx")]
pub type UInt16 = sqlx_pg_uint::PgU16;
#[cfg(not(feature = "sqlx"))]
pub type UInt8 = u8;
#[cfg(feature = "sqlx")]
pub type UInt8 = sqlx_pg_uint::PgU8;
#[derive(Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[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.
/// ///
@ -165,7 +210,7 @@ pub struct UrlBundle {
impl UrlBundle { impl UrlBundle {
/// Creates a new UrlBundle from the relevant urls. /// Creates a new UrlBundle from the relevant urls.
pub fn new(root: String, api: String, wss: String, cdn: String) -> Self { pub fn new(root: &str, api: &str, wss: &str, cdn: &str) -> Self {
Self { Self {
root: UrlBundle::parse_url(root), root: UrlBundle::parse_url(root),
api: UrlBundle::parse_url(api), api: UrlBundle::parse_url(api),
@ -182,17 +227,17 @@ impl UrlBundle {
/// let url = parse_url("localhost:3000"); /// let url = parse_url("localhost:3000");
/// ``` /// ```
/// `-> Outputs "http://localhost:3000".` /// `-> Outputs "http://localhost:3000".`
pub fn parse_url(url: String) -> String { pub fn parse_url(url: &str) -> String {
let url = match Url::parse(&url) { let url = match Url::parse(url) {
Ok(url) => { Ok(url) => {
if url.scheme() == "localhost" { if url.scheme() == "localhost" {
return UrlBundle::parse_url(format!("http://{}", url)); return UrlBundle::parse_url(&format!("http://{}", url));
} }
url url
} }
Err(ParseError::RelativeUrlWithoutBase) => { Err(ParseError::RelativeUrlWithoutBase) => {
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"), // TODO: should not panic here Err(_) => panic!("Invalid URL"), // TODO: should not panic here
}; };
@ -215,7 +260,7 @@ impl UrlBundle {
/// of the above approaches fail, it is very likely that the instance is misconfigured, unreachable, or that /// of the above approaches fail, it is very likely that the instance is misconfigured, unreachable, or that
/// a wrong URL was provided. /// a wrong URL was provided.
pub async fn from_root_url(url: &str) -> ChorusResult<UrlBundle> { pub async fn from_root_url(url: &str) -> ChorusResult<UrlBundle> {
let parsed = UrlBundle::parse_url(url.to_string()); let parsed = UrlBundle::parse_url(url);
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let request_wellknown = client let request_wellknown = client
.get(format!("{}/.well-known/spacebar", &parsed)) .get(format!("{}/.well-known/spacebar", &parsed))
@ -253,10 +298,10 @@ impl UrlBundle {
.await .await
{ {
Ok(UrlBundle::new( Ok(UrlBundle::new(
url.to_string(), url,
body.api_endpoint, &body.api_endpoint,
body.gateway, &body.gateway,
body.cdn, &body.cdn,
)) ))
} else { } else {
Err(ChorusError::RequestFailed { Err(ChorusError::RequestFailed {
@ -273,13 +318,13 @@ mod lib {
#[test] #[test]
fn test_parse_url() { fn test_parse_url() {
let mut result = UrlBundle::parse_url(String::from("localhost:3000/")); let mut result = UrlBundle::parse_url("localhost:3000/");
assert_eq!(result, String::from("http://localhost:3000")); assert_eq!(result, "http://localhost:3000");
result = UrlBundle::parse_url(String::from("https://some.url.com/")); result = UrlBundle::parse_url("https://some.url.com/");
assert_eq!(result, String::from("https://some.url.com"));
result = UrlBundle::parse_url(String::from("https://some.url.com/"));
assert_eq!(result, String::from("https://some.url.com"));
result = UrlBundle::parse_url(String::from("https://some.url.com"));
assert_eq!(result, String::from("https://some.url.com")); assert_eq!(result, String::from("https://some.url.com"));
result = UrlBundle::parse_url("https://some.url.com/");
assert_eq!(result, "https://some.url.com");
result = UrlBundle::parse_url("https://some.url.com");
assert_eq!(result, "https://some.url.com");
} }
} }

View File

@ -162,11 +162,11 @@ impl Display for GuildFeaturesList {
} }
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
impl<'r> sqlx::Decode<'r, sqlx::Any> for GuildFeaturesList { impl<'r> sqlx::Decode<'r, sqlx::Postgres> for GuildFeaturesList {
fn decode( fn decode(
value: <sqlx::Any as sqlx::Database>::ValueRef<'r>, value: <sqlx::Postgres as sqlx::Database>::ValueRef<'r>,
) -> Result<Self, sqlx::error::BoxDynError> { ) -> Result<Self, sqlx::error::BoxDynError> {
let v = <String as sqlx::Decode<sqlx::Any>>::decode(value)?; let v = <String as sqlx::Decode<sqlx::Postgres>>::decode(value)?;
Ok(Self( Ok(Self(
v.split(',') v.split(',')
.filter(|f| !f.is_empty()) .filter(|f| !f.is_empty())
@ -177,10 +177,10 @@ impl<'r> sqlx::Decode<'r, sqlx::Any> for GuildFeaturesList {
} }
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
impl<'q> sqlx::Encode<'q, sqlx::Any> for GuildFeaturesList { impl<'q> sqlx::Encode<'q, sqlx::Postgres> for GuildFeaturesList {
fn encode_by_ref( fn encode_by_ref(
&self, &self,
buf: &mut <sqlx::Any as sqlx::Database>::ArgumentBuffer<'q>, buf: &mut <sqlx::Postgres as sqlx::Database>::ArgumentBuffer<'q>,
) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync>> {
if self.is_empty() { if self.is_empty() {
return Ok(sqlx::encode::IsNull::Yes); return Ok(sqlx::encode::IsNull::Yes);
@ -191,18 +191,18 @@ impl<'q> sqlx::Encode<'q, sqlx::Any> for GuildFeaturesList {
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(","); .join(",");
<String as sqlx::Encode<sqlx::Any>>::encode_by_ref(&features, buf) <String as sqlx::Encode<sqlx::Postgres>>::encode_by_ref(&features, buf)
} }
} }
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
impl sqlx::Type<sqlx::Any> for GuildFeaturesList { impl sqlx::Type<sqlx::Postgres> for GuildFeaturesList {
fn type_info() -> sqlx::any::AnyTypeInfo { fn type_info() -> <sqlx::Postgres as sqlx::Database>::TypeInfo {
<String as sqlx::Type<sqlx::Any>>::type_info() <String as sqlx::Type<sqlx::Postgres>>::type_info()
} }
fn compatible(ty: &sqlx::any::AnyTypeInfo) -> bool { fn compatible(ty: &<sqlx::Postgres as sqlx::Database>::TypeInfo) -> bool {
<String as sqlx::Type<sqlx::Any>>::compatible(ty) <String as sqlx::Type<sqlx::Postgres>>::compatible(ty)
} }
} }

View File

@ -224,7 +224,8 @@ pub struct ApplicationCommandOptionChoice {
#[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[repr(i32)] #[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
/// # Reference /// # Reference
/// See <https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-types> /// See <https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-types>
pub enum ApplicationCommandOptionType { pub enum ApplicationCommandOptionType {
@ -294,7 +295,8 @@ pub struct ApplicationCommandPermission {
Ord, Ord,
)] )]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[repr(u8)] #[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
/// See <https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-application-command-permission-type> /// See <https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-application-command-permission-type>
pub enum ApplicationCommandPermissionType { pub enum ApplicationCommandPermissionType {
#[default] #[default]

View File

@ -5,6 +5,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::utils::Snowflake; use crate::types::utils::Snowflake;
use crate::UInt64;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, PartialOrd)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, PartialOrd)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
@ -16,11 +17,11 @@ pub struct Attachment {
/// Max 1024 characters /// Max 1024 characters
pub description: Option<String>, pub description: Option<String>,
pub content_type: Option<String>, pub content_type: Option<String>,
pub size: u64, pub size: UInt64,
pub url: String, pub url: String,
pub proxy_url: String, pub proxy_url: String,
pub height: Option<u64>, pub height: Option<UInt64>,
pub width: Option<u64>, pub width: Option<UInt64>,
pub ephemeral: Option<bool>, pub ephemeral: Option<bool>,
/// The duration of the audio file (only for voice messages) /// The duration of the audio file (only for voice messages)
pub duration_secs: Option<f32>, pub duration_secs: Option<f32>,
@ -37,12 +38,12 @@ pub struct Attachment {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct PartialDiscordFileAttachment { pub struct PartialDiscordFileAttachment {
pub id: Option<i16>, pub id: Option<UInt64>,
pub filename: String, pub filename: String,
/// Max 1024 characters /// Max 1024 characters
pub description: Option<String>, pub description: Option<String>,
pub content_type: Option<String>, pub content_type: Option<String>,
pub size: Option<i64>, pub size: Option<UInt64>,
pub url: Option<String>, pub url: Option<String>,
pub proxy_url: Option<String>, pub proxy_url: Option<String>,
pub height: Option<i32>, pub height: Option<i32>,

View File

@ -12,6 +12,7 @@ use crate::types::utils::Snowflake;
use crate::types::{ use crate::types::{
AutoModerationRuleTriggerType, IntegrationType, PermissionOverwriteType, Shared, AutoModerationRuleTriggerType, IntegrationType, PermissionOverwriteType, Shared,
}; };
use crate::UInt64;
#[derive(Serialize, Deserialize, Debug, Default, Clone)] #[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
@ -108,7 +109,8 @@ pub struct AuditLogChange {
PartialOrd, PartialOrd,
Ord, Ord,
)] )]
#[repr(u8)] #[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
/// # Reference: /// # Reference:
/// See <https://docs.discord.sex/resources/audit-log#audit-log-events> /// See <https://docs.discord.sex/resources/audit-log#audit-log-events>
@ -251,16 +253,16 @@ pub struct AuditEntryInfo {
pub auto_moderation_rule_trigger_type: Option<AutoModerationRuleTriggerType>, pub auto_moderation_rule_trigger_type: Option<AutoModerationRuleTriggerType>,
pub channel_id: Option<Snowflake>, pub channel_id: Option<Snowflake>,
// #[serde(option_string)] // #[serde(option_string)]
pub count: Option<u64>, pub count: Option<UInt64>,
// #[serde(option_string)] // #[serde(option_string)]
pub delete_member_days: Option<u64>, pub delete_member_days: Option<UInt64>,
/// The ID of the overwritten entity /// The ID of the overwritten entity
pub id: Option<Snowflake>, pub id: Option<Snowflake>,
pub integration_type: Option<IntegrationType>, pub integration_type: Option<IntegrationType>,
// #[serde(option_string)] // #[serde(option_string)]
pub members_removed: Option<u64>, pub members_removed: Option<UInt64>,
// #[serde(option_string)] // #[serde(option_string)]
pub message_id: Option<u64>, pub message_id: Option<UInt64>,
pub role_name: Option<String>, pub role_name: Option<String>,
#[serde(rename = "type")] #[serde(rename = "type")]
pub overwrite_type: Option<PermissionOverwriteType>, pub overwrite_type: Option<PermissionOverwriteType>,

View File

@ -5,6 +5,7 @@
#[cfg(feature = "client")] #[cfg(feature = "client")]
use crate::gateway::Updateable; use crate::gateway::Updateable;
use crate::types::Shared; use crate::types::Shared;
use crate::UInt8;
#[cfg(feature = "client")] #[cfg(feature = "client")]
use chorus_macros::Updateable; use chorus_macros::Updateable;
@ -32,7 +33,8 @@ pub struct AutoModerationRule {
} }
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default, Copy)] #[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default, Copy)]
#[repr(u8)] #[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-event-types> /// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-event-types>
pub enum AutoModerationRuleEventType { pub enum AutoModerationRuleEventType {
@ -43,7 +45,8 @@ pub enum AutoModerationRuleEventType {
#[derive( #[derive(
Serialize_repr, Deserialize_repr, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy, Serialize_repr, Deserialize_repr, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy,
)] )]
#[repr(u8)] #[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-types> /// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-types>
pub enum AutoModerationRuleTriggerType { pub enum AutoModerationRuleTriggerType {
@ -80,18 +83,20 @@ pub struct AutoModerationRuleTriggerMetadataForKeywordPreset {
pub allow_list: Vec<String>, pub allow_list: Vec<String>,
} }
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy)] #[allow(missing_copy_implementations)]
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata> /// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata>
pub struct AutoModerationRuleTriggerMetadataForMentionSpam { pub struct AutoModerationRuleTriggerMetadataForMentionSpam {
/// Max 50 /// Max 50
pub mention_total_limit: u8, pub mention_total_limit: UInt8,
pub mention_raid_protection_enabled: bool, pub mention_raid_protection_enabled: bool,
} }
#[derive( #[derive(
Serialize_repr, Deserialize_repr, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy, Serialize_repr, Deserialize_repr, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy,
)] )]
#[repr(u8)] #[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-keyword-preset-types> /// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-keyword-preset-types>
pub enum AutoModerationRuleKeywordPresetType { pub enum AutoModerationRuleKeywordPresetType {
@ -110,9 +115,20 @@ pub struct AutoModerationAction {
} }
#[derive( #[derive(
Serialize_repr, Deserialize_repr, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy, Hash Serialize_repr,
Deserialize_repr,
Debug,
Clone,
Default,
PartialEq,
Eq,
PartialOrd,
Ord,
Copy,
Hash,
)] )]
#[repr(u8)] #[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-action-object-action-types> /// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-action-object-action-types>
pub enum AutoModerationActionType { pub enum AutoModerationActionType {

View File

@ -22,6 +22,7 @@ use crate::gateway::GatewayHandle;
#[cfg(feature = "client")] #[cfg(feature = "client")]
use crate::gateway::Updateable; use crate::gateway::Updateable;
use crate::UInt64;
#[cfg(feature = "client")] #[cfg(feature = "client")]
use chorus_macros::{observe_option_vec, Composite, Updateable}; use chorus_macros::{observe_option_vec, Composite, Updateable};
@ -41,13 +42,7 @@ use super::{option_arc_rwlock_ptr_eq, option_vec_arc_rwlock_ptr_eq};
/// See <https://discord-userdoccers.vercel.app/resources/channel#channels-resource> /// See <https://discord-userdoccers.vercel.app/resources/channel#channels-resource>
pub struct Channel { pub struct Channel {
pub application_id: Option<Snowflake>, pub application_id: Option<Snowflake>,
#[cfg(feature = "sqlx")]
pub applied_tags: Option<sqlx::types::Json<Vec<String>>>,
#[cfg(not(feature = "sqlx"))]
pub applied_tags: Option<Vec<String>>, pub applied_tags: Option<Vec<String>>,
#[cfg(feature = "sqlx")]
pub available_tags: Option<sqlx::types::Json<Vec<Tag>>>,
#[cfg(not(feature = "sqlx"))]
pub available_tags: Option<Vec<Tag>>, pub available_tags: Option<Vec<Tag>>,
pub bitrate: Option<i32>, pub bitrate: Option<i32>,
#[serde(rename = "type")] #[serde(rename = "type")]
@ -55,9 +50,7 @@ pub struct Channel {
pub created_at: Option<chrono::DateTime<Utc>>, pub created_at: Option<chrono::DateTime<Utc>>,
pub default_auto_archive_duration: Option<i32>, pub default_auto_archive_duration: Option<i32>,
pub default_forum_layout: Option<i32>, pub default_forum_layout: Option<i32>,
#[cfg(feature = "sqlx")] // DefaultReaction could be stored in a separate table. However, there are a lot of default emojis. How would we handle that?
pub default_reaction_emoji: Option<sqlx::types::Json<DefaultReaction>>,
#[cfg(not(feature = "sqlx"))]
pub default_reaction_emoji: Option<DefaultReaction>, pub default_reaction_emoji: Option<DefaultReaction>,
pub default_sort_order: Option<i32>, pub default_sort_order: Option<i32>,
pub default_thread_rate_limit_per_user: Option<i32>, pub default_thread_rate_limit_per_user: Option<i32>,
@ -179,6 +172,8 @@ fn compare_permission_overwrites(
/// ///
/// # Reference /// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/channel#forum-tag-object> /// See <https://discord-userdoccers.vercel.app/resources/channel#forum-tag-object>
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow, sqlx::Type))]
#[cfg_attr(feature = "sqlx", sqlx(type_name = "interface_type"))]
pub struct Tag { pub struct Tag {
pub id: Snowflake, pub id: Snowflake,
/// The name of the tag (max 20 characters) /// The name of the tag (max 20 characters)
@ -202,7 +197,8 @@ pub struct PermissionOverwrite {
} }
#[derive(Debug, Serialize_repr, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)] #[derive(Debug, Serialize_repr, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)]
#[repr(u8)] #[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
/// # Reference /// # Reference
/// ///
/// See <https://docs.discord.sex/resources/channel#permission-overwrite-type> /// See <https://docs.discord.sex/resources/channel#permission-overwrite-type>
@ -301,7 +297,7 @@ pub struct ThreadMember {
pub id: Option<Snowflake>, pub id: Option<Snowflake>,
pub user_id: Option<Snowflake>, pub user_id: Option<Snowflake>,
pub join_timestamp: Option<DateTime<Utc>>, pub join_timestamp: Option<DateTime<Utc>>,
pub flags: Option<u64>, pub flags: Option<UInt64>,
pub member: Option<Shared<GuildMember>>, pub member: Option<Shared<GuildMember>>,
} }
@ -321,6 +317,8 @@ impl PartialEq for ThreadMember {
/// ///
/// # Reference /// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/channel#default-reaction-object> /// See <https://discord-userdoccers.vercel.app/resources/channel#default-reaction-object>
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow, sqlx::Type))]
#[cfg_attr(feature = "sqlx", sqlx(type_name = "interface_type"))]
pub struct DefaultReaction { pub struct DefaultReaction {
#[serde(default)] #[serde(default)]
pub emoji_id: Option<Snowflake>, pub emoji_id: Option<Snowflake>,
@ -342,7 +340,7 @@ pub struct DefaultReaction {
)] )]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[repr(u32)] #[repr(i32)]
/// # Reference /// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/channel#channel-type> /// See <https://discord-userdoccers.vercel.app/resources/channel#channel-type>
pub enum ChannelType { pub enum ChannelType {

View File

@ -32,9 +32,6 @@ use super::option_arc_rwlock_ptr_eq;
pub struct Emoji { pub struct Emoji {
pub id: Snowflake, pub id: Snowflake,
pub name: Option<String>, pub name: Option<String>,
#[cfg(feature = "sqlx")]
pub roles: Option<sqlx::types::Json<Vec<Snowflake>>>,
#[cfg(not(feature = "sqlx"))]
pub roles: Option<Vec<Snowflake>>, pub roles: Option<Vec<Snowflake>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub user: Option<Shared<User>>, pub user: Option<Shared<User>>,

View File

@ -17,6 +17,7 @@ use crate::types::{
interfaces::WelcomeScreenObject, interfaces::WelcomeScreenObject,
utils::Snowflake, utils::Snowflake,
}; };
use crate::UInt64;
use super::{option_arc_rwlock_ptr_eq, vec_arc_rwlock_ptr_eq, PublicUser}; use super::{option_arc_rwlock_ptr_eq, vec_arc_rwlock_ptr_eq, PublicUser};
@ -273,7 +274,7 @@ pub struct GuildScheduledEvent {
pub entity_id: Option<Snowflake>, pub entity_id: Option<Snowflake>,
pub entity_metadata: Option<GuildScheduledEventEntityMetadata>, pub entity_metadata: Option<GuildScheduledEventEntityMetadata>,
pub creator: Option<Shared<User>>, pub creator: Option<Shared<User>>,
pub user_count: Option<u64>, pub user_count: Option<UInt64>,
pub image: Option<String>, pub image: Option<String>,
} }
@ -300,7 +301,8 @@ impl PartialEq for GuildScheduledEvent {
} }
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, PartialEq, Copy)] #[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, PartialEq, Copy)]
#[repr(u8)] #[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
/// See <https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-privacy-level> /// See <https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-privacy-level>
pub enum GuildScheduledEventPrivacyLevel { pub enum GuildScheduledEventPrivacyLevel {
#[default] #[default]
@ -308,7 +310,8 @@ pub enum GuildScheduledEventPrivacyLevel {
} }
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, PartialEq, Copy)] #[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, PartialEq, Copy)]
#[repr(u8)] #[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
/// See <https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-status> /// See <https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-status>
pub enum GuildScheduledEventStatus { pub enum GuildScheduledEventStatus {
#[default] #[default]
@ -331,7 +334,8 @@ pub enum GuildScheduledEventStatus {
Copy, Copy,
Hash, Hash,
)] )]
#[repr(u8)] #[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
/// See <https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-entity-types> /// See <https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-entity-types>
pub enum GuildScheduledEventEntityType { pub enum GuildScheduledEventEntityType {
#[default] #[default]
@ -369,7 +373,8 @@ pub struct VoiceRegion {
Ord, Ord,
)] )]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[repr(u8)] #[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord-userdoccers.vercel.app/resources/guild#message-notification-level> /// See <https://discord-userdoccers.vercel.app/resources/guild#message-notification-level>
pub enum MessageNotificationLevel { pub enum MessageNotificationLevel {
@ -392,7 +397,8 @@ pub enum MessageNotificationLevel {
Ord, Ord,
)] )]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[repr(u8)] #[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord-userdoccers.vercel.app/resources/guild#explicit-content-filter-level> /// See <https://discord-userdoccers.vercel.app/resources/guild#explicit-content-filter-level>
pub enum ExplicitContentFilterLevel { pub enum ExplicitContentFilterLevel {
@ -416,7 +422,8 @@ pub enum ExplicitContentFilterLevel {
Ord, Ord,
)] )]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[repr(u8)] #[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord-userdoccers.vercel.app/resources/guild#verification-level> /// See <https://discord-userdoccers.vercel.app/resources/guild#verification-level>
pub enum VerificationLevel { pub enum VerificationLevel {
@ -442,7 +449,8 @@ pub enum VerificationLevel {
Ord, Ord,
)] )]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[repr(u8)] #[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://docs.discord.sex/resources/guild#mfa-level> /// See <https://docs.discord.sex/resources/guild#mfa-level>
pub enum MFALevel { pub enum MFALevel {
@ -465,7 +473,8 @@ pub enum MFALevel {
Ord, Ord,
)] )]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[repr(u8)] #[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://docs.discord.sex/resources/guild#nsfw-level> /// See <https://docs.discord.sex/resources/guild#nsfw-level>
pub enum NSFWLevel { pub enum NSFWLevel {
@ -490,7 +499,8 @@ pub enum NSFWLevel {
Ord, Ord,
)] )]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[repr(u8)] #[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
// Note: Maybe rename this to GuildPremiumTier? // Note: Maybe rename this to GuildPremiumTier?
/// **Guild** premium (Boosting) tier /// **Guild** premium (Boosting) tier

View File

@ -11,6 +11,7 @@ use crate::types::{
utils::Snowflake, utils::Snowflake,
Shared, Shared,
}; };
use crate::{UInt16, UInt8};
#[derive(Default, Debug, Deserialize, Serialize, Clone)] #[derive(Default, Debug, Deserialize, Serialize, Clone)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
@ -25,7 +26,7 @@ pub struct Integration {
pub role_id: Option<String>, pub role_id: Option<String>,
pub enabled_emoticons: Option<bool>, pub enabled_emoticons: Option<bool>,
pub expire_behaviour: Option<IntegrationExpireBehaviour>, pub expire_behaviour: Option<IntegrationExpireBehaviour>,
pub expire_grace_period: Option<u16>, pub expire_grace_period: Option<UInt16>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub user: Option<Shared<User>>, pub user: Option<Shared<User>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]

View File

@ -5,8 +5,12 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::{Snowflake, WelcomeScreenObject, Shared, InviteFlags, InviteType, InviteTargetType, Guild, VerificationLevel};
use crate::types::types::guild_configuration::GuildFeaturesList; use crate::types::types::guild_configuration::GuildFeaturesList;
use crate::types::{
Guild, InviteFlags, InviteTargetType, InviteType, Shared, Snowflake, VerificationLevel,
WelcomeScreenObject,
};
use crate::{UInt32, UInt8};
use super::guild::GuildScheduledEvent; use super::guild::GuildScheduledEvent;
use super::{Application, Channel, GuildMember, NSFWLevel, User}; use super::{Application, Channel, GuildMember, NSFWLevel, User};
@ -36,8 +40,8 @@ pub struct Invite {
pub invite_type: Option<InviteType>, pub invite_type: Option<InviteType>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub inviter: Option<User>, pub inviter: Option<User>,
pub max_age: Option<u32>, pub max_age: Option<UInt32>,
pub max_uses: Option<u8>, pub max_uses: Option<UInt8>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub stage_instance: Option<InviteStageInstance>, pub stage_instance: Option<InviteStageInstance>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
@ -47,7 +51,7 @@ pub struct Invite {
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub target_user: Option<User>, pub target_user: Option<User>,
pub temporary: Option<bool>, pub temporary: Option<bool>,
pub uses: Option<u32>, pub uses: Option<UInt32>,
} }
/// The guild an invite is for. /// The guild an invite is for.

View File

@ -15,6 +15,7 @@ use crate::types::{
utils::Snowflake, utils::Snowflake,
Shared, Shared,
}; };
use crate::{UInt32, UInt8};
use super::option_arc_rwlock_ptr_eq; use super::option_arc_rwlock_ptr_eq;
@ -150,7 +151,7 @@ pub enum MessageReferenceType {
pub struct MessageInteraction { pub struct MessageInteraction {
pub id: Snowflake, pub id: Snowflake,
#[serde(rename = "type")] #[serde(rename = "type")]
pub interaction_type: u8, pub interaction_type: UInt8,
pub name: String, pub name: String,
pub user: User, pub user: User,
pub member: Option<Shared<GuildMember>>, pub member: Option<Shared<GuildMember>>,
@ -282,8 +283,8 @@ pub struct EmbedField {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Reaction { pub struct Reaction {
pub count: u32, pub count: UInt32,
pub burst_count: u32, pub burst_count: UInt32,
#[serde(default)] #[serde(default)]
pub me: bool, pub me: bool,
#[serde(default)] #[serde(default)]
@ -296,6 +297,8 @@ pub struct Reaction {
} }
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, Eq, PartialOrd, Ord)] #[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, Eq, PartialOrd, Ord)]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
pub enum Component { pub enum Component {
ActionRow = 1, ActionRow = 1,
Button = 2, Button = 2,
@ -320,7 +323,8 @@ pub struct MessageActivity {
Debug, Default, PartialEq, Clone, Copy, Serialize_repr, Deserialize_repr, Eq, PartialOrd, Ord, Debug, Default, PartialEq, Clone, Copy, Serialize_repr, Deserialize_repr, Eq, PartialOrd, Ord,
)] )]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[repr(u8)] #[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
/// # Reference /// # Reference
/// See <https://docs.discord.sex/resources/message#message-type> /// See <https://docs.discord.sex/resources/message#message-type>
@ -464,7 +468,8 @@ pub struct PartialEmoji {
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, PartialOrd, Ord, Eq, Hash)] #[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, PartialOrd, Ord, Eq, Hash)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[repr(u8)] #[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
pub enum ReactionType { pub enum ReactionType {
Normal = 0, Normal = 0,
Burst = 1, // The dreaded super reactions Burst = 1, // The dreaded super reactions

View File

@ -132,10 +132,10 @@ pub trait Composite<T: Updateable + Clone + Debug> {
pub trait IntoShared { pub trait IntoShared {
/// Uses [`Shared`] to provide an ergonomic alternative to `Arc::new(RwLock::new(obj))`. /// Uses [`Shared`] to provide an ergonomic alternative to `Arc::new(RwLock::new(obj))`.
/// ///
/// [`Shared<Self>`] can then be observed using the [`Gateway`], turning the underlying /// [`Shared<Self>`] can then be observed using the gateway, turning the underlying
/// `dyn Composite<Self>` into a self-updating struct, which is a tracked variant of a chorus /// `dyn Composite<Self>` into a self-updating struct, which is a tracked variant of a chorus
/// entity struct, updating its' held information when new information concerning itself arrives /// entity struct, updating its' held information when new information concerning itself arrives
/// over the [`Gateway`] connection, reducing the need for expensive network-API calls. /// over the gateway connection, reducing the need for expensive network-API calls.
fn into_shared(self) -> Shared<Self>; fn into_shared(self) -> Shared<Self>;
} }

View File

@ -45,7 +45,8 @@ impl PartialEq for Relationship {
Copy, Copy,
Hash, Hash,
)] )]
#[repr(u8)] #[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
/// See <https://discord-userdoccers.vercel.app/resources/user#relationship-type> /// See <https://discord-userdoccers.vercel.app/resources/user#relationship-type>
pub enum RelationshipType { pub enum RelationshipType {
Suggestion = 6, Suggestion = 6,

View File

@ -8,6 +8,7 @@ use serde_aux::prelude::deserialize_option_number_from_string;
use std::fmt::Debug; use std::fmt::Debug;
use crate::types::utils::Snowflake; use crate::types::utils::Snowflake;
use crate::{UInt16, UInt32};
#[cfg(feature = "client")] #[cfg(feature = "client")]
use chorus_macros::{Composite, Updateable}; use chorus_macros::{Composite, Updateable};
@ -32,7 +33,7 @@ pub struct RoleObject {
pub hoist: bool, pub hoist: bool,
pub icon: Option<String>, pub icon: Option<String>,
pub unicode_emoji: Option<String>, pub unicode_emoji: Option<String>,
pub position: u16, pub position: UInt16,
#[serde(default)] #[serde(default)]
pub permissions: PermissionFlags, pub permissions: PermissionFlags,
pub managed: bool, pub managed: bool,
@ -47,11 +48,13 @@ pub struct RoleObject {
pub struct RoleSubscriptionData { pub struct RoleSubscriptionData {
pub role_subscription_listing_id: Snowflake, pub role_subscription_listing_id: Snowflake,
pub tier_name: String, pub tier_name: String,
pub total_months_subscribed: u32, pub total_months_subscribed: UInt32,
pub is_renewal: bool, pub is_renewal: bool,
} }
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Hash, Copy, PartialOrd, Ord)] #[derive(
Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Hash, Copy, PartialOrd, Ord,
)]
/// See <https://discord.com/developers/docs/topics/permissions#role-object-role-tags-structure> /// See <https://discord.com/developers/docs/topics/permissions#role-object-role-tags-structure>
pub struct RoleTags { pub struct RoleTags {
#[serde(default)] #[serde(default)]

View File

@ -5,6 +5,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::utils::Snowflake; use crate::types::utils::Snowflake;
use crate::UInt64;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
@ -13,7 +14,7 @@ pub struct SecurityKey {
pub user_id: String, pub user_id: String,
pub key_id: String, pub key_id: String,
pub public_key: String, pub public_key: String,
pub counter: u64, pub counter: UInt64,
pub name: String, pub name: String,
} }
@ -24,7 +25,8 @@ impl Default for SecurityKey {
user_id: String::new(), user_id: String::new(),
key_id: String::new(), key_id: String::new(),
public_key: String::new(), public_key: String::new(),
counter: 0, #[allow(clippy::useless_conversion)]
counter: 0u64.into(),
name: String::new(), name: String::new(),
} }
} }

View File

@ -21,8 +21,11 @@ pub struct StageInstance {
pub guild_scheduled_event_id: Option<Snowflake>, pub guild_scheduled_event_id: Option<Snowflake>,
} }
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default, Copy, PartialEq, Eq, PartialOrd, Ord)] #[derive(
#[repr(u8)] Serialize_repr, Deserialize_repr, Debug, Clone, Default, Copy, PartialEq, Eq, PartialOrd, Ord,
)]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord.com/developers/docs/resources/stage-instance#stage-instance-object-privacy-level> /// See <https://discord.com/developers/docs/resources/stage-instance#stage-instance-object-privacy-level>
pub enum StageInstancePrivacyLevel { pub enum StageInstancePrivacyLevel {

View File

@ -77,7 +77,8 @@ pub struct StickerItem {
#[derive( #[derive(
Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Hash, Serialize_repr, Deserialize_repr, Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Hash, Serialize_repr, Deserialize_repr,
)] )]
#[repr(u8)] #[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[serde(rename = "SCREAMING_SNAKE_CASE")] #[serde(rename = "SCREAMING_SNAKE_CASE")]
/// # Reference /// # Reference
@ -93,7 +94,8 @@ pub enum StickerType {
#[derive( #[derive(
Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Hash, Serialize_repr, Deserialize_repr, Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Hash, Serialize_repr, Deserialize_repr,
)] )]
#[repr(u8)] #[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
/// # Reference /// # Reference
/// See <https://docs.discord.sex/resources/sticker#sticker-format-types> /// See <https://docs.discord.sex/resources/sticker#sticker-format-types>

View File

@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
use crate::types::entities::User; use crate::types::entities::User;
use crate::types::Shared; use crate::types::Shared;
use crate::types::Snowflake; use crate::types::Snowflake;
use crate::UInt8;
use super::arc_rwlock_ptr_eq; use super::arc_rwlock_ptr_eq;
@ -34,7 +35,7 @@ impl PartialEq for Team {
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Deserialize, Serialize, Clone)]
pub struct TeamMember { pub struct TeamMember {
pub membership_state: u8, pub membership_state: UInt8,
pub permissions: Vec<String>, pub permissions: Vec<String>,
pub team_id: Snowflake, pub team_id: Snowflake,
pub user: Shared<User>, pub user: Shared<User>,

View File

@ -6,10 +6,11 @@ use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::{ use crate::types::{
Shared,
entities::{Guild, User}, entities::{Guild, User},
utils::Snowflake, utils::Snowflake,
Shared,
}; };
use crate::UInt64;
/// See <https://docs.spacebar.chat/routes/#cmp--schemas-template> /// See <https://docs.spacebar.chat/routes/#cmp--schemas-template>
#[derive(Serialize, Deserialize, Debug, Default, Clone)] #[derive(Serialize, Deserialize, Debug, Default, Clone)]
@ -18,7 +19,7 @@ pub struct GuildTemplate {
pub code: String, pub code: String,
pub name: String, pub name: String,
pub description: Option<String>, pub description: Option<String>,
pub usage_count: Option<u64>, pub usage_count: Option<UInt64>,
pub creator_id: Snowflake, pub creator_id: Snowflake,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub creator: Shared<User>, pub creator: Shared<User>,

View File

@ -4,6 +4,7 @@
use crate::errors::ChorusError; use crate::errors::ChorusError;
use crate::types::utils::Snowflake; use crate::types::utils::Snowflake;
use crate::{UInt32, UInt8};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_aux::prelude::{deserialize_option_number_from_string, deserialize_default_from_null}; use serde_aux::prelude::{deserialize_option_number_from_string, deserialize_default_from_null};
@ -50,7 +51,7 @@ pub struct User {
pub bot: Option<bool>, pub bot: Option<bool>,
pub system: Option<bool>, pub system: Option<bool>,
pub mfa_enabled: Option<bool>, pub mfa_enabled: Option<bool>,
pub accent_color: Option<u32>, pub accent_color: Option<UInt32>,
#[cfg_attr(feature = "sqlx", sqlx(default))] #[cfg_attr(feature = "sqlx", sqlx(default))]
pub locale: Option<String>, pub locale: Option<String>,
pub verified: Option<bool>, pub verified: Option<bool>,
@ -116,32 +117,32 @@ impl TryFrom<Vec<u8>> for ThemeColors {
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
// TODO: Add tests for Encode and Decode. // TODO: Add tests for Encode and Decode.
impl<'q> sqlx::Encode<'q, sqlx::Any> for ThemeColors { impl<'q> sqlx::Encode<'q, sqlx::Postgres> for ThemeColors {
fn encode_by_ref( fn encode_by_ref(
&self, &self,
buf: &mut <sqlx::Any as sqlx::Database>::ArgumentBuffer<'q>, buf: &mut <sqlx::Postgres as sqlx::Database>::ArgumentBuffer<'q>,
) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync>> {
let mut vec_u8 = Vec::new(); let mut vec_u8 = Vec::new();
vec_u8.extend_from_slice(&self.inner.0.to_be_bytes()); vec_u8.extend_from_slice(&self.inner.0.to_be_bytes());
vec_u8.extend_from_slice(&self.inner.1.to_be_bytes()); vec_u8.extend_from_slice(&self.inner.1.to_be_bytes());
<Vec<u8> as sqlx::Encode<sqlx::Any>>::encode_by_ref(&vec_u8, buf) <Vec<u8> as sqlx::Encode<sqlx::Postgres>>::encode_by_ref(&vec_u8, buf)
} }
} }
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
impl<'d> sqlx::Decode<'d, sqlx::Any> for ThemeColors { impl<'d> sqlx::Decode<'d, sqlx::Postgres> for ThemeColors {
fn decode( fn decode(
value: <sqlx::Any as sqlx::Database>::ValueRef<'d>, value: <sqlx::Postgres as sqlx::Database>::ValueRef<'d>,
) -> Result<Self, sqlx::error::BoxDynError> { ) -> Result<Self, sqlx::error::BoxDynError> {
let value_vec = <Vec<u8> as sqlx::Decode<'d, sqlx::Any>>::decode(value)?; let value_vec = <Vec<u8> as sqlx::Decode<'d, sqlx::Postgres>>::decode(value)?;
value_vec.try_into().map_err(|e: ChorusError| e.into()) value_vec.try_into().map_err(|e: ChorusError| e.into())
} }
} }
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
impl sqlx::Type<sqlx::Any> for ThemeColors { impl sqlx::Type<sqlx::Postgres> for ThemeColors {
fn type_info() -> <sqlx::Any as sqlx::Database>::TypeInfo { fn type_info() -> <sqlx::Postgres as sqlx::Database>::TypeInfo {
<String as sqlx::Type<sqlx::Any>>::type_info() <String as sqlx::Type<sqlx::Postgres>>::type_info()
} }
} }
@ -153,7 +154,7 @@ pub struct PublicUser {
pub username: Option<String>, pub username: Option<String>,
pub discriminator: Option<String>, pub discriminator: Option<String>,
pub avatar: Option<String>, pub avatar: Option<String>,
pub accent_color: Option<u32>, pub accent_color: Option<UInt32>,
pub banner: Option<String>, pub banner: Option<String>,
pub theme_colors: Option<ThemeColors>, pub theme_colors: Option<ThemeColors>,
pub pronouns: Option<String>, pub pronouns: Option<String>,

View File

@ -6,9 +6,12 @@ use chrono::{serde::ts_milliseconds_option, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::Shared; use crate::types::Shared;
use crate::{UInt16, UInt32, UInt8};
use serde_aux::field_attributes::deserialize_option_number_from_string; use serde_aux::field_attributes::deserialize_option_number_from_string;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Copy, PartialOrd, Ord, Hash)] #[derive(
Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Copy, PartialOrd, Ord, Hash,
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum UserStatus { pub enum UserStatus {
@ -26,7 +29,9 @@ impl std::fmt::Display for UserStatus {
} }
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Copy, PartialOrd, Ord, Hash)] #[derive(
Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Copy, PartialOrd, Ord, Hash,
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum UserTheme { pub enum UserTheme {
@ -38,36 +43,23 @@ pub enum UserTheme {
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct UserSettings { pub struct UserSettings {
pub afk_timeout: Option<u16>, pub afk_timeout: Option<UInt16>,
pub allow_accessibility_detection: bool, pub allow_accessibility_detection: bool,
pub animate_emoji: bool, pub animate_emoji: bool,
pub animate_stickers: u8, pub animate_stickers: UInt8,
pub contact_sync_enabled: bool, pub contact_sync_enabled: bool,
pub convert_emoticons: bool, pub convert_emoticons: bool,
#[cfg(feature = "sqlx")]
pub custom_status: Option<sqlx::types::Json<CustomStatus>>,
#[cfg(not(feature = "sqlx"))]
pub custom_status: Option<CustomStatus>, pub custom_status: Option<CustomStatus>,
pub default_guilds_restricted: bool, pub default_guilds_restricted: bool,
pub detect_platform_accounts: bool, pub detect_platform_accounts: bool,
pub developer_mode: bool, pub developer_mode: bool,
pub disable_games_tab: bool, pub disable_games_tab: bool,
pub enable_tts_command: bool, pub enable_tts_command: bool,
pub explicit_content_filter: u8, pub explicit_content_filter: UInt8,
#[cfg(feature = "sqlx")]
pub friend_source_flags: sqlx::types::Json<FriendSourceFlags>,
#[cfg(not(feature = "sqlx"))]
pub friend_source_flags: FriendSourceFlags, pub friend_source_flags: FriendSourceFlags,
pub gateway_connected: Option<bool>, pub gateway_connected: Option<bool>,
pub gif_auto_play: bool, pub gif_auto_play: bool,
#[cfg(feature = "sqlx")]
pub guild_folders: sqlx::types::Json<Vec<GuildFolder>>,
#[cfg(not(feature = "sqlx"))]
pub guild_folders: Vec<GuildFolder>, pub guild_folders: Vec<GuildFolder>,
#[cfg(feature = "sqlx")]
#[serde(default)]
pub guild_positions: sqlx::types::Json<Vec<String>>,
#[cfg(not(feature = "sqlx"))]
#[serde(default)] #[serde(default)]
pub guild_positions: Vec<String>, pub guild_positions: Vec<String>,
pub inline_attachment_media: bool, pub inline_attachment_media: bool,
@ -77,9 +69,6 @@ pub struct UserSettings {
pub native_phone_integration_enabled: bool, pub native_phone_integration_enabled: bool,
pub render_embeds: bool, pub render_embeds: bool,
pub render_reactions: bool, pub render_reactions: bool,
#[cfg(feature = "sqlx")]
pub restricted_guilds: sqlx::types::Json<Vec<String>>,
#[cfg(not(feature = "sqlx"))]
pub restricted_guilds: Vec<String>, pub restricted_guilds: Vec<String>,
pub show_current_game: bool, pub show_current_game: bool,
pub status: Shared<UserStatus>, pub status: Shared<UserStatus>,
@ -91,10 +80,14 @@ pub struct UserSettings {
impl Default for UserSettings { impl Default for UserSettings {
fn default() -> Self { fn default() -> Self {
Self { Self {
afk_timeout: Some(3600), #[allow(clippy::useless_conversion)]
afk_timeout: Some(3600u16.into()),
allow_accessibility_detection: true, allow_accessibility_detection: true,
animate_emoji: true, animate_emoji: true,
#[cfg(not(feature = "sqlx"))]
animate_stickers: 0, animate_stickers: 0,
#[cfg(feature = "sqlx")]
animate_stickers: 0.into(),
contact_sync_enabled: false, contact_sync_enabled: false,
convert_emoticons: false, convert_emoticons: false,
custom_status: None, custom_status: None,
@ -103,7 +96,10 @@ impl Default for UserSettings {
developer_mode: true, developer_mode: true,
disable_games_tab: true, disable_games_tab: true,
enable_tts_command: false, enable_tts_command: false,
#[cfg(not(feature = "sqlx"))]
explicit_content_filter: 0, explicit_content_filter: 0,
#[cfg(feature = "sqlx")]
explicit_content_filter: 0.into(),
friend_source_flags: Default::default(), friend_source_flags: Default::default(),
gateway_connected: Some(false), gateway_connected: Some(false),
gif_auto_play: false, gif_auto_play: false,
@ -127,7 +123,8 @@ impl Default for UserSettings {
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow, sqlx::Type))]
#[cfg_attr(feature = "sqlx", sqlx(type_name = "interface_type"))]
pub struct CustomStatus { pub struct CustomStatus {
pub emoji_id: Option<String>, pub emoji_id: Option<String>,
pub emoji_name: Option<String>, pub emoji_name: Option<String>,
@ -137,6 +134,7 @@ pub struct CustomStatus {
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow, sqlx::Type))]
pub struct FriendSourceFlags { pub struct FriendSourceFlags {
pub all: bool, pub all: bool,
} }
@ -148,8 +146,10 @@ impl Default for FriendSourceFlags {
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow, sqlx::Type))]
#[cfg_attr(feature = "sqlx", sqlx(type_name = "interface_type"))]
pub struct GuildFolder { pub struct GuildFolder {
pub color: Option<u32>, pub color: Option<UInt32>,
pub guild_ids: Vec<String>, pub guild_ids: Vec<String>,
// FIXME: What is this thing? // FIXME: What is this thing?
// It's not a snowflake, and it's sometimes a string and sometimes an integer. // It's not a snowflake, and it's sometimes a string and sometimes an integer.

View File

@ -71,7 +71,8 @@ impl PartialEq for Webhook {
#[derive( #[derive(
Serialize, Deserialize, Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash,
)] )]
#[repr(u8)] #[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
pub enum WebhookType { pub enum WebhookType {
#[default] #[default]

View File

@ -5,7 +5,7 @@
use bitflags::bitflags; use bitflags::bitflags;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::{ChannelType, DefaultReaction, entities::PermissionOverwrite, Snowflake}; use crate::types::{entities::PermissionOverwrite, ChannelType, DefaultReaction, Snowflake};
#[derive(Debug, Deserialize, Serialize, Default, PartialEq, PartialOrd)] #[derive(Debug, Deserialize, Serialize, Default, PartialEq, PartialOrd)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
@ -141,7 +141,8 @@ bitflags! {
#[derive(Debug, Deserialize, Serialize, Clone, Copy, Default, PartialOrd, Ord, PartialEq, Eq)] #[derive(Debug, Deserialize, Serialize, Clone, Copy, Default, PartialOrd, Ord, PartialEq, Eq)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[repr(u8)] #[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
pub enum InviteType { pub enum InviteType {
#[default] #[default]
Guild = 0, Guild = 0,
@ -152,7 +153,8 @@ pub enum InviteType {
#[derive(Debug, Deserialize, Serialize, Clone, Copy, Default, PartialOrd, Ord, PartialEq, Eq)] #[derive(Debug, Deserialize, Serialize, Clone, Copy, Default, PartialOrd, Ord, PartialEq, Eq)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[repr(u8)] #[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
pub enum InviteTargetType { pub enum InviteTargetType {
#[default] #[default]
Stream = 1, Stream = 1,
@ -169,7 +171,9 @@ pub struct AddChannelRecipientSchema {
} }
/// See <https://discord-userdoccers.vercel.app/resources/channel#add-channel-recipient> /// See <https://discord-userdoccers.vercel.app/resources/channel#add-channel-recipient>
#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialOrd, Ord, PartialEq, Eq, Copy, Hash)] #[derive(
Debug, Deserialize, Serialize, Clone, Default, PartialOrd, Ord, PartialEq, Eq, Copy, Hash,
)]
pub struct ModifyChannelPositionsSchema { pub struct ModifyChannelPositionsSchema {
pub id: Snowflake, pub id: Snowflake,
pub position: Option<u32>, pub position: Option<u32>,
@ -178,7 +182,9 @@ pub struct ModifyChannelPositionsSchema {
} }
/// See <https://docs.discord.sex/resources/channel#follow-channel> /// See <https://docs.discord.sex/resources/channel#follow-channel>
#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialOrd, Ord, PartialEq, Eq, Copy, Hash)] #[derive(
Debug, Deserialize, Serialize, Clone, Default, PartialOrd, Ord, PartialEq, Eq, Copy, Hash,
)]
pub struct AddFollowingChannelSchema { pub struct AddFollowingChannelSchema {
pub webhook_channel_id: Snowflake, pub webhook_channel_id: Snowflake,
} }

View File

@ -78,6 +78,11 @@ pub struct UserModifySchema {
/// # Note /// # Note
/// ///
/// This is not yet implemented on Spacebar /// This is not yet implemented on Spacebar
///
/// [UserFlags]: crate::types::UserFlags
/// [UserFlags::PREMIUM_PROMO_DISMISSED]: crate::types::UserFlags::PREMIUM_PROMO_DISMISSED
/// [UserFlags::HAS_UNREAD_URGENT_MESSAGES]:
/// crate::types::UserFlags::HAS_UNREAD_URGENT_MESSAGES
pub flags: Option<u64>, pub flags: Option<u64>,
/// The user's date of birth, can only be set once /// The user's date of birth, can only be set once
/// ///

View File

@ -3,11 +3,14 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
use crate::types::utils::Snowflake; use crate::types::utils::Snowflake;
use jsonwebtoken::{encode, EncodingKey, Header}; use jsonwebtoken::errors::Error;
use jsonwebtoken::{
decode, encode, Algorithm, DecodingKey, EncodingKey, Header, TokenData, Validation,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub fn generate_token(id: &Snowflake, email: String, jwt_key: &str) -> String { pub fn generate_token(id: &Snowflake, email: &str, jwt_key: &str) -> String {
let claims = Claims::new(&email, id); let claims = Claims::new(email, id);
build_token(&claims, jwt_key).unwrap() build_token(&claims, jwt_key).unwrap()
} }
@ -42,8 +45,13 @@ pub fn build_token(claims: &Claims, jwt_key: &str) -> Result<String, jsonwebtoke
) )
} }
/*pub fn decode_token(token: &str) -> Result<TokenData<Claims>, Error> { pub fn decode_token(token: &str, jwt_secret: &str) -> Result<TokenData<Claims>, Error> {
let mut validation = Validation::new(Algorithm::HS256); let mut validation = Validation::new(Algorithm::HS256);
validation.sub = Some("quartzauth".to_string()); //TODO: What is this?
decode(token, &DecodingKey::from_secret(JWT_SECRET), &validation) //validation.sub = Some("quartzauth".to_string());
}*/ decode(
token,
&DecodingKey::from_secret(jwt_secret.as_bytes()),
&validation,
)
}

View File

@ -26,7 +26,7 @@ impl Snowflake {
const PROCESS_ID: u64 = 1; const PROCESS_ID: u64 = 1;
static INCREMENT: AtomicUsize = AtomicUsize::new(0); static INCREMENT: AtomicUsize = AtomicUsize::new(0);
let time = (Utc::now().naive_utc().timestamp_millis() - EPOCH) << 22; let time = (Utc::now().naive_utc().and_utc().timestamp_millis() - EPOCH) << 22;
let worker = WORKER_ID << 17; let worker = WORKER_ID << 17;
let process = PROCESS_ID << 12; let process = PROCESS_ID << 12;
let increment = INCREMENT.fetch_add(1, Ordering::Relaxed) as u64 % 32; let increment = INCREMENT.fetch_add(1, Ordering::Relaxed) as u64 % 32;
@ -53,12 +53,15 @@ impl Display for Snowflake {
} }
} }
impl<T> From<T> for Snowflake impl From<u64> for Snowflake {
where fn from(item: u64) -> Self {
T: Into<u64>, Self(item)
{ }
fn from(item: T) -> Self { }
Self(item.into())
impl From<Snowflake> for u64 {
fn from(item: Snowflake) -> Self {
item.0
} }
} }
@ -99,29 +102,39 @@ impl<'de> serde::Deserialize<'de> for Snowflake {
} }
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
impl sqlx::Type<sqlx::Any> for Snowflake { impl sqlx::Type<sqlx::Postgres> for Snowflake {
fn type_info() -> <sqlx::Any as sqlx::Database>::TypeInfo { fn type_info() -> <sqlx::Postgres as sqlx::Database>::TypeInfo {
<String as sqlx::Type<sqlx::Any>>::type_info() <sqlx_pg_uint::PgU64 as sqlx::Type<sqlx::Postgres>>::type_info()
} }
} }
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
impl<'q> sqlx::Encode<'q, sqlx::Any> for Snowflake { impl sqlx::postgres::PgHasArrayType for Snowflake {
fn array_type_info() -> sqlx::postgres::PgTypeInfo {
<Vec<sqlx_pg_uint::PgU64> as sqlx::Type<sqlx::Postgres>>::type_info()
}
}
#[cfg(feature = "sqlx")]
impl<'q> sqlx::Encode<'q, sqlx::Postgres> for Snowflake {
fn encode_by_ref( fn encode_by_ref(
&self, &self,
buf: &mut <sqlx::Any as sqlx::Database>::ArgumentBuffer<'q>, buf: &mut <sqlx::Postgres as sqlx::Database>::ArgumentBuffer<'q>,
) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> { ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
<String as sqlx::Encode<'q, sqlx::Any>>::encode_by_ref(&self.0.to_string(), buf) <sqlx_pg_uint::PgU64 as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(
&sqlx_pg_uint::PgU64::from(self.0),
buf,
)
} }
} }
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
impl<'d> sqlx::Decode<'d, sqlx::Any> for Snowflake { impl<'d> sqlx::Decode<'d, sqlx::Postgres> for Snowflake {
fn decode( fn decode(
value: <sqlx::Any as sqlx::Database>::ValueRef<'d>, value: <sqlx::Postgres as sqlx::Database>::ValueRef<'d>,
) -> Result<Self, sqlx::error::BoxDynError> { ) -> Result<Self, sqlx::error::BoxDynError> {
<String as sqlx::Decode<'d, sqlx::Any>>::decode(value) <sqlx_pg_uint::PgU64 as sqlx::Decode<'d, sqlx::Postgres>>::decode(value)
.map(|s| s.parse::<u64>().map(Snowflake).unwrap()) .map(|s| s.to_uint().into())
} }
} }

View File

@ -41,7 +41,7 @@ pub struct VoiceGateway {
impl VoiceGateway { impl VoiceGateway {
#[allow(clippy::new_ret_no_self)] #[allow(clippy::new_ret_no_self)]
pub async fn spawn(websocket_url: String) -> Result<VoiceGatewayHandle, VoiceGatewayError> { pub async fn spawn(websocket_url: &str) -> Result<VoiceGatewayHandle, VoiceGatewayError> {
// Append the needed things to the websocket url // Append the needed things to the websocket url
let processed_url = format!("wss://{}/?v=7", websocket_url); let processed_url = format!("wss://{}/?v=7", websocket_url);
trace!("VGW: Connecting to {}", processed_url.clone()); trace!("VGW: Connecting to {}", processed_url.clone());
@ -110,7 +110,7 @@ impl VoiceGateway {
}); });
Ok(VoiceGatewayHandle { Ok(VoiceGatewayHandle {
url: websocket_url.clone(), url: websocket_url.to_string(),
events: shared_events, events: shared_events,
websocket_send: shared_websocket_send.clone(), websocket_send: shared_websocket_send.clone(),
kill_send: kill_send.clone(), kill_send: kill_send.clone(),

View File

@ -72,7 +72,7 @@ impl VoiceGatewayHandle {
/// Sends a speaking event to the gateway /// Sends a speaking event to the gateway
pub async fn send_speaking(&self, to_send: Speaking) { pub async fn send_speaking(&self, to_send: Speaking) {
let to_send_value = serde_json::to_value(&to_send).unwrap(); let to_send_value = serde_json::to_value(to_send).unwrap();
trace!("VGW: Sending Speaking"); trace!("VGW: Sending Speaking");

View File

@ -79,11 +79,7 @@ async fn test_login_with_token() {
let mut bundle = common::setup().await; let mut bundle = common::setup().await;
let token = &bundle.user.token; let token = &bundle.user.token;
let other_user = bundle let other_user = bundle.instance.login_with_token(token).await.unwrap();
.instance
.login_with_token(token.clone())
.await
.unwrap();
assert_eq!( assert_eq!(
bundle.user.object.read().unwrap().id, bundle.user.object.read().unwrap().id,
other_user.object.read().unwrap().id other_user.object.read().unwrap().id
@ -98,8 +94,8 @@ async fn test_login_with_token() {
async fn test_login_with_invalid_token() { async fn test_login_with_invalid_token() {
let mut bundle = common::setup().await; let mut bundle = common::setup().await;
let token = "invalid token lalalalala".to_string(); let token = "invalid token lalalalala";
let other_user = bundle.instance.login_with_token(token.clone()).await; let other_user = bundle.instance.login_with_token(token).await;
assert!(other_user.is_err()); assert!(other_user.is_err());

View File

@ -50,7 +50,7 @@ impl TestBundle {
limits: self.user.limits.clone(), limits: self.user.limits.clone(),
settings: self.user.settings.clone(), settings: self.user.settings.clone(),
object: self.user.object.clone(), object: self.user.object.clone(),
gateway: Gateway::spawn(self.instance.urls.wss.clone(), GatewayOptions::default()) gateway: Gateway::spawn(&self.instance.urls.wss, GatewayOptions::default())
.await .await
.unwrap(), .unwrap(),
} }
@ -66,7 +66,9 @@ pub(crate) async fn setup() -> TestBundle {
) )
.init(); .init();
let instance = Instance::new("http://localhost:3001/api").await.unwrap(); let instance = Instance::new("http://localhost:3001/api", None)
.await
.unwrap();
// Requires the existence of the below user. // Requires the existence of the below user.
let reg = RegisterSchema { let reg = RegisterSchema {
username: "integrationtestuser".into(), username: "integrationtestuser".into(),
@ -124,10 +126,10 @@ pub(crate) async fn setup() -> TestBundle {
.unwrap(); .unwrap();
let urls = UrlBundle::new( let urls = UrlBundle::new(
"http://localhost:3001/api".to_string(), "http://localhost:3001/api",
"http://localhost:3001/api".to_string(), "http://localhost:3001/api",
"ws://localhost:3001/".to_string(), "ws://localhost:3001/",
"http://localhost:3001".to_string(), "http://localhost:3001",
); );
TestBundle { TestBundle {
urls, urls,

View File

@ -31,7 +31,7 @@ use wasmtimer::tokio::sleep;
async fn test_gateway_establish() { async fn test_gateway_establish() {
let bundle = common::setup().await; let bundle = common::setup().await;
let _: GatewayHandle = Gateway::spawn(bundle.urls.wss.clone(), GatewayOptions::default()) let _: GatewayHandle = Gateway::spawn(&bundle.urls.wss, GatewayOptions::default())
.await .await
.unwrap(); .unwrap();
common::teardown(bundle).await common::teardown(bundle).await
@ -55,7 +55,7 @@ impl Subscriber<GatewayReady> for GatewayReadyObserver {
async fn test_gateway_authenticate() { async fn test_gateway_authenticate() {
let bundle = common::setup().await; let bundle = common::setup().await;
let gateway: GatewayHandle = Gateway::spawn(bundle.urls.wss.clone(), GatewayOptions::default()) let gateway: GatewayHandle = Gateway::spawn(&bundle.urls.wss, GatewayOptions::default())
.await .await
.unwrap(); .unwrap();