diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 0f680cc..a69aff6 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -2,133 +2,133 @@ name: Build and Test on: push: - branches: [ "main" ] + branches: ["main"] pull_request: - branches: [ "main", "dev" ] + branches: ["main", "dev"] env: CARGO_TERM_COLOR: always jobs: linux: - runs-on: ubuntu-latest timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - - name: Clone spacebar server - run: | - git clone https://github.com/bitfl0wer/server.git - - uses: actions/setup-node@v4 - with: + - uses: actions/checkout@v4 + - name: Clone spacebar server + run: | + git clone https://github.com/bitfl0wer/server.git + - uses: actions/setup-node@v4 + with: node-version: 18 - cache: 'npm' + cache: "npm" cache-dependency-path: server/package-lock.json - - name: Prepare and start Spacebar server - run: | - npm install - npm run setup - npm run start & - working-directory: ./server - - uses: Swatinem/rust-cache@v2 - with: - cache-all-crates: "true" - prefix-key: "linux" - - name: Build, Test and Publish Coverage - run: | - if [ -n "${{ secrets.COVERALLS_REPO_TOKEN }}" ]; then - 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 cargo-tarpaulin --force - cargo tarpaulin --all-features --avoid-cfg-tarpaulin --tests --verbose --skip-clean --coveralls ${{ secrets.COVERALLS_REPO_TOKEN }} --timeout 120 - else - echo "Code Coverage step is skipped on forks!" - cargo build --verbose --all-features - cargo test --verbose --all-features - fi - # wasm-safari: - # runs-on: macos-latest - # steps: - # - uses: actions/checkout@v4 - # - name: Clone spacebar server - # run: | - # git clone https://github.com/bitfl0wer/server.git - # - uses: actions/setup-node@v4 - # with: - # node-version: 18 - # cache: 'npm' - # cache-dependency-path: server/package-lock.json - # - name: Prepare and start Spacebar server - # run: | - # npm install - # npm run setup - # npm run start & - # working-directory: ./server - # - uses: Swatinem/rust-cache@v2 - # with: - # cache-all-crates: "true" - # prefix-key: "macos-safari" - # - name: Run WASM tests with Safari, Firefox, Chrome - # run: | - # rustup target add wasm32-unknown-unknown - # curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash - # cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force - # SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" --no-fail-fast + - name: Prepare and start Spacebar server + run: | + npm install + npm run setup + npm run start & + working-directory: ./server + - uses: Swatinem/rust-cache@v2 + with: + cache-all-crates: "true" + prefix-key: "linux" + - uses: taiki-e/install-action@nextest + - name: Build, Test with nextest, Publish Coverage + run: | + if [ -n "${{ secrets.COVERALLS_REPO_TOKEN }}" ]; then + if [ "${{github.event.pull_request.head.ref}}" = "main" ]; then + 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 cargo-tarpaulin --force + cargo tarpaulin --all-features --avoid-cfg-tarpaulin --tests --verbose --skip-clean --coveralls ${{ secrets.COVERALLS_REPO_TOKEN }} --timeout 120 + else + echo "Code Coverage step is skipped on non-main PRs and PRs from forks." + cargo nextest run --verbose --all-features + fi + else + echo "Code Coverage step is skipped on non-main PRs and PRs from forks." + cargo nextest run --verbose --all-features + fi + linux-non-default-features: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - name: Check common non-default feature configurations + run: | + echo "No features:" + cargo check --features="" --no-default-features + echo "Only client:" + cargo check --features="client" --no-default-features + echo "Only backend:" + cargo check --features="backend" --no-default-features + echo "Only voice:" + cargo check --features="voice" --no-default-features + echo "Only voice gateway:" + cargo check --features="voice_gateway" --no-default-features + echo "Backend + client:" + cargo check --features="backend, client" --no-default-features + echo "Backend + voice:" + cargo check --features="backend, voice" --no-default-features + echo "Backend + voice gateway:" + cargo check --features="backend, voice_gateway" --no-default-features + echo "Client + voice gateway:" + cargo check --features="client, voice_gateway" --no-default-features wasm-gecko: - runs-on: macos-latest + runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v4 - - name: Clone spacebar server - run: | - git clone https://github.com/bitfl0wer/server.git - - uses: actions/setup-node@v4 - with: + - uses: actions/checkout@v4 + - name: Clone spacebar server + run: | + git clone https://github.com/bitfl0wer/server.git + - uses: actions/setup-node@v4 + with: node-version: 18 - cache: 'npm' + cache: "npm" cache-dependency-path: server/package-lock.json - - name: Prepare and start Spacebar server - run: | - npm install - npm run setup - npm run start & - working-directory: ./server - - uses: Swatinem/rust-cache@v2 - with: - cache-all-crates: "true" - prefix-key: "macos" - - name: Run WASM tests with Safari, Firefox, Chrome - run: | - rustup target add wasm32-unknown-unknown - curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash - cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force - GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway" + - name: Prepare and start Spacebar server + run: | + npm install + npm run setup + npm run start & + working-directory: ./server + - uses: Swatinem/rust-cache@v2 + with: + cache-all-crates: "true" + prefix-key: "macos" + - name: Run WASM tests with Safari, Firefox, Chrome + run: | + rustup target add wasm32-unknown-unknown + curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash + cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.92" --force + GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway" wasm-chrome: - runs-on: macos-latest + runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v4 - - name: Clone spacebar server - run: | - git clone https://github.com/bitfl0wer/server.git - - uses: actions/setup-node@v4 - with: + - uses: actions/checkout@v4 + - name: Clone spacebar server + run: | + git clone https://github.com/bitfl0wer/server.git + - uses: actions/setup-node@v4 + with: node-version: 18 - cache: 'npm' + cache: "npm" cache-dependency-path: server/package-lock.json - - name: Prepare and start Spacebar server - run: | - npm install - npm run setup - npm run start & - working-directory: ./server - - uses: Swatinem/rust-cache@v2 - with: - cache-all-crates: "true" - prefix-key: "macos" - - name: Run WASM tests with Safari, Firefox, Chrome - run: | - rustup target add wasm32-unknown-unknown - curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash - cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force - CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway" + - name: Prepare and start Spacebar server + run: | + npm install + npm run setup + npm run start & + working-directory: ./server + - uses: Swatinem/rust-cache@v2 + with: + cache-all-crates: "true" + prefix-key: "macos" + - name: Run WASM tests with Safari, Firefox, Chrome + run: | + rustup target add wasm32-unknown-unknown + curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash + cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.92" --force + CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 34a1153..84e7021 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,8 +3,10 @@ **Please refer to the [contribution guidelines](https://github.com/polyphony-chat/.github/blob/main/CONTRIBUTION_GUIDELINES.md) and [our Code of Conduct](https://github.com/polyphony-chat/.github/blob/main/CODE_OF_CONDUCT.md) before making a contribution.** +Contributions should always fork from and merge back into the `dev` branch. + Chorus is currently missing voice support and a lot of API endpoints, many of which should be trivial to implement, ever since [we streamlined the process of doing so](https://github.com/polyphony-chat/chorus/discussions/401). If you'd like to contribute new functionality, check out [The 'Meta'-issues.](https://github.com/polyphony-chat/chorus/issues?q=is%3Aissue+label%3A%22Type%3A+Meta%22+) They contain a comprehensive list of all features which are yet missing for full Discord.com compatibility. -Please feel free to open an Issue with the idea you have, or a Pull Request. \ No newline at end of file +Please feel free to open an Issue with the idea you have, or a Pull Request. diff --git a/Cargo.lock b/Cargo.lock index 456ae43..5d3bba6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ "gimli", ] @@ -29,9 +29,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.7" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "getrandom", @@ -42,18 +42,18 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "android-tzdata" @@ -72,13 +72,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.77" +version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.70", ] [[package]] @@ -102,26 +102,22 @@ dependencies = [ ] [[package]] -name = "atomic-write-file" -version = "0.1.2" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edcdbedc2236483ab103a53415653d6b4442ea6141baf1ffa85df29635e88436" -dependencies = [ - "nix", - "rand", -] +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" dependencies = [ "addr2line", "cc", @@ -144,6 +140,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.6.0" @@ -158,9 +160,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" dependencies = [ "serde", ] @@ -176,9 +178,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byteorder" @@ -188,18 +190,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.83" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] +checksum = "eaff6f8ce506b9773fa786672d63fc7a191ffea1be33f72bbd4aeacefca9ffc8" [[package]] name = "cfg-if" @@ -207,37 +206,44 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "chorus" version = "0.15.0" dependencies = [ "async-trait", "base64 0.21.7", - "bitflags 2.4.1", + "bitflags 2.6.0", "chorus-macros", "chrono", "crypto_secretbox", "custom_error", "discortp", + "flate2", "futures-util", "getrandom", "hostname", - "http", + "http 0.2.12", "jsonwebtoken", "lazy_static", "log", - "native-tls", "poem", + "pubserve", "rand", "regex", "reqwest", "rustls", - "rustls-native-certs", "serde", "serde-aux", "serde_json", "serde_repr", "serde_with", + "simple_logger", "sqlx", "thiserror", "tokio", @@ -247,25 +253,24 @@ dependencies = [ "wasm-bindgen-futures", "wasm-bindgen-test", "wasmtimer", + "webpki-roots 0.26.3", "ws_stream_wasm", ] [[package]] name = "chorus-macros" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a81545a60b926f815517dadbbd40cd502294ae2baea25fa8194d854d607512b0" +version = "0.4.1" dependencies = [ "async-trait", "quote", - "syn 2.0.48", + "syn 2.0.70", ] [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -273,7 +278,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -330,9 +335,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.0.1" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" dependencies = [ "crc-catalog", ] @@ -343,6 +348,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-queue" version = "0.3.11" @@ -354,9 +368,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crypto-common" @@ -392,9 +406,9 @@ checksum = "4f8a51dd197fa6ba5b4dc98a990a43cc13693c23eb0089ebb0fcc1f04152bca6" [[package]] name = "darling" -version = "0.20.3" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", @@ -402,40 +416,40 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.3" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.48", + "syn 2.0.70", ] [[package]] name = "darling_macro" -version = "0.20.3" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.48", + "syn 2.0.70", ] [[package]] name = "data-encoding" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "der" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "pem-rfc7468", @@ -482,18 +496,18 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "either" -version = "1.9.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" dependencies = [ "serde", ] [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] @@ -506,9 +520,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -533,15 +547,19 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "fastrand" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] -name = "finl_unicode" -version = "1.2.0" +name = "flate2" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "miniz_oxide", +] [[package]] name = "flume" @@ -560,21 +578,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.1" @@ -651,7 +654,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.70", ] [[package]] @@ -697,9 +700,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", @@ -710,23 +713,42 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "h2" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http", - "indexmap 2.1.0", + "http 0.2.12", + "indexmap 2.2.6", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -741,9 +763,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", @@ -755,19 +777,19 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] name = "headers" -version = "0.3.9" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" dependencies = [ "base64 0.21.7", "bytes", "headers-core", - "http", + "http 1.1.0", "httpdate", "mime", "sha1", @@ -775,11 +797,11 @@ dependencies = [ [[package]] name = "headers-core" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" dependencies = [ - "http", + "http 1.1.0", ] [[package]] @@ -793,9 +815,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -843,9 +865,20 @@ dependencies = [ [[package]] name = "http" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -859,15 +892,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", "pin-project-lite", ] [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "httpdate" @@ -877,17 +933,17 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.28" +version = "0.14.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -900,23 +956,59 @@ dependencies = [ ] [[package]] -name = "hyper-tls" -version = "0.5.0" +name = "hyper" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", - "hyper", - "native-tls", + "futures-channel", + "futures-util", + "h2 0.4.5", + "http 1.1.0", + "http-body 1.0.0", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.30", + "rustls", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "hyper-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "hyper 1.4.1", + "pin-project-lite", "tokio", - "tokio-native-tls", ] [[package]] name = "iana-time-zone" -version = "0.1.59" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -964,12 +1056,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.1.0" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "serde", ] @@ -997,26 +1089,17 @@ dependencies = [ "serde", ] -[[package]] -name = "itertools" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" -dependencies = [ - "either", -] - [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.66" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -1037,18 +1120,18 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.5.2", + "spin 0.9.8", ] [[package]] name = "libc" -version = "0.2.152" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libm" @@ -1069,15 +1152,15 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -1085,9 +1168,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "match_cfg" @@ -1107,9 +1190,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "mime" @@ -1119,9 +1202,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", @@ -1135,50 +1218,52 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.11" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" dependencies = [ + "hermit-abi", "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] -name = "native-tls" -version = "0.2.11" +name = "multer" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", + "bytes", + "encoding_rs", + "futures-util", + "http 1.1.0", + "httparse", + "memchr", + "mime", + "spin 0.9.8", + "tokio", + "version_check", ] [[package]] name = "nix" -version = "0.27.1" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.6.0", "cfg-if", + "cfg_aliases", "libc", ] @@ -1200,11 +1285,10 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -1227,20 +1311,25 @@ dependencies = [ ] [[package]] -name = "num-integer" -version = "0.1.45" +name = "num-conv" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", @@ -1249,29 +1338,19 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "object" -version = "0.32.2" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" dependencies = [ "memchr", ] @@ -1284,59 +1363,15 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - -[[package]] -name = "openssl" -version = "0.10.62" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671" -dependencies = [ - "bitflags 2.4.1", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -1344,22 +1379,22 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.2", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pem" @@ -1397,9 +1432,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -1430,9 +1465,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "pnet_base" @@ -1466,17 +1501,19 @@ dependencies = [ [[package]] name = "poem" -version = "1.3.59" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504774c97b0744c1ee108a37e5a65a9745a4725c4c06277521dabc28eb53a904" +checksum = "e88b6912ed1e8833d7c22c9c986c517f4518d7d37e3c04566d917c789aaea591" dependencies = [ - "async-trait", "bytes", "futures-util", "headers", - "http", - "hyper", + "http 1.1.0", + "http-body-util", + "hyper 1.4.1", + "hyper-util", "mime", + "multer", "nix", "parking_lot", "percent-encoding", @@ -1488,6 +1525,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", + "sync_wrapper 1.0.1", "thiserror", "tokio", "tokio-util", @@ -1497,14 +1535,14 @@ dependencies = [ [[package]] name = "poem-derive" -version = "1.3.59" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ddcf4680d8d867e1e375116203846acb088483fa2070244f90589f458bbb31" +checksum = "c2b961d58a6c53380c20236394381d9292fda03577f902b158f1638932964dcf" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.70", ] [[package]] @@ -1532,28 +1570,36 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro-crate" -version = "2.0.1" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97dc5fea232fc28d2f597b37c4876b348a40e33f3b02cc975c8d006d78d94b1a" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ - "toml_datetime", "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.76" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] -name = "quote" -version = "1.0.35" +name = "pubserve" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "6a2cf5f495fc9c61de736666ebcbc473fe28a2a1aaf7e5619e5925b13c0275a4" +dependencies = [ + "async-trait", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -1598,10 +1644,19 @@ dependencies = [ ] [[package]] -name = "regex" -version = "1.10.2" +name = "redox_syscall" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "regex" +version = "1.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", @@ -1611,9 +1666,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", @@ -1622,54 +1677,57 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "reqwest" -version = "0.11.23" +version = "0.11.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" +checksum = "78bf93c4af7a8bb7d879d51cebe797356ff10ae8516ace542b5182d9dcac10b2" dependencies = [ "base64 0.21.7", "bytes", "encoding_rs", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-tls", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.30", + "hyper-rustls", "ipnet", "js-sys", "log", "mime", "mime_guess", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", + "rustls", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper 0.1.2", "system-configuration", "tokio", - "tokio-native-tls", + "tokio-rustls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots 0.25.4", "winreg", ] [[package]] name = "rfc7239" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "087317b3cf7eb481f13bd9025d729324b7cd068d6f470e2d76d049e191f5ba47" +checksum = "b106a85eeb5b0336d16d6a20eab857f92861d4fbb1eb9a239866fb98fb6a1063" dependencies = [ "uncased", ] @@ -1691,16 +1749,17 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", "getrandom", "libc", "spin 0.9.8", "untrusted 0.9.0", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1725,9 +1784,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc_version" @@ -1740,11 +1799,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -1753,28 +1812,16 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.10" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.17.7", + "ring 0.17.8", "rustls-webpki", "sct", ] -[[package]] -name = "rustls-native-certs" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" -dependencies = [ - "openssl-probe", - "rustls-pemfile", - "schannel", - "security-framework", -] - [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -1784,21 +1831,27 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + [[package]] name = "rustls-webpki" version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.7", + "ring 0.17.8", "untrusted 0.9.0", ] [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "salsa20" @@ -1809,15 +1862,6 @@ dependencies = [ "cipher", ] -[[package]] -name = "schannel" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" -dependencies = [ - "windows-sys 0.52.0", -] - [[package]] name = "scoped-tls" version = "1.0.1" @@ -1836,38 +1880,15 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.7", + "ring 0.17.8", "untrusted 0.9.0", ] -[[package]] -name = "security-framework" -version = "2.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "semver" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "send_wrapper" @@ -1877,18 +1898,18 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.195" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde-aux" -version = "4.3.1" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "184eba62ebddb71658697c8b08822edee89970bf318c5362189f0de27f85b498" +checksum = "0d2e8bfba469d06512e11e3311d4d051a4a387a5b42d010404fecf3200321c95" dependencies = [ "chrono", "serde", @@ -1897,20 +1918,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.195" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.70", ] [[package]] name = "serde_json" -version = "1.0.111" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "itoa", "ryu", @@ -1919,13 +1940,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.70", ] [[package]] @@ -1942,16 +1963,17 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.4.0" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23" +checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.1.0", + "indexmap 2.2.6", "serde", + "serde_derive", "serde_json", "serde_with_macros", "time", @@ -1959,14 +1981,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.4.0" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788" +checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.70", ] [[package]] @@ -2013,6 +2035,16 @@ dependencies = [ "time", ] +[[package]] +name = "simple_logger" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c5dfa5e08767553704aa0ffd9d9794d527103c736aba9854773851fd7497eb" +dependencies = [ + "log", + "windows-sys 0.48.0", +] + [[package]] name = "slab" version = "0.4.9" @@ -2024,18 +2056,18 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2065,20 +2097,19 @@ dependencies = [ [[package]] name = "sqlformat" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c" +checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" dependencies = [ - "itertools", "nom", "unicode_categories", ] [[package]] name = "sqlx" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf" +checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" dependencies = [ "sqlx-core", "sqlx-macros", @@ -2089,9 +2120,9 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd" +checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" dependencies = [ "ahash", "atoi", @@ -2100,7 +2131,6 @@ dependencies = [ "chrono", "crc", "crossbeam-queue", - "dotenvy", "either", "event-listener", "futures-channel", @@ -2110,14 +2140,15 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap 2.1.0", + "indexmap 2.2.6", "ipnetwork", "log", "memchr", - "native-tls", "once_cell", "paste", "percent-encoding", + "rustls", + "rustls-pemfile", "serde", "serde_json", "sha2", @@ -2128,13 +2159,14 @@ dependencies = [ "tokio-stream", "tracing", "url", + "webpki-roots 0.25.4", ] [[package]] name = "sqlx-macros" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5" +checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" dependencies = [ "proc-macro2", "quote", @@ -2145,11 +2177,10 @@ dependencies = [ [[package]] name = "sqlx-macros-core" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841" +checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" dependencies = [ - "atomic-write-file", "dotenvy", "either", "heck", @@ -2172,13 +2203,13 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4" +checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" dependencies = [ "atoi", "base64 0.21.7", - "bitflags 2.4.1", + "bitflags 2.6.0", "byteorder", "bytes", "chrono", @@ -2215,13 +2246,13 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24" +checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" dependencies = [ "atoi", "base64 0.21.7", - "bitflags 2.4.1", + "bitflags 2.6.0", "byteorder", "chrono", "crc", @@ -2244,7 +2275,6 @@ dependencies = [ "rand", "serde", "serde_json", - "sha1", "sha2", "smallvec", "sqlx-core", @@ -2256,9 +2286,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "210976b7d948c7ba9fced8ca835b11cbb2d677c59c79de41ac0d397e14547490" +checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" dependencies = [ "atoi", "chrono", @@ -2280,26 +2310,26 @@ dependencies = [ [[package]] name = "stringprep" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" dependencies = [ - "finl_unicode", "unicode-bidi", "unicode-normalization", + "unicode-properties", ] [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -2314,15 +2344,30 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -2346,45 +2391,45 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.9.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", "rustix", "windows-sys 0.52.0", ] [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.70", ] [[package]] name = "time" -version = "0.3.31" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", + "num-conv", "powerfmt", "serde", "time-core", @@ -2399,18 +2444,19 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ + "num-conv", "time-core", ] [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -2423,40 +2469,29 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.1" +version = "1.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "pin-project-lite", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", + "syn 2.0.70", ] [[package]] @@ -2471,9 +2506,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -2489,39 +2524,38 @@ dependencies = [ "futures-util", "log", "rustls", - "rustls-native-certs", "tokio", "tokio-rustls", "tungstenite", + "webpki-roots 0.25.4", ] [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" [[package]] name = "toml_edit" -version = "0.20.2" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.1.0", + "indexmap 2.2.6", "toml_datetime", "winnow", ] @@ -2552,7 +2586,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.70", ] [[package]] @@ -2579,7 +2613,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http", + "http 0.2.12", "httparse", "log", "rand", @@ -2598,9 +2632,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "uncased" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b9bc53168a4be7402ab86c3aad243a84dd7381d09be0eddc81280c1da95ca68" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" dependencies = [ "version_check", ] @@ -2616,9 +2650,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" @@ -2628,18 +2662,24 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] -name = "unicode-segmentation" -version = "1.10.1" +name = "unicode-properties" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode_categories" @@ -2671,9 +2711,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", @@ -2727,9 +2767,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2737,24 +2777,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.70", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.39" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -2764,9 +2804,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2774,28 +2814,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.70", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wasm-bindgen-test" -version = "0.3.39" +version = "0.3.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cf9242c0d27999b831eae4767b2a146feb0b27d332d553e605864acd2afd403" +checksum = "d9bf62a58e0780af3e852044583deee40983e5886da43a271dd772379987667b" dependencies = [ "console_error_panic_hook", "js-sys", @@ -2807,13 +2847,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.39" +version = "0.3.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "794645f5408c9a039fd09f4d113cdfb2e7eba5ff1956b07bcf701cf4b394fe89" +checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.70", ] [[package]] @@ -2832,29 +2872,44 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.66" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] -name = "whoami" -version = "1.5.0" +name = "webpki-roots" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fec781d48b41f8163426ed18e8fc2864c12937df9ce54c88ede7bd47270893e" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "webpki-roots" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" dependencies = [ - "redox_syscall", + "rustls-pki-types", +] + +[[package]] +name = "whoami" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +dependencies = [ + "redox_syscall 0.4.1", "wasite", ] [[package]] name = "wildmatch" -version = "2.3.0" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "495ec47bf3c1345005f40724f0269362c8556cbc43aed0526ed44cae1d35fceb" +checksum = "3928939971918220fed093266b809d1ee4ec6c1a2d72692ff6876898f3b16c19" [[package]] name = "winapi" @@ -2884,7 +2939,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", ] [[package]] @@ -2902,7 +2957,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", ] [[package]] @@ -2922,17 +2977,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -2943,9 +2999,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -2955,9 +3011,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -2967,9 +3023,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -2979,9 +3041,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -2991,9 +3053,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -3003,9 +3065,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -3015,15 +3077,15 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.5.34" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" dependencies = [ "memchr", ] @@ -3059,26 +3121,26 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.70", ] [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 45060d4..75a25f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,68 +13,77 @@ rust-version = "1.67.1" [features] default = ["client", "rt-multi-thread"] -backend = ["dep:poem", "dep:sqlx"] +backend = ["poem", "sqlx"] rt-multi-thread = ["tokio/rt-multi-thread"] rt = ["tokio/rt"] -client = [] +client = ["flate2"] voice = ["voice_udp", "voice_gateway"] voice_udp = ["dep:discortp", "dep:crypto_secretbox"] voice_gateway = [] [dependencies] -tokio = { version = "1.35.1", features = ["macros", "sync"] } -serde = { version = "1.0.195", features = ["derive", "rc"] } -serde_json = { version = "1.0.111", features = ["raw_value"] } -serde-aux = "4.3.1" -serde_with = "3.4.0" -serde_repr = "0.1.18" -reqwest = { features = ["multipart", "json"], version = "0.11.23" } -url = "2.5.0" -chrono = { version = "0.4.31", features = ["serde"] } -regex = "1.10.2" +tokio = { version = "1.38.1", features = ["macros", "sync"] } +serde = { version = "1.0.204", features = ["derive", "rc"] } +serde_json = { version = "1.0.120", features = ["raw_value"] } +serde-aux = "4.5.0" +serde_with = "3.9.0" +serde_repr = "0.1.19" +reqwest = { features = [ + "multipart", + "json", + "rustls-tls-webpki-roots", +], version = "=0.11.26", default-features = false } +url = "2.5.2" +chrono = { version = "0.4.38", features = ["serde"] } +regex = "1.10.5" custom_error = "1.9.2" futures-util = "0.3.30" -http = "0.2.11" +http = "0.2.12" base64 = "0.21.7" -bitflags = { version = "2.4.1", features = ["serde"] } -lazy_static = "1.4.0" -poem = { version = "1.3.59", optional = true } -thiserror = "1.0.56" +bitflags = { version = "2.6.0", features = ["serde"] } +lazy_static = "1.5.0" +poem = { version = "3.0.1", features = ["multipart"], optional = true } +thiserror = "1.0.63" jsonwebtoken = "8.3.0" -log = "0.4.20" -async-trait = "0.1.77" -chorus-macros = "0.2.0" -sqlx = { version = "0.7.3", features = [ +log = "0.4.22" +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! +sqlx = { version = "0.7.4", features = [ "mysql", "sqlite", "json", "chrono", "ipnetwork", - "runtime-tokio-native-tls", + "runtime-tokio-rustls", "any", ], optional = true } -discortp = { version = "0.5.0", optional = true, features = ["rtp", "discord", "demux"] } +discortp = { version = "0.5.0", optional = true, features = [ + "rtp", + "discord", + "demux", +] } crypto_secretbox = { version = "0.1.1", optional = true } rand = "0.8.5" +flate2 = { version = "1.0.30", optional = true } +webpki-roots = "0.26.3" +pubserve = { version = "1.1.0", features = ["async", "send"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -rustls = "0.21.10" -rustls-native-certs = "0.6.3" +rustls = "0.21.12" tokio-tungstenite = { version = "0.20.1", features = [ - "rustls-tls-native-roots", - "rustls-native-certs", + "rustls-tls-webpki-roots", ] } -native-tls = "0.2.11" hostname = "0.3.1" -getrandom = { version = "0.2.12" } +getrandom = { version = "0.2.15" } [target.'cfg(target_arch = "wasm32")'.dependencies] -getrandom = { version = "0.2.12", features = ["js"] } +getrandom = { version = "0.2.15", features = ["js"] } ws_stream_wasm = "0.7.4" -wasm-bindgen-futures = "0.4.39" +wasm-bindgen-futures = "0.4.42" wasmtimer = "0.2.0" [dev-dependencies] -lazy_static = "1.4.0" -wasm-bindgen-test = "0.3.39" -wasm-bindgen = "0.2.89" +lazy_static = "1.5.0" +wasm-bindgen-test = "0.3.42" +wasm-bindgen = "0.2.92" +simple_logger = { version = "5.0.0", default-features = false } diff --git a/README.md b/README.md index dc49230..bde7f65 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,25 @@ 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 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) diff --git a/build-wasm.sh b/build-wasm.sh new file mode 100755 index 0000000..85c55c3 --- /dev/null +++ b/build-wasm.sh @@ -0,0 +1,6 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. +#!/bin/sh + +cargo build --no-default-features --target=wasm32-unknown-unknown "$@" \ No newline at end of file diff --git a/chorus-macros/Cargo.lock b/chorus-macros/Cargo.lock index 17404a2..e74f64c 100644 --- a/chorus-macros/Cargo.lock +++ b/chorus-macros/Cargo.lock @@ -15,7 +15,7 @@ dependencies = [ [[package]] name = "chorus-macros" -version = "0.1.0" +version = "0.4.1" dependencies = [ "async-trait", "quote", diff --git a/chorus-macros/Cargo.toml b/chorus-macros/Cargo.toml index 272d99f..6aa416a 100644 --- a/chorus-macros/Cargo.toml +++ b/chorus-macros/Cargo.toml @@ -1,9 +1,10 @@ [package] name = "chorus-macros" -version = "0.2.0" +version = "0.4.1" edition = "2021" -license = "AGPL-3.0" +license = "MPL-2.0" description = "Macros for the chorus crate." +repository = "https://github.com/polyphony-chat/chorus" [lib] proc-macro = true diff --git a/chorus-macros/src/lib.rs b/chorus-macros/src/lib.rs index ba8f27e..aa4f1bc 100644 --- a/chorus-macros/src/lib.rs +++ b/chorus-macros/src/lib.rs @@ -6,6 +6,18 @@ use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, Data, DeriveInput, Field, Fields, FieldsNamed}; +#[proc_macro_derive(WebSocketEvent)] +pub fn websocket_event_macro_derive(input: TokenStream) -> TokenStream { + let ast: syn::DeriveInput = syn::parse(input).unwrap(); + + let name = &ast.ident; + + quote! { + impl WebSocketEvent for #name {} + } + .into() +} + #[proc_macro_derive(Updateable)] pub fn updateable_macro_derive(input: TokenStream) -> TokenStream { let ast: syn::DeriveInput = syn::parse(input).unwrap(); @@ -143,3 +155,70 @@ pub fn composite_derive(input: TokenStream) -> TokenStream { _ => panic!("Composite derive macro only supports structs"), } } + + +#[proc_macro_derive(SqlxBitFlags)] +pub fn sqlx_bitflag_derive(input: TokenStream) -> TokenStream { + let ast: syn::DeriveInput = syn::parse(input).unwrap(); + + let name = &ast.ident; + + quote!{ + #[cfg(feature = "sqlx")] + impl sqlx::Type for #name { + fn type_info() -> sqlx::mysql::MySqlTypeInfo { + u64::type_info() + } + } + + #[cfg(feature = "sqlx")] + impl<'q> sqlx::Encode<'q, sqlx::MySql> for #name { + fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> sqlx::encode::IsNull { + u64::encode_by_ref(&self.bits(), buf) + } + } + + #[cfg(feature = "sqlx")] + impl<'q> sqlx::Decode<'q, sqlx::MySql> for #name { + fn decode(value: >::ValueRef) -> Result { + u64::decode(value).map(|d| #name::from_bits(d).unwrap()) + } + } + } + .into() +} + +#[proc_macro_derive(SerdeBitFlags)] +pub fn serde_bitflag_derive(input: TokenStream) -> TokenStream { + let ast: syn::DeriveInput = syn::parse(input).unwrap(); + + let name = &ast.ident; + + quote! { + impl std::str::FromStr for #name { + type Err = std::num::ParseIntError; + + fn from_str(s: &str) -> Result<#name, Self::Err> { + s.parse::().map(#name::from_bits).map(|f| f.unwrap_or(#name::empty())) + } + } + + impl serde::Serialize for #name { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(&self.bits().to_string()) + } + } + + impl<'de> serde::Deserialize<'de> for #name { + fn deserialize(deserializer: D) -> Result<#name, D::Error> where D: serde::de::Deserializer<'de> + Sized { + // let s = String::deserialize(deserializer)?.parse::().map_err(serde::de::Error::custom)?; + let s = crate::types::serde::string_or_u64(deserializer)?; + + // Note: while truncating may not be ideal, it's better than a panic if there are + // extra flags + Ok(Self::from_bits_truncate(s)) + } + } + } + .into() +} diff --git a/examples/gateway_observers.rs b/examples/gateway_observers.rs index 0f15759..a43d17c 100644 --- a/examples/gateway_observers.rs +++ b/examples/gateway_observers.rs @@ -3,6 +3,8 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. // This example showcase how to properly use gateway observers. +// (This assumes you have a manually created gateway, if you created +// a ChorusUser by e.g. logging in, you can access the gateway with user.gateway) // // To properly run it, you will need to change the token below. @@ -12,12 +14,12 @@ const TOKEN: &str = ""; const GATEWAY_URL: &str = "wss://gateway.old.server.spacebar.chat/"; use async_trait::async_trait; -use chorus::gateway::Gateway; +use chorus::gateway::{Gateway, GatewayOptions}; use chorus::{ self, - gateway::Observer, types::{GatewayIdentifyPayload, GatewayReady}, }; +use pubserve::Subscriber; use std::{sync::Arc, time::Duration}; use tokio::{self}; @@ -32,11 +34,16 @@ use wasmtimer::tokio::sleep; #[derive(Debug)] pub struct ExampleObserver {} -// This struct can observe GatewayReady events when subscribed, because it implements the trait Observer. -// The Observer trait can be implemented for a struct for a given websocketevent to handle observing it +// This struct can observe GatewayReady events when subscribed, because it implements the trait Subscriber. +// The Subscriber trait can be implemented for a struct for a given websocketevent to handle observing it +// +// Note that this trait is quite generic and can be use to observe any type. +// +// It is just used for WebSocketEvents in chorus. +// // One struct can be an observer of multiple websocketevents, if needed #[async_trait] -impl Observer for ExampleObserver { +impl Subscriber for ExampleObserver { // After we subscribe to an event this function is called every time we receive it async fn update(&self, _data: &GatewayReady) { println!("Observed Ready!"); @@ -47,8 +54,16 @@ impl Observer for ExampleObserver { async fn main() { let gateway_websocket_url = GATEWAY_URL.to_string(); + // These options specify the encoding format, compression, etc + // + // For most cases the defaults should work, though some implementations + // might only support some formats or not support compression + let options = GatewayOptions::default(); + // Initiate the gateway connection - let gateway = Gateway::spawn(gateway_websocket_url).await.unwrap(); + let gateway = Gateway::spawn(gateway_websocket_url, options) + .await + .unwrap(); // Create an instance of our observer let observer = ExampleObserver {}; diff --git a/examples/gateway_simple.rs b/examples/gateway_simple.rs index e8ff59a..7f66287 100644 --- a/examples/gateway_simple.rs +++ b/examples/gateway_simple.rs @@ -3,7 +3,7 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. // This example showcases how to initiate a gateway connection manually -// (e. g. not through ChorusUser) +// (e. g. not through ChorusUser or Instance) // // To properly run it, you will need to modify the token below. @@ -14,7 +14,7 @@ const GATEWAY_URL: &str = "wss://gateway.old.server.spacebar.chat/"; use std::time::Duration; -use chorus::gateway::Gateway; +use chorus::gateway::{Gateway, GatewayOptions}; use chorus::{self, types::GatewayIdentifyPayload}; #[cfg(not(target_arch = "wasm32"))] @@ -26,9 +26,15 @@ use wasmtimer::tokio::sleep; #[tokio::main(flavor = "current_thread")] async fn main() { let gateway_websocket_url = GATEWAY_URL.to_string(); + + // These options specify the encoding format, compression, etc + // + // For most cases the defaults should work, though some implementations + // might only support some formats or not support compression + let options = GatewayOptions::default(); // Initiate the gateway connection, starting a listener in one thread and a heartbeat handler in another - let gateway = Gateway::spawn(gateway_websocket_url).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 diff --git a/examples/voice_simple/src/main.rs b/examples/voice_simple/src/main.rs index a6f77db..f0f5f8f 100644 --- a/examples/voice_simple/src/main.rs +++ b/examples/voice_simple/src/main.rs @@ -22,11 +22,9 @@ use simplelog::{TermLogger, Config, WriteLogger}; use std::{net::SocketAddrV4, sync::Arc, fs::File, time::Duration}; use chorus::{ - gateway::{Observer, Gateway}, + gateway::{Gateway, GatewayOptions, Observer}, types::{ - GatewayReady, SelectProtocol, SelectProtocolData, SessionDescription, Snowflake, Speaking, - SpeakingBitflags, SsrcDefinition, VoiceEncryptionMode, VoiceIdentify, VoiceProtocol, - VoiceReady, VoiceServerUpdate, GatewayIdentifyPayload, UpdateVoiceState, + GatewayIdentifyPayload, GatewayReady, SelectProtocol, SelectProtocolData, SessionDescription, Snowflake, Speaking, SpeakingBitflags, SsrcDefinition, UpdateVoiceState, VoiceEncryptionMode, VoiceIdentify, VoiceProtocol, VoiceReady, VoiceServerUpdate }, voice::{ gateway::{VoiceGateway, VoiceGatewayHandle}, @@ -219,7 +217,7 @@ impl Observer for VoiceHandler { println!( "Received Speaking! (SRRC: {}, flags: {:?})", data.ssrc, - SpeakingBitflags::from_bits(data.speaking).unwrap() + SpeakingBitflags::from_bits(data.speaking.into()).unwrap() ); } } @@ -253,7 +251,7 @@ async fn main() { ]) .unwrap(); - let gateway = Gateway::spawn(GATEWAY_URL.to_string()) + let gateway = Gateway::spawn(GATEWAY_URL.to_string(), GatewayOptions::default()) .await .unwrap(); @@ -281,7 +279,7 @@ async fn main() { .session .ready .subscribe(voice_handler.clone()); - + // Data which channel to update the local user to be joined into. // // guild_id and channel_id can be some to join guild voice channels diff --git a/semver_release_checks.yml b/semver_release_checks.yml new file mode 100644 index 0000000..12dd718 --- /dev/null +++ b/semver_release_checks.yml @@ -0,0 +1,18 @@ +name: Semver release checks + +on: + pull_request: + branches: ["main"] + +env: + CARGO_TERM_COLOR: always + +jobs: + semver-checks: + + runs-on: ubuntu-latest + timeout-minutes: 60 + + steps: + - uses: actions/checkout@v4 + - uses: obi1kenobi/cargo-semver-checks-action@v2 diff --git a/src/api/auth/login.rs b/src/api/auth/login.rs index 7689af7..7c58e0e 100644 --- a/src/api/auth/login.rs +++ b/src/api/auth/login.rs @@ -11,7 +11,7 @@ use crate::errors::ChorusResult; use crate::gateway::Gateway; use crate::instance::{ChorusUser, Instance}; use crate::ratelimiter::ChorusRequest; -use crate::types::{GatewayIdentifyPayload, LimitType, LoginResult, LoginSchema}; +use crate::types::{GatewayIdentifyPayload, LimitType, LoginResult, LoginSchema, User}; impl Instance { /// Logs into an existing account on the spacebar server. @@ -30,27 +30,22 @@ impl Instance { // We do not have a user yet, and the UserRateLimits will not be affected by a login // request (since login is an instance wide limit), which is why we are just cloning the // instances' limits to pass them on as user_rate_limits later. - let mut shell = + let mut user = ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None".to_string()).await; + let login_result = chorus_request - .deserialize_response::(&mut shell) + .deserialize_response::(&mut user) .await?; - let object = self.get_user(login_result.token.clone(), None).await?; - if self.limits_information.is_some() { - self.limits_information.as_mut().unwrap().ratelimits = shell.limits.clone().unwrap(); - } + user.set_token(login_result.token); + user.settings = login_result.settings; + + let object = User::get(&mut user, None).await?; + *user.object.write().unwrap() = object; + let mut identify = GatewayIdentifyPayload::common(); - let gateway = Gateway::spawn(self.urls.wss.clone()).await.unwrap(); - identify.token = login_result.token.clone(); - gateway.send_identify(identify).await; - let user = ChorusUser::new( - Arc::new(RwLock::new(self.clone())), - login_result.token, - self.clone_limits_if_some(), - login_result.settings, - Arc::new(RwLock::new(object)), - gateway, - ); + identify.token = user.token(); + user.gateway.send_identify(identify).await; + Ok(user) } } diff --git a/src/api/auth/mod.rs b/src/api/auth/mod.rs index 5bd539f..498080e 100644 --- a/src/api/auth/mod.rs +++ b/src/api/auth/mod.rs @@ -23,26 +23,19 @@ pub mod register; impl Instance { /// Logs into an existing account on the spacebar server, using only a token. pub async fn login_with_token(&mut self, token: String) -> ChorusResult { - let object_result = self.get_user(token.clone(), None).await; - if let Err(e) = object_result { - return Result::Err(e); - } + let mut user = + ChorusUser::shell(Arc::new(RwLock::new(self.clone())), token).await; + + let object = User::get(&mut user, None).await?; + let settings = User::get_settings(&mut user).await?; + + *user.object.write().unwrap() = object; + *user.settings.write().unwrap() = settings; - let user_settings = User::get_settings(&token, &self.urls.api, &mut self.clone()) - .await - .unwrap(); let mut identify = GatewayIdentifyPayload::common(); - let gateway = Gateway::spawn(self.urls.wss.clone()).await.unwrap(); - identify.token = token.clone(); - gateway.send_identify(identify).await; - let user = ChorusUser::new( - Arc::new(RwLock::new(self.clone())), - token.clone(), - self.clone_limits_if_some(), - Arc::new(RwLock::new(user_settings)), - Arc::new(RwLock::new(object_result.unwrap())), - gateway, - ); + identify.token = user.token(); + user.gateway.send_identify(identify).await; + Ok(user) } } diff --git a/src/api/auth/register.rs b/src/api/auth/register.rs index deece4d..6b94a4d 100644 --- a/src/api/auth/register.rs +++ b/src/api/auth/register.rs @@ -8,7 +8,7 @@ use reqwest::Client; use serde_json::to_string; use crate::gateway::{Gateway, GatewayHandle}; -use crate::types::GatewayIdentifyPayload; +use crate::types::{GatewayIdentifyPayload, User}; use crate::{ errors::ChorusResult, instance::{ChorusUser, Instance, Token}, @@ -37,29 +37,25 @@ impl Instance { // We do not have a user yet, and the UserRateLimits will not be affected by a login // request (since register is an instance wide limit), which is why we are just cloning // the instances' limits to pass them on as user_rate_limits later. - let mut shell = + let mut user = ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None".to_string()).await; + let token = chorus_request - .deserialize_response::(&mut shell) + .deserialize_response::(&mut user) .await? .token; - if self.limits_information.is_some() { - self.limits_information.as_mut().unwrap().ratelimits = shell.limits.unwrap(); - } - let user_object = self.get_user(token.clone(), None).await.unwrap(); - let settings = ChorusUser::get_settings(&token, &self.urls.api.clone(), self).await?; + user.set_token(token); + + let object = User::get(&mut user, None).await?; + let settings = User::get_settings(&mut user).await?; + + *user.object.write().unwrap() = object; + *user.settings.write().unwrap() = settings; + let mut identify = GatewayIdentifyPayload::common(); - let gateway: GatewayHandle = Gateway::spawn(self.urls.wss.clone()).await.unwrap(); - identify.token = token.clone(); - gateway.send_identify(identify).await; - let user = ChorusUser::new( - Arc::new(RwLock::new(self.clone())), - token.clone(), - self.clone_limits_if_some(), - Arc::new(RwLock::new(settings)), - Arc::new(RwLock::new(user_object)), - gateway, - ); + identify.token = user.token(); + user.gateway.send_identify(identify).await; + Ok(user) } } diff --git a/src/api/channels/reactions.rs b/src/api/channels/reactions.rs index b7c42e1..f2de33d 100644 --- a/src/api/channels/reactions.rs +++ b/src/api/channels/reactions.rs @@ -10,7 +10,7 @@ use crate::{ }; /// Useful metadata for working with [`types::Reaction`], bundled together nicely. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash, PartialOrd, Ord)] pub struct ReactionMeta { pub message_id: types::Snowflake, pub channel_id: types::Snowflake, diff --git a/src/api/users/guilds.rs b/src/api/users/guilds.rs index aac2f7a..46a25fb 100644 --- a/src/api/users/guilds.rs +++ b/src/api/users/guilds.rs @@ -44,6 +44,16 @@ impl ChorusUser { &mut self, query: Option, ) -> ChorusResult> { + + let query_parameters = { + if let Some(query_some) = query { + query_some.to_query() + } + else { + Vec::new() + } + }; + let url = format!( "{}/users/@me/guilds", self.belongs_to.read().unwrap().urls.api, @@ -53,7 +63,7 @@ impl ChorusUser { .get(url) .header("Authorization", self.token()) .header("Content-Type", "application/json") - .body(to_string(&query).unwrap()), + .query(&query_parameters), limit_type: LimitType::Global, }; diff --git a/src/api/users/users.rs b/src/api/users/users.rs index b80bc1e..4f6ef57 100644 --- a/src/api/users/users.rs +++ b/src/api/users/users.rs @@ -30,13 +30,9 @@ impl ChorusUser { /// Gets the user's settings. /// /// # Notes - /// This functions is a wrapper around [`User::get_settings`]. - pub async fn get_settings( - token: &String, - url_api: &String, - instance: &mut Instance, - ) -> ChorusResult { - User::get_settings(token, url_api, instance).await + /// This function is a wrapper around [`User::get_settings`]. + pub async fn get_settings(&mut self) -> ChorusResult { + User::get_settings(self).await } /// Modifies the current user's representation. (See [`User`]) @@ -44,12 +40,18 @@ impl ChorusUser { /// # Reference /// See pub async fn modify(&mut self, modify_schema: UserModifySchema) -> ChorusResult { - if modify_schema.new_password.is_some() + + // See , note 1 + let requires_current_password = modify_schema.username.is_some() + || modify_schema.discriminator.is_some() || modify_schema.email.is_some() - || modify_schema.code.is_some() - { + || modify_schema.date_of_birth.is_some() + || modify_schema.new_password.is_some(); + + if requires_current_password && modify_schema.current_password.is_none() { return Err(ChorusError::PasswordRequired); } + let request = Client::new() .patch(format!( "{}/users/@me", @@ -118,56 +120,21 @@ impl User { /// /// # Reference /// See - pub async fn get_settings( - token: &String, - url_api: &String, - instance: &mut Instance, - ) -> ChorusResult { + pub async fn get_settings(user: &mut ChorusUser) -> ChorusResult { + let url_api = user.belongs_to.read().unwrap().urls.api.clone(); let request: reqwest::RequestBuilder = Client::new() .get(format!("{}/users/@me/settings", url_api)) - .header("Authorization", token); - let mut user = - ChorusUser::shell(Arc::new(RwLock::new(instance.clone())), token.clone()).await; + .header("Authorization", user.token()); let chorus_request = ChorusRequest { request, limit_type: LimitType::Global, }; - let result = match chorus_request.send_request(&mut user).await { - Ok(result) => Ok(serde_json::from_str(&result.text().await.unwrap()).unwrap()), + match chorus_request.send_request(user).await { + Ok(result) => { + let result_text = result.text().await.unwrap(); + Ok(serde_json::from_str(&result_text).unwrap()) + } Err(e) => Err(e), - }; - if instance.limits_information.is_some() { - instance.limits_information.as_mut().unwrap().ratelimits = user - .belongs_to - .read() - .unwrap() - .clone_limits_if_some() - .unwrap(); } - result - } -} - -impl Instance { - /// Gets a user by id, or if the id is None, gets the current user. - /// - /// # Notes - /// This function is a wrapper around [`User::get`]. - /// - /// # Reference - /// See and - /// - pub async fn get_user(&mut self, token: String, id: Option<&String>) -> ChorusResult { - let mut user = ChorusUser::shell(Arc::new(RwLock::new(self.clone())), token).await; - let result = User::get(&mut user, id).await; - if self.limits_information.is_some() { - self.limits_information.as_mut().unwrap().ratelimits = user - .belongs_to - .read() - .unwrap() - .clone_limits_if_some() - .unwrap(); - } - result } } diff --git a/src/errors.rs b/src/errors.rs index a2f174d..0d130dd 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -6,6 +6,7 @@ use custom_error::custom_error; use crate::types::WebSocketEvent; +use chorus_macros::WebSocketEvent; custom_error! { #[derive(PartialEq, Eq, Clone, Hash)] @@ -72,7 +73,7 @@ custom_error! { /// Supposed to be sent as numbers, though they are sent as string most of the time? /// /// Also includes errors when initiating a connection and unexpected opcodes - #[derive(PartialEq, Eq, Default, Clone)] + #[derive(PartialEq, Eq, Default, Clone, WebSocketEvent)] pub GatewayError // Errors we have received from the gateway #[default] @@ -92,22 +93,20 @@ custom_error! { DisallowedIntents = "You sent a disallowed intent. You may have tried to specify an intent that you have not enabled or are not approved for", // Errors when initiating a gateway connection - CannotConnect{error: String} = "Cannot connect due to a tungstenite error: {error}", + CannotConnect{error: String} = "Cannot connect due to a websocket error: {error}", NonHelloOnInitiate{opcode: u8} = "Received non hello on initial gateway connection ({opcode}), something is definitely wrong", // Other misc errors UnexpectedOpcodeReceived{opcode: u8} = "Received an opcode we weren't expecting to receive: {opcode}", } -impl WebSocketEvent for GatewayError {} - custom_error! { /// Voice Gateway errors /// /// Similar to [GatewayError]. /// /// See ; - #[derive(Clone, Default, PartialEq, Eq)] + #[derive(Clone, Default, PartialEq, Eq, WebSocketEvent)] pub VoiceGatewayError // Errors we receive #[default] @@ -125,18 +124,16 @@ custom_error! { UnknownEncryptionMode = "Server failed to decrypt data", // Errors when initiating a gateway connection - CannotConnect{error: String} = "Cannot connect due to a tungstenite error: {error}", + CannotConnect{error: String} = "Cannot connect due to a websocket error: {error}", NonHelloOnInitiate{opcode: u8} = "Received non hello on initial gateway connection ({opcode}), something is definitely wrong", // Other misc errors UnexpectedOpcodeReceived{opcode: u8} = "Received an opcode we weren't expecting to receive: {opcode}", } -impl WebSocketEvent for VoiceGatewayError {} - custom_error! { /// Voice UDP errors. - #[derive(Clone, PartialEq, Eq)] + #[derive(Clone, PartialEq, Eq, WebSocketEvent)] pub VoiceUdpError // General errors @@ -155,4 +152,3 @@ custom_error! { CannotConnect{error: String} = "Cannot connect due to a UDP error: {error}", } -impl WebSocketEvent for VoiceUdpError {} diff --git a/src/gateway/backends/tungstenite.rs b/src/gateway/backends/tungstenite.rs index a9f9f64..34dc825 100644 --- a/src/gateway/backends/tungstenite.rs +++ b/src/gateway/backends/tungstenite.rs @@ -2,6 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +use custom_error::custom_error; use futures_util::{ stream::{SplitSink, SplitStream}, StreamExt, @@ -11,10 +12,9 @@ use tokio_tungstenite::{ connect_async_tls_with_config, tungstenite, Connector, MaybeTlsStream, WebSocketStream, }; -use crate::errors::GatewayError; -use crate::gateway::GatewayMessage; +use crate::gateway::{GatewayMessage, RawGatewayMessage}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub struct TungsteniteBackend; // These could be made into inherent associated types when that's stabilized @@ -22,23 +22,29 @@ pub type TungsteniteSink = SplitSink>, tungstenite::Message>; pub type TungsteniteStream = SplitStream>>; +custom_error! { + pub TungsteniteBackendError + FailedToLoadCerts{error: std::io::Error} = "failed to load platform native certs: {error}", + TungsteniteError{error: tungstenite::error::Error} = "encountered a tungstenite error: {error}", +} + impl TungsteniteBackend { pub async fn connect( websocket_url: &str, - ) -> Result<(TungsteniteSink, TungsteniteStream), crate::errors::GatewayError> { - let mut roots = rustls::RootCertStore::empty(); - let certs = rustls_native_certs::load_native_certs(); - - if let Err(e) = certs { - log::error!("Failed to load platform native certs! {:?}", e); - return Err(GatewayError::CannotConnect { - error: format!("{:?}", e), - }); - } - - for cert in certs.unwrap() { - roots.add(&rustls::Certificate(cert.0)).unwrap(); - } + ) -> Result<(TungsteniteSink, TungsteniteStream), TungsteniteBackendError> { + 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, @@ -54,11 +60,7 @@ impl TungsteniteBackend { .await { Ok(websocket_stream) => websocket_stream, - Err(e) => { - return Err(GatewayError::CannotConnect { - error: e.to_string(), - }) - } + Err(e) => return Err(TungsteniteBackendError::TungsteniteError { error: e }), }; Ok(websocket_stream.split()) @@ -76,3 +78,22 @@ impl From for GatewayMessage { Self(value.to_string()) } } + +impl From for tungstenite::Message { + fn from(message: RawGatewayMessage) -> Self { + match message { + RawGatewayMessage::Text(text) => tungstenite::Message::Text(text), + RawGatewayMessage::Bytes(bytes) => tungstenite::Message::Binary(bytes), + } + } +} + +impl From for RawGatewayMessage { + fn from(value: tungstenite::Message) -> Self { + match value { + tungstenite::Message::Binary(bytes) => RawGatewayMessage::Bytes(bytes), + tungstenite::Message::Text(text) => RawGatewayMessage::Text(text), + _ => RawGatewayMessage::Text(value.to_string()), + } + } +} diff --git a/src/gateway/backends/wasm.rs b/src/gateway/backends/wasm.rs index 83f4b37..e0fd9c6 100644 --- a/src/gateway/backends/wasm.rs +++ b/src/gateway/backends/wasm.rs @@ -9,8 +9,7 @@ use futures_util::{ use ws_stream_wasm::*; -use crate::errors::GatewayError; -use crate::gateway::GatewayMessage; +use crate::gateway::{GatewayMessage, RawGatewayMessage}; #[derive(Debug, Clone)] pub struct WasmBackend; @@ -22,13 +21,8 @@ pub type WasmStream = SplitStream; impl WasmBackend { pub async fn connect( websocket_url: &str, - ) -> Result<(WasmSink, WasmStream), crate::errors::GatewayError> { - let (_, websocket_stream) = match WsMeta::connect(websocket_url, None).await { - Ok(stream) => Ok(stream), - Err(e) => Err(GatewayError::CannotConnect { - error: e.to_string(), - }), - }?; + ) -> Result<(WasmSink, WasmStream), ws_stream_wasm::WsErr> { + let (_, websocket_stream) = WsMeta::connect(websocket_url, None).await?; Ok(websocket_stream.split()) } @@ -52,3 +46,21 @@ impl From for GatewayMessage { } } } + +impl From for WsMessage { + fn from(message: RawGatewayMessage) -> Self { + match message { + RawGatewayMessage::Text(text) => WsMessage::Text(text), + RawGatewayMessage::Bytes(bytes) => WsMessage::Binary(bytes), + } + } +} + +impl From for RawGatewayMessage { + fn from(value: WsMessage) -> Self { + match value { + WsMessage::Binary(bytes) => RawGatewayMessage::Bytes(bytes), + WsMessage::Text(text) => RawGatewayMessage::Text(text), + } + } +} diff --git a/src/gateway/events.rs b/src/gateway/events.rs index 8d38cca..049434b 100644 --- a/src/gateway/events.rs +++ b/src/gateway/events.rs @@ -2,6 +2,8 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +use pubserve::Publisher; + use super::*; use crate::types; @@ -23,144 +25,144 @@ pub struct Events { pub call: Call, pub voice: Voice, pub webhooks: Webhooks, - pub gateway_identify_payload: GatewayEvent, - pub gateway_resume: GatewayEvent, - pub error: GatewayEvent, + pub gateway_identify_payload: Publisher, + pub gateway_resume: Publisher, + pub error: Publisher, } #[derive(Default, Debug)] pub struct Application { - pub command_permissions_update: GatewayEvent, + pub command_permissions_update: Publisher, } #[derive(Default, Debug)] pub struct AutoModeration { - pub rule_create: GatewayEvent, - pub rule_update: GatewayEvent, - pub rule_delete: GatewayEvent, - pub action_execution: GatewayEvent, + pub rule_create: Publisher, + pub rule_update: Publisher, + pub rule_delete: Publisher, + pub action_execution: Publisher, } #[derive(Default, Debug)] pub struct Session { - pub ready: GatewayEvent, - pub ready_supplemental: GatewayEvent, - pub replace: GatewayEvent, - pub reconnect: GatewayEvent, - pub invalid: GatewayEvent, + pub ready: Publisher, + pub ready_supplemental: Publisher, + pub replace: Publisher, + pub reconnect: Publisher, + pub invalid: Publisher, } #[derive(Default, Debug)] pub struct StageInstance { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, + pub create: Publisher, + pub update: Publisher, + pub delete: Publisher, } #[derive(Default, Debug)] pub struct Message { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - pub delete_bulk: GatewayEvent, - pub reaction_add: GatewayEvent, - pub reaction_remove: GatewayEvent, - pub reaction_remove_all: GatewayEvent, - pub reaction_remove_emoji: GatewayEvent, - pub ack: GatewayEvent, + pub create: Publisher, + pub update: Publisher, + pub delete: Publisher, + pub delete_bulk: Publisher, + pub reaction_add: Publisher, + pub reaction_remove: Publisher, + pub reaction_remove_all: Publisher, + pub reaction_remove_emoji: Publisher, + pub ack: Publisher, } #[derive(Default, Debug)] pub struct User { - pub update: GatewayEvent, - pub guild_settings_update: GatewayEvent, - pub presence_update: GatewayEvent, - pub typing_start: GatewayEvent, + pub update: Publisher, + pub guild_settings_update: Publisher, + pub presence_update: Publisher, + pub typing_start: Publisher, } #[derive(Default, Debug)] pub struct Relationship { - pub add: GatewayEvent, - pub remove: GatewayEvent, + pub add: Publisher, + pub remove: Publisher, } #[derive(Default, Debug)] pub struct Channel { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub unread_update: GatewayEvent, - pub delete: GatewayEvent, - pub pins_update: GatewayEvent, + pub create: Publisher, + pub update: Publisher, + pub unread_update: Publisher, + pub delete: Publisher, + pub pins_update: Publisher, } #[derive(Default, Debug)] pub struct Thread { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - pub list_sync: GatewayEvent, - pub member_update: GatewayEvent, - pub members_update: GatewayEvent, + pub create: Publisher, + pub update: Publisher, + pub delete: Publisher, + pub list_sync: Publisher, + pub member_update: Publisher, + pub members_update: Publisher, } #[derive(Default, Debug)] pub struct Guild { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - pub audit_log_entry_create: GatewayEvent, - pub ban_add: GatewayEvent, - pub ban_remove: GatewayEvent, - pub emojis_update: GatewayEvent, - pub stickers_update: GatewayEvent, - pub integrations_update: GatewayEvent, - pub member_add: GatewayEvent, - pub member_remove: GatewayEvent, - pub member_update: GatewayEvent, - pub members_chunk: GatewayEvent, - pub role_create: GatewayEvent, - pub role_update: GatewayEvent, - pub role_delete: GatewayEvent, - pub role_scheduled_event_create: GatewayEvent, - pub role_scheduled_event_update: GatewayEvent, - pub role_scheduled_event_delete: GatewayEvent, - pub role_scheduled_event_user_add: GatewayEvent, - pub role_scheduled_event_user_remove: GatewayEvent, - pub passive_update_v1: GatewayEvent, + pub create: Publisher, + pub update: Publisher, + pub delete: Publisher, + pub audit_log_entry_create: Publisher, + pub ban_add: Publisher, + pub ban_remove: Publisher, + pub emojis_update: Publisher, + pub stickers_update: Publisher, + pub integrations_update: Publisher, + pub member_add: Publisher, + pub member_remove: Publisher, + pub member_update: Publisher, + pub members_chunk: Publisher, + pub role_create: Publisher, + pub role_update: Publisher, + pub role_delete: Publisher, + pub role_scheduled_event_create: Publisher, + pub role_scheduled_event_update: Publisher, + pub role_scheduled_event_delete: Publisher, + pub role_scheduled_event_user_add: Publisher, + pub role_scheduled_event_user_remove: Publisher, + pub passive_update_v1: Publisher, } #[derive(Default, Debug)] pub struct Invite { - pub create: GatewayEvent, - pub delete: GatewayEvent, + pub create: Publisher, + pub delete: Publisher, } #[derive(Default, Debug)] pub struct Integration { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, + pub create: Publisher, + pub update: Publisher, + pub delete: Publisher, } #[derive(Default, Debug)] pub struct Interaction { - pub create: GatewayEvent, + pub create: Publisher, } #[derive(Default, Debug)] pub struct Call { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, + pub create: Publisher, + pub update: Publisher, + pub delete: Publisher, } #[derive(Default, Debug)] pub struct Voice { - pub state_update: GatewayEvent, - pub server_update: GatewayEvent, + pub state_update: Publisher, + pub server_update: Publisher, } #[derive(Default, Debug)] pub struct Webhooks { - pub update: GatewayEvent, + pub update: Publisher, } diff --git a/src/gateway/gateway.rs b/src/gateway/gateway.rs index dabfeb6..f1fc03c 100644 --- a/src/gateway/gateway.rs +++ b/src/gateway/gateway.rs @@ -4,8 +4,10 @@ use std::time::Duration; +use flate2::Decompress; use futures_util::{SinkExt, StreamExt}; use log::*; +use pubserve::Publisher; #[cfg(not(target_arch = "wasm32"))] use tokio::task; @@ -19,6 +21,9 @@ use crate::types::{ WebSocketEvent, }; +/// Tells us we have received enough of the buffer to decompress it +const ZLIB_SUFFIX: [u8; 4] = [0, 0, 255, 255]; + #[derive(Debug)] pub struct Gateway { events: Arc>, @@ -28,14 +33,36 @@ pub struct Gateway { kill_send: tokio::sync::broadcast::Sender<()>, kill_receive: tokio::sync::broadcast::Receiver<()>, store: Arc>>>>, + /// Url which was used to initialize the gateway url: String, + /// Options which were used to initialize the gateway + options: GatewayOptions, + zlib_inflate: Option, + zlib_buffer: Option>, } impl Gateway { #[allow(clippy::new_ret_no_self)] - pub async fn spawn(websocket_url: String) -> Result { - let (websocket_send, mut websocket_receive) = - WebSocketBackend::connect(&websocket_url).await?; + /// Creates / opens a new gateway connection. + /// + /// # Note + /// The websocket url should begin with the prefix wss:// or ws:// (for unsecure connections) + pub async fn spawn( + websocket_url: String, + options: GatewayOptions, + ) -> Result { + let url = options.add_to_url(websocket_url); + + debug!("GW: Connecting to {}", url); + + let (websocket_send, mut websocket_receive) = match WebSocketBackend::connect(&url).await { + Ok(streams) => streams, + Err(e) => { + return Err(GatewayError::CannotConnect { + error: format!("{:?}", e), + }); + } + }; let shared_websocket_send = Arc::new(Mutex::new(websocket_send)); @@ -45,10 +72,34 @@ impl Gateway { // Wait for the first hello and then spawn both tasks so we avoid nested tasks // This automatically spawns the heartbeat task, but from the main thread #[cfg(not(target_arch = "wasm32"))] - let msg: GatewayMessage = websocket_receive.next().await.unwrap().unwrap().into(); + let received: RawGatewayMessage = websocket_receive.next().await.unwrap().unwrap().into(); #[cfg(target_arch = "wasm32")] - let msg: GatewayMessage = websocket_receive.next().await.unwrap().into(); - let gateway_payload: types::GatewayReceivePayload = serde_json::from_str(&msg.0).unwrap(); + let received: RawGatewayMessage = websocket_receive.next().await.unwrap().into(); + + let message: GatewayMessage; + + let zlib_buffer; + let zlib_inflate; + + match options.transport_compression { + GatewayTransportCompression::None => { + zlib_buffer = None; + zlib_inflate = None; + message = GatewayMessage::from_raw_json_message(received).unwrap(); + } + GatewayTransportCompression::ZLibStream => { + zlib_buffer = Some(Vec::new()); + let mut inflate = Decompress::new(true); + + message = + GatewayMessage::from_zlib_stream_json_message(received, &mut inflate).unwrap(); + + zlib_inflate = Some(inflate); + } + } + + let gateway_payload: types::GatewayReceivePayload = + serde_json::from_str(&message.0).unwrap(); if gateway_payload.op_code != GATEWAY_HELLO { return Err(GatewayError::NonHelloOnInitiate { @@ -78,7 +129,10 @@ impl Gateway { kill_send: kill_send.clone(), kill_receive: kill_send.subscribe(), store: store.clone(), - url: websocket_url.clone(), + url: url.clone(), + options, + zlib_inflate, + zlib_buffer, }; // Now we can continuously check for messages in a different task, since we aren't going to receive another hello @@ -92,7 +146,7 @@ impl Gateway { }); Ok(GatewayHandle { - url: websocket_url.clone(), + url: url.clone(), events: shared_events, websocket_send: shared_websocket_send.clone(), kill_send: kill_send.clone(), @@ -101,7 +155,7 @@ impl Gateway { } /// The main gateway listener task; - pub async fn gateway_listen_task(&mut self) { + async fn gateway_listen_task(&mut self) { loop { let msg; @@ -118,12 +172,12 @@ impl Gateway { // PRETTYFYME: Remove inline conditional compiling #[cfg(not(target_arch = "wasm32"))] if let Some(Ok(message)) = msg { - self.handle_message(message.into()).await; + self.handle_raw_message(message.into()).await; continue; } #[cfg(target_arch = "wasm32")] if let Some(message) = msg { - self.handle_message(message.into()).await; + self.handle_raw_message(message.into()).await; continue; } @@ -144,7 +198,7 @@ impl Gateway { #[allow(dead_code)] // TODO: Remove this allow annotation async fn handle_event<'a, T: WebSocketEvent + serde::Deserialize<'a>>( data: &'a str, - event: &mut GatewayEvent, + event: &mut Publisher, ) -> Result<(), serde_json::Error> { let data_deserialize_result: Result = serde_json::from_str(data); @@ -152,12 +206,46 @@ impl Gateway { return Err(data_deserialize_result.err().unwrap()); } - event.notify(data_deserialize_result.unwrap()).await; + event.publish(data_deserialize_result.unwrap()).await; Ok(()) } + /// Takes a [RawGatewayMessage], converts it to [GatewayMessage] based + /// of connection options and calls handle_message + async fn handle_raw_message(&mut self, raw_message: RawGatewayMessage) { + let message; + + match self.options.transport_compression { + GatewayTransportCompression::None => { + message = GatewayMessage::from_raw_json_message(raw_message).unwrap() + } + GatewayTransportCompression::ZLibStream => { + let message_bytes = raw_message.into_bytes(); + + let can_decompress = message_bytes.len() > 4 + && message_bytes[message_bytes.len() - 4..] == ZLIB_SUFFIX; + + let zlib_buffer = self.zlib_buffer.as_mut().unwrap(); + zlib_buffer.extend(message_bytes.clone()); + + if !can_decompress { + return; + } + + let zlib_buffer = self.zlib_buffer.as_ref().unwrap(); + let inflate = self.zlib_inflate.as_mut().unwrap(); + + message = + GatewayMessage::from_zlib_stream_json_bytes(zlib_buffer, inflate).unwrap(); + self.zlib_buffer = Some(Vec::new()); + } + }; + + self.handle_message(message).await; + } + /// This handles a message as a websocket event and updates its events along with the events' observers - pub async fn handle_message(&mut self, msg: GatewayMessage) { + async fn handle_message(&mut self, msg: GatewayMessage) { if msg.0.is_empty() { return; } @@ -166,7 +254,7 @@ impl Gateway { if let Some(error) = msg.error() { warn!("GW: Received error {:?}, connection will close..", error); self.close().await; - self.events.lock().await.error.notify(error).await; + self.events.lock().await.error.publish(error).await; } else { warn!( "Message unrecognised: {:?}, please open an issue on the chorus github", @@ -194,7 +282,10 @@ impl Gateway { let event = &mut self.events.lock().await.$($path).+; let json = gateway_payload.event_data.unwrap().get(); match serde_json::from_str(json) { - Err(err) => warn!("Failed to parse gateway event {event_name} ({err})"), + Err(err) => { + warn!("Failed to parse gateway event {event_name} ({err})"); + trace!("Event data: {json}"); + }, Ok(message) => { $( let mut message: $message_type = message; @@ -202,7 +293,7 @@ impl Gateway { let id = if message.id().is_some() { message.id().unwrap() } else { - event.notify(message).await; + event.publish(message).await; return; }; if let Some(to_update) = store.get(&id) { @@ -224,25 +315,22 @@ impl Gateway { } } )? - event.notify(message).await; + event.publish(message).await; } } },)* "RESUMED" => (), "SESSIONS_REPLACE" => { - let result: Result, serde_json::Error> = - serde_json::from_str(gateway_payload.event_data.unwrap().get()); + let json = gateway_payload.event_data.unwrap().get(); + let result: Result, serde_json::Error> = serde_json::from_str(json); match result { Err(err) => { - warn!( - "Failed to parse gateway event {} ({})", - event_name, - err - ); + warn!("Failed to parse gateway event {event_name} ({err})"); + trace!("Event data: {json}"); return; } Ok(sessions) => { - self.events.lock().await.session.replace.notify( + self.events.lock().await.session.replace.publish( types::SessionsReplace {sessions} ).await; } @@ -250,6 +338,7 @@ impl Gateway { }, _ => { warn!("Received unrecognized gateway event ({event_name})! Please open an issue on the chorus github so we can implement it"); + trace!("Event data: {}", gateway_payload.event_data.unwrap().get()); } } }; @@ -358,7 +447,7 @@ impl Gateway { .await .session .reconnect - .notify(reconnect) + .publish(reconnect) .await; } GATEWAY_INVALID_SESSION => { @@ -383,7 +472,7 @@ impl Gateway { .await .session .invalid - .notify(invalid_session) + .publish(invalid_session) .await; } // Starts our heartbeat diff --git a/src/gateway/handle.rs b/src/gateway/handle.rs index 6af5f0d..6bcdba8 100644 --- a/src/gateway/handle.rs +++ b/src/gateway/handle.rs @@ -8,7 +8,7 @@ use log::*; use std::fmt::Debug; use super::{events::Events, *}; -use crate::types::{self, Composite}; +use crate::types::{self, Composite, Shared}; /// Represents a handle to a Gateway connection. A Gateway connection will create observable /// [`GatewayEvents`](GatewayEvent), which you can subscribe to. Gateway events include all currently @@ -154,7 +154,7 @@ impl GatewayHandle { /// Sends a call sync to the server pub async fn send_call_sync(&self, to_send: types::CallSync) { - let to_send_value = serde_json::to_value(&to_send).unwrap(); + let to_send_value = serde_json::to_value(to_send).unwrap(); trace!("GW: Sending Call Sync.."); diff --git a/src/gateway/message.rs b/src/gateway/message.rs index 44d912e..7d44af6 100644 --- a/src/gateway/message.rs +++ b/src/gateway/message.rs @@ -2,11 +2,41 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +use std::string::FromUtf8Error; + use crate::types; use super::*; -/// Represents a message received from the gateway. This will be either a [types::GatewayReceivePayload], containing events, or a [GatewayError]. +/// Defines a raw gateway message, being either string json or bytes +/// +/// This is used as an intermediary type between types from different websocket implementations +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) enum RawGatewayMessage { + Text(String), + Bytes(Vec), +} + +impl RawGatewayMessage { + /// Attempt to consume the message into a String, will try to convert binary to utf8 + pub fn into_text(self) -> Result { + match self { + RawGatewayMessage::Text(text) => Ok(text), + RawGatewayMessage::Bytes(bytes) => String::from_utf8(bytes), + } + } + + /// Consume the message into bytes, will convert text to binary + pub fn into_bytes(self) -> Vec { + match self { + RawGatewayMessage::Text(text) => text.as_bytes().to_vec(), + RawGatewayMessage::Bytes(bytes) => bytes, + } + } +} + +/// Represents a json message received from the gateway. +/// This will be either a [types::GatewayReceivePayload], containing events, or a [GatewayError]. /// This struct is used internally when handling messages. #[derive(Clone, Debug)] pub struct GatewayMessage(pub String); @@ -44,4 +74,48 @@ impl GatewayMessage { pub fn payload(&self) -> Result { serde_json::from_str(&self.0) } + + /// Create self from an uncompressed json [RawGatewayMessage] + pub(crate) fn from_raw_json_message( + message: RawGatewayMessage, + ) -> Result { + let text = message.into_text()?; + Ok(GatewayMessage(text)) + } + + /// Attempt to create self by decompressing zlib-stream bytes + // Thanks to , their + // code helped a lot with the stream implementation + pub(crate) fn from_zlib_stream_json_bytes( + bytes: &[u8], + inflate: &mut flate2::Decompress, + ) -> Result { + + // Note: is there a better way to handle the size of this output buffer? + // + // This used to be 10, I measured it at 11.5, so a safe bet feels like 20 + // + // ^ - This dude is naive. apparently not even 20x is okay. Measured at 47.9x!!!! + // If it is >100x ever, I will literally explode + // + // About an hour later, you ^ will literally explode. + // 133 vs 13994 -- 105.21805x ratio + // Let's hope it doesn't go above 200?? + let mut output = Vec::with_capacity(bytes.len() * 200); + let _status = inflate.decompress_vec(bytes, &mut output, flate2::FlushDecompress::Sync)?; + + output.shrink_to_fit(); + + let string = String::from_utf8(output).unwrap(); + + Ok(GatewayMessage(string)) + } + + /// Attempt to create self by decompressing a zlib-stream bytes raw message + pub(crate) fn from_zlib_stream_json_message( + message: RawGatewayMessage, + inflate: &mut flate2::Decompress, + ) -> Result { + Self::from_zlib_stream_json_bytes(&message.into_bytes(), inflate) + } } diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 5a5881a..6ee0678 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -2,7 +2,6 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use async_trait::async_trait; pub mod backends; pub mod events; @@ -10,15 +9,17 @@ pub mod gateway; pub mod handle; pub mod heartbeat; pub mod message; +pub mod options; pub use backends::*; pub use gateway::*; pub use handle::*; use heartbeat::*; pub use message::*; +pub use options::*; use crate::errors::GatewayError; -use crate::types::{Snowflake, WebSocketEvent}; +use crate::types::Snowflake; use std::any::Any; use std::collections::HashMap; @@ -76,67 +77,11 @@ const GATEWAY_LAZY_REQUEST: u8 = 14; pub type ObservableObject = dyn Send + Sync + Any; +/// Note: this is a reexport of [pubserve::Subscriber], +/// exported not to break the public api and make development easier +pub use pubserve::Subscriber as Observer; + /// An entity type which is supposed to be updateable via the Gateway. This is implemented for all such types chorus supports, implementing it for your own types is likely a mistake. pub trait Updateable: 'static + Send + Sync { fn id(&self) -> Snowflake; } - -/// Trait which defines the behavior of an Observer. An Observer is an object which is subscribed to -/// an Observable. The Observer is notified when the Observable's data changes. -/// In this case, the Observable is a [`GatewayEvent`], which is a wrapper around a WebSocketEvent. -/// Note that `Debug` is used to tell `Observer`s apart when unsubscribing. -#[async_trait] -pub trait Observer: Sync + Send + std::fmt::Debug { - async fn update(&self, data: &T); -} - -/// GatewayEvent is a wrapper around a WebSocketEvent. It is used to notify the observers of a -/// change in the WebSocketEvent. GatewayEvents are observable. -#[derive(Default, Debug)] -pub struct GatewayEvent { - observers: Vec>>, -} - -impl GatewayEvent { - pub fn new() -> Self { - Self { - observers: Vec::new(), - } - } - - /// Returns true if the GatewayEvent is observed by at least one Observer. - pub fn is_observed(&self) -> bool { - !self.observers.is_empty() - } - - /// Subscribes an Observer to the GatewayEvent. - pub fn subscribe(&mut self, observable: Arc>) { - self.observers.push(observable); - } - - /// Unsubscribes an Observer from the GatewayEvent. - pub fn unsubscribe(&mut self, observable: &dyn Observer) { - // .retain()'s closure retains only those elements of the vector, which have a different - // pointer value than observable. - // The usage of the debug format to compare the generic T of observers is quite stupid, but the only thing to compare between them is T and if T == T they are the same - // anddd there is no way to do that without using format - let to_remove = format!("{:?}", observable); - self.observers - .retain(|obs| format!("{:?}", obs) != to_remove); - } - - /// Notifies the observers of the GatewayEvent. - pub(crate) async fn notify(&self, new_event_data: T) { - for observer in &self.observers { - observer.update(&new_event_data).await; - } - } -} - -/// A type alias for [`Arc>`], used to make the public facing API concerned with -/// Composite structs more ergonomic. -/// ## Note -/// -/// While `T` does not have to implement `Composite` to be used with `Shared`, -/// the primary use of `Shared` is with types that implement `Composite`. -pub type Shared = Arc>; diff --git a/src/gateway/options.rs b/src/gateway/options.rs new file mode 100644 index 0000000..23942b2 --- /dev/null +++ b/src/gateway/options.rs @@ -0,0 +1,116 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#[derive(Clone, PartialEq, Eq, Ord, PartialOrd, Debug, Default, Copy)] +/// Options passed when initializing the gateway connection. +/// +/// E.g. compression +/// +/// # Note +/// +/// Discord allows specifying the api version (v10, v9, ...) as well, but chorus is built upon one +/// main version (v9). +/// +/// Similarly, discord also supports etf encoding, while chorus does not (yet). +/// We are looking into supporting it as an option, since it is faster and more lightweight. +/// +/// See +pub struct GatewayOptions { + pub encoding: GatewayEncoding, + pub transport_compression: GatewayTransportCompression, +} + +impl GatewayOptions { + /// Adds the options to an existing gateway url + /// + /// Returns the new url + pub(crate) fn add_to_url(&self, url: String) -> String { + let mut url = url; + + let mut parameters = Vec::with_capacity(2); + + let encoding = self.encoding.to_url_parameter(); + parameters.push(encoding); + + let compression = self.transport_compression.to_url_parameter(); + if let Some(some_compression) = compression { + parameters.push(some_compression); + } + + let mut has_parameters = url.contains('?') && url.contains('='); + + if !has_parameters { + // Insure it ends in a /, so we don't get a 400 error + if !url.ends_with('/') { + url.push('/'); + } + + // Lets hope that if it already has parameters the person knew to add '/' + } + + for parameter in parameters { + if !has_parameters { + url = format!("{}?{}", url, parameter); + has_parameters = true; + } else { + url = format!("{}&{}", url, parameter); + } + } + + url + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Debug, Default)] +/// Possible transport compression options for the gateway. +/// +/// See +pub enum GatewayTransportCompression { + /// Do not transport compress packets + None, + /// Transport compress using zlib stream + #[default] + ZLibStream, +} + +impl GatewayTransportCompression { + /// Returns the option as a url parameter. + /// + /// If set to [GatewayTransportCompression::None] returns [None]. + /// + /// If set to anything else, returns a string like "compress=zlib-stream" + pub(crate) fn to_url_parameter(self) -> Option { + match self { + Self::None => None, + Self::ZLibStream => Some(String::from("compress=zlib-stream")), + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Debug, Default)] +/// See +pub enum GatewayEncoding { + /// Javascript object notation, a standard for websocket connections, + /// but contains a lot of overhead + #[default] + Json, + /// A binary format originating from Erlang + /// + /// Should be lighter and faster than json. + /// + /// !! Chorus does not implement ETF yet !! + ETF, +} + +impl GatewayEncoding { + /// Returns the option as a url parameter. + /// + /// Returns a string like "encoding=json" + pub(crate) fn to_url_parameter(self) -> String { + match self { + Self::Json => String::from("encoding=json"), + Self::ETF => String::from("encoding=etf"), + } + } +} diff --git a/src/instance.rs b/src/instance.rs index 1661042..f826ab5 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -13,11 +13,11 @@ use reqwest::Client; use serde::{Deserialize, Serialize}; use crate::errors::ChorusResult; -use crate::gateway::{Gateway, GatewayHandle, Shared}; +use crate::gateway::{Gateway, GatewayHandle, GatewayOptions}; use crate::ratelimiter::ChorusRequest; use crate::types::types::subconfigs::limits::rates::RateLimits; use crate::types::{ - GeneralConfiguration, Limit, LimitType, LimitsConfiguration, User, UserSettings, + GeneralConfiguration, Limit, LimitType, LimitsConfiguration, Shared, User, UserSettings, }; use crate::UrlBundle; @@ -31,24 +31,8 @@ pub struct Instance { pub limits_information: Option, #[serde(skip)] pub client: Client, -} - -impl PartialEq for Instance { - fn eq(&self, other: &Self) -> bool { - self.urls == other.urls - && self.instance_info == other.instance_info - && self.limits_information == other.limits_information - } -} - -impl std::hash::Hash for Instance { - fn hash(&self, state: &mut H) { - self.urls.hash(state); - self.instance_info.hash(state); - if let Some(inf) = &self.limits_information { - inf.hash(state); - } - } + #[serde(skip)] + pub gateway_options: GatewayOptions, } #[derive(Debug, Clone, Serialize, Deserialize, Default, Eq)] @@ -67,6 +51,7 @@ impl std::hash::Hash for LimitsInformation { } } +#[cfg(not(tarpaulin_include))] impl PartialEq for LimitsInformation { fn eq(&self, other: &Self) -> bool { self.ratelimits.iter().eq(other.ratelimits.iter()) @@ -104,6 +89,7 @@ impl Instance { instance_info: GeneralConfiguration::default(), limits_information: limit_information, client: Client::new(), + gateway_options: GatewayOptions::default(), }; instance.instance_info = match instance.general_configuration_schema().await { Ok(schema) => schema, @@ -139,6 +125,13 @@ impl Instance { Err(_) => Ok(None), } } + + /// Sets the [`GatewayOptions`] the instance will use when spawning new connections. + /// + /// These options are used on the gateways created when logging in and registering. + pub fn set_gateway_options(&mut self, options: GatewayOptions) { + self.gateway_options = options; + } } #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -165,14 +158,6 @@ pub struct ChorusUser { pub gateway: GatewayHandle, } -impl PartialEq for ChorusUser { - fn eq(&self, other: &Self) -> bool { - self.token == other.token - && self.limits == other.limits - && self.gateway.url == other.gateway.url - } -} - impl ChorusUser { pub fn token(&self) -> String { self.token.clone() @@ -215,7 +200,9 @@ impl ChorusUser { let object = Arc::new(RwLock::new(User::default())); let wss_url = instance.read().unwrap().urls.wss.clone(); // Dummy gateway object - let gateway = Gateway::spawn(wss_url).await.unwrap(); + let gateway = Gateway::spawn(wss_url, GatewayOptions::default()) + .await + .unwrap(); ChorusUser { token, belongs_to: instance.clone(), diff --git a/src/lib.rs b/src/lib.rs index 6ffd8b8..7767480 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,7 +98,6 @@ This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read )] #![allow(clippy::module_inception)] #![deny( - missing_debug_implementations, clippy::extra_unused_lifetimes, clippy::from_over_into, clippy::needless_borrow, @@ -110,7 +109,9 @@ This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read clippy::unimplemented, clippy::dbg_macro, clippy::print_stdout, - clippy::print_stderr + clippy::print_stderr, + missing_debug_implementations, + missing_copy_implementations )] #[cfg(all(feature = "rt", feature = "rt_multi_thread"))] compile_error!("feature \"rt\" and feature \"rt_multi_thread\" cannot be enabled at the same time"); diff --git a/src/ratelimiter.rs b/src/ratelimiter.rs index 5e69d95..5ffcca6 100644 --- a/src/ratelimiter.rs +++ b/src/ratelimiter.rs @@ -6,7 +6,6 @@ use std::collections::HashMap; -use log::{self, debug}; use reqwest::{Client, RequestBuilder, Response}; use serde::Deserialize; use serde_json::from_str; @@ -88,7 +87,7 @@ impl ChorusRequest { let client = user.belongs_to.read().unwrap().client.clone(); let result = match client.execute(self.request.build().unwrap()).await { Ok(result) => { - debug!("Request successful: {:?}", result); + log::trace!("Request successful: {:?}", result); result } Err(error) => { @@ -494,7 +493,7 @@ impl ChorusRequest { user: &mut ChorusUser, ) -> ChorusResult { let response = self.send_request(user).await?; - debug!("Got response: {:?}", response); + log::trace!("Got response: {:?}", response); let response_text = match response.text().await { Ok(string) => string, Err(e) => { diff --git a/src/types/config/types/defaults_configuration.rs b/src/types/config/types/defaults_configuration.rs index e6e0867..f607c9b 100644 --- a/src/types/config/types/defaults_configuration.rs +++ b/src/types/config/types/defaults_configuration.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use crate::types::config::types::subconfigs::defaults::{guild::GuildDefaults, user::UserDefaults}; -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, Copy)] #[serde(rename_all = "camelCase")] pub struct DefaultsConfiguration { pub guild: GuildDefaults, diff --git a/src/types/config/types/guild_configuration.rs b/src/types/config/types/guild_configuration.rs index af40b30..53a2d45 100644 --- a/src/types/config/types/guild_configuration.rs +++ b/src/types/config/types/guild_configuration.rs @@ -3,19 +3,10 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::fmt::{Display, Formatter}; -#[cfg(feature = "sqlx")] -use std::io::Write; use std::ops::{Deref, DerefMut}; use std::str::FromStr; use serde::{Deserialize, Serialize}; -#[cfg(feature = "sqlx")] -use sqlx::{ - database::{HasArguments, HasValueRef}, - encode::IsNull, - error::BoxDynError, - Decode, MySql, -}; use crate::types::config::types::subconfigs::guild::{ autojoin::AutoJoinConfiguration, discovery::DiscoverConfiguration, @@ -172,8 +163,8 @@ impl Display for GuildFeaturesList { #[cfg(feature = "sqlx")] impl<'r> sqlx::Decode<'r, sqlx::MySql> for GuildFeaturesList { - fn decode(value: >::ValueRef) -> Result { - let v = <&str as Decode>::decode(value)?; + fn decode(value: >::ValueRef) -> Result { + let v = >::decode(value)?; Ok(Self( v.split(',') .filter(|f| !f.is_empty()) @@ -185,9 +176,9 @@ impl<'r> sqlx::Decode<'r, sqlx::MySql> for GuildFeaturesList { #[cfg(feature = "sqlx")] impl<'q> sqlx::Encode<'q, sqlx::MySql> for GuildFeaturesList { - fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> sqlx::encode::IsNull { if self.is_empty() { - return IsNull::Yes; + return sqlx::encode::IsNull::Yes; } let features = self .iter() @@ -195,30 +186,18 @@ impl<'q> sqlx::Encode<'q, sqlx::MySql> for GuildFeaturesList { .collect::>() .join(","); - let _ = buf.write(features.as_bytes()); - IsNull::No + >::encode_by_ref(&features, buf) } } #[cfg(feature = "sqlx")] impl sqlx::Type for GuildFeaturesList { fn type_info() -> sqlx::mysql::MySqlTypeInfo { - <&str as sqlx::Type>::type_info() + >::type_info() } fn compatible(ty: &sqlx::mysql::MySqlTypeInfo) -> bool { - <&str as sqlx::Type>::compatible(ty) - } -} - -#[cfg(feature = "sqlx")] -impl sqlx::TypeInfo for GuildFeaturesList { - fn is_null(&self) -> bool { - false - } - - fn name(&self) -> &str { - "TEXT" + >::compatible(ty) } } @@ -376,6 +355,12 @@ impl FromStr for GuildFeatures { } } +impl From> for GuildFeaturesList { + fn from(features: Vec) -> GuildFeaturesList { + Self(features) + } +} + impl GuildFeatures { pub fn to_str(&self) -> &'static str { match *self { diff --git a/src/types/config/types/login_configuration.rs b/src/types/config/types/login_configuration.rs index 83125e0..ed797b5 100644 --- a/src/types/config/types/login_configuration.rs +++ b/src/types/config/types/login_configuration.rs @@ -4,7 +4,9 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive( + Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, Copy, Hash, PartialOrd, Ord, +)] #[serde(rename_all = "camelCase")] pub struct LoginConfiguration { pub require_captcha: bool, diff --git a/src/types/config/types/metrics_configuration.rs b/src/types/config/types/metrics_configuration.rs index 98d3536..d11644b 100644 --- a/src/types/config/types/metrics_configuration.rs +++ b/src/types/config/types/metrics_configuration.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Ord, PartialOrd, Copy, Hash)] #[serde(rename_all = "camelCase")] pub struct MetricsConfiguration { pub timeout: u64, diff --git a/src/types/config/types/password_reset_configuration.rs b/src/types/config/types/password_reset_configuration.rs index d1c730e..e7de663 100644 --- a/src/types/config/types/password_reset_configuration.rs +++ b/src/types/config/types/password_reset_configuration.rs @@ -4,7 +4,9 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive( + Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, Copy, Hash, PartialOrd, Ord, +)] #[serde(rename_all = "camelCase")] pub struct PasswordResetConfiguration { pub require_captcha: bool, diff --git a/src/types/config/types/register_configuration.rs b/src/types/config/types/register_configuration.rs index a4573bf..4afc40c 100644 --- a/src/types/config/types/register_configuration.rs +++ b/src/types/config/types/register_configuration.rs @@ -3,10 +3,11 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use serde::{Deserialize, Serialize}; +use serde_aux::prelude::deserialize_number_from_string; -use crate::types::config::types::subconfigs::register::{ +use crate::types::{config::types::subconfigs::register::{ DateOfBirthConfiguration, PasswordConfiguration, RegistrationEmailConfiguration, -}; +}, Rights}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -22,7 +23,8 @@ pub struct RegisterConfiguration { pub allow_multiple_accounts: bool, pub block_proxies: bool, pub incrementing_discriminators: bool, - pub default_rights: String, + #[serde(deserialize_with = "deserialize_number_from_string")] + pub default_rights: Rights, } impl Default for RegisterConfiguration { @@ -39,7 +41,7 @@ impl Default for RegisterConfiguration { allow_multiple_accounts: true, block_proxies: true, incrementing_discriminators: false, - default_rights: String::from("875069521787904"), + default_rights: Rights::from_bits(648540060672).expect("failed to parse default_rights"), } } } diff --git a/src/types/config/types/subconfigs/defaults/guild.rs b/src/types/config/types/subconfigs/defaults/guild.rs index 8509fe5..f33f279 100644 --- a/src/types/config/types/subconfigs/defaults/guild.rs +++ b/src/types/config/types/subconfigs/defaults/guild.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use crate::types::{ExplicitContentFilterLevel, MessageNotificationLevel}; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash, PartialOrd, Ord)] #[serde(rename_all = "camelCase")] pub struct GuildDefaults { pub max_presences: u64, diff --git a/src/types/config/types/subconfigs/defaults/user.rs b/src/types/config/types/subconfigs/defaults/user.rs index d7dc7b3..a533b0a 100644 --- a/src/types/config/types/subconfigs/defaults/user.rs +++ b/src/types/config/types/subconfigs/defaults/user.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash, PartialOrd, Ord)] #[serde(rename_all = "camelCase")] pub struct UserDefaults { pub premium: bool, diff --git a/src/types/config/types/subconfigs/guild/discovery.rs b/src/types/config/types/subconfigs/guild/discovery.rs index 50738f1..689528b 100644 --- a/src/types/config/types/subconfigs/guild/discovery.rs +++ b/src/types/config/types/subconfigs/guild/discovery.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)] #[serde(rename_all = "camelCase")] pub struct DiscoverConfiguration { pub show_all_guilds: bool, diff --git a/src/types/config/types/subconfigs/limits/channel.rs b/src/types/config/types/subconfigs/limits/channel.rs index 2415726..9e03283 100644 --- a/src/types/config/types/subconfigs/limits/channel.rs +++ b/src/types/config/types/subconfigs/limits/channel.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord)] #[serde(rename_all = "camelCase")] pub struct ChannelLimits { pub max_pins: u16, diff --git a/src/types/config/types/subconfigs/limits/global.rs b/src/types/config/types/subconfigs/limits/global.rs index 0140447..87e25f6 100644 --- a/src/types/config/types/subconfigs/limits/global.rs +++ b/src/types/config/types/subconfigs/limits/global.rs @@ -4,9 +4,9 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)] pub struct GlobalRateLimit { - pub limit: u16, + pub limit: u64, pub window: u64, pub enabled: bool, } @@ -21,7 +21,7 @@ impl Default for GlobalRateLimit { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)] #[serde(rename_all = "camelCase")] pub struct GlobalRateLimits { pub register: GlobalRateLimit, diff --git a/src/types/config/types/subconfigs/limits/guild.rs b/src/types/config/types/subconfigs/limits/guild.rs index 9ef8f90..4c58404 100644 --- a/src/types/config/types/subconfigs/limits/guild.rs +++ b/src/types/config/types/subconfigs/limits/guild.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash, PartialOrd, Ord)] #[serde(rename_all = "camelCase")] pub struct GuildLimits { pub max_roles: u16, diff --git a/src/types/config/types/subconfigs/limits/message.rs b/src/types/config/types/subconfigs/limits/message.rs index 3beb76e..fc2237b 100644 --- a/src/types/config/types/subconfigs/limits/message.rs +++ b/src/types/config/types/subconfigs/limits/message.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)] #[serde(rename_all = "camelCase")] pub struct MessageLimits { pub max_characters: u32, diff --git a/src/types/config/types/subconfigs/limits/ratelimits/auth.rs b/src/types/config/types/subconfigs/limits/ratelimits/auth.rs index 78f1908..25adcab 100644 --- a/src/types/config/types/subconfigs/limits/ratelimits/auth.rs +++ b/src/types/config/types/subconfigs/limits/ratelimits/auth.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use crate::types::config::types::subconfigs::limits::ratelimits::RateLimitOptions; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)] pub struct AuthRateLimit { pub login: RateLimitOptions, pub register: RateLimitOptions, diff --git a/src/types/config/types/subconfigs/limits/ratelimits/mod.rs b/src/types/config/types/subconfigs/limits/ratelimits/mod.rs index 501afe9..16106cd 100644 --- a/src/types/config/types/subconfigs/limits/ratelimits/mod.rs +++ b/src/types/config/types/subconfigs/limits/ratelimits/mod.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; pub mod auth; pub mod route; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, PartialOrd, Ord, Copy)] #[serde(rename_all = "camelCase")] pub struct RateLimitOptions { pub bot: Option, diff --git a/src/types/config/types/subconfigs/limits/ratelimits/route.rs b/src/types/config/types/subconfigs/limits/ratelimits/route.rs index 7c70fdc..8379c26 100644 --- a/src/types/config/types/subconfigs/limits/ratelimits/route.rs +++ b/src/types/config/types/subconfigs/limits/ratelimits/route.rs @@ -8,7 +8,7 @@ use crate::types::config::types::subconfigs::limits::ratelimits::{ auth::AuthRateLimit, RateLimitOptions, }; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, Copy, PartialOrd, Ord)] pub struct RouteRateLimit { pub guild: RateLimitOptions, pub webhook: RateLimitOptions, diff --git a/src/types/config/types/subconfigs/limits/rates.rs b/src/types/config/types/subconfigs/limits/rates.rs index 4c9b9a1..e837d96 100644 --- a/src/types/config/types/subconfigs/limits/rates.rs +++ b/src/types/config/types/subconfigs/limits/rates.rs @@ -50,14 +50,14 @@ impl Default for RateLimits { impl RateLimits { pub fn to_hash_map(&self) -> HashMap { let mut map = HashMap::new(); - map.insert(LimitType::AuthLogin, self.routes.auth.login.clone()); - map.insert(LimitType::AuthRegister, self.routes.auth.register.clone()); - map.insert(LimitType::ChannelBaseline, self.routes.channel.clone()); - map.insert(LimitType::Error, self.error.clone()); - map.insert(LimitType::Global, self.global.clone()); - map.insert(LimitType::Ip, self.ip.clone()); - map.insert(LimitType::WebhookBaseline, self.routes.webhook.clone()); - map.insert(LimitType::GuildBaseline, self.routes.guild.clone()); + map.insert(LimitType::AuthLogin, self.routes.auth.login); + map.insert(LimitType::AuthRegister, self.routes.auth.register); + map.insert(LimitType::ChannelBaseline, self.routes.channel); + map.insert(LimitType::Error, self.error); + map.insert(LimitType::Global, self.global); + map.insert(LimitType::Ip, self.ip); + map.insert(LimitType::WebhookBaseline, self.routes.webhook); + map.insert(LimitType::GuildBaseline, self.routes.guild); map } } diff --git a/src/types/config/types/subconfigs/limits/user.rs b/src/types/config/types/subconfigs/limits/user.rs index 473535a..4ac0e9d 100644 --- a/src/types/config/types/subconfigs/limits/user.rs +++ b/src/types/config/types/subconfigs/limits/user.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord, Copy, Hash)] #[serde(rename_all = "camelCase")] pub struct UserLimits { pub max_guilds: u64, diff --git a/src/types/config/types/subconfigs/region/mod.rs b/src/types/config/types/subconfigs/region/mod.rs index 1661c09..42ad564 100644 --- a/src/types/config/types/subconfigs/region/mod.rs +++ b/src/types/config/types/subconfigs/region/mod.rs @@ -4,13 +4,13 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, PartialOrd, Copy)] pub struct LatLong { pub latitude: f64, pub longitude: f64, } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, PartialOrd)] pub struct Region { pub id: String, pub name: String, diff --git a/src/types/config/types/subconfigs/register/date_of_birth.rs b/src/types/config/types/subconfigs/register/date_of_birth.rs index 6689297..961fca4 100644 --- a/src/types/config/types/subconfigs/register/date_of_birth.rs +++ b/src/types/config/types/subconfigs/register/date_of_birth.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)] pub struct DateOfBirthConfiguration { pub required: bool, pub minimum: u8, diff --git a/src/types/config/types/subconfigs/register/password.rs b/src/types/config/types/subconfigs/register/password.rs index 1d380ba..a192dc5 100644 --- a/src/types/config/types/subconfigs/register/password.rs +++ b/src/types/config/types/subconfigs/register/password.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)] #[serde(rename_all = "camelCase")] pub struct PasswordConfiguration { pub required: bool, diff --git a/src/types/config/types/subconfigs/security/twofactor.rs b/src/types/config/types/subconfigs/security/twofactor.rs index 1182a45..248ba11 100644 --- a/src/types/config/types/subconfigs/security/twofactor.rs +++ b/src/types/config/types/subconfigs/security/twofactor.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash, PartialOrd, Ord)] #[serde(rename_all = "camelCase")] pub struct TwoFactorConfiguration { pub generate_backup_codes: bool, diff --git a/src/types/config/types/template_configuration.rs b/src/types/config/types/template_configuration.rs index 9f370b4..b88fe2e 100644 --- a/src/types/config/types/template_configuration.rs +++ b/src/types/config/types/template_configuration.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)] #[serde(rename_all = "camelCase")] pub struct TemplateConfiguration { pub enabled: bool, diff --git a/src/types/entities/application.rs b/src/types/entities/application.rs index 4014e55..f265f21 100644 --- a/src/types/entities/application.rs +++ b/src/types/entities/application.rs @@ -7,10 +7,13 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use serde_repr::{Deserialize_repr, Serialize_repr}; -use crate::gateway::Shared; use crate::types::utils::Snowflake; +use crate::types::Shared; use crate::types::{Team, User}; +#[allow(unused_imports)] +use super::{arc_rwlock_ptr_eq, option_arc_rwlock_ptr_eq}; + #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] /// # Reference @@ -31,7 +34,7 @@ pub struct Application { pub verify_key: String, #[cfg_attr(feature = "sqlx", sqlx(skip))] pub owner: Shared, - pub flags: u64, + pub flags: ApplicationFlags, #[cfg(feature = "sqlx")] pub redirect_uris: Option>>, #[cfg(not(feature = "sqlx"))] @@ -59,6 +62,61 @@ pub struct Application { pub team: Option, } +#[cfg(not(tarpaulin_include))] +impl PartialEq for Application { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + && self.name == other.name + && self.icon == other.icon + && self.description == other.description + && self.summary == other.summary + && self.r#type == other.r#type + && self.hook == other.hook + && self.bot_public == other.bot_public + && self.bot_require_code_grant == other.bot_require_code_grant + && self.verify_key == other.verify_key + && arc_rwlock_ptr_eq(&self.owner, &other.owner) + && self.flags == other.flags + && self.redirect_uris == other.redirect_uris + && self.rpc_application_state == other.rpc_application_state + && self.store_application_state == other.store_application_state + && self.verification_state == other.verification_state + && self.interactions_endpoint_url == other.interactions_endpoint_url + && self.integration_public == other.integration_public + && self.integration_require_code_grant == other.integration_require_code_grant + && self.discoverability_state == other.discoverability_state + && self.discovery_eligibility_flags == other.discovery_eligibility_flags + && self.tags == other.tags + && self.cover_image == other.cover_image + && compare_install_params(&self.install_params, &other.install_params) + && self.terms_of_service_url == other.terms_of_service_url + && self.privacy_policy_url == other.privacy_policy_url + && self.team == other.team + } +} + +#[cfg(not(tarpaulin_include))] +#[cfg(feature = "sqlx")] +fn compare_install_params( + a: &Option>, + b: &Option>, +) -> bool { + match (a, b) { + (Some(a), Some(b)) => a.encode_to_string() == b.encode_to_string(), + (None, None) => true, + _ => false, + } +} + +#[cfg(not(tarpaulin_include))] +#[cfg(not(feature = "sqlx"))] +fn compare_install_params( + a: &Option>, + b: &Option>, +) -> bool { + option_arc_rwlock_ptr_eq(a, b) +} + impl Default for Application { fn default() -> Self { Self { @@ -73,7 +131,7 @@ impl Default for Application { bot_require_code_grant: false, verify_key: "".to_string(), owner: Default::default(), - flags: 0, + flags: ApplicationFlags::empty(), redirect_uris: None, rpc_application_state: 0, store_application_state: 1, @@ -93,12 +151,6 @@ impl Default for Application { } } -impl Application { - pub fn flags(&self) -> ApplicationFlags { - ApplicationFlags::from_bits(self.flags.to_owned()).unwrap() - } -} - #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] /// # Reference /// See @@ -108,7 +160,8 @@ pub struct InstallParams { } bitflags! { - #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, chorus_macros::SerdeBitFlags)] + #[cfg_attr(feature = "sqlx", derive(chorus_macros::SqlxBitFlags))] /// # Reference /// See pub struct ApplicationFlags: u64 { @@ -212,7 +265,9 @@ pub struct GuildApplicationCommandPermissions { pub permissions: Vec>, } -#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] +#[derive( + Debug, Default, Clone, PartialEq, Serialize, Deserialize, Copy, Eq, Hash, PartialOrd, Ord, +)] /// See pub struct ApplicationCommandPermission { pub id: Snowflake, @@ -222,7 +277,19 @@ pub struct ApplicationCommandPermission { pub permission: bool, } -#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, PartialEq, Eq, Hash)] +#[derive( + Serialize_repr, + Deserialize_repr, + Debug, + Default, + Clone, + PartialEq, + Eq, + Hash, + Copy, + PartialOrd, + Ord, +)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[repr(u8)] /// See diff --git a/src/types/entities/audit_log.rs b/src/types/entities/audit_log.rs index 477fb20..17f3275 100644 --- a/src/types/entities/audit_log.rs +++ b/src/types/entities/audit_log.rs @@ -2,25 +2,85 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use serde::{Deserialize, Serialize}; +#[allow(unused_imports)] +use super::option_vec_arc_rwlock_ptr_eq; + +use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; -use crate::gateway::Shared; use crate::types::utils::Snowflake; +use crate::types::{ + AutoModerationRuleTriggerType, IntegrationType, PermissionOverwriteType, Shared, +}; #[derive(Serialize, Deserialize, Debug, Default, Clone)] +#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] /// See pub struct AuditLogEntry { pub target_id: Option, + #[cfg(feature = "sqlx")] + pub changes: sqlx::types::Json>>>, + #[cfg(not(feature = "sqlx"))] pub changes: Option>>, pub user_id: Option, pub id: Snowflake, - // to:do implement an enum for these types - pub action_type: u8, - // to:do add better options type - pub options: Option, + pub action_type: AuditLogActionType, + #[cfg(feature = "sqlx")] + pub options: Option>, + #[cfg(not(feature = "sqlx"))] + pub options: Option, pub reason: Option, } +impl PartialEq for AuditLogEntry { + fn eq(&self, other: &Self) -> bool { + self.target_id == other.target_id + && self.user_id == other.user_id + && self.id == other.id + && self.action_type == other.action_type + && compare_options(&self.options, &other.options) + && self.reason == other.reason + && compare_changes(&self.changes, &other.changes) + } +} + +#[cfg(not(tarpaulin_include))] +#[cfg(feature = "sqlx")] +fn compare_options( + a: &Option>, + b: &Option>, +) -> bool { + match (a, b) { + (Some(a), Some(b)) => a.encode_to_string() == b.encode_to_string(), + (None, None) => true, + _ => false, + } +} + +#[cfg(not(tarpaulin_include))] +#[cfg(not(feature = "sqlx"))] +fn compare_options(a: &Option, b: &Option) -> bool { + a == b +} + +#[cfg(not(tarpaulin_include))] +#[cfg(feature = "sqlx")] +fn compare_changes( + a: &sqlx::types::Json>>>, + b: &sqlx::types::Json>>>, +) -> bool { + a.encode_to_string() == b.encode_to_string() +} + +#[cfg(not(tarpaulin_include))] +#[cfg(not(feature = "sqlx"))] +fn compare_changes( + a: &Option>>, + b: &Option>>, +) -> bool { + option_vec_arc_rwlock_ptr_eq(a, b) +} + #[derive(Serialize, Deserialize, Debug, Default, Clone)] /// See pub struct AuditLogChange { @@ -28,3 +88,175 @@ pub struct AuditLogChange { pub old_value: Option, pub key: String, } + +#[derive( + Default, + Serialize_repr, + Deserialize_repr, + Debug, + Clone, + Copy, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, +)] +#[repr(u8)] +#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] +/// # Reference: +/// See +pub enum AuditLogActionType { + #[default] + /// Guild settings were updated + GuildUpdate = 1, + /// Channel was created + ChannelCreate = 10, + /// Channel settings were updated + ChannelUpdate = 11, + /// Channel was deleted + ChannelDelete = 12, + /// Permission overwrite was added to a channel + ChannelOverwriteCreate = 13, + /// Permission overwrite was updated for a channel + ChannelOverwriteUpdate = 14, + /// Permission overwrite was deleted from a channel + ChannelOverwriteDelete = 15, + /// Member was removed from guild + MemberKick = 20, + /// Members were pruned from guild + MemberPrune = 21, + /// Member was banned from guild + MemberBanAdd = 22, + /// Member was unbanned from guild + MemberBanRemove = 23, + /// Member was updated in guild + MemberUpdate = 24, + /// Member was added or removed from a role + MemberRoleUpdate = 25, + /// Member was moved to a different voice channel + MemberMove = 26, + /// Member was disconnected from a voice channel + MemberDisconnect = 27, + /// Bot user was added to guild + BotAdd = 28, + /// Role was created + RoleCreate = 30, + /// Role was edited + RoleUpdate = 31, + /// Role was deleted + RoleDelete = 32, + /// Guild invite was created + InviteCreate = 40, + /// Guild invite was updated + InviteUpdate = 41, + /// Guild invite was deleted + InviteDelete = 42, + /// Webhook was created + WebhookCreate = 50, + /// Webhook properties or channel were updated + WebhookUpdate = 51, + /// Webhook was deleted + WebhookDelete = 52, + /// Emoji was created + EmojiCreate = 60, + /// Emoji name was updated + EmojiUpdate = 61, + /// Emoji was deleted + EmojiDelete = 62, + /// Single message was deleted + MessageDelete = 72, + /// Multiple messages were deleted + MessageBulkDelete = 73, + /// Message was pinned to a channel + MessagePin = 74, + /// Message was unpinned from a channel + MessageUnpin = 75, + /// Interaction was added to guild + IntegrationCreate = 80, + /// Integration was updated (e.g. its scopes were updated) + IntegrationUpdate = 81, + /// Integration was removed from guild + IntegrationDelete = 82, + /// Stage instance was created (stage channel becomes live) + StageInstanceCreate = 83, + /// Stage instance details were updated + StageInstanceUpdate = 84, + /// Stage instance was deleted (stage channel no longer live) + StageInstanceDelete = 85, + /// Sticker was created + StickerCreate = 90, + /// Sticker details were updated + StickerUpdate = 91, + /// Sticker was deleted + StickerDelete = 92, + /// Event was created + GuildScheduledEventCreate = 100, + /// Event was updated + GuildScheduledEventUpdate = 101, + /// Event was cancelled + GuildScheduledEventDelete = 102, + /// Thread was created in a channel + ThreadCreate = 110, + /// Thread was updated + ThreadUpdate = 111, + /// Thread was deleted + ThreadDelete = 112, + /// Permissions were updated for a command + ApplicationCommandPermissionUpdate = 121, + /// AutoMod rule created + AutoModerationRuleCreate = 140, + /// AutoMod rule was updated + AutoModerationRuleUpdate = 141, + /// AutoMod rule was deleted + AutoModerationRuleDelete = 142, + /// Message was blocked by AutoMod + AutoModerationBlockMessage = 143, + /// Message was flagged by AutoMod + AutoModerationFlagToChannel = 144, + /// Member was timed out by AutoMod + AutoModerationUserCommunicationDisabled = 145, + /// Member was quarantined by AutoMod + AutoModerationQuarantineUser = 146, + /// Creator monetization request was created + CreatorMonetizationRequestCreated = 150, + /// Creator monetization terms were accepted + CreatorMonetizationTermsAccepted = 151, + /// Onboarding prompt was created + OnboardingPromptCreate = 163, + /// Onboarding prompt was updated + OnboardingPromptUpdate = 164, + /// Onboarding prompt was deleted + OnboardingPromptDelete = 165, + /// Onboarding was created + OnboardingCreate = 166, + /// Onboarding was updated + OnboardingUpdate = 167, + /// Voice channel status was updated + VoiceChannelStatusUpdate = 192, + /// Voice channel status was deleted + VoiceChannelStatusDelete = 193, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)] +pub struct AuditEntryInfo { + pub application_id: Option, + pub auto_moderation_rule_name: Option, + pub auto_moderation_rule_trigger_type: Option, + pub channel_id: Option, + // #[serde(option_string)] + pub count: Option, + // #[serde(option_string)] + pub delete_member_days: Option, + /// The ID of the overwritten entity + pub id: Option, + pub integration_type: Option, + // #[serde(option_string)] + pub members_removed: Option, + // #[serde(option_string)] + pub message_id: Option, + pub role_name: Option, + #[serde(rename = "type")] + pub overwrite_type: Option, + pub status: Option, +} diff --git a/src/types/entities/auto_moderation.rs b/src/types/entities/auto_moderation.rs index cd69bf2..3caa16c 100644 --- a/src/types/entities/auto_moderation.rs +++ b/src/types/entities/auto_moderation.rs @@ -2,9 +2,9 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use crate::gateway::Shared; #[cfg(feature = "client")] use crate::gateway::Updateable; +use crate::types::Shared; #[cfg(feature = "client")] use chorus_macros::Updateable; @@ -31,7 +31,7 @@ pub struct AutoModerationRule { pub exempt_channels: Vec, } -#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)] +#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default, Copy)] #[repr(u8)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] /// See @@ -40,7 +40,9 @@ pub enum AutoModerationRuleEventType { MessageSend = 1, } -#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)] +#[derive( + Serialize_repr, Deserialize_repr, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy, +)] #[repr(u8)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] /// See @@ -52,7 +54,7 @@ pub enum AutoModerationRuleTriggerType { MentionSpam = 5, } -#[derive(Serialize, Deserialize, Debug, Clone, Default)] +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)] #[serde(untagged)] /// See pub enum AutoModerationRuleTriggerMetadata { @@ -63,7 +65,7 @@ pub enum AutoModerationRuleTriggerMetadata { None, } -#[derive(Serialize, Deserialize, Debug, Clone, Default)] +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)] /// See pub struct AutoModerationRuleTriggerMetadataForKeyword { pub keyword_filter: Vec, @@ -71,14 +73,14 @@ pub struct AutoModerationRuleTriggerMetadataForKeyword { pub allow_list: Vec, } -#[derive(Serialize, Deserialize, Debug, Clone, Default)] +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)] /// See pub struct AutoModerationRuleTriggerMetadataForKeywordPreset { pub presets: Vec, pub allow_list: Vec, } -#[derive(Serialize, Deserialize, Debug, Clone, Default)] +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy)] /// See pub struct AutoModerationRuleTriggerMetadataForMentionSpam { /// Max 50 @@ -86,7 +88,9 @@ pub struct AutoModerationRuleTriggerMetadataForMentionSpam { pub mention_raid_protection_enabled: bool, } -#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)] +#[derive( + Serialize_repr, Deserialize_repr, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy, +)] #[repr(u8)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] /// See @@ -105,7 +109,9 @@ pub struct AutoModerationAction { pub metadata: Option>, } -#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)] +#[derive( + Serialize_repr, Deserialize_repr, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy, Hash +)] #[repr(u8)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] /// See @@ -116,7 +122,7 @@ pub enum AutoModerationActionType { Timeout = 3, } -#[derive(Serialize, Deserialize, Debug, Clone, Default)] +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)] #[serde(untagged)] /// See pub enum AutoModerationActionMetadata { @@ -127,19 +133,19 @@ pub enum AutoModerationActionMetadata { None, } -#[derive(Serialize, Deserialize, Debug, Clone, Default)] +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)] /// See pub struct AutoModerationActionMetadataForBlockMessage { pub custom_message: Option, } -#[derive(Serialize, Deserialize, Debug, Clone, Default)] +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy)] /// See pub struct AutoModerationActionMetadataForSendAlertMessage { pub channel_id: Snowflake, } -#[derive(Serialize, Deserialize, Debug, Clone, Default)] +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy)] /// See pub struct AutoModerationActionMetadataForTimeout { /// Max 2419200 diff --git a/src/types/entities/channel.rs b/src/types/entities/channel.rs index 7d000dc..622f14d 100644 --- a/src/types/entities/channel.rs +++ b/src/types/entities/channel.rs @@ -3,15 +3,15 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; -use serde_aux::prelude::deserialize_string_from_number; +use serde::{Deserialize, Deserializer, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; -use std::fmt::Debug; +use std::fmt::{Debug, Formatter}; +use std::str::FromStr; -use crate::gateway::Shared; use crate::types::{ entities::{GuildMember, User}, utils::Snowflake, + PermissionFlags, Shared, }; #[cfg(feature = "client")] @@ -25,6 +25,12 @@ use crate::gateway::Updateable; #[cfg(feature = "client")] use chorus_macros::{observe_option_vec, Composite, Updateable}; +use serde::de::{Error, Visitor}; + +#[cfg(feature = "sqlx")] +use sqlx::types::Json; + +use super::{option_arc_rwlock_ptr_eq, option_vec_arc_rwlock_ptr_eq}; #[derive(Default, Debug, Serialize, Deserialize, Clone)] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] @@ -64,7 +70,9 @@ pub struct Channel { pub managed: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] pub member: Option, + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub member_count: Option, + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub message_count: Option, pub name: Option, pub nsfw: Option, @@ -75,6 +83,7 @@ pub struct Channel { #[cfg(not(feature = "sqlx"))] #[cfg_attr(feature = "client", observe_option_vec)] pub permission_overwrites: Option>>, + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub permissions: Option, pub position: Option, pub rate_limit_per_user: Option, @@ -85,19 +94,28 @@ pub struct Channel { #[cfg_attr(feature = "sqlx", sqlx(skip))] pub thread_metadata: Option, pub topic: Option, + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub total_message_sent: Option, pub user_limit: Option, pub video_quality_mode: Option, } +#[cfg(not(tarpaulin_include))] +#[allow(clippy::nonminimal_bool)] impl PartialEq for Channel { fn eq(&self, other: &Self) -> bool { self.application_id == other.application_id + && self.applied_tags == other.applied_tags + && self.applied_tags == other.applied_tags + && self.available_tags == other.available_tags + && self.available_tags == other.available_tags && self.bitrate == other.bitrate && self.channel_type == other.channel_type && self.created_at == other.created_at && self.default_auto_archive_duration == other.default_auto_archive_duration && self.default_forum_layout == other.default_forum_layout + && self.default_reaction_emoji == other.default_reaction_emoji + && self.default_reaction_emoji == other.default_reaction_emoji && self.default_sort_order == other.default_sort_order && self.default_thread_rate_limit_per_user == other.default_thread_rate_limit_per_user && self.flags == other.flags @@ -107,16 +125,23 @@ impl PartialEq for Channel { && self.last_message_id == other.last_message_id && self.last_pin_timestamp == other.last_pin_timestamp && self.managed == other.managed + && self.member == other.member && self.member_count == other.member_count && self.message_count == other.message_count && self.name == other.name && self.nsfw == other.nsfw && self.owner_id == other.owner_id && self.parent_id == other.parent_id + && compare_permission_overwrites( + &self.permission_overwrites, + &other.permission_overwrites, + ) && self.permissions == other.permissions && self.position == other.position && self.rate_limit_per_user == other.rate_limit_per_user + && option_vec_arc_rwlock_ptr_eq(&self.recipients, &other.recipients) && self.rtc_region == other.rtc_region + && self.thread_metadata == other.thread_metadata && self.topic == other.topic && self.total_message_sent == other.total_message_sent && self.user_limit == other.user_limit @@ -124,7 +149,29 @@ impl PartialEq for Channel { } } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[cfg(not(tarpaulin_include))] +#[cfg(feature = "sqlx")] +fn compare_permission_overwrites( + a: &Option>>, + b: &Option>>, +) -> bool { + match (a, b) { + (Some(a), Some(b)) => a.encode_to_string() == b.encode_to_string(), + (None, None) => true, + _ => false, + } +} + +#[cfg(not(tarpaulin_include))] +#[cfg(not(feature = "sqlx"))] +fn compare_permission_overwrites( + a: &Option>>, + b: &Option>>, +) -> bool { + option_vec_arc_rwlock_ptr_eq(a, b) +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] /// A tag that can be applied to a thread in a [ChannelType::GuildForum] or [ChannelType::GuildMedia] channel. /// /// # Reference @@ -144,26 +191,104 @@ pub struct Tag { pub struct PermissionOverwrite { pub id: Snowflake, #[serde(rename = "type")] - #[serde(deserialize_with = "deserialize_string_from_number")] - pub overwrite_type: String, + pub overwrite_type: PermissionOverwriteType, #[serde(default)] - #[serde(deserialize_with = "deserialize_string_from_number")] - pub allow: String, + pub allow: PermissionFlags, #[serde(default)] - #[serde(deserialize_with = "deserialize_string_from_number")] - pub deny: String, + pub deny: PermissionFlags, } -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +#[derive(Debug, Serialize_repr, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)] +#[repr(u8)] +/// # Reference +/// +/// See +pub enum PermissionOverwriteType { + Role = 0, + Member = 1, +} + +impl From for PermissionOverwriteType { + fn from(v: u8) -> Self { + match v { + 0 => PermissionOverwriteType::Role, + 1 => PermissionOverwriteType::Member, + _ => unreachable!(), + } + } +} + +impl FromStr for PermissionOverwriteType { + type Err = serde::de::value::Error; + + fn from_str(s: &str) -> Result { + match s { + "role" => Ok(PermissionOverwriteType::Role), + "member" => Ok(PermissionOverwriteType::Member), + _ => Err(Self::Err::custom("invalid permission overwrite type")), + } + } +} + +struct PermissionOverwriteTypeVisitor; + +impl<'de> Visitor<'de> for PermissionOverwriteTypeVisitor { + type Value = PermissionOverwriteType; + + fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + formatter.write_str("a valid permission overwrite type") + } + + fn visit_u8(self, v: u8) -> Result + where + E: Error, + { + Ok(PermissionOverwriteType::from(v)) + } + + fn visit_u64(self, v: u64) -> Result + where + E: Error, + { + self.visit_u8(v as u8) + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + PermissionOverwriteType::from_str(v).map_err(E::custom) + } + + fn visit_string(self, v: String) -> Result + where + E: Error, + { + self.visit_str(v.as_str()) + } +} + +impl<'de> Deserialize<'de> for PermissionOverwriteType { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let val = deserializer.deserialize_any(PermissionOverwriteTypeVisitor)?; + + Ok(val) + } +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)] /// # Reference /// See pub struct ThreadMetadata { pub archived: bool, pub auto_archive_duration: i32, - pub archive_timestamp: String, + pub archive_timestamp: DateTime, pub locked: bool, pub invitable: Option, - pub create_timestamp: Option, + pub create_timestamp: Option>, } #[derive(Default, Debug, Deserialize, Serialize, Clone)] @@ -172,12 +297,23 @@ pub struct ThreadMetadata { pub struct ThreadMember { pub id: Option, pub user_id: Option, - pub join_timestamp: Option, + pub join_timestamp: Option>, pub flags: Option, pub member: Option>, } -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +#[cfg(not(tarpaulin_include))] +impl PartialEq for ThreadMember { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + && self.user_id == other.user_id + && self.join_timestamp == other.join_timestamp + && self.flags == other.flags + && option_arc_rwlock_ptr_eq(&self.member, &other.member) + } +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd)] /// Specifies the emoji to use as the default way to react to a [ChannelType::GuildForum] or [ChannelType::GuildMedia] channel post. /// /// # Reference @@ -256,3 +392,11 @@ pub enum ChannelType { // TODO: Couldn't find reference Unhandled = 255, } + +/// # Reference +/// See +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Copy, Hash, PartialOrd, Ord)] +pub struct FollowedChannel { + pub channel_id: Snowflake, + pub webhook_id: Snowflake, +} diff --git a/src/types/entities/emoji.rs b/src/types/entities/emoji.rs index e84b025..82f3e37 100644 --- a/src/types/entities/emoji.rs +++ b/src/types/entities/emoji.rs @@ -6,9 +6,9 @@ use std::fmt::Debug; use serde::{Deserialize, Serialize}; -use crate::gateway::Shared; use crate::types::entities::User; use crate::types::Snowflake; +use crate::types::{PartialEmoji, Shared}; #[cfg(feature = "client")] use crate::gateway::GatewayHandle; @@ -22,6 +22,8 @@ use crate::gateway::Updateable; #[cfg(feature = "client")] use chorus_macros::{Composite, Updateable}; +use super::option_arc_rwlock_ptr_eq; + #[derive(Debug, Clone, Deserialize, Serialize, Default)] #[cfg_attr(feature = "client", derive(Updateable, Composite))] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] @@ -42,27 +44,33 @@ pub struct Emoji { pub available: Option, } -impl std::hash::Hash for Emoji { - fn hash(&self, state: &mut H) { - self.id.hash(state); - self.name.hash(state); - self.roles.hash(state); - self.roles.hash(state); - self.require_colons.hash(state); - self.managed.hash(state); - self.animated.hash(state); - self.available.hash(state); +#[cfg(not(tarpaulin_include))] +#[allow(clippy::nonminimal_bool)] +impl PartialEq for Emoji { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + && self.name == other.name + && self.roles == other.roles + && self.roles == other.roles + && option_arc_rwlock_ptr_eq(&self.user, &other.user) + && self.require_colons == other.require_colons + && self.managed == other.managed + && self.animated == other.animated + && self.available == other.available } } -impl PartialEq for Emoji { - fn eq(&self, other: &Self) -> bool { - !(self.id != other.id - || self.name != other.name - || self.roles != other.roles - || self.require_colons != other.require_colons - || self.managed != other.managed - || self.animated != other.animated - || self.available != other.available) +impl From for Emoji { + fn from(value: PartialEmoji) -> Self { + Self { + id: value.id.unwrap_or_default(), // TODO: Make this method an impl to TryFrom<> instead + name: Some(value.name), + roles: None, + user: None, + require_colons: Some(value.animated), + managed: None, + animated: Some(value.animated), + available: None, + } } } diff --git a/src/types/entities/guild.rs b/src/types/entities/guild.rs index 52ec5e5..cca9b94 100644 --- a/src/types/entities/guild.rs +++ b/src/types/entities/guild.rs @@ -3,27 +3,28 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::fmt::Debug; +use std::hash::Hash; use bitflags::bitflags; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; -use crate::gateway::Shared; use crate::types::types::guild_configuration::GuildFeaturesList; +use crate::types::Shared; use crate::types::{ entities::{Channel, Emoji, RoleObject, Sticker, User, VoiceState, Webhook}, interfaces::WelcomeScreenObject, utils::Snowflake, }; -use super::PublicUser; +use super::{option_arc_rwlock_ptr_eq, vec_arc_rwlock_ptr_eq, PublicUser}; #[cfg(feature = "client")] use crate::gateway::Updateable; #[cfg(feature = "client")] -use chorus_macros::{observe_option_vec, observe_vec, Composite, Updateable}; +use chorus_macros::{observe_vec, Composite, Updateable}; #[cfg(feature = "client")] use crate::types::Composite; @@ -46,10 +47,12 @@ pub struct Guild { pub approximate_presence_count: Option, pub banner: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub bans: Option>, + #[serde(default)] + pub bans: Vec, #[cfg_attr(feature = "sqlx", sqlx(skip))] - #[cfg_attr(feature = "client", observe_option_vec)] - pub channels: Option>>, + #[cfg_attr(feature = "client", observe_vec)] + #[serde(default)] + pub channels: Vec>, pub default_message_notifications: Option, pub description: Option, pub discovery_splash: Option, @@ -57,17 +60,19 @@ pub struct Guild { #[cfg_attr(feature = "client", observe_vec)] #[serde(default)] pub emojis: Vec>, - pub explicit_content_filter: Option, + pub explicit_content_filter: Option, //#[cfg_attr(feature = "sqlx", sqlx(try_from = "String"))] - pub features: Option, + #[serde(default)] + pub features: GuildFeaturesList, pub icon: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] pub icon_hash: Option, pub id: Snowflake, #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub invites: Option>, + #[serde(default)] + pub invites: Vec, #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub joined_at: Option, + pub joined_at: Option>, pub large: Option, pub max_members: Option, pub max_presences: Option, @@ -91,86 +96,39 @@ pub struct Guild { pub public_updates_channel_id: Option, pub region: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] - #[cfg_attr(feature = "client", observe_option_vec)] - pub roles: Option>>, + #[cfg_attr(feature = "client", observe_vec)] + #[serde(default)] + pub roles: Vec>, #[cfg_attr(feature = "sqlx", sqlx(skip))] pub rules_channel: Option, pub rules_channel_id: Option, pub splash: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub stickers: Option>, - pub system_channel_flags: Option, + #[serde(default)] + pub stickers: Vec, + pub system_channel_flags: Option, pub system_channel_id: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] pub vanity_url_code: Option, pub verification_level: Option, + #[serde(default)] #[cfg_attr(feature = "sqlx", sqlx(skip))] - #[cfg_attr(feature = "client", observe_option_vec)] - pub voice_states: Option>>, + #[cfg_attr(feature = "client", observe_vec)] + pub voice_states: Vec>, + #[serde(default)] #[cfg_attr(feature = "sqlx", sqlx(skip))] - #[cfg_attr(feature = "client", observe_option_vec)] - pub webhooks: Option>>, + #[cfg_attr(feature = "client", observe_vec)] + pub webhooks: Vec>, #[cfg(feature = "sqlx")] - pub welcome_screen: Option>, + pub welcome_screen: sqlx::types::Json>, #[cfg(not(feature = "sqlx"))] pub welcome_screen: Option, pub widget_channel_id: Option, pub widget_enabled: Option, } -impl std::hash::Hash for Guild { - fn hash(&self, state: &mut H) { - self.afk_channel_id.hash(state); - self.afk_timeout.hash(state); - self.application_id.hash(state); - self.approximate_member_count.hash(state); - self.approximate_presence_count.hash(state); - self.banner.hash(state); - self.bans.hash(state); - self.default_message_notifications.hash(state); - self.description.hash(state); - self.discovery_splash.hash(state); - self.explicit_content_filter.hash(state); - self.features.hash(state); - self.icon.hash(state); - self.icon_hash.hash(state); - self.id.hash(state); - self.invites.hash(state); - self.joined_at.hash(state); - self.large.hash(state); - self.max_members.hash(state); - self.max_presences.hash(state); - self.max_stage_video_channel_users.hash(state); - self.max_video_channel_users.hash(state); - self.mfa_level.hash(state); - self.name.hash(state); - self.nsfw_level.hash(state); - self.owner.hash(state); - self.owner_id.hash(state); - self.permissions.hash(state); - self.preferred_locale.hash(state); - self.premium_progress_bar_enabled.hash(state); - self.premium_subscription_count.hash(state); - self.premium_tier.hash(state); - self.primary_category_id.hash(state); - self.public_updates_channel_id.hash(state); - self.region.hash(state); - self.rules_channel.hash(state); - self.rules_channel_id.hash(state); - self.splash.hash(state); - self.stickers.hash(state); - self.system_channel_flags.hash(state); - self.system_channel_id.hash(state); - self.vanity_url_code.hash(state); - self.verification_level.hash(state); - self.welcome_screen.hash(state); - self.welcome_screen.hash(state); - self.widget_channel_id.hash(state); - self.widget_enabled.hash(state); - } -} - -impl std::cmp::PartialEq for Guild { +#[cfg(not(tarpaulin_include))] +impl PartialEq for Guild { fn eq(&self, other: &Self) -> bool { self.afk_channel_id == other.afk_channel_id && self.afk_timeout == other.afk_timeout @@ -179,14 +137,17 @@ impl std::cmp::PartialEq for Guild { && self.approximate_presence_count == other.approximate_presence_count && self.banner == other.banner && self.bans == other.bans + && vec_arc_rwlock_ptr_eq(&self.channels, &other.channels) && self.default_message_notifications == other.default_message_notifications && self.description == other.description && self.discovery_splash == other.discovery_splash + && vec_arc_rwlock_ptr_eq(&self.emojis, &other.emojis) && self.explicit_content_filter == other.explicit_content_filter && self.features == other.features && self.icon == other.icon && self.icon_hash == other.icon_hash && self.id == other.id + && self.invites == other.invites && self.joined_at == other.joined_at && self.large == other.large && self.max_members == other.max_members @@ -206,6 +167,7 @@ impl std::cmp::PartialEq for Guild { && self.primary_category_id == other.primary_category_id && self.public_updates_channel_id == other.public_updates_channel_id && self.region == other.region + && vec_arc_rwlock_ptr_eq(&self.roles, &other.roles) && self.rules_channel == other.rules_channel && self.rules_channel_id == other.rules_channel_id && self.splash == other.splash @@ -214,6 +176,8 @@ impl std::cmp::PartialEq for Guild { && self.system_channel_id == other.system_channel_id && self.vanity_url_code == other.vanity_url_code && self.verification_level == other.verification_level + && vec_arc_rwlock_ptr_eq(&self.voice_states, &other.voice_states) + && vec_arc_rwlock_ptr_eq(&self.webhooks, &other.webhooks) && self.welcome_screen == other.welcome_screen && self.welcome_screen == other.welcome_screen && self.widget_channel_id == other.widget_channel_id @@ -225,6 +189,7 @@ impl std::cmp::PartialEq for Guild { #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] pub struct GuildBan { + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub user: PublicUser, pub reason: Option, } @@ -252,32 +217,41 @@ pub struct GuildInvite { pub vanity_url: Option, } -impl std::hash::Hash for GuildInvite { - fn hash(&self, state: &mut H) { - self.code.hash(state); - self.temporary.hash(state); - self.uses.hash(state); - self.max_uses.hash(state); - self.max_age.hash(state); - self.created_at.hash(state); - self.expires_at.hash(state); - self.guild_id.hash(state); - self.channel_id.hash(state); - self.inviter_id.hash(state); - self.target_user_id.hash(state); - self.target_user.hash(state); - self.target_user_type.hash(state); - self.vanity_url.hash(state); +#[cfg(not(tarpaulin_include))] +impl PartialEq for GuildInvite { + fn eq(&self, other: &Self) -> bool { + self.code == other.code + && self.temporary == other.temporary + && self.uses == other.uses + && self.max_uses == other.max_uses + && self.max_age == other.max_age + && self.created_at == other.created_at + && self.expires_at == other.expires_at + && self.guild_id == other.guild_id + && option_arc_rwlock_ptr_eq(&self.guild, &other.guild) + && self.channel_id == other.channel_id + && option_arc_rwlock_ptr_eq(&self.channel, &other.channel) + && self.inviter_id == other.inviter_id + && option_arc_rwlock_ptr_eq(&self.inviter, &other.inviter) + && self.target_user_id == other.target_user_id + && self.target_user == other.target_user + && self.target_user_type == other.target_user_type + && self.vanity_url == other.vanity_url } } -#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Hash)] +#[derive( + Serialize, Deserialize, Debug, Default, Clone, PartialEq, Hash, Eq, PartialOrd, Ord, Copy, +)] pub struct UnavailableGuild { pub id: Snowflake, - pub unavailable: bool, + pub unavailable: Option, + pub geo_restricted: Option, } -#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)] +#[derive( + Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, +)] pub struct GuildCreateResponse { pub id: Snowflake, } @@ -303,7 +277,29 @@ pub struct GuildScheduledEvent { pub image: Option, } -#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone)] +#[cfg(not(tarpaulin_include))] +impl PartialEq for GuildScheduledEvent { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + && self.guild_id == other.guild_id + && self.channel_id == other.channel_id + && self.creator_id == other.creator_id + && self.name == other.name + && self.description == other.description + && self.scheduled_start_time == other.scheduled_start_time + && self.scheduled_end_time == other.scheduled_end_time + && self.privacy_level == other.privacy_level + && self.status == other.status + && self.entity_type == other.entity_type + && self.entity_id == other.entity_id + && self.entity_metadata == other.entity_metadata + && option_arc_rwlock_ptr_eq(&self.creator, &other.creator) + && self.user_count == other.user_count + && self.image == other.image + } +} + +#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, PartialEq, Copy)] #[repr(u8)] /// See pub enum GuildScheduledEventPrivacyLevel { @@ -311,7 +307,7 @@ pub enum GuildScheduledEventPrivacyLevel { GuildOnly = 2, } -#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone)] +#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, PartialEq, Copy)] #[repr(u8)] /// See pub enum GuildScheduledEventStatus { @@ -322,7 +318,19 @@ pub enum GuildScheduledEventStatus { Canceled = 4, } -#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone)] +#[derive( + Serialize_repr, + Deserialize_repr, + Debug, + Default, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Copy, + Hash, +)] #[repr(u8)] /// See pub enum GuildScheduledEventEntityType { @@ -332,7 +340,7 @@ pub enum GuildScheduledEventEntityType { External = 3, } -#[derive(Serialize, Deserialize, Debug, Default, Clone)] +#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] /// See pub struct GuildScheduledEventEntityMetadata { pub location: Option, @@ -347,7 +355,19 @@ pub struct VoiceRegion { custom: bool, } -#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, Eq, PartialEq, Hash, Copy)] +#[derive( + Serialize_repr, + Deserialize_repr, + Debug, + Default, + Clone, + Eq, + PartialEq, + Hash, + Copy, + PartialOrd, + Ord, +)] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[repr(u8)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] @@ -358,7 +378,19 @@ pub enum MessageNotificationLevel { OnlyMentions = 1, } -#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, Eq, PartialEq, Hash, Copy)] +#[derive( + Serialize_repr, + Deserialize_repr, + Debug, + Default, + Clone, + Eq, + PartialEq, + Hash, + Copy, + PartialOrd, + Ord, +)] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[repr(u8)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] @@ -370,7 +402,19 @@ pub enum ExplicitContentFilterLevel { AllMembers = 2, } -#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, Eq, PartialEq, Hash, Copy)] +#[derive( + Serialize_repr, + Deserialize_repr, + Debug, + Default, + Clone, + Eq, + PartialEq, + Hash, + Copy, + PartialOrd, + Ord, +)] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[repr(u8)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] @@ -384,7 +428,19 @@ pub enum VerificationLevel { VeryHigh = 4, } -#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, Eq, PartialEq, Hash, Copy)] +#[derive( + Serialize_repr, + Deserialize_repr, + Debug, + Default, + Clone, + Eq, + PartialEq, + Hash, + Copy, + PartialOrd, + Ord, +)] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[repr(u8)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] @@ -395,7 +451,19 @@ pub enum MFALevel { Elevated = 1, } -#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, Eq, PartialEq, Hash, Copy)] +#[derive( + Serialize_repr, + Deserialize_repr, + Debug, + Default, + Clone, + Eq, + PartialEq, + Hash, + Copy, + PartialOrd, + Ord, +)] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[repr(u8)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] @@ -408,7 +476,19 @@ pub enum NSFWLevel { AgeRestricted = 3, } -#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, Eq, PartialEq, Hash, Copy)] +#[derive( + Serialize_repr, + Deserialize_repr, + Debug, + Default, + Clone, + Eq, + PartialEq, + Hash, + Copy, + PartialOrd, + Ord, +)] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[repr(u8)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] @@ -422,7 +502,8 @@ pub enum PremiumTier { } bitflags! { - #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, chorus_macros::SerdeBitFlags)] + #[cfg_attr(feature = "sqlx", derive(chorus_macros::SqlxBitFlags))] /// # Reference /// See pub struct SystemChannelFlags: u64 { diff --git a/src/types/entities/guild_member.rs b/src/types/entities/guild_member.rs index 14414c5..87d94a7 100644 --- a/src/types/entities/guild_member.rs +++ b/src/types/entities/guild_member.rs @@ -2,27 +2,52 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use crate::gateway::Shared; use crate::types::{entities::PublicUser, Snowflake}; +use crate::types::{GuildMemberFlags, PermissionFlags, Shared}; + +use super::option_arc_rwlock_ptr_eq; #[derive(Debug, Deserialize, Default, Serialize, Clone)] +#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] /// Represents a participating user in a guild. /// /// # Reference /// See pub struct GuildMember { + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub user: Option>, pub nick: Option, pub avatar: Option, + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub roles: Vec, - pub joined_at: String, - pub premium_since: Option, + pub joined_at: DateTime, + pub premium_since: Option>, pub deaf: bool, pub mute: bool, - pub flags: Option, + pub flags: Option, pub pending: Option, - pub permissions: Option, - pub communication_disabled_until: Option, + #[serde(default)] + pub permissions: PermissionFlags, + pub communication_disabled_until: Option>, +} + +#[cfg(not(tarpaulin_include))] +impl PartialEq for GuildMember { + fn eq(&self, other: &Self) -> bool { + self.nick == other.nick + && self.avatar == other.avatar + && self.roles == other.roles + && self.joined_at == other.joined_at + && self.premium_since == other.premium_since + && self.deaf == other.deaf + && self.mute == other.mute + && self.flags == other.flags + && self.pending == other.pending + && self.permissions == other.permissions + && self.communication_disabled_until == other.communication_disabled_until + && option_arc_rwlock_ptr_eq(&self.user, &other.user) + } } diff --git a/src/types/entities/integration.rs b/src/types/entities/integration.rs index 97d21c3..16cd991 100644 --- a/src/types/entities/integration.rs +++ b/src/types/entities/integration.rs @@ -5,10 +5,10 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use crate::gateway::Shared; use crate::types::{ entities::{Application, User}, utils::Snowflake, + Shared, }; #[derive(Default, Debug, Deserialize, Serialize, Clone)] @@ -18,7 +18,7 @@ pub struct Integration { pub id: Snowflake, pub name: String, #[serde(rename = "type")] - pub integration_type: String, + pub integration_type: IntegrationType, pub enabled: bool, pub syncing: Option, pub role_id: Option, @@ -43,3 +43,17 @@ pub struct IntegrationAccount { pub id: String, pub name: String, } + +#[derive( + Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Hash, +)] +#[serde(rename_all = "snake_case")] +#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] +#[cfg_attr(feature = "sqlx", sqlx(rename_all = "snake_case"))] +pub enum IntegrationType { + #[default] + Twitch, + Youtube, + Discord, + GuildSubscription, +} diff --git a/src/types/entities/invite.rs b/src/types/entities/invite.rs index 720203a..0160ac9 100644 --- a/src/types/entities/invite.rs +++ b/src/types/entities/invite.rs @@ -5,37 +5,49 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use crate::gateway::Shared; -use crate::types::{Snowflake, WelcomeScreenObject}; +use crate::types::{Snowflake, WelcomeScreenObject, Shared, InviteFlags, InviteType, InviteTargetType, Guild, VerificationLevel}; +use crate::types::types::guild_configuration::GuildFeaturesList; use super::guild::GuildScheduledEvent; use super::{Application, Channel, GuildMember, NSFWLevel, User}; /// Represents a code that when used, adds a user to a guild or group DM channel, or creates a relationship between two users. /// See -#[derive(Debug, Serialize, Deserialize)] +#[derive(Default, Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] pub struct Invite { + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub approximate_member_count: Option, + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub approximate_presence_count: Option, + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub channel: Option, pub code: String, pub created_at: Option>, pub expires_at: Option>, - pub flags: Option, + pub flags: Option, + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub guild: Option, pub guild_id: Option, + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub guild_scheduled_event: Option>, #[serde(rename = "type")] - pub invite_type: Option, + #[cfg_attr(feature = "sqlx", sqlx(rename = "type"))] + pub invite_type: Option, + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub inviter: Option, - pub max_age: Option, - pub max_uses: Option, + pub max_age: Option, + pub max_uses: Option, + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub stage_instance: Option, + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub target_application: Option, - pub target_type: Option, + #[cfg_attr(feature = "sqlx", sqlx(rename = "target_user_type"))] + pub target_type: Option, + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub target_user: Option, pub temporary: Option, - pub uses: Option, + pub uses: Option, } /// The guild an invite is for. @@ -46,8 +58,8 @@ pub struct InviteGuild { pub name: String, pub icon: Option, pub splash: Option, - pub verification_level: i32, - pub features: Vec, + pub verification_level: VerificationLevel, + pub features: GuildFeaturesList, pub vanity_url_code: Option, pub description: Option, pub banner: Option, @@ -59,6 +71,29 @@ pub struct InviteGuild { pub welcome_screen: Option, } +impl From for InviteGuild { + fn from(value: Guild) -> Self { + Self { + id: value.id, + name: value.name.unwrap_or_default(), + icon: value.icon, + splash: value.splash, + verification_level: value.verification_level.unwrap_or_default(), + features: value.features, + vanity_url_code: value.vanity_url_code, + description: value.description, + banner: value.banner, + premium_subscription_count: value.premium_subscription_count, + nsfw_deprecated: None, + nsfw_level: value.nsfw_level.unwrap_or_default(), + #[cfg(feature = "sqlx")] + welcome_screen: value.welcome_screen.0, + #[cfg(not(feature = "sqlx"))] + welcome_screen: value.welcome_screen, + } + } +} + /// See #[derive(Debug, Serialize, Deserialize)] pub struct InviteStageInstance { diff --git a/src/types/entities/message.rs b/src/types/entities/message.rs index d764243..dbea3d5 100644 --- a/src/types/entities/message.rs +++ b/src/types/entities/message.rs @@ -2,17 +2,22 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +use bitflags::bitflags; +use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; -use crate::gateway::Shared; use crate::types::{ entities::{ Application, Attachment, Channel, Emoji, GuildMember, PublicUser, RoleSubscriptionData, Sticker, StickerItem, User, }, utils::Snowflake, + Shared, }; +use super::option_arc_rwlock_ptr_eq; + #[derive(Debug, Serialize, Deserialize, Default, Clone)] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] /// Represents a message sent in a channel. @@ -25,8 +30,8 @@ pub struct Message { #[cfg_attr(feature = "sqlx", sqlx(skip))] pub author: Option, pub content: Option, - pub timestamp: String, - pub edited_timestamp: Option, + pub timestamp: DateTime, + pub edited_timestamp: Option>, pub tts: Option, pub mention_everyone: bool, #[cfg_attr(feature = "sqlx", sqlx(skip))] @@ -38,7 +43,7 @@ pub struct Message { #[cfg_attr(feature = "sqlx", sqlx(skip))] pub attachments: Option>, #[cfg(feature = "sqlx")] - pub embeds: Vec>, + pub embeds: sqlx::types::Json>, #[cfg(not(feature = "sqlx"))] pub embeds: Option>, #[cfg(feature = "sqlx")] @@ -49,7 +54,7 @@ pub struct Message { pub pinned: bool, pub webhook_id: Option, #[serde(rename = "type")] - pub message_type: i32, + pub message_type: MessageType, #[cfg(feature = "sqlx")] pub activity: Option>, #[cfg(not(feature = "sqlx"))] @@ -61,17 +66,26 @@ pub struct Message { pub message_reference: Option>, #[cfg(not(feature = "sqlx"))] pub message_reference: Option, - pub flags: Option, + pub flags: Option, + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub referenced_message: Option>, + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub interaction: Option, + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub thread: Option, + #[cfg(feature = "sqlx")] + pub components: Option>>, + #[cfg(not(feature = "sqlx"))] pub components: Option>, + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub sticker_items: Option>, + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub stickers: Option>, - pub position: Option, + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub role_subscription_data: Option, } +#[cfg(not(tarpaulin_include))] impl PartialEq for Message { fn eq(&self, other: &Self) -> bool { self.id == other.id @@ -88,35 +102,50 @@ impl PartialEq for Message { && self.attachments == other.attachments && self.embeds == other.embeds && self.embeds == other.embeds + && self.reactions == other.reactions + && self.reactions == other.reactions && self.nonce == other.nonce && self.pinned == other.pinned && self.webhook_id == other.webhook_id && self.message_type == other.message_type && self.activity == other.activity && self.activity == other.activity + && self.application == other.application && self.application_id == other.application_id && self.message_reference == other.message_reference && self.message_reference == other.message_reference && self.flags == other.flags && self.referenced_message == other.referenced_message + && self.interaction == other.interaction && self.thread == other.thread && self.components == other.components + && self.components == other.components && self.sticker_items == other.sticker_items - && self.position == other.position + && self.stickers == other.stickers && self.role_subscription_data == other.role_subscription_data } } -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Eq, Ord, PartialOrd)] +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Eq, Ord, PartialOrd, Copy)] /// # Reference /// See pub struct MessageReference { + #[serde(rename = "type")] + pub reference_type: MessageReferenceType, pub message_id: Snowflake, pub channel_id: Snowflake, pub guild_id: Option, pub fail_if_not_exists: Option, } +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Eq, Ord, PartialOrd, Copy)] +pub enum MessageReferenceType { + /// A standard reference used by replies and system messages + Default = 0, + /// A reference used to point to a message at a point in time + Forward = 1, +} + #[derive(Debug, Clone, Deserialize, Serialize)] pub struct MessageInteraction { pub id: Snowflake, @@ -127,7 +156,18 @@ pub struct MessageInteraction { pub member: Option>, } -#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, Eq, PartialOrd, Ord)] +#[cfg(not(tarpaulin_include))] +impl PartialEq for MessageInteraction { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + && self.interaction_type == other.interaction_type + && self.name == other.name + && self.user == other.user + && option_arc_rwlock_ptr_eq(&self.member, &other.member) + } +} + +#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, Eq, PartialOrd, Ord, Hash)] pub struct AllowedMention { parse: Vec, roles: Vec, @@ -135,7 +175,7 @@ pub struct AllowedMention { replied_user: bool, } -#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, Eq, PartialOrd, Ord)] +#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, Eq, PartialOrd, Ord, Hash)] #[serde(rename_all = "snake_case")] pub enum AllowedMentionType { Roles, @@ -152,11 +192,11 @@ pub struct ChannelMention { name: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Hash, Ord)] pub struct Embed { title: Option, #[serde(rename = "type")] - embed_type: Option, + embed_type: Option, description: Option, url: Option, timestamp: Option, @@ -170,14 +210,32 @@ pub struct Embed { fields: Option>, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[serde(rename_all = "snake_case")] +pub enum EmbedType { + #[deprecated] + ApplicationNews, + Article, + AutoModerationMessage, + AutoModerationNotification, + Gift, + #[serde(rename = "gifv")] + GifVideo, + Image, + Link, + PostPreview, + Rich, + Video, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct EmbedFooter { text: String, icon_url: Option, proxy_icon_url: Option, } -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord, Hash)] pub struct EmbedImage { url: String, proxy_url: String, @@ -185,7 +243,7 @@ pub struct EmbedImage { width: Option, } -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord, Hash)] pub struct EmbedThumbnail { url: String, proxy_url: Option, @@ -193,7 +251,7 @@ pub struct EmbedThumbnail { width: Option, } -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord, Hash)] struct EmbedVideo { url: Option, proxy_url: Option, @@ -201,13 +259,13 @@ struct EmbedVideo { width: Option, } -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord, Hash)] pub struct EmbedProvider { name: Option, url: Option, } -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord, Hash)] pub struct EmbedAuthor { name: String, url: Option, @@ -215,7 +273,7 @@ pub struct EmbedAuthor { proxy_icon_url: Option, } -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord, Hash)] pub struct EmbedField { name: String, value: String, @@ -226,10 +284,15 @@ pub struct EmbedField { pub struct Reaction { pub count: u32, pub burst_count: u32, + #[serde(default)] pub me: bool, + #[serde(default)] pub burst_me: bool, pub burst_colors: Vec, pub emoji: Emoji, + #[cfg(feature = "sqlx")] + #[serde(skip)] + pub user_ids: Vec, } #[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, Eq, PartialOrd, Ord)] @@ -252,3 +315,157 @@ pub struct MessageActivity { pub activity_type: i64, pub party_id: Option, } + +#[derive( + Debug, Default, PartialEq, Clone, Copy, Serialize_repr, Deserialize_repr, Eq, PartialOrd, Ord, +)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +#[repr(u8)] +#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] +/// # Reference +/// See +pub enum MessageType { + /// A default message + #[default] + Default = 0, + /// A message sent when a user is added to a group DM or thread + RecipientAdd = 1, + /// A message sent when a user is removed from a group DM or thread + RecipientRemove = 2, + /// A message sent when a user creates a call in a private channel + Call = 3, + /// A message sent when a group DM or thread's name is changed + ChannelNameChange = 4, + /// A message sent when a group DM's icon is changed + ChannelIconChange = 5, + /// A message sent when a message is pinned in a channel + ChannelPinnedMessage = 6, + /// A message sent when a user joins a guild + GuildMemberJoin = 7, + /// A message sent when a user subscribes to (boosts) a guild + UserPremiumGuildSubscription = 8, + /// A message sent when a user subscribes to (boosts) a guild to tier 1 + UserPremiumGuildSubscriptionTier1 = 9, + /// A message sent when a user subscribes to (boosts) a guild to tier 2 + UserPremiumGuildSubscriptionTier2 = 10, + /// A message sent when a user subscribes to (boosts) a guild to tier 3 + UserPremiumGuildSubscriptionTier3 = 11, + /// A message sent when a news channel is followed + ChannelFollowAdd = 12, + /// A message sent when a user starts streaming in a guild (deprecated) + #[deprecated] + GuildStream = 13, + /// A message sent when a guild is disqualified from discovery + GuildDiscoveryDisqualified = 14, + /// A message sent when a guild requalifies for discovery + GuildDiscoveryRequalified = 15, + /// A message sent when a guild has failed discovery requirements for a week + GuildDiscoveryGracePeriodInitial = 16, + /// A message sent when a guild has failed discovery requirements for 3 weeks + GuildDiscoveryGracePeriodFinal = 17, + /// A message sent when a thread is created + ThreadCreated = 18, + /// A message sent when a user replies to a message + Reply = 19, + /// A message sent when a user uses a slash command + #[serde(rename = "CHAT_INPUT_COMMAND")] + ApplicationCommand = 20, + /// A message sent when a thread starter message is added to a thread + ThreadStarterMessage = 21, + /// A message sent to remind users to invite friends to a guild + GuildInviteReminder = 22, + /// A message sent when a user uses a context menu command + ContextMenuCommand = 23, + /// A message sent when auto moderation takes an action + AutoModerationAction = 24, + /// A message sent when a user purchases or renews a role subscription + RoleSubscriptionPurchase = 25, + /// A message sent when a user is upsold to a premium interaction + InteractionPremiumUpsell = 26, + /// A message sent when a stage channel starts + StageStart = 27, + /// A message sent when a stage channel ends + StageEnd = 28, + /// A message sent when a user starts speaking in a stage channel + StageSpeaker = 29, + /// A message sent when a user raises their hand in a stage channel + StageRaiseHand = 30, + /// A message sent when a stage channel's topic is changed + StageTopic = 31, + /// A message sent when a user purchases an application premium subscription + GuildApplicationPremiumSubscription = 32, + /// A message sent when a user adds an application to group DM + PrivateChannelIntegrationAdded = 33, + /// A message sent when a user removed an application from a group DM + PrivateChannelIntegrationRemoved = 34, + /// A message sent when a user gifts a premium (Nitro) referral + PremiumReferral = 35, + /// A message sent when a user enabled lockdown for the guild + GuildIncidentAlertModeEnabled = 36, + /// A message sent when a user disables lockdown for the guild + GuildIncidentAlertModeDisabled = 37, + /// A message sent when a user reports a raid for the guild + GuildIncidentReportRaid = 38, + /// A message sent when a user reports a false alarm for the guild + GuildIncidentReportFalseAlarm = 39, + /// A message sent when no one sends a message in the current channel for 1 hour + GuildDeadchatRevivePrompt = 40, + /// A message sent when a user buys another user a gift + CustomGift = 41, + GuildGamingStatsPrompt = 42, + /// A message sent when a user purchases a guild product + PurchaseNotification = 44, +} + +bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, chorus_macros::SerdeBitFlags)] + #[cfg_attr(feature = "sqlx", derive(chorus_macros::SqlxBitFlags))] + /// # Reference + /// See + pub struct MessageFlags: u64 { + /// This message has been published to subscribed channels (via Channel Following) + const CROSSPOSTED = 1 << 0; + /// This message originated from a message in another channel (via Channel Following) + const IS_CROSSPOST = 1 << 1; + /// Embeds will not be included when serializing this message + const SUPPRESS_EMBEDS = 1 << 2; + /// The source message for this crosspost has been deleted (via Channel Following) + const SOURCE_MESSAGE_DELETED = 1 << 3; + /// This message came from the urgent message system + const URGENT = 1 << 4; + /// This message has an associated thread, with the same ID as the message + const HAS_THREAD = 1 << 5; + /// This message is only visible to the user who invoked the interaction + const EPHEMERAL = 1 << 6; + /// This message is an interaction response and the bot is "thinking" + const LOADING = 1 << 7; + /// Some roles were not mentioned and added to the thread + const FAILED_TO_MENTION_SOME_ROLES_IN_THREAD = 1 << 8; + /// This message contains a link that impersonates Discord + const SHOULD_SHOW_LINK_NOT_DISCORD_WARNING = 1 << 10; + /// This message will not trigger push and desktop notifications + const SUPPRESS_NOTIFICATIONS = 1 << 12; + /// This message's audio attachments are rendered as voice messages + const VOICE_MESSAGE = 1 << 13; + /// This message has a forwarded message snapshot attached + const HAS_SNAPSHOT = 1 << 14; + } +} + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct PartialEmoji { + #[serde(default)] + pub id: Option, + pub name: String, + #[serde(default)] + pub animated: bool, +} + +#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, PartialOrd, Ord, Eq, Hash)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] +#[repr(u8)] +pub enum ReactionType { + Normal = 0, + Burst = 1, // The dreaded super reactions +} diff --git a/src/types/entities/mod.rs b/src/types/entities/mod.rs index 1400d0e..2fec4ca 100644 --- a/src/types/entities/mod.rs +++ b/src/types/entities/mod.rs @@ -27,7 +27,10 @@ pub use user_settings::*; pub use voice_state::*; pub use webhook::*; -use crate::gateway::Shared; +use crate::types::Shared; +#[cfg(feature = "client")] +use std::sync::{Arc, RwLock}; + #[cfg(feature = "client")] use crate::gateway::Updateable; @@ -39,8 +42,6 @@ use async_trait::async_trait; #[cfg(feature = "client")] use std::fmt::Debug; -#[cfg(feature = "client")] -use std::sync::{Arc, RwLock}; mod application; mod attachment; @@ -134,8 +135,53 @@ pub trait IntoShared { fn into_shared(self) -> Shared; } +#[cfg(feature = "client")] impl IntoShared for T { fn into_shared(self) -> Shared { Arc::new(RwLock::new(self)) } } + +/// Internal function to compare two `Shared`s by comparing their pointers. +#[cfg_attr(not(feature = "client"), allow(unused_variables))] +pub(crate) fn arc_rwlock_ptr_eq(a: &Shared, b: &Shared) -> bool { + #[cfg(feature = "client")] + { + Shared::ptr_eq(a, b) + } + #[cfg(not(feature = "client"))] + { + true + } +} + +/// Internal function to compare two `Vec>`s by comparing their pointers. +pub(crate) fn vec_arc_rwlock_ptr_eq(a: &[Shared], b: &[Shared]) -> bool { + for (a, b) in a.iter().zip(b.iter()) { + if !arc_rwlock_ptr_eq(a, b) { + return false; + } + } + true +} + +/// Internal function to compare two `Option>`s by comparing their pointers. +pub(crate) fn option_arc_rwlock_ptr_eq(a: &Option>, b: &Option>) -> bool { + match (a, b) { + (Some(a), Some(b)) => arc_rwlock_ptr_eq(a, b), + (None, None) => true, + _ => false, + } +} + +/// Internal function to compare two `Option>>`s by comparing their pointers. +pub(crate) fn option_vec_arc_rwlock_ptr_eq( + a: &Option>>, + b: &Option>>, +) -> bool { + match (a, b) { + (Some(a), Some(b)) => vec_arc_rwlock_ptr_eq(a, b), + (None, None) => true, + _ => false, + } +} diff --git a/src/types/entities/ratelimits.rs b/src/types/entities/ratelimits.rs index 1823e76..dc084cf 100644 --- a/src/types/entities/ratelimits.rs +++ b/src/types/entities/ratelimits.rs @@ -11,7 +11,9 @@ use crate::types::Snowflake; /// The different types of ratelimits that can be applied to a request. Includes "Baseline"-variants /// for when the Snowflake is not yet known. /// See for more information. -#[derive(Clone, Copy, Eq, PartialEq, Debug, Default, Hash, Serialize, Deserialize)] +#[derive( + Clone, Copy, Eq, PartialEq, Debug, Default, Hash, Serialize, Deserialize, PartialOrd, Ord, +)] pub enum LimitType { AuthRegister, AuthLogin, @@ -29,7 +31,7 @@ pub enum LimitType { /// A struct that represents the current ratelimits, either instance-wide or user-wide. /// See for more information. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Copy, PartialOrd, Ord)] pub struct Limit { pub bucket: LimitType, pub limit: u64, diff --git a/src/types/entities/relationship.rs b/src/types/entities/relationship.rs index a568256..e3276db 100644 --- a/src/types/entities/relationship.rs +++ b/src/types/entities/relationship.rs @@ -6,10 +6,9 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; -use crate::gateway::Shared; -use crate::types::Snowflake; +use crate::types::{Shared, Snowflake}; -use super::PublicUser; +use super::{arc_rwlock_ptr_eq, PublicUser}; #[derive(Debug, Deserialize, Serialize, Clone, Default)] /// See @@ -22,16 +21,30 @@ pub struct Relationship { pub since: Option>, } +#[cfg(not(tarpaulin_include))] impl PartialEq for Relationship { fn eq(&self, other: &Self) -> bool { self.id == other.id && self.relationship_type == other.relationship_type - && self.since == other.since && self.nickname == other.nickname + && arc_rwlock_ptr_eq(&self.user, &other.user) + && self.since == other.since } } -#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default, Eq, PartialEq)] +#[derive( + Serialize_repr, + Deserialize_repr, + Debug, + Clone, + Default, + Eq, + PartialEq, + PartialOrd, + Ord, + Copy, + Hash, +)] #[repr(u8)] /// See pub enum RelationshipType { diff --git a/src/types/entities/role.rs b/src/types/entities/role.rs index 1b5e91e..20d2fcf 100644 --- a/src/types/entities/role.rs +++ b/src/types/entities/role.rs @@ -4,7 +4,7 @@ use bitflags::bitflags; use serde::{Deserialize, Serialize}; -use serde_aux::prelude::{deserialize_option_number_from_string, deserialize_string_from_number}; +use serde_aux::prelude::deserialize_option_number_from_string; use std::fmt::Debug; use crate::types::utils::Snowflake; @@ -34,8 +34,7 @@ pub struct RoleObject { pub unicode_emoji: Option, pub position: u16, #[serde(default)] - #[serde(deserialize_with = "deserialize_string_from_number")] - pub permissions: String, + pub permissions: PermissionFlags, pub managed: bool, pub mentionable: bool, #[cfg(feature = "sqlx")] @@ -52,7 +51,7 @@ pub struct RoleSubscriptionData { pub is_renewal: bool, } -#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Hash)] +#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Hash, Copy, PartialOrd, Ord)] /// See pub struct RoleTags { #[serde(default)] @@ -71,7 +70,8 @@ pub struct RoleTags { } bitflags! { - #[derive(Debug, Default, Clone, Hash, Serialize, Deserialize, PartialEq, Eq)] + #[derive(Debug, Default, Clone, Hash, PartialEq, Eq, PartialOrd, chorus_macros::SerdeBitFlags)] + #[cfg_attr(feature = "sqlx", derive(chorus_macros::SqlxBitFlags))] /// Permissions limit what users of certain roles can do on a Guild to Guild basis. /// /// # Reference: diff --git a/src/types/entities/stage_instance.rs b/src/types/entities/stage_instance.rs index d48231b..38e2817 100644 --- a/src/types/entities/stage_instance.rs +++ b/src/types/entities/stage_instance.rs @@ -21,7 +21,7 @@ pub struct StageInstance { pub guild_scheduled_event_id: Option, } -#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)] +#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default, Copy, PartialEq, Eq, PartialOrd, Ord)] #[repr(u8)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] /// See diff --git a/src/types/entities/sticker.rs b/src/types/entities/sticker.rs index 8b95bc4..6fcc708 100644 --- a/src/types/entities/sticker.rs +++ b/src/types/entities/sticker.rs @@ -3,9 +3,11 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; -use crate::gateway::Shared; -use crate::types::{entities::User, utils::Snowflake}; +use crate::types::{entities::User, utils::Snowflake, Shared}; + +use super::option_arc_rwlock_ptr_eq; #[derive(Debug, Serialize, Deserialize, Clone, Default)] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] @@ -19,34 +21,21 @@ pub struct Sticker { pub pack_id: Option, pub name: String, pub description: Option, - pub tags: String, + pub tags: Option, + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub asset: Option, #[serde(rename = "type")] - pub sticker_type: u8, - pub format_type: u8, + pub sticker_type: StickerType, + pub format_type: StickerFormatType, pub available: Option, pub guild_id: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] pub user: Option>, + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub sort_value: Option, } -impl std::hash::Hash for Sticker { - fn hash(&self, state: &mut H) { - self.id.hash(state); - self.pack_id.hash(state); - self.name.hash(state); - self.description.hash(state); - self.tags.hash(state); - self.asset.hash(state); - self.sticker_type.hash(state); - self.format_type.hash(state); - self.available.hash(state); - self.guild_id.hash(state); - self.sort_value.hash(state); - } -} - +#[cfg(not(tarpaulin_include))] impl PartialEq for Sticker { fn eq(&self, other: &Self) -> bool { self.id == other.id @@ -59,53 +48,16 @@ impl PartialEq for Sticker { && self.format_type == other.format_type && self.available == other.available && self.guild_id == other.guild_id + && option_arc_rwlock_ptr_eq(&self.user, &other.user) && self.sort_value == other.sort_value } } -impl PartialOrd for Sticker { - fn partial_cmp(&self, other: &Self) -> Option { - match self.id.partial_cmp(&other.id) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.pack_id.partial_cmp(&other.pack_id) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.name.partial_cmp(&other.name) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.description.partial_cmp(&other.description) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.tags.partial_cmp(&other.tags) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.asset.partial_cmp(&other.asset) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.sticker_type.partial_cmp(&other.sticker_type) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.format_type.partial_cmp(&other.format_type) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.available.partial_cmp(&other.available) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.guild_id.partial_cmp(&other.guild_id) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - self.sort_value.partial_cmp(&other.sort_value) +impl Sticker { + pub fn tags(&self) -> Vec { + self.tags.as_ref().map_or(vec![], |s| { + s.split(',').map(|tag| tag.trim().to_string()).collect() + }) } } @@ -119,5 +71,68 @@ impl PartialOrd for Sticker { pub struct StickerItem { pub id: Snowflake, pub name: String, - pub format_type: u8, + pub format_type: StickerFormatType, +} + +#[derive( + Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Hash, Serialize_repr, Deserialize_repr, +)] +#[repr(u8)] +#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] +#[serde(rename = "SCREAMING_SNAKE_CASE")] +/// # Reference +/// See +pub enum StickerType { + /// An official sticker in a current or legacy purchasable pack + Standard = 1, + #[default] + /// A sticker uploaded to a guild for the guild's members + Guild = 2, +} + +#[derive( + Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Hash, Serialize_repr, Deserialize_repr, +)] +#[repr(u8)] +#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] +/// # Reference +/// See +pub enum StickerFormatType { + #[default] + /// A PNG image + PNG = 1, + /// An animated PNG image, using the APNG format - uses CDN + APNG = 2, + /// A lottie animation; requires the VERIFIED and/or PARTNERED guild feature - uses CDN + LOTTIE = 3, + /// An animated GIF image - does not use CDN + GIF = 4, +} + +impl StickerFormatType { + pub fn is_animated(&self) -> bool { + matches!( + self, + StickerFormatType::APNG | StickerFormatType::LOTTIE | StickerFormatType::GIF + ) + } + + pub const fn to_mime(&self) -> &'static str { + match self { + StickerFormatType::PNG => "image/png", + StickerFormatType::APNG => "image/apng", + StickerFormatType::LOTTIE => "application/json", + StickerFormatType::GIF => "image/gif", + } + } + + pub fn from_mime(mime: &str) -> Option { + match mime { + "image/png" => Some(StickerFormatType::PNG), + "image/apng" => Some(StickerFormatType::APNG), + "application/json" => Some(StickerFormatType::LOTTIE), + "image/gif" => Some(StickerFormatType::GIF), + _ => None, + } + } } diff --git a/src/types/entities/team.rs b/src/types/entities/team.rs index 98bd23e..4748fad 100644 --- a/src/types/entities/team.rs +++ b/src/types/entities/team.rs @@ -4,10 +4,12 @@ use serde::{Deserialize, Serialize}; -use crate::gateway::Shared; use crate::types::entities::User; +use crate::types::Shared; use crate::types::Snowflake; +use super::arc_rwlock_ptr_eq; + #[derive(Debug, Deserialize, Serialize, Clone)] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] pub struct Team { @@ -19,6 +21,17 @@ pub struct Team { pub owner_user_id: Snowflake, } +#[cfg(not(tarpaulin_include))] +impl PartialEq for Team { + fn eq(&self, other: &Self) -> bool { + self.icon == other.icon + && self.id == other.id + && self.members == other.members + && self.name == other.name + && self.owner_user_id == other.owner_user_id + } +} + #[derive(Debug, Deserialize, Serialize, Clone)] pub struct TeamMember { pub membership_state: u8, @@ -26,3 +39,13 @@ pub struct TeamMember { pub team_id: Snowflake, pub user: Shared, } + +#[cfg(not(tarpaulin_include))] +impl PartialEq for TeamMember { + fn eq(&self, other: &Self) -> bool { + self.membership_state == other.membership_state + && self.permissions == other.permissions + && self.team_id == other.team_id + && arc_rwlock_ptr_eq(&self.user, &other.user) + } +} diff --git a/src/types/entities/template.rs b/src/types/entities/template.rs index f34fbd7..e82ec17 100644 --- a/src/types/entities/template.rs +++ b/src/types/entities/template.rs @@ -5,8 +5,8 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use crate::gateway::Shared; use crate::types::{ + Shared, entities::{Guild, User}, utils::Snowflake, }; diff --git a/src/types/entities/user.rs b/src/types/entities/user.rs index 66fbab8..674c931 100644 --- a/src/types/entities/user.rs +++ b/src/types/entities/user.rs @@ -2,10 +2,12 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +use crate::errors::ChorusError; use crate::types::utils::Snowflake; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_aux::prelude::deserialize_option_number_from_string; +use std::array::TryFromSliceError; use std::fmt::Debug; #[cfg(feature = "client")] @@ -45,7 +47,7 @@ pub struct User { pub bot: Option, pub system: Option, pub mfa_enabled: Option, - pub accent_color: Option, + pub accent_color: Option, #[cfg_attr(feature = "sqlx", sqlx(default))] pub locale: Option, pub verified: Option, @@ -54,14 +56,14 @@ pub struct User { /// So we need to account for that #[serde(default)] #[serde(deserialize_with = "deserialize_option_number_from_string")] - pub flags: Option, + pub flags: Option, pub premium_since: Option>, pub premium_type: Option, pub pronouns: Option, - pub public_flags: Option, + pub public_flags: Option, pub banner: Option, pub bio: Option, - pub theme_colors: Option>, + pub theme_colors: Option, pub phone: Option, pub nsfw_allowed: Option, pub premium: Option, @@ -70,21 +72,87 @@ pub struct User { pub disabled: Option, } +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Copy)] +pub struct ThemeColors { + #[serde(flatten)] + inner: (u32, u32), +} + +impl TryFrom> for ThemeColors { + type Error = ChorusError; + + fn try_from(value: Vec) -> Result { + if value.len() % 4 != 0 || value.len() > 8 { + return Err(ChorusError::InvalidArguments { + error: "Value has incorrect length to be decodeable from Vec".to_string(), + }); + } + let first: Result<[u8; 4], TryFromSliceError> = value[0..3].try_into(); + let second: Result<[u8; 4], TryFromSliceError> = { + if value.len() == 8 { + value[0..3].try_into() + } else { + [0; 4][0..3].try_into() + } + }; + + match (first, second) { + (Ok(first), Ok(second)) => Ok(Self { + inner: (u32::from_be_bytes(first), u32::from_be_bytes(second)), + }), + _ => Err(ChorusError::InvalidArguments { + error: "ThemeColors cannot be built from this Vec".to_string(), + }), + } + } +} + +#[cfg(feature = "sqlx")] +// TODO: Add tests for Encode and Decode. +impl<'q> sqlx::Encode<'q, sqlx::MySql> for ThemeColors { + fn encode_by_ref( + &self, + buf: &mut >::ArgumentBuffer, + ) -> sqlx::encode::IsNull { + let mut vec_u8 = Vec::new(); + vec_u8.extend_from_slice(&self.inner.0.to_be_bytes()); + vec_u8.extend_from_slice(&self.inner.1.to_be_bytes()); + as sqlx::Encode<'q, sqlx::MySql>>::encode_by_ref(&vec_u8, buf) + } +} + +#[cfg(feature = "sqlx")] +impl<'d> sqlx::Decode<'d, sqlx::MySql> for ThemeColors { + fn decode( + value: >::ValueRef, + ) -> Result { + let value_vec = as sqlx::Decode<'d, sqlx::MySql>>::decode(value)?; + value_vec.try_into().map_err(|e: ChorusError| e.into()) + } +} + +#[cfg(feature = "sqlx")] +impl sqlx::Type for ThemeColors { + fn type_info() -> ::TypeInfo { + >::type_info() + } +} + #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] pub struct PublicUser { pub id: Snowflake, pub username: Option, pub discriminator: Option, pub avatar: Option, - pub accent_color: Option, + pub accent_color: Option, pub banner: Option, - pub theme_colors: Option>, + pub theme_colors: Option, pub pronouns: Option, pub bot: Option, pub bio: Option, pub premium_type: Option, pub premium_since: Option>, - pub public_flags: Option, + pub public_flags: Option, } impl From for PublicUser { @@ -111,8 +179,8 @@ impl From for PublicUser { const CUSTOM_USER_FLAG_OFFSET: u64 = 1 << 32; bitflags::bitflags! { - #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)] - #[cfg_attr(feature = "sqlx", derive(sqlx::Type))] + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, chorus_macros::SerdeBitFlags)] + #[cfg_attr(feature = "sqlx", derive(chorus_macros::SqlxBitFlags))] pub struct UserFlags: u64 { const DISCORD_EMPLOYEE = 1 << 0; const PARTNERED_SERVER_OWNER = 1 << 1; @@ -144,7 +212,7 @@ pub struct UserProfileMetadata { pub bio: Option, pub banner: Option, pub accent_color: Option, - pub theme_colors: Option>, + pub theme_colors: Option, pub popout_animation_particle_type: Option, pub emoji: Option, } diff --git a/src/types/entities/user_settings.rs b/src/types/entities/user_settings.rs index 6c072a8..395db2d 100644 --- a/src/types/entities/user_settings.rs +++ b/src/types/entities/user_settings.rs @@ -2,14 +2,13 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use std::sync::{Arc, RwLock}; - use chrono::{serde::ts_milliseconds_option, Utc}; use serde::{Deserialize, Serialize}; -use crate::gateway::Shared; +use crate::types::Shared; +use serde_aux::field_attributes::deserialize_option_number_from_string; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Copy, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[serde(rename_all = "lowercase")] pub enum UserStatus { @@ -27,7 +26,7 @@ impl std::fmt::Display for UserStatus { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Copy, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[serde(rename_all = "lowercase")] pub enum UserTheme { @@ -39,7 +38,7 @@ pub enum UserTheme { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] pub struct UserSettings { - pub afk_timeout: u16, + pub afk_timeout: Option, pub allow_accessibility_detection: bool, pub animate_emoji: bool, pub animate_stickers: u8, @@ -92,7 +91,7 @@ pub struct UserSettings { impl Default for UserSettings { fn default() -> Self { Self { - afk_timeout: 3600, + afk_timeout: Some(3600), allow_accessibility_detection: true, animate_emoji: true, animate_stickers: 0, @@ -119,7 +118,7 @@ impl Default for UserSettings { render_reactions: true, restricted_guilds: Default::default(), show_current_game: true, - status: Arc::new(RwLock::new(UserStatus::Online)), + status: Default::default(), stream_notifications_enabled: false, theme: UserTheme::Dark, timezone_offset: 0, @@ -137,7 +136,7 @@ pub struct CustomStatus { pub text: Option, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)] pub struct FriendSourceFlags { pub all: bool, } @@ -150,10 +149,17 @@ impl Default for FriendSourceFlags { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GuildFolder { - pub color: u32, + pub color: Option, pub guild_ids: Vec, - pub id: u16, - pub name: String, + // FIXME: What is this thing? + // It's not a snowflake, and it's sometimes a string and sometimes an integer. + // + // Ex: 1249181105 + // + // It can also be negative somehow? Ex: -1176643795 + #[serde(deserialize_with = "deserialize_option_number_from_string")] + pub id: Option, + pub name: Option, } #[derive(Debug, Serialize, Deserialize)] diff --git a/src/types/entities/voice_state.rs b/src/types/entities/voice_state.rs index 304b724..4491bea 100644 --- a/src/types/entities/voice_state.rs +++ b/src/types/entities/voice_state.rs @@ -5,7 +5,8 @@ #[cfg(feature = "client")] use chorus_macros::Composite; -use crate::gateway::Shared; +use crate::types::Shared; + #[cfg(feature = "client")] use crate::types::Composite; @@ -24,6 +25,8 @@ use crate::types::{ utils::Snowflake, }; +use super::option_arc_rwlock_ptr_eq; + /// The VoiceState struct. Note, that Discord does not have an `id` field for this, whereas Spacebar /// does. /// @@ -33,9 +36,11 @@ use crate::types::{ #[cfg_attr(feature = "client", derive(Composite))] pub struct VoiceState { pub guild_id: Option, + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub guild: Option, pub channel_id: Option, pub user_id: Snowflake, + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub member: Option>, /// Includes alphanumeric characters, not a snowflake pub session_id: String, @@ -51,6 +56,29 @@ pub struct VoiceState { pub id: Option, // Only exists on Spacebar } +#[cfg(not(tarpaulin_include))] +impl PartialEq for VoiceState { + fn eq(&self, other: &Self) -> bool { + self.guild_id == other.guild_id + && self.guild == other.guild + && self.channel_id == other.channel_id + && self.user_id == other.user_id + && option_arc_rwlock_ptr_eq(&self.member, &other.member) + && self.session_id == other.session_id + && self.token == other.token + && self.deaf == other.deaf + && self.mute == other.mute + && self.self_deaf == other.self_deaf + && self.self_mute == other.self_mute + && self.self_stream == other.self_stream + && self.self_video == other.self_video + && self.suppress == other.suppress + && self.request_to_speak_timestamp == other.request_to_speak_timestamp + && self.id == other.id + } +} + +#[cfg(feature = "client")] impl Updateable for VoiceState { #[cfg(not(tarpaulin_include))] fn id(&self) -> Snowflake { diff --git a/src/types/entities/webhook.rs b/src/types/entities/webhook.rs index f973956..19f6203 100644 --- a/src/types/entities/webhook.rs +++ b/src/types/entities/webhook.rs @@ -6,7 +6,8 @@ use std::fmt::Debug; use serde::{Deserialize, Serialize}; -use crate::gateway::Shared; +use crate::types::Shared; + #[cfg(feature = "client")] use crate::gateway::Updateable; @@ -24,6 +25,8 @@ use crate::types::{ utils::Snowflake, }; +use super::option_arc_rwlock_ptr_eq; + /// See #[derive(Serialize, Deserialize, Debug, Default, Clone)] #[cfg_attr(feature = "client", derive(Updateable, Composite))] @@ -31,13 +34,13 @@ use crate::types::{ pub struct Webhook { pub id: Snowflake, #[serde(rename = "type")] - pub webhook_type: i32, + pub webhook_type: WebhookType, pub name: String, pub avatar: String, pub token: String, pub guild_id: Snowflake, pub channel_id: Snowflake, - pub application_id: Snowflake, + pub application_id: Option, #[serde(skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "sqlx", sqlx(skip))] pub user: Option>, @@ -47,3 +50,32 @@ pub struct Webhook { #[serde(skip_serializing_if = "Option::is_none")] pub url: Option, } + +#[cfg(not(tarpaulin_include))] +impl PartialEq for Webhook { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + && self.webhook_type == other.webhook_type + && self.name == other.name + && self.avatar == other.avatar + && self.token == other.token + && self.guild_id == other.guild_id + && self.channel_id == other.channel_id + && self.application_id == other.application_id + && option_arc_rwlock_ptr_eq(&self.user, &other.user) + && option_arc_rwlock_ptr_eq(&self.source_guild, &other.source_guild) + && self.url == other.url + } +} + +#[derive( + Serialize, Deserialize, Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, +)] +#[repr(u8)] +#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] +pub enum WebhookType { + #[default] + Incoming = 1, + ChannelFollower = 2, + Application = 3, +} diff --git a/src/types/errors.rs b/src/types/errors.rs index f0a488c..7e2aa78 100644 --- a/src/types/errors.rs +++ b/src/types/errors.rs @@ -21,15 +21,18 @@ pub enum Error { #[error(transparent)] Guild(#[from] GuildError), + + #[error("Invalid flags value: {0}")] + InvalidFlags(u64), } -#[derive(Debug, PartialEq, Eq, thiserror::Error)] +#[derive(Debug, PartialEq, Eq, thiserror::Error, Copy, Clone)] pub enum GuildError { #[error("Invalid Guild Feature")] InvalidGuildFeature, } -#[derive(Debug, PartialEq, Eq, thiserror::Error)] +#[derive(Debug, PartialEq, Eq, thiserror::Error, Copy, Clone)] pub enum FieldFormatError { #[error("Password must be between 1 and 72 characters.")] PasswordError, diff --git a/src/types/events/application.rs b/src/types/events/application.rs index 43537ed..c39b896 100644 --- a/src/types/events/application.rs +++ b/src/types/events/application.rs @@ -5,12 +5,11 @@ use serde::{Deserialize, Serialize}; use crate::types::{GuildApplicationCommandPermissions, WebSocketEvent}; +use chorus_macros::WebSocketEvent; -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// See pub struct ApplicationCommandPermissionsUpdate { #[serde(flatten)] pub permissions: GuildApplicationCommandPermissions, } - -impl WebSocketEvent for ApplicationCommandPermissionsUpdate {} diff --git a/src/types/events/auto_moderation.rs b/src/types/events/auto_moderation.rs index fd42207..a7e3a34 100644 --- a/src/types/events/auto_moderation.rs +++ b/src/types/events/auto_moderation.rs @@ -2,28 +2,27 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use crate::types::{JsonField, SourceUrlField}; -use chorus_macros::{JsonField, SourceUrlField}; +use crate::types::{JsonField, SourceUrlField, WebSocketEvent}; +use chorus_macros::{JsonField, SourceUrlField, WebSocketEvent}; use serde::{Deserialize, Serialize}; use crate::types::{ AutoModerationAction, AutoModerationRule, AutoModerationRuleTriggerType, Snowflake, - WebSocketEvent, }; #[cfg(feature = "client")] use super::UpdateMessage; -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// See pub struct AutoModerationRuleCreate { #[serde(flatten)] pub rule: AutoModerationRule, } -impl WebSocketEvent for AutoModerationRuleCreate {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone, JsonField, SourceUrlField)] +#[derive( + Debug, Deserialize, Serialize, Default, Clone, JsonField, SourceUrlField, WebSocketEvent, +)] /// See pub struct AutoModerationRuleUpdate { #[serde(flatten)] @@ -43,18 +42,14 @@ impl UpdateMessage for AutoModerationRuleUpdate { } } -impl WebSocketEvent for AutoModerationRuleUpdate {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// See pub struct AutoModerationRuleDelete { #[serde(flatten)] pub rule: AutoModerationRule, } -impl WebSocketEvent for AutoModerationRuleDelete {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// See pub struct AutoModerationActionExecution { pub guild_id: Snowflake, @@ -69,5 +64,3 @@ pub struct AutoModerationActionExecution { pub matched_keyword: Option, pub matched_content: Option, } - -impl WebSocketEvent for AutoModerationActionExecution {} diff --git a/src/types/events/call.rs b/src/types/events/call.rs index 11a8801..7efdce1 100644 --- a/src/types/events/call.rs +++ b/src/types/events/call.rs @@ -5,8 +5,9 @@ use serde::{Deserialize, Serialize}; use crate::types::{Snowflake, VoiceState, WebSocketEvent}; +use chorus_macros::WebSocketEvent; -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// Officially Undocumented; /// Is sent to a client by the server to signify a new call being created; /// @@ -23,9 +24,7 @@ pub struct CallCreate { pub channel_id: Snowflake, } -impl WebSocketEvent for CallCreate {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq, WebSocketEvent)] /// Officially Undocumented; /// Updates the client on which calls are ringing, along with a specific call?; /// @@ -40,9 +39,19 @@ pub struct CallUpdate { pub channel_id: Snowflake, } -impl WebSocketEvent for CallUpdate {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)] +#[derive( + Debug, + Deserialize, + Serialize, + Default, + Clone, + PartialEq, + Eq, + WebSocketEvent, + Copy, + PartialOrd, + Ord, +)] /// Officially Undocumented; /// Deletes a ringing call; /// Ex: {"t":"CALL_DELETE","s":8,"op":0,"d":{"channel_id":"837609115475771392"}} @@ -50,9 +59,19 @@ pub struct CallDelete { pub channel_id: Snowflake, } -impl WebSocketEvent for CallDelete {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)] +#[derive( + Debug, + Deserialize, + Serialize, + Default, + Clone, + PartialEq, + Eq, + WebSocketEvent, + Copy, + PartialOrd, + Ord, +)] /// Officially Undocumented; /// See ; /// @@ -60,5 +79,3 @@ impl WebSocketEvent for CallDelete {} pub struct CallSync { pub channel_id: Snowflake, } - -impl WebSocketEvent for CallSync {} diff --git a/src/types/events/channel.rs b/src/types/events/channel.rs index 748d04a..a51a74b 100644 --- a/src/types/events/channel.rs +++ b/src/types/events/channel.rs @@ -3,7 +3,6 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::types::events::WebSocketEvent; -use crate::types::IntoShared; use crate::types::{entities::Channel, JsonField, Snowflake, SourceUrlField}; use chorus_macros::{JsonField, SourceUrlField}; use chrono::{DateTime, Utc}; @@ -13,12 +12,15 @@ use serde::{Deserialize, Serialize}; use super::UpdateMessage; #[cfg(feature = "client")] -use crate::gateway::Shared; +use crate::types::Shared; + +#[cfg(feature = "client")] +use crate::types::IntoShared; #[cfg(feature = "client")] use crate::types::Guild; -#[derive(Debug, Default, Deserialize, Serialize)] +#[derive(Debug, Default, Deserialize, Serialize, WebSocketEvent, Copy, PartialEq, Clone, Eq, Hash, PartialOrd, Ord)] /// See pub struct ChannelPinsUpdate { pub guild_id: Option, @@ -26,9 +28,7 @@ pub struct ChannelPinsUpdate { pub last_pin_timestamp: Option>, } -impl WebSocketEvent for ChannelPinsUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField, WebSocketEvent)] /// See pub struct ChannelCreate { #[serde(flatten)] @@ -39,8 +39,6 @@ pub struct ChannelCreate { pub source_url: String, } -impl WebSocketEvent for ChannelCreate {} - #[cfg(feature = "client")] impl UpdateMessage for ChannelCreate { #[cfg(not(tarpaulin_include))] @@ -51,15 +49,11 @@ impl UpdateMessage for ChannelCreate { fn update(&mut self, object_to_update: Shared) { let mut write = object_to_update.write().unwrap(); let update = self.channel.clone().into_shared(); - if write.channels.is_some() { - write.channels.as_mut().unwrap().push(update); - } else { - write.channels = Some(Vec::from([update])); - } + write.channels.push(update); } } -#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField, WebSocketEvent)] /// See pub struct ChannelUpdate { #[serde(flatten)] @@ -70,8 +64,6 @@ pub struct ChannelUpdate { pub source_url: String, } -impl WebSocketEvent for ChannelUpdate {} - #[cfg(feature = "client")] impl UpdateMessage for ChannelUpdate { fn update(&mut self, object_to_update: Shared) { @@ -85,7 +77,7 @@ impl UpdateMessage for ChannelUpdate { } } -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// Officially undocumented. /// Sends updates to client about a new message with its id /// {"channel_unread_updates": [{"id": "816412869766938648", "last_message_id": "1085892012085104680"}} @@ -94,18 +86,16 @@ pub struct ChannelUnreadUpdate { pub guild_id: Snowflake, } -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] /// Contains very few fields from [Channel] /// See also [ChannelUnreadUpdate] pub struct ChannelUnreadUpdateObject { pub id: Snowflake, pub last_message_id: Snowflake, - pub last_pin_timestamp: Option, + pub last_pin_timestamp: Option>, } -impl WebSocketEvent for ChannelUnreadUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField, WebSocketEvent)] /// See pub struct ChannelDelete { #[serde(flatten)] @@ -128,16 +118,15 @@ impl UpdateMessage for ChannelDelete { return; } let mut write = object_to_update.write().unwrap(); - if write.channels.is_none() { + if write.channels.is_empty() { return; } - for (iteration, item) in (0_u32..).zip(write.channels.as_mut().unwrap().iter()) { + for (iteration, item) in (0_u32..).zip(write.channels.iter()) { if item.read().unwrap().id == self.id().unwrap() { - write.channels.as_mut().unwrap().remove(iteration as usize); + write.channels.remove(iteration as usize); return; } } } } -impl WebSocketEvent for ChannelDelete {} diff --git a/src/types/events/guild.rs b/src/types/events/guild.rs index 92f46ea..f599d1f 100644 --- a/src/types/events/guild.rs +++ b/src/types/events/guild.rs @@ -9,8 +9,8 @@ use serde::{Deserialize, Serialize}; use crate::types::entities::{Guild, PublicUser, UnavailableGuild}; use crate::types::events::WebSocketEvent; use crate::types::{ - AuditLogEntry, Emoji, GuildMember, GuildScheduledEvent, IntoShared, JsonField, RoleObject, - Snowflake, SourceUrlField, Sticker, + AuditLogEntry, Emoji, GuildMember, GuildScheduledEvent, JsonField, RoleObject, Snowflake, + SourceUrlField, Sticker, }; use super::PresenceUpdate; @@ -18,9 +18,21 @@ use super::PresenceUpdate; #[cfg(feature = "client")] use super::UpdateMessage; #[cfg(feature = "client")] -use crate::gateway::Shared; +use crate::types::IntoShared; +#[cfg(feature = "client")] +use crate::types::Shared; -#[derive(Debug, Deserialize, Serialize, Default, Clone, SourceUrlField, JsonField)] +#[derive( + Debug, + Deserialize, + Serialize, + Default, + Clone, + SourceUrlField, + JsonField, + WebSocketEvent, + PartialEq, +)] /// See ; /// Received to give data about a guild; // This one is particularly painful, it can be a Guild object with an extra field or an unavailable guild object @@ -47,7 +59,7 @@ impl UpdateMessage for GuildCreate { fn update(&mut self, _: Shared) {} } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] #[serde(untagged)] pub enum GuildCreateDataOption { UnavailableGuild(UnavailableGuild), @@ -60,9 +72,31 @@ impl Default for GuildCreateDataOption { } } -impl WebSocketEvent for GuildCreate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Clone, PartialEq)] +pub enum GuildEvents { + Create(GuildCreate), + Update(GuildUpdate), + Delete(GuildDelete), + BanAdd(GuildBanAdd), + BanRemove(GuildBanRemove), + EmojisUpdate(GuildEmojisUpdate), + StickersUpdate(GuildStickersUpdate), + IntegrationsUpdate(GuildIntegrationsUpdate), + MemberAdd(GuildMemberAdd), + MemberRemove(GuildMemberRemove), + MemberUpdate(GuildMemberUpdate), + MembersChunk(GuildMembersChunk), + RoleCreate(GuildRoleCreate), + RoleUpdate(GuildRoleUpdate), + RoleDelete(GuildRoleDelete), + ScheduledEventCreate(GuildScheduledEventCreate), + ScheduledEventUpdate(GuildScheduledEventUpdate), + ScheduledEventDelete(GuildScheduledEventDelete), + ScheduledEventUserAdd(GuildScheduledEventUserAdd), + ScheduledEventUserRemove(GuildScheduledEventUserRemove), + AuditLogEntryCreate(GuildAuditLogEntryCreate), +} +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)] /// See ; /// Received to give info about a user being banned from a guild; pub struct GuildBanAdd { @@ -70,9 +104,7 @@ pub struct GuildBanAdd { pub user: PublicUser, } -impl WebSocketEvent for GuildBanAdd {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)] /// See ; /// Received to give info about a user being unbanned from a guild; pub struct GuildBanRemove { @@ -80,9 +112,17 @@ pub struct GuildBanRemove { pub user: PublicUser, } -impl WebSocketEvent for GuildBanRemove {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone, SourceUrlField, JsonField)] +#[derive( + Debug, + Default, + Deserialize, + Serialize, + Clone, + SourceUrlField, + JsonField, + WebSocketEvent, + PartialEq, +)] /// See ; /// Received to give info about a guild being updated; pub struct GuildUpdate { @@ -94,8 +134,6 @@ pub struct GuildUpdate { pub json: String, } -impl WebSocketEvent for GuildUpdate {} - #[cfg(feature = "client")] impl UpdateMessage for GuildUpdate { #[cfg(not(tarpaulin_include))] @@ -104,7 +142,17 @@ impl UpdateMessage for GuildUpdate { } } -#[derive(Debug, Default, Deserialize, Serialize, Clone, SourceUrlField, JsonField)] +#[derive( + Debug, + Default, + Deserialize, + Serialize, + Clone, + SourceUrlField, + JsonField, + WebSocketEvent, + PartialEq, +)] /// See ; /// Received to tell the client about a guild being deleted; pub struct GuildDelete { @@ -125,9 +173,7 @@ impl UpdateMessage for GuildDelete { fn update(&mut self, _: Shared) {} } -impl WebSocketEvent for GuildDelete {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)] /// See ; /// Received to the client about an audit log entry being added; pub struct GuildAuditLogEntryCreate { @@ -135,9 +181,7 @@ pub struct GuildAuditLogEntryCreate { pub entry: AuditLogEntry, } -impl WebSocketEvent for GuildAuditLogEntryCreate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)] /// See ; /// Received to tell the client about a change to a guild's emoji list; pub struct GuildEmojisUpdate { @@ -145,9 +189,7 @@ pub struct GuildEmojisUpdate { pub emojis: Vec, } -impl WebSocketEvent for GuildEmojisUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)] /// See ; /// Received to tell the client about a change to a guild's sticker list; pub struct GuildStickersUpdate { @@ -155,17 +197,13 @@ pub struct GuildStickersUpdate { pub stickers: Vec, } -impl WebSocketEvent for GuildStickersUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq, Copy, Eq, Hash, PartialOrd, Ord)] /// See pub struct GuildIntegrationsUpdate { pub guild_id: Snowflake, } -impl WebSocketEvent for GuildIntegrationsUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)] /// See ; /// Received to tell the client about a user joining a guild; pub struct GuildMemberAdd { @@ -174,9 +212,7 @@ pub struct GuildMemberAdd { pub guild_id: Snowflake, } -impl WebSocketEvent for GuildMemberAdd {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)] /// See ; /// Received to tell the client about a user leaving a guild; pub struct GuildMemberRemove { @@ -184,9 +220,7 @@ pub struct GuildMemberRemove { pub user: PublicUser, } -impl WebSocketEvent for GuildMemberRemove {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)] /// See pub struct GuildMemberUpdate { pub guild_id: Snowflake, @@ -202,9 +236,7 @@ pub struct GuildMemberUpdate { pub communication_disabled_until: Option>, } -impl WebSocketEvent for GuildMemberUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)] /// See pub struct GuildMembersChunk { pub guild_id: Snowflake, @@ -216,9 +248,17 @@ pub struct GuildMembersChunk { pub nonce: Option, } -impl WebSocketEvent for GuildMembersChunk {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)] +#[derive( + Debug, + Default, + Deserialize, + Serialize, + Clone, + JsonField, + SourceUrlField, + WebSocketEvent, + PartialEq, +)] /// See pub struct GuildRoleCreate { pub guild_id: Snowflake, @@ -229,8 +269,6 @@ pub struct GuildRoleCreate { pub source_url: String, } -impl WebSocketEvent for GuildRoleCreate {} - #[cfg(feature = "client")] impl UpdateMessage for GuildRoleCreate { #[cfg(not(tarpaulin_include))] @@ -240,19 +278,21 @@ impl UpdateMessage for GuildRoleCreate { fn update(&mut self, object_to_update: Shared) { let mut object_to_update = object_to_update.write().unwrap(); - if object_to_update.roles.is_some() { - object_to_update - .roles - .as_mut() - .unwrap() - .push(self.role.clone().into_shared()); - } else { - object_to_update.roles = Some(Vec::from([self.role.clone().into_shared()])); - } + object_to_update.roles.push(self.role.clone().into_shared()); } } -#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)] +#[derive( + Debug, + Default, + Deserialize, + Serialize, + Clone, + JsonField, + SourceUrlField, + WebSocketEvent, + PartialEq, +)] /// See pub struct GuildRoleUpdate { pub guild_id: Snowflake, @@ -263,8 +303,6 @@ pub struct GuildRoleUpdate { pub source_url: String, } -impl WebSocketEvent for GuildRoleUpdate {} - #[cfg(feature = "client")] impl UpdateMessage for GuildRoleUpdate { #[cfg(not(tarpaulin_include))] @@ -278,43 +316,35 @@ impl UpdateMessage for GuildRoleUpdate { } } -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)] /// See pub struct GuildRoleDelete { pub guild_id: Snowflake, pub role_id: Snowflake, } -impl WebSocketEvent for GuildRoleDelete {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)] /// See pub struct GuildScheduledEventCreate { #[serde(flatten)] pub event: GuildScheduledEvent, } -impl WebSocketEvent for GuildScheduledEventCreate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)] /// See pub struct GuildScheduledEventUpdate { #[serde(flatten)] pub event: GuildScheduledEvent, } -impl WebSocketEvent for GuildScheduledEventUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)] /// See pub struct GuildScheduledEventDelete { #[serde(flatten)] pub event: GuildScheduledEvent, } -impl WebSocketEvent for GuildScheduledEventDelete {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq, Copy, Eq, Hash, PartialOrd, Ord)] /// See pub struct GuildScheduledEventUserAdd { pub guild_scheduled_event_id: Snowflake, @@ -322,14 +352,10 @@ pub struct GuildScheduledEventUserAdd { pub guild_id: Snowflake, } -impl WebSocketEvent for GuildScheduledEventUserAdd {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq, Copy, Eq, Hash, PartialOrd, Ord)] /// See pub struct GuildScheduledEventUserRemove { pub guild_scheduled_event_id: Snowflake, pub user_id: Snowflake, pub guild_id: Snowflake, } - -impl WebSocketEvent for GuildScheduledEventUserRemove {} diff --git a/src/types/events/heartbeat.rs b/src/types/events/heartbeat.rs index 2b4141b..8be2a52 100644 --- a/src/types/events/heartbeat.rs +++ b/src/types/events/heartbeat.rs @@ -5,17 +5,52 @@ use crate::types::events::WebSocketEvent; use serde::{Deserialize, Serialize}; -#[derive(Debug, Default, Deserialize, Serialize)] +#[derive( + Debug, Deserialize, Serialize, WebSocketEvent, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, +)] pub struct GatewayHeartbeat { pub op: u8, pub d: Option, } -impl WebSocketEvent for GatewayHeartbeat {} +impl GatewayHeartbeat { + /// The Heartbeat packet a server would receive from a new or fresh Gateway connection. + pub fn first() -> Self { + Self::default() + } -#[derive(Debug, Default, Deserialize, Serialize, Clone)] + /// Quickly create a [GatewayHeartbeat] with the correct `opcode` and the given `sequence_number`. + /// + /// Shorthand for + /// ```rs + /// Self { + /// op: 1, + /// d: Some(sequence_number) + /// } + /// ``` + pub fn new(sequence_number: u64) -> Self { + Self { + op: 1, + d: Some(sequence_number), + } + } +} + +impl std::default::Default for GatewayHeartbeat { + fn default() -> Self { + Self { op: 1, d: None } + } +} + +#[derive( + Debug, Deserialize, Serialize, Clone, WebSocketEvent, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, +)] pub struct GatewayHeartbeatAck { pub op: i32, } -impl WebSocketEvent for GatewayHeartbeatAck {} +impl std::default::Default for GatewayHeartbeatAck { + fn default() -> Self { + Self { op: 11 } + } +} diff --git a/src/types/events/hello.rs b/src/types/events/hello.rs index 83542c9..a828f13 100644 --- a/src/types/events/hello.rs +++ b/src/types/events/hello.rs @@ -3,22 +3,42 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::types::WebSocketEvent; +use chorus_macros::WebSocketEvent; use serde::{Deserialize, Serialize}; /// Received on gateway init, tells the client how often to send heartbeats; -#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)] +#[derive( + Debug, Deserialize, Serialize, Clone, PartialEq, Eq, WebSocketEvent, Copy, Hash, PartialOrd, Ord, +)] pub struct GatewayHello { pub op: i32, pub d: HelloData, } -impl WebSocketEvent for GatewayHello {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq, Copy)] +#[derive( + Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Copy, WebSocketEvent, Hash, PartialOrd, Ord, +)] /// Contains info on how often the client should send heartbeats to the server; pub struct HelloData { /// How often a client should send heartbeats, in milliseconds pub heartbeat_interval: u64, } -impl WebSocketEvent for HelloData {} +impl std::default::Default for GatewayHello { + fn default() -> Self { + Self { + // "HELLO" opcode is 10 + op: 10, + d: Default::default(), + } + } +} + +impl std::default::Default for HelloData { + fn default() -> Self { + Self { + // Discord docs mention 45000 seconds - discord.sex mentions 41250. Defaulting to 45s + heartbeat_interval: 45000, + } + } +} diff --git a/src/types/events/identify.rs b/src/types/events/identify.rs index 648b554..2829706 100644 --- a/src/types/events/identify.rs +++ b/src/types/events/identify.rs @@ -6,7 +6,7 @@ use crate::types::events::{PresenceUpdate, WebSocketEvent}; use serde::{Deserialize, Serialize}; use serde_with::serde_as; -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, WebSocketEvent)] pub struct GatewayIdentifyPayload { pub token: String, pub properties: GatewayIdentifyConnectionProps, @@ -70,9 +70,7 @@ impl GatewayIdentifyPayload { } } -impl WebSocketEvent for GatewayIdentifyPayload {} - -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, WebSocketEvent)] #[serde_as] pub struct GatewayIdentifyConnectionProps { /// Almost always sent @@ -159,7 +157,7 @@ impl GatewayIdentifyConnectionProps { // 25% of the web //default.browser_user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36".to_string(); browser: String::from("Chrome"), - browser_version: String::from("113.0.0.0"), + browser_version: String::from("126.0.0.0"), system_locale: String::from("en-US"), os: String::from("Windows"), os_version: Some(String::from("10")), diff --git a/src/types/events/integration.rs b/src/types/events/integration.rs index 3550c4e..716cc30 100644 --- a/src/types/events/integration.rs +++ b/src/types/events/integration.rs @@ -5,8 +5,9 @@ use serde::{Deserialize, Serialize}; use crate::types::{Integration, Snowflake, WebSocketEvent}; +use chorus_macros::WebSocketEvent; -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct IntegrationCreate { #[serde(flatten)] @@ -14,9 +15,7 @@ pub struct IntegrationCreate { pub guild_id: Snowflake, } -impl WebSocketEvent for IntegrationCreate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct IntegrationUpdate { #[serde(flatten)] @@ -24,9 +23,7 @@ pub struct IntegrationUpdate { pub guild_id: Snowflake, } -impl WebSocketEvent for IntegrationUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] /// See pub struct IntegrationDelete { pub id: Snowflake, @@ -34,4 +31,3 @@ pub struct IntegrationDelete { pub application_id: Option, } -impl WebSocketEvent for IntegrationDelete {} diff --git a/src/types/events/interaction.rs b/src/types/events/interaction.rs index 48d5526..c90c519 100644 --- a/src/types/events/interaction.rs +++ b/src/types/events/interaction.rs @@ -5,12 +5,12 @@ use serde::{Deserialize, Serialize}; use crate::types::{Interaction, WebSocketEvent}; +use chorus_macros::WebSocketEvent; -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct InteractionCreate { #[serde(flatten)] pub interaction: Interaction, } -impl WebSocketEvent for InteractionCreate {} diff --git a/src/types/events/invalid_session.rs b/src/types/events/invalid_session.rs index ae61879..12b8fb9 100644 --- a/src/types/events/invalid_session.rs +++ b/src/types/events/invalid_session.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use super::WebSocketEvent; -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)] /// Your session is now invalid. /// /// Either reauthenticate and reidentify or resume if possible. @@ -14,4 +14,3 @@ pub struct GatewayInvalidSession { pub resumable: bool, } -impl WebSocketEvent for GatewayInvalidSession {} diff --git a/src/types/events/invite.rs b/src/types/events/invite.rs index 01e76ff..13b91b4 100644 --- a/src/types/events/invite.rs +++ b/src/types/events/invite.rs @@ -5,17 +5,16 @@ use serde::{Deserialize, Serialize}; use crate::types::{GuildInvite, Snowflake, WebSocketEvent}; +use chorus_macros::WebSocketEvent; -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct InviteCreate { #[serde(flatten)] pub invite: GuildInvite, } -impl WebSocketEvent for InviteCreate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct InviteDelete { pub channel_id: Snowflake, @@ -23,4 +22,3 @@ pub struct InviteDelete { pub code: String, } -impl WebSocketEvent for InviteDelete {} diff --git a/src/types/events/lazy_request.rs b/src/types/events/lazy_request.rs index 0c80c13..6e17b8e 100644 --- a/src/types/events/lazy_request.rs +++ b/src/types/events/lazy_request.rs @@ -7,10 +7,9 @@ use std::collections::HashMap; use serde::{Deserialize, Serialize}; use crate::types::Snowflake; - use super::WebSocketEvent; -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// Officially Undocumented /// /// Sent to the server to signify lazy loading of a guild; @@ -31,4 +30,3 @@ pub struct LazyRequest { pub channels: Option>>>, } -impl WebSocketEvent for LazyRequest {} diff --git a/src/types/events/message.rs b/src/types/events/message.rs index 4b1779e..1b855df 100644 --- a/src/types/events/message.rs +++ b/src/types/events/message.rs @@ -6,12 +6,12 @@ use serde::{Deserialize, Serialize}; use crate::types::{ entities::{Emoji, GuildMember, Message, PublicUser}, - Snowflake, + Snowflake, WebSocketEvent, }; -use super::WebSocketEvent; +use chorus_macros::WebSocketEvent; -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// # Reference /// See pub struct TypingStartEvent { @@ -22,9 +22,7 @@ pub struct TypingStartEvent { pub member: Option, } -impl WebSocketEvent for TypingStartEvent {} - -#[derive(Debug, Serialize, Deserialize, Default, Clone)] +#[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)] /// See pub struct MessageCreate { #[serde(flatten)] @@ -34,7 +32,7 @@ pub struct MessageCreate { pub mentions: Option>, } -#[derive(Debug, Serialize, Deserialize, Default, Clone)] +#[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)] /// See pub struct MessageCreateUser { #[serde(flatten)] @@ -42,9 +40,7 @@ pub struct MessageCreateUser { pub member: Option, } -impl WebSocketEvent for MessageCreate {} - -#[derive(Debug, Serialize, Deserialize, Default, Clone)] +#[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)] /// # Reference /// See pub struct MessageUpdate { @@ -55,9 +51,20 @@ pub struct MessageUpdate { pub mentions: Option>, } -impl WebSocketEvent for MessageUpdate {} - -#[derive(Debug, Serialize, Deserialize, Default, Clone)] +#[derive( + Debug, + Serialize, + Deserialize, + Default, + Clone, + WebSocketEvent, + Copy, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, +)] /// # Reference /// See pub struct MessageDelete { @@ -66,9 +73,19 @@ pub struct MessageDelete { pub guild_id: Option, } -impl WebSocketEvent for MessageDelete {} - -#[derive(Debug, Serialize, Deserialize, Default, Clone)] +#[derive( + Debug, + Serialize, + Deserialize, + Default, + Clone, + WebSocketEvent, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, +)] /// # Reference /// See pub struct MessageDeleteBulk { @@ -77,9 +94,7 @@ pub struct MessageDeleteBulk { pub guild_id: Option, } -impl WebSocketEvent for MessageDeleteBulk {} - -#[derive(Debug, Serialize, Deserialize, Default, Clone)] +#[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)] /// # Reference /// See pub struct MessageReactionAdd { @@ -91,9 +106,7 @@ pub struct MessageReactionAdd { pub emoji: Emoji, } -impl WebSocketEvent for MessageReactionAdd {} - -#[derive(Debug, Serialize, Deserialize, Default, Clone)] +#[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)] /// # Reference /// See pub struct MessageReactionRemove { @@ -104,9 +117,20 @@ pub struct MessageReactionRemove { pub emoji: Emoji, } -impl WebSocketEvent for MessageReactionRemove {} - -#[derive(Debug, Serialize, Deserialize, Default, Clone)] +#[derive( + Debug, + Serialize, + Deserialize, + Default, + Clone, + WebSocketEvent, + Copy, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, +)] /// # Reference /// See pub struct MessageReactionRemoveAll { @@ -115,9 +139,7 @@ pub struct MessageReactionRemoveAll { pub guild_id: Option, } -impl WebSocketEvent for MessageReactionRemoveAll {} - -#[derive(Debug, Serialize, Deserialize, Default, Clone)] +#[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)] /// # Reference /// See pub struct MessageReactionRemoveEmoji { @@ -127,9 +149,7 @@ pub struct MessageReactionRemoveEmoji { pub emoji: Emoji, } -impl WebSocketEvent for MessageReactionRemoveEmoji {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// Officially Undocumented /// /// Not documented anywhere unofficially @@ -139,8 +159,8 @@ impl WebSocketEvent for MessageReactionRemoveEmoji {} /// /// {"t":"MESSAGE_ACK","s":3,"op":0,"d":{"version":52,"message_id":"1107236673638633472","last_viewed":null,"flags":null,"channel_id":"967363950217936897"}} pub struct MessageACK { - /// ? - pub version: u16, + // No ideas. See 206933 + pub version: u32, pub message_id: Snowflake, /// This is an integer??? /// Not even unix, see '3070'??? @@ -149,5 +169,3 @@ pub struct MessageACK { pub flags: Option, pub channel_id: Snowflake, } - -impl WebSocketEvent for MessageACK {} diff --git a/src/types/events/mod.rs b/src/types/events/mod.rs index 6faafd1..280ebb8 100644 --- a/src/types/events/mod.rs +++ b/src/types/events/mod.rs @@ -33,6 +33,8 @@ pub use voice::*; pub use voice_gateway::*; pub use webhooks::*; +use chorus_macros::WebSocketEvent; + #[cfg(feature = "client")] use super::Snowflake; @@ -46,7 +48,7 @@ use serde_json::{from_str, from_value, to_value, Value}; use std::collections::HashMap; #[cfg(feature = "client")] -use crate::gateway::Shared; +use crate::types::Shared; use std::fmt::Debug; #[cfg(feature = "client")] @@ -84,7 +86,7 @@ mod voice_gateway; pub trait WebSocketEvent: Send + Sync + Debug {} -#[derive(Debug, Default, Serialize, Clone)] +#[derive(Debug, Default, Serialize, Clone, WebSocketEvent)] /// The payload used for sending events to the gateway /// /// Similar to [GatewayReceivePayload], except we send a [serde_json::value::Value] for d whilst we receive a [serde_json::value::RawValue] @@ -102,8 +104,6 @@ pub struct GatewaySendPayload { pub sequence_number: Option, } -impl WebSocketEvent for GatewaySendPayload {} - #[derive(Debug, Default, Deserialize, Clone)] /// The payload used for receiving events from the gateway pub struct GatewayReceivePayload<'a> { diff --git a/src/types/events/passive_update.rs b/src/types/events/passive_update.rs index 0f728a2..088fb22 100644 --- a/src/types/events/passive_update.rs +++ b/src/types/events/passive_update.rs @@ -7,15 +7,17 @@ use serde::{Deserialize, Serialize}; use super::{ChannelUnreadUpdateObject, WebSocketEvent}; use crate::types::{GuildMember, Snowflake, VoiceState}; -#[derive(Debug, Deserialize, Serialize, Default)] +#[derive(Debug, Deserialize, Serialize, Default, WebSocketEvent)] /// Officially Undocumented /// /// Seems to be passively set to update the client on guild details (though, why not just send the update events?) pub struct PassiveUpdateV1 { + #[serde(default)] pub voice_states: Vec, - pub members: Option>, + #[serde(default)] + pub members: Vec, pub guild_id: Snowflake, + #[serde(default)] pub channels: Vec, } -impl WebSocketEvent for PassiveUpdateV1 {} diff --git a/src/types/events/presence.rs b/src/types/events/presence.rs index afbf633..d96d984 100644 --- a/src/types/events/presence.rs +++ b/src/types/events/presence.rs @@ -6,7 +6,7 @@ use crate::types::{events::WebSocketEvent, UserStatus}; use crate::types::{Activity, ClientStatusObject, PublicUser, Snowflake}; use serde::{Deserialize, Serialize}; -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// Sent by the client to update its status and presence; /// See pub struct UpdatePresence { @@ -18,16 +18,18 @@ pub struct UpdatePresence { pub afk: bool, } -#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, WebSocketEvent)] /// Received to tell the client that a user updated their presence / status +/// /// See +/// (Same structure as ) pub struct PresenceUpdate { pub user: PublicUser, #[serde(default)] pub guild_id: Option, pub status: UserStatus, + #[serde(default)] pub activities: Vec, pub client_status: ClientStatusObject, } -impl WebSocketEvent for PresenceUpdate {} diff --git a/src/types/events/ready.rs b/src/types/events/ready.rs index 7e88bdf..ffba526 100644 --- a/src/types/events/ready.rs +++ b/src/types/events/ready.rs @@ -6,13 +6,14 @@ use serde::{Deserialize, Serialize}; use crate::types::entities::{Guild, User}; use crate::types::events::{Session, WebSocketEvent}; -use crate::types::interfaces::ClientStatusObject; -use crate::types::{Activity, GuildMember, PresenceUpdate, VoiceState}; +use crate::types::{Activity, Channel, ClientStatusObject, GuildMember, PresenceUpdate, Snowflake, VoiceState}; -#[derive(Debug, Deserialize, Serialize, Default, Clone)] -/// 1/2 half documented; +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] +/// 1/2 officially documented; /// Received after identifying, provides initial user info; -/// See +/// +/// See and +// TODO: There are a LOT of fields missing here pub struct GatewayReady { pub analytics_token: Option, pub auth_session_id_hash: Option, @@ -30,42 +31,49 @@ pub struct GatewayReady { pub shard: Option<(u64, u64)>, } -impl WebSocketEvent for GatewayReady {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// Officially Undocumented; -/// Sent after the READY event when a client is a user, seems to somehow add onto the ready event; +/// Sent after the READY event when a client is a user, +/// seems to somehow add onto the ready event; +/// +/// See pub struct GatewayReadySupplemental { + /// The presences of the user's relationships and guild presences sent at startup pub merged_presences: MergedPresences, pub merged_members: Vec>, - // ? - pub lazy_private_channels: Vec, + pub lazy_private_channels: Vec, pub guilds: Vec, - // ? pomelo + // "Upcoming changes that the client should disclose to the user" (discord.sex) pub disclose: Vec, } -impl WebSocketEvent for GatewayReadySupplemental {} - #[derive(Debug, Deserialize, Serialize, Default, Clone)] +/// See pub struct MergedPresences { - pub guilds: Vec>, + /// "Presences of the user's guilds in the same order as the guilds array in ready" + /// (discord.sex) + pub guilds: Vec>, + /// "Presences of the user's friends and implicit relationships" (discord.sex) pub friends: Vec, } #[derive(Debug, Deserialize, Serialize, Default, Clone)] +/// Not documented even unofficially pub struct MergedPresenceFriend { - pub user_id: String, + pub user_id: Snowflake, pub status: String, - /// Looks like ms?? - pub last_modified: u128, + // Looks like ms?? + // + // Not always sent + pub last_modified: Option, pub client_status: ClientStatusObject, pub activities: Vec, } #[derive(Debug, Deserialize, Serialize, Default, Clone)] +/// Not documented even unofficially pub struct MergedPresenceGuild { - pub user_id: String, + pub user_id: Snowflake, pub status: String, // ? pub game: Option, @@ -74,8 +82,10 @@ pub struct MergedPresenceGuild { } #[derive(Debug, Deserialize, Serialize, Default, Clone)] +/// See pub struct SupplementalGuild { + pub id: Snowflake, pub voice_states: Option>, - pub id: String, + /// Field not documented even unofficially pub embedded_activities: Vec, } diff --git a/src/types/events/reconnect.rs b/src/types/events/reconnect.rs index d2751cc..17381b2 100644 --- a/src/types/events/reconnect.rs +++ b/src/types/events/reconnect.rs @@ -2,11 +2,10 @@ use serde::{Deserialize, Serialize}; use super::WebSocketEvent; -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] /// "The reconnect event is dispatched when a client should reconnect to the Gateway (and resume their existing session, if they have one). This event usually occurs during deploys to migrate sessions gracefully off old hosts" /// /// # Reference /// See pub struct GatewayReconnect {} -impl WebSocketEvent for GatewayReconnect {} diff --git a/src/types/events/relationship.rs b/src/types/events/relationship.rs index 6352d91..7f81375 100644 --- a/src/types/events/relationship.rs +++ b/src/types/events/relationship.rs @@ -5,7 +5,7 @@ use crate::types::{events::WebSocketEvent, Relationship, RelationshipType, Snowflake}; use serde::{Deserialize, Serialize}; -#[derive(Debug, Deserialize, Serialize, Default)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// See pub struct RelationshipAdd { #[serde(flatten)] @@ -13,9 +13,7 @@ pub struct RelationshipAdd { pub should_notify: bool, } -impl WebSocketEvent for RelationshipAdd {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)] /// See pub struct RelationshipRemove { pub id: Snowflake, @@ -23,4 +21,3 @@ pub struct RelationshipRemove { pub relationship_type: RelationshipType, } -impl WebSocketEvent for RelationshipRemove {} diff --git a/src/types/events/request_members.rs b/src/types/events/request_members.rs index 0e4d9dd..a6cffdf 100644 --- a/src/types/events/request_members.rs +++ b/src/types/events/request_members.rs @@ -5,7 +5,7 @@ use crate::types::{events::WebSocketEvent, Snowflake}; use serde::{Deserialize, Serialize}; -#[derive(Debug, Deserialize, Serialize, Default)] +#[derive(Debug, Deserialize, Serialize, Default, WebSocketEvent)] /// See pub struct GatewayRequestGuildMembers { pub guild_id: Snowflake, @@ -17,4 +17,3 @@ pub struct GatewayRequestGuildMembers { pub nonce: Option, } -impl WebSocketEvent for GatewayRequestGuildMembers {} diff --git a/src/types/events/resume.rs b/src/types/events/resume.rs index 86b3dff..2485dc3 100644 --- a/src/types/events/resume.rs +++ b/src/types/events/resume.rs @@ -5,11 +5,10 @@ use crate::types::events::WebSocketEvent; use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Deserialize, Serialize, Default)] +#[derive(Debug, Clone, Deserialize, Serialize, Default, WebSocketEvent)] pub struct GatewayResume { pub token: String, pub session_id: String, pub seq: String, } -impl WebSocketEvent for GatewayResume {} diff --git a/src/types/events/session.rs b/src/types/events/session.rs index 2e5de7a..a76ebc3 100644 --- a/src/types/events/session.rs +++ b/src/types/events/session.rs @@ -2,11 +2,12 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +use chorus_macros::WebSocketEvent; use serde::{Deserialize, Serialize}; use crate::types::{Activity, WebSocketEvent}; -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// Officially Undocumented /// Seems like it sends active session info to users on connect /// [{"activities":[],"client_info":{"client":"web","os":"other","version":0},"session_id":"ab5941b50d818b1f8d93b4b1b581b192","status":"online"}] @@ -33,4 +34,3 @@ pub struct ClientInfo { pub version: u8, } -impl WebSocketEvent for SessionsReplace {} diff --git a/src/types/events/stage_instance.rs b/src/types/events/stage_instance.rs index 3a0fa64..32c5a17 100644 --- a/src/types/events/stage_instance.rs +++ b/src/types/events/stage_instance.rs @@ -5,30 +5,26 @@ use serde::{Deserialize, Serialize}; use crate::types::{StageInstance, WebSocketEvent}; +use chorus_macros::WebSocketEvent; -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// See pub struct StageInstanceCreate { #[serde(flatten)] pub stage_instance: StageInstance, } -impl WebSocketEvent for StageInstanceCreate {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// See pub struct StageInstanceUpdate { #[serde(flatten)] pub stage_instance: StageInstance, } -impl WebSocketEvent for StageInstanceUpdate {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// See pub struct StageInstanceDelete { #[serde(flatten)] pub stage_instance: StageInstance, } -impl WebSocketEvent for StageInstanceDelete {} diff --git a/src/types/events/thread.rs b/src/types/events/thread.rs index abfecf9..bf67e83 100644 --- a/src/types/events/thread.rs +++ b/src/types/events/thread.rs @@ -12,16 +12,14 @@ use crate::types::{JsonField, Snowflake, SourceUrlField}; #[cfg(feature = "client")] use super::UpdateMessage; -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct ThreadCreate { #[serde(flatten)] pub thread: Channel, } -impl WebSocketEvent for ThreadCreate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField, WebSocketEvent)] /// See pub struct ThreadUpdate { #[serde(flatten)] @@ -32,8 +30,6 @@ pub struct ThreadUpdate { pub source_url: String, } -impl WebSocketEvent for ThreadUpdate {} - #[cfg(feature = "client")] impl UpdateMessage for ThreadUpdate { #[cfg(not(tarpaulin_include))] @@ -42,16 +38,14 @@ impl UpdateMessage for ThreadUpdate { } } -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct ThreadDelete { #[serde(flatten)] pub thread: Channel, } -impl WebSocketEvent for ThreadDelete {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct ThreadListSync { pub guild_id: Snowflake, @@ -60,9 +54,7 @@ pub struct ThreadListSync { pub members: Option>, } -impl WebSocketEvent for ThreadListSync {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See /// The inner payload is a thread member object with an extra field. pub struct ThreadMemberUpdate { @@ -71,9 +63,7 @@ pub struct ThreadMemberUpdate { pub guild_id: Snowflake, } -impl WebSocketEvent for ThreadMemberUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)] /// See pub struct ThreadMembersUpdate { pub id: Snowflake, @@ -84,4 +74,3 @@ pub struct ThreadMembersUpdate { pub removed_members: Option>, } -impl WebSocketEvent for ThreadMembersUpdate {} diff --git a/src/types/events/user.rs b/src/types/events/user.rs index 130ddd1..877c96c 100644 --- a/src/types/events/user.rs +++ b/src/types/events/user.rs @@ -8,7 +8,7 @@ use crate::types::entities::PublicUser; use crate::types::events::WebSocketEvent; use crate::types::utils::Snowflake; -#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq, WebSocketEvent)] /// See ; /// Sent to indicate updates to a user object; (name changes, discriminator changes, etc); pub struct UserUpdate { @@ -16,9 +16,7 @@ pub struct UserUpdate { pub user: PublicUser, } -impl WebSocketEvent for UserUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq, WebSocketEvent)] /// Undocumented; /// /// Possibly an update for muted guild / channel settings for the current user; @@ -41,8 +39,6 @@ pub struct UserGuildSettingsUpdate { pub channel_overrides: Vec, } -impl WebSocketEvent for UserGuildSettingsUpdate {} - #[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)] /// Undocumented; /// diff --git a/src/types/events/voice.rs b/src/types/events/voice.rs index 0a4484f..a2b80b9 100644 --- a/src/types/events/voice.rs +++ b/src/types/events/voice.rs @@ -5,7 +5,7 @@ use crate::types::{events::WebSocketEvent, Snowflake, VoiceState}; use serde::{Deserialize, Serialize}; -#[derive(Debug, Deserialize, Serialize, Default, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, Copy, PartialEq, Eq, WebSocketEvent)] /// /// Sent to the server to indicate an update of the voice state (leave voice channel, join voice channel, mute, deafen); /// @@ -17,9 +17,7 @@ pub struct UpdateVoiceState { pub self_deaf: bool, } -impl WebSocketEvent for UpdateVoiceState {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)] /// See ; /// /// Received from the server to indicate an update in a user's voice state (leave voice channel, join voice channel, mute, deafen, etc); @@ -30,9 +28,7 @@ pub struct VoiceStateUpdate { pub state: VoiceState, } -impl WebSocketEvent for VoiceStateUpdate {} - -#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq, WebSocketEvent)] /// See ; /// /// Received to indicate which voice endpoint, token and guild_id to use; @@ -45,4 +41,3 @@ pub struct VoiceServerUpdate { pub endpoint: Option, } -impl WebSocketEvent for VoiceServerUpdate {} diff --git a/src/types/events/voice_gateway/client_connect.rs b/src/types/events/voice_gateway/client_connect.rs index 3929275..b5cbc78 100644 --- a/src/types/events/voice_gateway/client_connect.rs +++ b/src/types/events/voice_gateway/client_connect.rs @@ -3,9 +3,10 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::types::{Snowflake, WebSocketEvent}; +use chorus_macros::WebSocketEvent; use serde::{Deserialize, Serialize}; -#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy, WebSocketEvent)] /// Sent when another user connects to the voice server. /// /// Contains the user id and "flags". @@ -21,9 +22,7 @@ pub struct VoiceClientConnectFlags { pub flags: Option, } -impl WebSocketEvent for VoiceClientConnectFlags {} - -#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy, WebSocketEvent)] /// Sent when another user connects to the voice server. /// /// Contains the user id and "platform". @@ -37,4 +36,3 @@ pub struct VoiceClientConnectPlatform { pub platform: u8, } -impl WebSocketEvent for VoiceClientConnectPlatform {} diff --git a/src/types/events/voice_gateway/client_disconnect.rs b/src/types/events/voice_gateway/client_disconnect.rs index cc1d949..3b6b201 100644 --- a/src/types/events/voice_gateway/client_disconnect.rs +++ b/src/types/events/voice_gateway/client_disconnect.rs @@ -3,9 +3,10 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::types::{Snowflake, WebSocketEvent}; +use chorus_macros::WebSocketEvent; use serde::{Deserialize, Serialize}; -#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy, WebSocketEvent)] /// Sent when another user disconnects from the voice server. /// /// When received, the SSRC of the user should be discarded. @@ -15,4 +16,3 @@ pub struct VoiceClientDisconnection { pub user_id: Snowflake, } -impl WebSocketEvent for VoiceClientDisconnection {} diff --git a/src/types/events/voice_gateway/hello.rs b/src/types/events/voice_gateway/hello.rs index 08bd09e..2bd8c72 100644 --- a/src/types/events/voice_gateway/hello.rs +++ b/src/types/events/voice_gateway/hello.rs @@ -3,9 +3,10 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::types::WebSocketEvent; +use chorus_macros::WebSocketEvent; use serde::{Deserialize, Serialize}; -#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy, WebSocketEvent)] /// Contains info on how often the client should send heartbeats to the server; /// /// Differs from the normal hello data in that discord sends heartbeat interval as a float. @@ -21,4 +22,3 @@ pub struct VoiceHelloData { pub heartbeat_interval: f64, } -impl WebSocketEvent for VoiceHelloData {} diff --git a/src/types/events/voice_gateway/identify.rs b/src/types/events/voice_gateway/identify.rs index d33cd40..383aabb 100644 --- a/src/types/events/voice_gateway/identify.rs +++ b/src/types/events/voice_gateway/identify.rs @@ -3,9 +3,10 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::types::{Snowflake, WebSocketEvent}; +use chorus_macros::WebSocketEvent; use serde::{Deserialize, Serialize}; -#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq, WebSocketEvent)] /// The identify payload for the voice gateway connection; /// /// Contains authentication info and context to authenticate to the voice gateway. @@ -22,4 +23,3 @@ pub struct VoiceIdentify { // TODO: Add video streams } -impl WebSocketEvent for VoiceIdentify {} diff --git a/src/types/events/voice_gateway/media_sink_wants.rs b/src/types/events/voice_gateway/media_sink_wants.rs index 1f79eda..be6a497 100644 --- a/src/types/events/voice_gateway/media_sink_wants.rs +++ b/src/types/events/voice_gateway/media_sink_wants.rs @@ -3,9 +3,10 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::types::WebSocketEvent; +use chorus_macros::WebSocketEvent; use serde::{Deserialize, Serialize}; -#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy, WebSocketEvent)] /// What does this do? /// /// {"op":15,"d":{"any":100}} @@ -15,4 +16,3 @@ pub struct VoiceMediaSinkWants { pub any: u16, } -impl WebSocketEvent for VoiceMediaSinkWants {} diff --git a/src/types/events/voice_gateway/mod.rs b/src/types/events/voice_gateway/mod.rs index 0546d29..bb632e2 100644 --- a/src/types/events/voice_gateway/mod.rs +++ b/src/types/events/voice_gateway/mod.rs @@ -30,7 +30,7 @@ mod speaking; mod ssrc_definition; mod voice_backend_version; -#[derive(Debug, Default, Serialize, Clone)] +#[derive(Debug, Default, Serialize, Clone, WebSocketEvent)] /// The payload used for sending events to the voice gateway. /// /// Similar to [VoiceGatewayReceivePayload], except we send a [Value] for d whilst we receive a [serde_json::value::RawValue] @@ -42,8 +42,6 @@ pub struct VoiceGatewaySendPayload { pub data: Value, } -impl WebSocketEvent for VoiceGatewaySendPayload {} - #[derive(Debug, Deserialize, Clone)] /// The payload used for receiving events from the voice gateway. /// diff --git a/src/types/events/voice_gateway/ready.rs b/src/types/events/voice_gateway/ready.rs index 1f7f90f..833f8d5 100644 --- a/src/types/events/voice_gateway/ready.rs +++ b/src/types/events/voice_gateway/ready.rs @@ -5,11 +5,12 @@ use std::net::Ipv4Addr; use crate::types::WebSocketEvent; +use chorus_macros::WebSocketEvent; use serde::{Deserialize, Serialize}; use super::VoiceEncryptionMode; -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, WebSocketEvent)] /// The voice gateway's ready event; /// /// Gives the user info about the UDP connection IP and port, srrc to use, @@ -43,4 +44,3 @@ impl Default for VoiceReady { } } -impl WebSocketEvent for VoiceReady {} diff --git a/src/types/events/voice_gateway/session_description.rs b/src/types/events/voice_gateway/session_description.rs index 9c1b3d4..16b6390 100644 --- a/src/types/events/voice_gateway/session_description.rs +++ b/src/types/events/voice_gateway/session_description.rs @@ -1,8 +1,9 @@ use super::{AudioCodec, VideoCodec, VoiceEncryptionMode}; use crate::types::WebSocketEvent; +use chorus_macros::WebSocketEvent; use serde::{Deserialize, Serialize}; -#[derive(Debug, Deserialize, Serialize, Clone, Default)] +#[derive(Debug, Deserialize, Serialize, Clone, Default, WebSocketEvent)] /// Event that describes our encryption mode and secret key for encryption /// /// See @@ -19,9 +20,7 @@ pub struct SessionDescription { pub keyframe_interval: Option, } -impl WebSocketEvent for SessionDescription {} - -#[derive(Debug, Deserialize, Serialize, Clone, Default)] +#[derive(Debug, Deserialize, Serialize, Clone, Default, WebSocketEvent)] /// Event that might be sent to update session parameters /// /// See @@ -36,4 +35,3 @@ pub struct SessionUpdate { pub new_media_session_id: Option, } -impl WebSocketEvent for SessionUpdate {} diff --git a/src/types/events/voice_gateway/speaking.rs b/src/types/events/voice_gateway/speaking.rs index a18ba77..0d50345 100644 --- a/src/types/events/voice_gateway/speaking.rs +++ b/src/types/events/voice_gateway/speaking.rs @@ -6,13 +6,14 @@ use bitflags::bitflags; use serde::{Deserialize, Serialize}; use crate::types::{Snowflake, WebSocketEvent}; +use chorus_macros::WebSocketEvent; /// Event that tells the server we are speaking; /// /// Essentially, what allows us to send UDP data and lights up the green circle around your avatar. /// /// See -#[derive(Debug, Deserialize, Serialize, Clone, Default)] +#[derive(Debug, Deserialize, Serialize, Clone, Default, WebSocketEvent, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)] pub struct Speaking { /// Data about the audio we're transmitting. /// @@ -27,14 +28,12 @@ pub struct Speaking { pub delay: u64, } -impl WebSocketEvent for Speaking {} - bitflags! { /// Bitflags of speaking types; /// /// See - #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Serialize, Deserialize)] - pub struct SpeakingBitflags: u8 { + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, chorus_macros::SerdeBitFlags)] + pub struct SpeakingBitflags: u64 { /// Whether we'll be transmitting normal voice audio const MICROPHONE = 1 << 0; /// Whether we'll be transmitting context audio for video, no speaking indicator diff --git a/src/types/events/voice_gateway/ssrc_definition.rs b/src/types/events/voice_gateway/ssrc_definition.rs index 6692dc9..69c1336 100644 --- a/src/types/events/voice_gateway/ssrc_definition.rs +++ b/src/types/events/voice_gateway/ssrc_definition.rs @@ -3,6 +3,7 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::types::{Snowflake, WebSocketEvent}; +use chorus_macros::WebSocketEvent; use serde::{Deserialize, Serialize}; /// Defines an event which provides ssrcs for voice and video for a user id. @@ -24,7 +25,7 @@ use serde::{Deserialize, Serialize}; /// ```json /// {"op":12,"d":{"audio_ssrc":2307250864,"video_ssrc":0,"rtx_ssrc":0,"streams":[{"type":"video","rid":"100","ssrc":26595,"active":false,"quality":100,"rtx_ssrc":26596,"max_bitrate":2500000,"max_framerate":30,"max_resolution":{"type":"fixed","width":1280,"height":720}}]}} /// ``` -#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq, WebSocketEvent)] pub struct SsrcDefinition { /// The ssrc used for video communications. /// @@ -50,4 +51,3 @@ pub struct SsrcDefinition { pub streams: Vec, } -impl WebSocketEvent for SsrcDefinition {} diff --git a/src/types/events/voice_gateway/voice_backend_version.rs b/src/types/events/voice_gateway/voice_backend_version.rs index f8ee76e..0e8ce88 100644 --- a/src/types/events/voice_gateway/voice_backend_version.rs +++ b/src/types/events/voice_gateway/voice_backend_version.rs @@ -3,9 +3,10 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use crate::types::WebSocketEvent; +use chorus_macros::WebSocketEvent; use serde::{Deserialize, Serialize}; -#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, WebSocketEvent)] /// Received from the voice gateway server to describe the backend version. /// /// See @@ -18,4 +19,3 @@ pub struct VoiceBackendVersion { pub rtc_worker_version: String, } -impl WebSocketEvent for VoiceBackendVersion {} diff --git a/src/types/events/webhooks.rs b/src/types/events/webhooks.rs index 814589c..be39e3d 100644 --- a/src/types/events/webhooks.rs +++ b/src/types/events/webhooks.rs @@ -4,15 +4,13 @@ use serde::{Deserialize, Serialize}; -use crate::types::Snowflake; +use crate::types::{Snowflake, WebSocketEvent}; +use chorus_macros::WebSocketEvent; -use super::WebSocketEvent; - -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] /// See pub struct WebhooksUpdate { pub guild_id: Snowflake, pub channel_id: Snowflake, } -impl WebSocketEvent for WebhooksUpdate {} diff --git a/src/types/interfaces/interaction.rs b/src/types/interfaces/interaction.rs index f88eaf9..1a4bb6a 100644 --- a/src/types/interfaces/interaction.rs +++ b/src/types/interfaces/interaction.rs @@ -20,7 +20,7 @@ pub struct Interaction { pub version: i32, } -#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize, Eq, PartialOrd, Ord, Hash, Copy)] pub enum InteractionType { #[default] SelfCommand = 0, @@ -28,7 +28,7 @@ pub enum InteractionType { ApplicationCommand = 2, } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Copy, Eq, Hash, PartialOrd, Ord)] pub enum InteractionResponseType { SelfCommandResponse = 0, Pong = 1, @@ -38,7 +38,7 @@ pub enum InteractionResponseType { AcknowledgeWithSource = 5, } -#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize, Eq, Hash, PartialOrd, Ord)] pub struct InteractionApplicationCommandCallbackData { pub tts: bool, pub content: String, diff --git a/src/types/mod.rs b/src/types/mod.rs index 8eee13a..f41a083 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -4,6 +4,9 @@ //! All the types, entities, events and interfaces of the Spacebar API. +#[cfg(feature = "client")] +use std::sync::{Arc, RwLock}; + pub use config::*; pub use entities::*; pub use errors::*; @@ -19,3 +22,17 @@ mod events; mod interfaces; mod schema; mod utils; + +/// A type alias for [`Arc>`], used to make the public facing API concerned with +/// Composite structs more ergonomic. +/// ## Note +/// +/// While `T` does not have to implement `Composite` to be used with `Shared`, +/// the primary use of `Shared` is with types that implement `Composite`. +/// +/// When the `client` feature is disabled, this does nothing (same as just `T`), +/// since `Composite` structures are disabled. +#[cfg(feature = "client")] +pub type Shared = Arc>; +#[cfg(not(feature = "client"))] +pub type Shared = T; diff --git a/src/types/schema/apierror.rs b/src/types/schema/apierror.rs index 0dd1f6f..4a242c8 100644 --- a/src/types/schema/apierror.rs +++ b/src/types/schema/apierror.rs @@ -6,7 +6,7 @@ use poem::{http::StatusCode, IntoResponse, Response}; use serde_json::{json, Value}; -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error, Copy, Clone, PartialEq, Eq, Hash)] pub enum APIError { #[error(transparent)] Auth(#[from] AuthError), @@ -20,7 +20,7 @@ impl APIError { } } -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error, Clone, Copy, PartialEq, Eq, Hash)] pub enum AuthError { #[error("INVALID_LOGIN")] InvalidLogin, diff --git a/src/types/schema/audit_log.rs b/src/types/schema/audit_log.rs new file mode 100644 index 0000000..e5c19e5 --- /dev/null +++ b/src/types/schema/audit_log.rs @@ -0,0 +1,28 @@ +use crate::types::{ + ApplicationCommand, AuditLogActionType, AuditLogEntry, AutoModerationRule, Channel, + GuildScheduledEvent, Integration, Snowflake, User, Webhook, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct AuditLogObject { + pub audit_log_entries: Vec, + pub application_commands: Vec, + pub auto_moderation_rules: Vec, + pub guild_scheduled_events: Vec, + pub integrations: Vec, + pub threads: Vec, + pub users: Vec, + pub webhooks: Vec, +} + +#[derive( + Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default, +)] +pub struct GetAuditLogsQuery { + pub before: Option, + pub after: Option, + pub limit: Option, + pub user_id: Option, + pub action_type: Option, +} diff --git a/src/types/schema/auth.rs b/src/types/schema/auth.rs index 2796805..83c88dc 100644 --- a/src/types/schema/auth.rs +++ b/src/types/schema/auth.rs @@ -2,6 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +use chrono::NaiveDate; use serde::{Deserialize, Serialize}; #[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)] @@ -13,7 +14,8 @@ pub struct RegisterSchema { pub email: Option, pub fingerprint: Option, pub invite: Option, - pub date_of_birth: Option, + /// The user's date of birth, serialized as an ISO8601 date + pub date_of_birth: Option, pub gift_code_sku_id: Option, pub captcha_key: Option, pub promotional_email_opt_in: Option, diff --git a/src/types/schema/channel.rs b/src/types/schema/channel.rs index 1502f97..0a3d468 100644 --- a/src/types/schema/channel.rs +++ b/src/types/schema/channel.rs @@ -5,8 +5,7 @@ use bitflags::bitflags; use serde::{Deserialize, Serialize}; -use crate::types::ChannelType; -use crate::types::{entities::PermissionOverwrite, Snowflake}; +use crate::types::{ChannelType, DefaultReaction, entities::PermissionOverwrite, Snowflake}; #[derive(Debug, Deserialize, Serialize, Default, PartialEq, PartialOrd)] #[serde(rename_all = "snake_case")] @@ -36,7 +35,7 @@ pub struct ChannelCreateSchema { #[serde(rename_all = "snake_case")] pub struct ChannelModifySchema { pub name: Option, - pub channel_type: Option, + pub channel_type: Option, pub topic: Option, pub icon: Option, pub bitrate: Option, @@ -48,7 +47,7 @@ pub struct ChannelModifySchema { pub nsfw: Option, pub rtc_region: Option, pub default_auto_archive_duration: Option, - pub default_reaction_emoji: Option, + pub default_reaction_emoji: Option, pub flags: Option, pub default_thread_rate_limit_per_user: Option, pub video_quality_mode: Option, @@ -59,7 +58,7 @@ pub struct GetChannelMessagesSchema { /// Between 1 and 100, defaults to 50. pub limit: Option, #[serde(flatten)] - pub anchor: ChannelMessagesAnchor, + pub anchor: Option, } #[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] @@ -74,21 +73,21 @@ impl GetChannelMessagesSchema { pub fn before(anchor: Snowflake) -> Self { Self { limit: None, - anchor: ChannelMessagesAnchor::Before(anchor), + anchor: Some(ChannelMessagesAnchor::Before(anchor)), } } pub fn around(anchor: Snowflake) -> Self { Self { limit: None, - anchor: ChannelMessagesAnchor::Around(anchor), + anchor: Some(ChannelMessagesAnchor::Around(anchor)), } } pub fn after(anchor: Snowflake) -> Self { Self { limit: None, - anchor: ChannelMessagesAnchor::After(anchor), + anchor: Some(ChannelMessagesAnchor::After(anchor)), } } @@ -109,7 +108,7 @@ pub struct CreateChannelInviteSchema { pub temporary: Option, pub unique: Option, pub validate: Option, - pub target_type: Option, + pub target_type: Option, pub target_user_id: Option, pub target_application_id: Option, } @@ -131,15 +130,30 @@ impl Default for CreateChannelInviteSchema { } bitflags! { - #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)] + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, chorus_macros::SerdeBitFlags)] + #[cfg_attr(feature = "sqlx", derive(chorus_macros::SqlxBitFlags))] pub struct InviteFlags: u64 { const GUEST = 1 << 0; + const VIEWED = 1 << 1; } } #[derive(Debug, Deserialize, Serialize, Clone, Copy, Default, PartialOrd, Ord, PartialEq, Eq)] +#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] +#[repr(u8)] pub enum InviteType { + #[default] + Guild = 0, + GroupDm = 1, + Friend = 2, +} + +#[derive(Debug, Deserialize, Serialize, Clone, Copy, Default, PartialOrd, Ord, PartialEq, Eq)] +#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +#[repr(u8)] +pub enum InviteTargetType { #[default] Stream = 1, EmbeddedApplication = 2, @@ -155,10 +169,22 @@ pub struct AddChannelRecipientSchema { } /// See -#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialOrd, Ord, PartialEq, Eq)] +#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialOrd, Ord, PartialEq, Eq, Copy, Hash)] pub struct ModifyChannelPositionsSchema { pub id: Snowflake, pub position: Option, pub lock_permissions: Option, pub parent_id: Option, } + +/// See +#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialOrd, Ord, PartialEq, Eq, Copy, Hash)] +pub struct AddFollowingChannelSchema { + pub webhook_channel_id: Snowflake, +} + +#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialOrd, Ord, PartialEq, Eq)] +pub struct CreateWebhookSchema { + pub name: String, + pub avatar: Option, +} diff --git a/src/types/schema/guild.rs b/src/types/schema/guild.rs index 2e29ce0..f53494e 100644 --- a/src/types/schema/guild.rs +++ b/src/types/schema/guild.rs @@ -5,15 +5,17 @@ use bitflags::bitflags; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use crate::types::entities::Channel; use crate::types::types::guild_configuration::GuildFeatures; use crate::types::{ - Emoji, ExplicitContentFilterLevel, MessageNotificationLevel, Snowflake, Sticker, - SystemChannelFlags, VerificationLevel, + Emoji, ExplicitContentFilterLevel, GenericSearchQueryWithLimit, MessageNotificationLevel, + Snowflake, Sticker, StickerFormatType, SystemChannelFlags, VerificationLevel, + WelcomeScreenChannel, }; -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] +#[derive(Debug, Deserialize, Serialize, Clone)] #[serde(rename_all = "snake_case")] /// Represents the schema which needs to be sent to create a Guild. /// See: @@ -27,15 +29,38 @@ pub struct GuildCreateSchema { pub rules_channel_id: Option, } +#[cfg(not(tarpaulin_include))] +impl PartialEq for GuildCreateSchema { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + && self.region == other.region + && self.icon == other.icon + && self.channels == other.channels + && self.guild_template_code == other.guild_template_code + && self.system_channel_id == other.system_channel_id + && self.rules_channel_id == other.rules_channel_id + } +} + #[derive(Debug, Deserialize, Serialize, Default, Clone, Copy, Eq, PartialEq)] #[serde(rename_all = "snake_case")] /// Represents the schema which needs to be sent to create a Guild Ban. /// See: pub struct GuildBanCreateSchema { + /// Deprecated pub delete_message_days: Option, pub delete_message_seconds: Option, } +#[derive(Debug, Deserialize, Serialize, Default, Clone, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +/// Represents the schema which needs to be sent to create a Guild Ban. +/// See: +pub struct GuildBanBulkCreateSchema { + pub user_ids: Vec, + pub delete_message_seconds: Option, +} + #[derive(Debug, Deserialize, Serialize, Default, Clone, Eq, PartialEq)] #[serde(rename_all = "snake_case")] /// Represents the schema used to modify a guild. @@ -69,7 +94,7 @@ pub struct GuildModifySchema { pub premium_progress_bar_enabled: Option, } -#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq, Ord, PartialOrd, Copy)] pub struct GetUserGuildSchema { pub before: Option, pub after: Option, @@ -77,6 +102,31 @@ pub struct GetUserGuildSchema { pub with_counts: Option, } +impl GetUserGuildSchema { + /// Converts self to query string parameters + pub fn to_query(self) -> Vec<(&'static str, String)> { + let mut query = Vec::with_capacity(4); + + if let Some(before) = self.before { + query.push(("before", before.to_string())); + } + + if let Some(after) = self.after { + query.push(("after", after.to_string())); + } + + if let Some(limit) = self.limit { + query.push(("limit", limit.to_string())); + } + + if let Some(with_counts) = self.with_counts { + query.push(("with_counts", with_counts.to_string())); + } + + query + } +} + impl std::default::Default for GetUserGuildSchema { fn default() -> Self { Self { @@ -119,6 +169,12 @@ impl Default for GuildMemberSearchSchema { } } +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd, Eq, Ord, Copy, Hash)] +pub struct GuildGetMembersQuery { + pub limit: Option, + pub after: Option, +} + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd, Eq, Ord)] pub struct ModifyGuildMemberSchema { pub nick: Option, @@ -131,7 +187,8 @@ pub struct ModifyGuildMemberSchema { } bitflags! { - #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)] + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, chorus_macros::SerdeBitFlags)] + #[cfg_attr(feature = "sqlx", derive(chorus_macros::SqlxBitFlags))] /// Represents the flags of a Guild Member. /// /// # Reference: @@ -166,10 +223,232 @@ pub struct ModifyGuildMemberProfileSchema { pub emoji_id: Option, } -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd, Eq, Ord)] +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd, Eq, Ord, Copy, Hash)] /// The limit argument is a number between 1 and 1000. pub struct GuildBansQuery { pub before: Option, pub after: Option, pub limit: Option, } + +/// Max query length is 32 characters. +/// The limit argument is a number between 1 and 10, defaults to 10. +pub type GuildBansSearchQuery = GenericSearchQueryWithLimit; + +/// Query is partial or full, username or nickname. +/// Limit argument is a number between 1 and 1000, defaults to 1. +pub type GuildMembersSearchQuery = GenericSearchQueryWithLimit; + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] +/// A guild's progress on meeting the requirements of joining discovery. +/// +/// Certain guilds, such as those that are verified, are exempt from discovery requirements. These guilds will not have a fully populated discovery requirements object, and are guaranteed to receive only sufficient and sufficient_without_grace_period. +/// +/// # Reference: +/// See +pub struct GuildDiscoveryRequirements { + pub guild_id: Option, + pub safe_environment: Option, + pub healthy: Option, + pub health_score_pending: Option, + pub size: Option, + pub nsfw_properties: Option, + pub protected: Option, + pub sufficient: Option, + pub sufficient_without_grace_period: Option, + pub valid_rules_channel: Option, + pub retention_healthy: Option, + pub engagement_healthy: Option, + pub age: Option, + pub minimum_age: Option, + pub health_score: Option, + pub minimum_size: Option, + pub grace_period_end_date: Option>, +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +/// # Reference: +/// See +pub struct GuildDiscoveryNsfwProperties { + pub channels: Vec, + pub channel_banned_keywords: HashMap>, + pub name: Option, + pub name_banned_keywords: Vec, + pub description: Option, + pub description_banned_keywords: Vec, +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Copy)] +/// Activity metrics are recalculated weekly, as an 8-week rolling average. If they are not yet eligible to be calculated, all fields will be null. +/// +/// # Reference: +/// See +pub struct GuildDiscoveryHealthScore { + pub avg_nonnew_communicators: u64, + pub avg_nonnew_participators: u64, + pub num_intentful_joiners: u64, + pub perc_ret_w1_intentful: f64, +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] +/// # Reference: +/// See +pub struct EmojiCreateSchema { + pub name: Option, + /// # Reference: + /// See + pub image: String, + #[serde(default)] + pub roles: Vec, +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] +/// # Reference: +/// See +pub struct EmojiModifySchema { + pub name: Option, + pub roles: Option>, +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] +/// # Reference: +/// See +pub struct GuildPruneQuerySchema { + pub days: u8, + /// Only used on POST + #[serde(default, skip_serializing_if = "Option::is_none")] + pub compute_prune_count: Option, + #[serde(default)] + pub include_roles: Vec, +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Copy)] +/// # Reference: +/// See +pub struct GuildPruneResult { + /// Null if compute_prune_count is false + pub pruned: Option, +} + +#[derive(Default, Debug, Deserialize, Serialize, Clone, PartialEq)] +/// # Reference: +/// See +pub struct GuildCreateStickerSchema { + pub name: String, + #[serde(default)] + pub description: Option, + #[serde(default)] + pub tags: Option, + pub file_data: Vec, + #[serde(skip)] + pub sticker_format_type: StickerFormatType, +} + +impl GuildCreateStickerSchema { + #[cfg(feature = "poem")] + pub async fn from_multipart(mut multipart: poem::web::Multipart) -> Result { + let mut _self = GuildCreateStickerSchema::default(); + while let Some(field) = multipart.next_field().await? { + let name = field.name().ok_or(poem::Error::from_string( + "All fields must be named", + poem::http::StatusCode::BAD_REQUEST, + ))?; + match name { + "name" => { + _self.name = field.text().await?; + } + "description" => { + _self.description = Some(field.text().await?); + } + "tags" => { + _self.tags = Some(field.text().await?); + } + "file_data" => { + if _self.name.is_empty() { + _self.name = + field + .file_name() + .map(String::from) + .ok_or(poem::Error::from_string( + "File name must be set", + poem::http::StatusCode::BAD_REQUEST, + ))?; + } + _self.sticker_format_type = StickerFormatType::from_mime( + field.content_type().ok_or(poem::Error::from_string( + "Content type must be set", + poem::http::StatusCode::BAD_REQUEST, + ))?, + ) + .ok_or(poem::Error::from_string( + "Unknown sticker format", + poem::http::StatusCode::BAD_REQUEST, + ))?; + _self.file_data = field.bytes().await?; + } + _ => {} + } + } + if _self.name.is_empty() || _self.file_data.is_empty() { + return Err(poem::Error::from_string( + "At least the name and file_data are required", + poem::http::StatusCode::BAD_REQUEST, + )); + } + + Ok(_self) + } + + // #[cfg(feature = "client")] + pub fn to_multipart(&self) -> reqwest::multipart::Form { + let mut form = reqwest::multipart::Form::new() + .text("name", self.name.clone()) + .part( + "file_data", + reqwest::multipart::Part::bytes(self.file_data.clone()) + .mime_str(self.sticker_format_type.to_mime()) + .unwrap(), + ); + + if let Some(description) = &self.description { + form = form.text("description", description.to_owned()); + } + + if let Some(tags) = &self.tags { + form = form.text("tags", tags.to_owned()) + } + form + } +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] +/// # Reference: +/// See +pub struct GuildModifyStickerSchema { + #[serde(default)] + pub name: Option, + #[serde(default)] + pub description: Option, + #[serde(default)] + pub tags: Option, +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] +/// # Reference: +/// See +pub struct GuildModifyWelcomeScreenSchema { + pub enabled: Option, + pub description: Option, + /// Max of 5 + pub welcome_channels: Option>, +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] +/// # Reference: +/// See +pub struct GuildTemplateCreateSchema { + /// Name of the template (1-100 characters) + pub name: String, + /// Description of the template (max 120 characters) + pub description: Option, +} diff --git a/src/types/schema/invites.rs b/src/types/schema/invites.rs new file mode 100644 index 0000000..542c990 --- /dev/null +++ b/src/types/schema/invites.rs @@ -0,0 +1,26 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] +/// Query parameters for the `Get Invite` route. +/// +/// # Reference: +/// Read: +pub struct GetInvitesSchema { + pub with_counts: Option, +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd, Eq, Ord)] +/// # Reference: +/// See +pub struct GuildVanityInviteResponse { + pub code: String, + #[serde(default)] + pub uses: Option +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd, Eq, Ord)] +/// # Reference: +/// See +pub struct GuildCreateVanitySchema { + pub code: String, +} \ No newline at end of file diff --git a/src/types/schema/message.rs b/src/types/schema/message.rs index 7551b6b..e8d57c7 100644 --- a/src/types/schema/message.rs +++ b/src/types/schema/message.rs @@ -7,13 +7,15 @@ use serde::{Deserialize, Serialize}; use crate::types::entities::{ AllowedMention, Component, Embed, MessageReference, PartialDiscordFileAttachment, }; -use crate::types::{Attachment, Snowflake}; +use crate::types::{ + Attachment, EmbedType, Message, MessageFlags, MessageType, ReactionType, Snowflake, +}; #[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)] #[serde(rename_all = "snake_case")] pub struct MessageSendSchema { #[serde(rename = "type")] - pub message_type: Option, + pub message_type: Option, pub content: Option, pub nonce: Option, pub tts: Option, @@ -25,7 +27,7 @@ pub struct MessageSendSchema { pub attachments: Option>, } -#[derive(Debug)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum MessageSearchEndpoint { GuildChannel(Snowflake), Channel(Snowflake), @@ -54,13 +56,13 @@ pub struct MessageSearchQuery { pub attachment_extension: Option>, pub attachment_filename: Option>, pub author_id: Option>, - pub author_type: Option>, + pub author_type: Option>, pub channel_id: Option>, pub command_id: Option>, pub content: Option, pub embed_provider: Option>, - pub embed_type: Option>, - pub has: Option>, + pub embed_type: Option>, + pub has: Option>, pub include_nsfw: Option, pub limit: Option, pub link_hostname: Option>, @@ -70,8 +72,8 @@ pub struct MessageSearchQuery { pub min_id: Option, pub offset: Option, pub pinned: Option, - pub sort_by: Option, - pub sort_order: Option, + pub sort_by: Option, + pub sort_order: Option, } impl std::default::Default for MessageSearchQuery { @@ -102,6 +104,79 @@ impl std::default::Default for MessageSearchQuery { } } +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Copy)] +#[serde(rename_all = "snake_case")] +pub enum AuthorType { + User, + #[serde(rename = "-user")] + NotUser, + Bot, + #[serde(rename = "-bot")] + NotBot, + Webhook, + #[serde(rename = "-webhook")] + NotWebhook, +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Copy)] +#[serde(rename_all = "snake_case")] +pub enum HasType { + Image, + #[serde(rename = "-image")] + NotImage, + Sound, + #[serde(rename = "-sound")] + NotSound, + Video, + #[serde(rename = "-video")] + NotVideo, + File, + #[serde(rename = "-file")] + NotFile, + Sticker, + #[serde(rename = "-sticker")] + NotSticker, + Embed, + #[serde(rename = "-embed")] + NotEmbed, + Link, + #[serde(rename = "-link")] + NotLink, + Poll, + #[serde(rename = "-poll")] + NotPoll, + Snapshot, + #[serde(rename = "-snapshot")] + NotSnapshot, +} + +#[derive( + Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Hash, +)] +#[serde(rename_all = "snake_case")] +pub enum SortType { + #[default] + Timestamp, + Relevance, +} + +#[derive( + Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Hash, +)] +pub enum SortOrder { + #[default] + #[serde(rename = "desc")] + Descending, + #[serde(rename = "asc")] + Ascending, +} + +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)] +pub struct MessageSearchResponse { + pub messages: Vec, + pub total_results: u64, +} + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct CreateGreetMessage { pub sticker_ids: Vec, @@ -118,13 +193,21 @@ pub struct MessageAck { #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd)] pub struct MessageModifySchema { - content: Option, - embeds: Option>, - embed: Option, - allowed_mentions: Option, - components: Option>, - flags: Option, - files: Option>, - payload_json: Option, - attachments: Option>, + pub content: Option, + pub embeds: Option>, + pub embed: Option, + pub allowed_mentions: Option, + pub components: Option>, + pub flags: Option, + pub files: Option>, + pub payload_json: Option, + pub attachments: Option>, +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd, Copy, Eq, Hash, Ord)] +pub struct ReactionQuerySchema { + pub after: Option, + pub limit: Option, + #[serde(rename = "type")] + pub reaction_type: Option, } diff --git a/src/types/schema/mod.rs b/src/types/schema/mod.rs index d353e09..09e542e 100644 --- a/src/types/schema/mod.rs +++ b/src/types/schema/mod.rs @@ -3,6 +3,7 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. pub use apierror::*; +pub use audit_log::*; pub use auth::*; pub use channel::*; pub use guild::*; @@ -10,8 +11,11 @@ pub use message::*; pub use relationship::*; pub use role::*; pub use user::*; +pub use invites::*; +pub use voice_state::*; mod apierror; +mod audit_log; mod auth; mod channel; mod guild; @@ -19,3 +23,11 @@ mod message; mod relationship; mod role; mod user; +mod invites; +mod voice_state; + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq, PartialOrd, Eq, Ord)] +pub struct GenericSearchQueryWithLimit { + pub query: String, + pub limit: Option, +} \ No newline at end of file diff --git a/src/types/schema/role.rs b/src/types/schema/role.rs index 168d999..805d026 100644 --- a/src/types/schema/role.rs +++ b/src/types/schema/role.rs @@ -2,16 +2,17 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +use crate::types::{PermissionFlags, Snowflake}; use serde::{Deserialize, Serialize}; -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] #[serde(rename_all = "snake_case")] /// Represents the schema which needs to be sent to create or modify a Role. /// See: [https://docs.spacebar.chat/routes/#cmp--schemas-rolemodifyschema](https://docs.spacebar.chat/routes/#cmp--schemas-rolemodifyschema) pub struct RoleCreateModifySchema { pub name: Option, - pub permissions: Option, - pub color: Option, + pub permissions: Option, + pub color: Option, pub hoist: Option, pub icon: Option>, pub unicode_emoji: Option, @@ -19,11 +20,11 @@ pub struct RoleCreateModifySchema { pub position: Option, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[serde(rename_all = "snake_case")] /// Represents the schema which needs to be sent to update a roles' position. /// See: [https://docs.spacebar.chat/routes/#cmp--schemas-rolepositionupdateschema](https://docs.spacebar.chat/routes/#cmp--schemas-rolepositionupdateschema) pub struct RolePositionUpdateSchema { - pub id: String, + pub id: Snowflake, pub position: u16, } diff --git a/src/types/schema/user.rs b/src/types/schema/user.rs index 7d21754..e2600a4 100644 --- a/src/types/schema/user.rs +++ b/src/types/schema/user.rs @@ -4,24 +4,91 @@ use std::collections::HashMap; +use chrono::NaiveDate; use serde::{Deserialize, Serialize}; use crate::types::Snowflake; -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)] #[serde(rename_all = "snake_case")] /// A schema used to modify a user. +/// +/// See pub struct UserModifySchema { + /// The user's new username (2-32 characters) + /// + /// Requires that `current_password` is set. pub username: Option, + // TODO: Maybe add a special discriminator type? + /// Requires that `current_password` is set. + pub discriminator: Option, + /// The user's display name (1-32 characters) + /// + /// # Note + /// + /// This is not yet implemented on Spacebar + pub global_name: Option, + // TODO: Add a CDN data type pub avatar: Option, - pub bio: Option, - pub accent_color: Option, - pub banner: Option, - pub current_password: Option, - pub new_password: Option, - pub code: Option, + /// Note: This is not yet implemented on Spacebar + pub avatar_decoration_id: Option, + /// Note: This is not yet implemented on Spacebar + pub avatar_decoration_sku_id: Option, + /// The user's email address; if changing from a verified email, email_token must be provided + /// + /// Requires that `current_password` is set. + // TODO: Is ^ up to date? One would think this may not be the case, since email_token exists pub email: Option, - pub discriminator: Option, + /// The user's email token from their previous email, required if a new email is set. + /// + /// See and + /// for changing the user's email. + /// + /// # Note + /// + /// This is not yet implemented on Spacebar + pub email_token: Option, + /// The user's pronouns (max 40 characters) + /// + /// # Note + /// + /// This is not yet implemented on Spacebar + pub pronouns: Option, + /// The user's banner. + /// + /// Can only be changed for premium users + pub banner: Option, + /// The user's bio (max 190 characters) + pub bio: Option, + /// The user's accent color, as a hex integer + pub accent_color: Option, + /// The user's [UserFlags]. + /// + /// Only [UserFlags::PREMIUM_PROMO_DISMISSED], [UserFlags::HAS_UNREAD_URGENT_MESSAGES] + /// and DISABLE_PREMIUM can be set. + /// + /// # Note + /// + /// This is not yet implemented on Spacebar + pub flags: Option, + /// The user's date of birth, can only be set once + /// + /// Requires that `current_password` is set. + pub date_of_birth: Option, + /// The user's current password (if the account does not have a password, this sets it) + /// + /// Required for updating `username`, `discriminator`, `email`, `date_of_birth` and + /// `new_password` + #[serde(rename = "password")] + pub current_password: Option, + /// The user's new password (8-72 characters) + /// + /// Requires that `current_password` is set. + /// + /// Regenerates the user's token + pub new_password: Option, + /// Spacebar only field, potentially same as `email_token` + pub code: Option, } /// A schema used to create a private channel. @@ -33,7 +100,7 @@ pub struct UserModifySchema { /// /// # Reference: /// Read: -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)] pub struct PrivateChannelCreateSchema { pub recipients: Option>, pub access_tokens: Option>, diff --git a/src/types/schema/voice_state.rs b/src/types/schema/voice_state.rs new file mode 100644 index 0000000..3beafa6 --- /dev/null +++ b/src/types/schema/voice_state.rs @@ -0,0 +1,15 @@ +use crate::types::Snowflake; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd, Copy)] +/// # Reference: +/// See +pub struct VoiceStateUpdateSchema { + /// The ID of the channel the user is currently in + pub channel_id: Option, + /// Whether to suppress the user + pub suppress: Option, + /// The time at which the user requested to speak + pub request_to_speak_timestamp: Option>, +} diff --git a/src/types/utils/jwt.rs b/src/types/utils/jwt.rs index 6addb4c..0919a5a 100644 --- a/src/types/utils/jwt.rs +++ b/src/types/utils/jwt.rs @@ -19,7 +19,7 @@ pub struct Claims { /// When the token was issued pub iat: i64, pub email: String, - pub id: String, + pub id: Snowflake, } impl Claims { @@ -27,7 +27,7 @@ impl Claims { let unix = chrono::Utc::now().timestamp(); Self { exp: unix + (60 * 60 * 24), - id: id.to_string(), + id: *id, iat: unix, email: user.to_string(), } diff --git a/src/types/utils/mod.rs b/src/types/utils/mod.rs index 8879688..5608fe7 100644 --- a/src/types/utils/mod.rs +++ b/src/types/utils/mod.rs @@ -11,3 +11,5 @@ pub mod jwt; mod regexes; mod rights; mod snowflake; +pub mod serde; + diff --git a/src/types/utils/rights.rs b/src/types/utils/rights.rs index 4b1aa13..598f782 100644 --- a/src/types/utils/rights.rs +++ b/src/types/utils/rights.rs @@ -2,7 +2,11 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +use std::num::ParseIntError; +use std::str::FromStr; use bitflags::bitflags; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use crate::types::UserFlags; bitflags! { /// Rights are instance-wide, per-user permissions for everything you may perform on the instance, @@ -14,6 +18,8 @@ bitflags! { /// /// # Reference /// See + #[derive(Debug, Clone, Copy, Eq, PartialEq, chorus_macros::SerdeBitFlags)] + #[cfg_attr(feature = "sqlx", derive(chorus_macros::SqlxBitFlags))] pub struct Rights: u64 { /// All rights const OPERATOR = 1 << 0; @@ -151,6 +157,12 @@ impl Rights { } } +impl Default for Rights { + fn default() -> Self { + Self::empty() + } +} + #[allow(dead_code)] // FIXME: Remove this when we use this fn all_rights() -> Rights { Rights::OPERATOR diff --git a/src/types/utils/serde.rs b/src/types/utils/serde.rs new file mode 100644 index 0000000..da41f4a --- /dev/null +++ b/src/types/utils/serde.rs @@ -0,0 +1,280 @@ +use chrono::{LocalResult, NaiveDateTime}; +use core::fmt; +use serde::de::Error; +use serde::{de, Deserialize, Deserializer}; + +#[doc(hidden)] +#[derive(Debug, Copy, Clone)] +pub struct SecondsStringTimestampVisitor; + +/// Ser/de to/from timestamps in seconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::{TimeZone, DateTime, Utc}; +/// # use serde::{Deserialize, Serialize}; +/// use chorus::types::serde::ts_seconds_str; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_seconds_str")] +/// time: DateTime +/// } +/// +/// let time = Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap(); +/// let my_s = S { +/// time: time.clone(), +/// }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":"1431684000"}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` + +pub mod ts_seconds_str { + use super::serde_from; + use super::SecondsStringTimestampVisitor; + use chrono::TimeZone; + use chrono::{DateTime, LocalResult, Utc}; + use core::fmt; + use serde::{de, ser}; + + /// Serialize a UTC datetime into an integer number of seconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{TimeZone, DateTime, Utc}; + /// # use serde::Serialize; + /// use chorus::types::serde::ts_seconds_str::serialize as to_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_ts")] + /// time: DateTime + /// } + /// + /// let my_s = S { + /// time: Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap(), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":"1431684000"}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(dt: &DateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_str(&format!("{}", dt.timestamp())) + } + + /// Deserialize a `DateTime` from a seconds timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, TimeZone, Utc}; + /// # use serde::Deserialize; + /// use chorus::types::serde::ts_seconds_str::deserialize as from_ts; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_ts")] + /// time: DateTime + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": "1431684000" }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1431684000, 0).unwrap() }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_str(SecondsStringTimestampVisitor) + } + + impl<'de> de::Visitor<'de> for SecondsStringTimestampVisitor { + type Value = DateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in seconds") + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + serde_from( + Utc.timestamp_opt(value.parse::().map_err(|e| E::custom(e))?, 0), + &value, + ) + } + } +} + +/// Ser/de to/from optional timestamps in seconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::{TimeZone, DateTime, Utc}; +/// # use serde::{Deserialize, Serialize}; +/// use chorus::types::serde::ts_seconds_option_str; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_seconds_option_str")] +/// time: Option> +/// } +/// +/// let time = Some(Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap()); +/// let my_s = S { +/// time: time.clone(), +/// }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":"1431684000"}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_seconds_option_str { + use super::SecondsStringTimestampVisitor; + use chrono::{DateTime, Utc}; + use core::fmt; + use serde::{de, ser}; + + /// Serialize a UTC datetime into an integer number of seconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{TimeZone, DateTime, Utc}; + /// # use serde::Serialize; + /// use chorus::types::serde::ts_seconds_option_str::serialize as from_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "from_tsopt")] + /// time: Option> + /// } + /// + /// let my_s = S { + /// time: Some(Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap()), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":"1431684000"}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(opt: &Option>, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.timestamp().to_string()), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `DateTime` from a seconds timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, TimeZone, Utc}; + /// # use serde::Deserialize; + /// use chorus::types::serde::ts_seconds_option_str::deserialize as from_tsopt; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_tsopt")] + /// time: Option> + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": "1431684000" }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1431684000, 0).single() }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionSecondsTimestampVisitor) + } + + struct OptionSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionSecondsTimestampVisitor { + type Value = Option>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in seconds or none") + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_str(SecondsStringTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + +pub(crate) fn serde_from(me: LocalResult, _ts: &V) -> Result +where + E: de::Error, + V: fmt::Display, + T: fmt::Display, +{ + // TODO: Make actual error type + match me { + LocalResult::None => Err(E::custom("value is not a legal timestamp")), + LocalResult::Ambiguous(_min, _max) => Err(E::custom("value is an ambiguous timestamp")), + LocalResult::Single(val) => Ok(val), + } +} + +#[derive(serde::Deserialize, serde::Serialize)] +#[serde(untagged)] +enum StringOrU64 { + String(String), + U64(u64), +} + +pub fn string_or_u64<'de, D>(d: D) -> Result +where + D: Deserializer<'de>, +{ + match StringOrU64::deserialize(d)? { + StringOrU64::String(s) => s.parse::().map_err(D::Error::custom), + StringOrU64::U64(u) => Ok(u), + } +} diff --git a/src/types/utils/snowflake.rs b/src/types/utils/snowflake.rs index 1582085..a021f90 100644 --- a/src/types/utils/snowflake.rs +++ b/src/types/utils/snowflake.rs @@ -8,8 +8,6 @@ use std::{ }; use chrono::{DateTime, TimeZone, Utc}; -#[cfg(feature = "sqlx")] -use sqlx::Type; /// 2015-01-01 const EPOCH: i64 = 1420070400000; @@ -19,8 +17,6 @@ const EPOCH: i64 = 1420070400000; /// # Reference /// See #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -#[cfg_attr(feature = "sqlx", derive(Type))] -#[cfg_attr(feature = "sqlx", sqlx(transparent))] pub struct Snowflake(pub u64); impl Snowflake { @@ -102,6 +98,27 @@ impl<'de> serde::Deserialize<'de> for Snowflake { } } +#[cfg(feature = "sqlx")] +impl sqlx::Type for Snowflake { + fn type_info() -> ::TypeInfo { + >::type_info() + } +} + +#[cfg(feature = "sqlx")] +impl<'q> sqlx::Encode<'q, sqlx::MySql> for Snowflake { + fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> sqlx::encode::IsNull { + >::encode_by_ref(&self.0.to_string(), buf) + } +} + +#[cfg(feature = "sqlx")] +impl<'d> sqlx::Decode<'d, sqlx::MySql> for Snowflake { + fn decode(value: >::ValueRef) -> Result { + >::decode(value).map(|s| s.parse::().map(Snowflake).unwrap()) + } +} + #[cfg(test)] mod test { use chrono::{DateTime, Utc}; diff --git a/src/voice/gateway/backends/mod.rs b/src/voice/gateway/backends/mod.rs index 7f3f3dd..23f2767 100644 --- a/src/voice/gateway/backends/mod.rs +++ b/src/voice/gateway/backends/mod.rs @@ -4,24 +4,7 @@ #[cfg(all(not(target_arch = "wasm32"), feature = "voice_gateway"))] pub mod tungstenite; -#[cfg(all(not(target_arch = "wasm32"), feature = "voice_gateway"))] -pub use tungstenite::*; #[cfg(all(target_arch = "wasm32", feature = "voice_gateway"))] pub mod wasm; -#[cfg(all(target_arch = "wasm32", feature = "voice_gateway"))] -pub use wasm::*; -#[cfg(all(not(target_arch = "wasm32"), feature = "voice_gateway"))] -pub type Sink = tungstenite::TungsteniteSink; -#[cfg(all(not(target_arch = "wasm32"), feature = "voice_gateway"))] -pub type Stream = tungstenite::TungsteniteStream; -#[cfg(all(not(target_arch = "wasm32"), feature = "voice_gateway"))] -pub type WebSocketBackend = tungstenite::TungsteniteBackend; - -#[cfg(all(target_arch = "wasm32", feature = "voice_gateway"))] -pub type Sink = wasm::WasmSink; -#[cfg(all(target_arch = "wasm32", feature = "voice_gateway"))] -pub type Stream = wasm::WasmStream; -#[cfg(all(target_arch = "wasm32", feature = "voice_gateway"))] -pub type WebSocketBackend = wasm::WasmBackend; diff --git a/src/voice/gateway/backends/tungstenite.rs b/src/voice/gateway/backends/tungstenite.rs index 26cc0fe..599274d 100644 --- a/src/voice/gateway/backends/tungstenite.rs +++ b/src/voice/gateway/backends/tungstenite.rs @@ -2,76 +2,16 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use futures_util::{ - stream::{SplitSink, SplitStream}, - StreamExt, -}; -use tokio::net::TcpStream; -use tokio_tungstenite::{ - connect_async_tls_with_config, tungstenite, Connector, MaybeTlsStream, WebSocketStream, -}; +use crate::voice::gateway::VoiceGatewayMessage; -use crate::{errors::VoiceGatewayError, voice::gateway::VoiceGatewayMessage}; - -#[derive(Debug, Clone)] -pub struct TungsteniteBackend; - -// These could be made into inherent associated types when that's stabilized -pub type TungsteniteSink = - SplitSink>, tungstenite::Message>; -pub type TungsteniteStream = SplitStream>>; - -impl TungsteniteBackend { - pub async fn connect( - websocket_url: &str, - ) -> Result<(TungsteniteSink, TungsteniteStream), crate::errors::VoiceGatewayError> { - let mut roots = rustls::RootCertStore::empty(); - let certs = rustls_native_certs::load_native_certs(); - - if let Err(e) = certs { - log::error!("Failed to load platform native certs! {:?}", e); - return Err(VoiceGatewayError::CannotConnect { - error: format!("{:?}", e), - }); - } - - for cert in certs.unwrap() { - roots.add(&rustls::Certificate(cert.0)).unwrap(); - } - 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(VoiceGatewayError::CannotConnect { - error: e.to_string(), - }) - } - }; - - Ok(websocket_stream.split()) - } -} - -impl From for tungstenite::Message { +impl From for tokio_tungstenite::tungstenite::Message { fn from(message: VoiceGatewayMessage) -> Self { Self::Text(message.0) } } -impl From for VoiceGatewayMessage { - fn from(value: tungstenite::Message) -> Self { +impl From for VoiceGatewayMessage { + fn from(value: tokio_tungstenite::tungstenite::Message) -> Self { Self(value.to_string()) } } diff --git a/src/voice/gateway/backends/wasm.rs b/src/voice/gateway/backends/wasm.rs index a39723e..7b069c6 100644 --- a/src/voice/gateway/backends/wasm.rs +++ b/src/voice/gateway/backends/wasm.rs @@ -2,36 +2,9 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use futures_util::{ - stream::{SplitSink, SplitStream}, - StreamExt, -}; - -use ws_stream_wasm::*; - -use crate::errors::VoiceGatewayError; +use ws_stream_wasm::WsMessage; use crate::voice::gateway::VoiceGatewayMessage; -#[derive(Debug, Clone)] -pub struct WasmBackend; - -// These could be made into inherent associated types when that's stabilized -pub type WasmSink = SplitSink; -pub type WasmStream = SplitStream; - -impl WasmBackend { - pub async fn connect(websocket_url: &str) -> Result<(WasmSink, WasmStream), VoiceGatewayError> { - let (_, websocket_stream) = match WsMeta::connect(websocket_url, None).await { - Ok(stream) => Ok(stream), - Err(e) => Err(VoiceGatewayError::CannotConnect { - error: e.to_string(), - }), - }?; - - Ok(websocket_stream.split()) - } -} - impl From for WsMessage { fn from(message: VoiceGatewayMessage) -> Self { Self::Text(message.0) @@ -50,3 +23,4 @@ impl From for VoiceGatewayMessage { } } } + diff --git a/src/voice/gateway/events.rs b/src/voice/gateway/events.rs index af043b3..22db83b 100644 --- a/src/voice/gateway/events.rs +++ b/src/voice/gateway/events.rs @@ -2,9 +2,10 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +use pubserve::Publisher; + use crate::{ errors::VoiceGatewayError, - gateway::GatewayEvent, types::{ SessionDescription, SessionUpdate, Speaking, SsrcDefinition, VoiceBackendVersion, VoiceClientConnectFlags, VoiceClientConnectPlatform, VoiceClientDisconnection, @@ -14,15 +15,15 @@ use crate::{ #[derive(Default, Debug)] pub struct VoiceEvents { - pub voice_ready: GatewayEvent, - pub backend_version: GatewayEvent, - pub session_description: GatewayEvent, - pub session_update: GatewayEvent, - pub speaking: GatewayEvent, - pub ssrc_definition: GatewayEvent, - pub client_disconnect: GatewayEvent, - pub client_connect_flags: GatewayEvent, - pub client_connect_platform: GatewayEvent, - pub media_sink_wants: GatewayEvent, - pub error: GatewayEvent, + pub voice_ready: Publisher, + pub backend_version: Publisher, + pub session_description: Publisher, + pub session_update: Publisher, + pub speaking: Publisher, + pub ssrc_definition: Publisher, + pub client_disconnect: Publisher, + pub client_connect_flags: Publisher, + pub client_connect_platform: Publisher, + pub media_sink_wants: Publisher, + pub error: Publisher, } diff --git a/src/voice/gateway/gateway.rs b/src/voice/gateway/gateway.rs index 4727ae4..ba4df80 100644 --- a/src/voice/gateway/gateway.rs +++ b/src/voice/gateway/gateway.rs @@ -6,14 +6,17 @@ use std::{sync::Arc, time::Duration}; use log::*; +use pubserve::Publisher; use tokio::sync::Mutex; use futures_util::SinkExt; use futures_util::StreamExt; +use crate::gateway::Sink; +use crate::gateway::Stream; +use crate::gateway::WebSocketBackend; use crate::{ errors::VoiceGatewayError, - gateway::GatewayEvent, types::{ VoiceGatewayReceivePayload, VoiceHelloData, WebSocketEvent, VOICE_BACKEND_VERSION, VOICE_CLIENT_CONNECT_FLAGS, VOICE_CLIENT_CONNECT_PLATFORM, VOICE_CLIENT_DISCONNECT, @@ -21,14 +24,10 @@ use crate::{ VOICE_READY, VOICE_RESUME, VOICE_SELECT_PROTOCOL, VOICE_SESSION_DESCRIPTION, VOICE_SESSION_UPDATE, VOICE_SPEAKING, VOICE_SSRC_DEFINITION, }, - voice::gateway::{ - heartbeat::VoiceHeartbeatThreadCommunication, VoiceGatewayMessage, WebSocketBackend, - }, + voice::gateway::{heartbeat::VoiceHeartbeatThreadCommunication, VoiceGatewayMessage}, }; -use super::{ - events::VoiceEvents, heartbeat::VoiceHeartbeatHandler, Sink, Stream, VoiceGatewayHandle, -}; +use super::{events::VoiceEvents, heartbeat::VoiceHeartbeatHandler, VoiceGatewayHandle}; #[derive(Debug)] pub struct VoiceGateway { @@ -45,10 +44,17 @@ impl VoiceGateway { pub async fn spawn(websocket_url: String) -> Result { // Append the needed things to the websocket url let processed_url = format!("wss://{}/?v=7", websocket_url); - trace!("Created voice socket url: {}", processed_url.clone()); + trace!("VGW: Connecting to {}", processed_url.clone()); let (websocket_send, mut websocket_receive) = - WebSocketBackend::connect(&processed_url).await?; + match WebSocketBackend::connect(&processed_url).await { + Ok(streams) => streams, + Err(e) => { + return Err(VoiceGatewayError::CannotConnect { + error: format!("{:?}", e), + }) + } + }; let shared_websocket_send = Arc::new(Mutex::new(websocket_send)); @@ -154,7 +160,7 @@ impl VoiceGateway { /// (Called for every event in handle_message) async fn handle_event<'a, T: WebSocketEvent + serde::Deserialize<'a>>( data: &'a str, - event: &mut GatewayEvent, + event: &mut Publisher, ) -> Result<(), serde_json::Error> { let data_deserialize_result: Result = serde_json::from_str(data); @@ -162,7 +168,7 @@ impl VoiceGateway { return Err(data_deserialize_result.err().unwrap()); } - event.notify(data_deserialize_result.unwrap()).await; + event.publish(data_deserialize_result.unwrap()).await; Ok(()) } @@ -176,7 +182,7 @@ impl VoiceGateway { if let Some(error) = msg.error() { warn!("GW: Received error {:?}, connection will close..", error); self.close().await; - self.events.lock().await.error.notify(error).await; + self.events.lock().await.error.publish(error).await; } else { warn!( "Message unrecognised: {:?}, please open an issue on the chorus github", diff --git a/src/voice/gateway/handle.rs b/src/voice/gateway/handle.rs index b48080a..8750f12 100644 --- a/src/voice/gateway/handle.rs +++ b/src/voice/gateway/handle.rs @@ -11,13 +11,16 @@ use futures_util::SinkExt; use serde_json::json; use tokio::sync::Mutex; -use crate::types::{ - SelectProtocol, Speaking, SsrcDefinition, VoiceGatewaySendPayload, VoiceIdentify, - VOICE_BACKEND_VERSION, VOICE_IDENTIFY, VOICE_SELECT_PROTOCOL, VOICE_SPEAKING, - VOICE_SSRC_DEFINITION, +use crate::{ + gateway::Sink, + types::{ + SelectProtocol, Speaking, SsrcDefinition, VoiceGatewaySendPayload, VoiceIdentify, + VOICE_BACKEND_VERSION, VOICE_IDENTIFY, VOICE_SELECT_PROTOCOL, VOICE_SPEAKING, + VOICE_SSRC_DEFINITION, + }, }; -use super::{events::VoiceEvents, Sink, VoiceGatewayMessage}; +use super::{events::VoiceEvents, VoiceGatewayMessage}; /// Represents a handle to a Voice Gateway connection. /// Using this handle you can send Gateway Events directly. diff --git a/src/voice/gateway/heartbeat.rs b/src/voice/gateway/heartbeat.rs index 2b9fde5..945bfbd 100644 --- a/src/voice/gateway/heartbeat.rs +++ b/src/voice/gateway/heartbeat.rs @@ -26,13 +26,11 @@ use tokio::sync::{ use tokio::task; use crate::{ - gateway::heartbeat::HEARTBEAT_ACK_TIMEOUT, + gateway::{heartbeat::HEARTBEAT_ACK_TIMEOUT, Sink}, types::{VoiceGatewaySendPayload, VOICE_HEARTBEAT, VOICE_HEARTBEAT_ACK}, voice::gateway::VoiceGatewayMessage, }; -use super::Sink; - /// Handles sending heartbeats to the voice gateway in another thread #[allow(dead_code)] // FIXME: Remove this, once all fields of VoiceHeartbeatHandler are used #[derive(Debug)] diff --git a/src/voice/udp/backends/tokio.rs b/src/voice/udp/backends/tokio.rs index e529a0c..de3ffe5 100644 --- a/src/voice/udp/backends/tokio.rs +++ b/src/voice/udp/backends/tokio.rs @@ -6,7 +6,7 @@ use std::net::SocketAddr; use crate::errors::VoiceUdpError; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub struct TokioBackend; pub type TokioSocket = tokio::net::UdpSocket; diff --git a/src/voice/udp/events.rs b/src/voice/udp/events.rs index d4917fe..8958b71 100644 --- a/src/voice/udp/events.rs +++ b/src/voice/udp/events.rs @@ -3,23 +3,24 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use discortp::{rtcp::Rtcp, rtp::Rtp}; +use pubserve::Publisher; -use crate::{gateway::GatewayEvent, types::WebSocketEvent}; +use crate::types::WebSocketEvent; impl WebSocketEvent for Rtp {} impl WebSocketEvent for Rtcp {} #[derive(Debug)] pub struct VoiceUDPEvents { - pub rtp: GatewayEvent, - pub rtcp: GatewayEvent, + pub rtp: Publisher, + pub rtcp: Publisher, } impl Default for VoiceUDPEvents { fn default() -> Self { Self { - rtp: GatewayEvent::new(), - rtcp: GatewayEvent::new(), + rtp: Publisher::new(), + rtcp: Publisher::new(), } } } diff --git a/src/voice/udp/handler.rs b/src/voice/udp/handler.rs index c21709b..df2a57c 100644 --- a/src/voice/udp/handler.rs +++ b/src/voice/udp/handler.rs @@ -214,7 +214,7 @@ impl UdpHandler { .lock() .await .rtp - .notify(rtp_with_decrypted_data) + .publish(rtp_with_decrypted_data) .await; } Demuxed::Rtcp(rtcp) => { @@ -251,7 +251,7 @@ impl UdpHandler { } }; - self.events.lock().await.rtcp.notify(rtcp_data).await; + self.events.lock().await.rtcp.publish(rtcp_data).await; } Demuxed::FailedParse(e) => { trace!("VUDP: Failed to parse packet: {:?}", e); diff --git a/test-wasm.sh b/test-wasm.sh new file mode 100755 index 0000000..ca803ef --- /dev/null +++ b/test-wasm.sh @@ -0,0 +1,6 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. +#!/bin/sh + +wasm-pack test --firefox --headless -- --no-default-features --target=wasm32-unknown-unknown "$@" \ No newline at end of file diff --git a/tests/auth.rs b/tests/auth.rs index 35007f1..705328a 100644 --- a/tests/auth.rs +++ b/tests/auth.rs @@ -2,12 +2,16 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +use std::str::FromStr; + use chorus::types::{LoginSchema, RegisterSchema}; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] wasm_bindgen_test_configure!(run_in_browser); +use chrono::NaiveDate; + mod common; #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] @@ -16,7 +20,7 @@ async fn test_registration() { let mut bundle = common::setup().await; let reg = RegisterSchema { username: "Hiiii".into(), - date_of_birth: Some("2000-01-01".to_string()), + date_of_birth: Some(NaiveDate::from_str("2000-01-01").unwrap()), consent: true, ..Default::default() }; @@ -32,7 +36,7 @@ async fn test_login() { username: "Hiiii".into(), email: Some("testuser1@integrationtesting.xyz".into()), password: Some("Correct-Horse-Battery-Staple1".into()), - date_of_birth: Some("2000-01-01".to_string()), + date_of_birth: Some(NaiveDate::from_str("2000-01-01").unwrap()), consent: true, ..Default::default() }; @@ -54,7 +58,7 @@ async fn test_wrong_login() { username: "Hiiii".into(), email: Some("testuser2@integrationtesting.xyz".into()), password: Some("Correct-Horse-Battery-Staple1".into()), - date_of_birth: Some("2000-01-01".to_string()), + date_of_birth: Some(NaiveDate::from_str("2000-01-01").unwrap()), consent: true, ..Default::default() }; diff --git a/tests/channels.rs b/tests/channels.rs index 14359d2..eb1c120 100644 --- a/tests/channels.rs +++ b/tests/channels.rs @@ -2,10 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use chorus::types::{ - self, Channel, GetChannelMessagesSchema, MessageSendSchema, PermissionFlags, - PermissionOverwrite, PrivateChannelCreateSchema, RelationshipType, Snowflake, -}; +use chorus::types::{self, Channel, GetChannelMessagesSchema, MessageSendSchema, PermissionFlags, PermissionOverwrite, PermissionOverwriteType, PrivateChannelCreateSchema, RelationshipType, Snowflake}; mod common; @@ -69,16 +66,13 @@ async fn modify_channel() { .unwrap(); assert_eq!(modified_channel.name, Some(CHANNEL_NAME.to_string())); - let permission_override = PermissionFlags::from_vec(Vec::from([ - PermissionFlags::MANAGE_CHANNELS, - PermissionFlags::MANAGE_MESSAGES, - ])); + let permission_override = PermissionFlags::MANAGE_CHANNELS | PermissionFlags::MANAGE_MESSAGES; let user_id: types::Snowflake = bundle.user.object.read().unwrap().id; let permission_override = PermissionOverwrite { id: user_id, - overwrite_type: "1".to_string(), + overwrite_type: PermissionOverwriteType::Member, allow: permission_override, - deny: "0".to_string(), + deny: PermissionFlags::empty(), }; let channel_id: Snowflake = bundle.channel.read().unwrap().id; Channel::modify_permissions( @@ -174,8 +168,7 @@ async fn create_dm() { dm_channel .recipients .as_ref() - .unwrap() - .get(0) + .unwrap().first() .unwrap() .read() .unwrap() @@ -248,8 +241,7 @@ async fn remove_add_person_from_to_dm() { dm_channel .recipients .as_ref() - .unwrap() - .get(0) + .unwrap().first() .unwrap() .read() .unwrap() diff --git a/tests/common/mod.rs b/tests/common/mod.rs index f24c7e6..f2f0663 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -2,17 +2,21 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use chorus::gateway::{Gateway, Shared}; -use chorus::types::IntoShared; +use std::str::FromStr; + +use chorus::gateway::{Gateway, GatewayOptions}; +use chorus::types::{IntoShared, PermissionFlags}; use chorus::{ instance::{ChorusUser, Instance}, types::{ Channel, ChannelCreateSchema, Guild, GuildCreateSchema, RegisterSchema, - RoleCreateModifySchema, RoleObject, + RoleCreateModifySchema, RoleObject, Shared }, UrlBundle, }; +use chrono::NaiveDate; + #[allow(dead_code)] #[derive(Debug)] pub(crate) struct TestBundle { @@ -30,7 +34,7 @@ impl TestBundle { let register_schema = RegisterSchema { username: username.to_string(), consent: true, - date_of_birth: Some("2000-01-01".to_string()), + date_of_birth: Some(NaiveDate::from_str("2000-01-01").unwrap()), ..Default::default() }; self.instance @@ -46,7 +50,7 @@ impl TestBundle { limits: self.user.limits.clone(), settings: self.user.settings.clone(), object: self.user.object.clone(), - gateway: Gateway::spawn(self.instance.urls.wss.clone()) + gateway: Gateway::spawn(self.instance.urls.wss.clone(), GatewayOptions::default()) .await .unwrap(), } @@ -55,12 +59,16 @@ impl TestBundle { // Set up a test by creating an Instance and a User. Reduces Test boilerplate. pub(crate) async fn setup() -> TestBundle { + + // So we can get logs when tests fail + let _ = simple_logger::SimpleLogger::with_level(simple_logger::SimpleLogger::new(), log::LevelFilter::Debug).init(); + let instance = Instance::new("http://localhost:3001/api").await.unwrap(); // Requires the existence of the below user. let reg = RegisterSchema { username: "integrationtestuser".into(), consent: true, - date_of_birth: Some("2000-01-01".to_string()), + date_of_birth: Some(NaiveDate::from_str("2000-01-01").unwrap()), ..Default::default() }; let guild_create_schema = GuildCreateSchema { @@ -100,7 +108,7 @@ pub(crate) async fn setup() -> TestBundle { let role_create_schema: chorus::types::RoleCreateModifySchema = RoleCreateModifySchema { name: Some("Bundle role".to_string()), - permissions: Some("8".to_string()), // Administrator permissions + permissions: PermissionFlags::from_bits(8), // Administrator permissions hoist: Some(true), icon: None, unicode_emoji: Some("".to_string()), @@ -115,7 +123,7 @@ pub(crate) async fn setup() -> TestBundle { let urls = UrlBundle::new( "http://localhost:3001/api".to_string(), "http://localhost:3001/api".to_string(), - "ws://localhost:3001".to_string(), + "ws://localhost:3001/".to_string(), "http://localhost:3001".to_string(), ); TestBundle { diff --git a/tests/gateway.rs b/tests/gateway.rs index 9f72a64..1c8f56a 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -14,6 +14,7 @@ use chorus::types::{ self, Channel, ChannelCreateSchema, ChannelModifySchema, GatewayReady, IntoShared, RoleCreateModifySchema, RoleObject, }; +use pubserve::Subscriber; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] @@ -30,7 +31,9 @@ use wasmtimer::tokio::sleep; async fn test_gateway_establish() { let bundle = common::setup().await; - let _: GatewayHandle = Gateway::spawn(bundle.urls.wss.clone()).await.unwrap(); + let _: GatewayHandle = Gateway::spawn(bundle.urls.wss.clone(), GatewayOptions::default()) + .await + .unwrap(); common::teardown(bundle).await } @@ -40,7 +43,7 @@ struct GatewayReadyObserver { } #[async_trait] -impl Observer for GatewayReadyObserver { +impl Subscriber for GatewayReadyObserver { async fn update(&self, _data: &GatewayReady) { self.channel.send(()).await.unwrap(); } @@ -52,7 +55,9 @@ impl Observer for GatewayReadyObserver { async fn test_gateway_authenticate() { let bundle = common::setup().await; - let gateway: GatewayHandle = Gateway::spawn(bundle.urls.wss.clone()).await.unwrap(); + let gateway: GatewayHandle = Gateway::spawn(bundle.urls.wss.clone(), GatewayOptions::default()) + .await + .unwrap(); let (ready_send, mut ready_receive) = tokio::sync::mpsc::channel(1); @@ -79,9 +84,9 @@ async fn test_gateway_authenticate() { println!("Timed out waiting for event, failing.."); assert!(false); } - // Sucess, we have received it + // Success, we have received it Some(_) = ready_receive.recv() => {} - }; + } common::teardown(bundle).await } @@ -125,7 +130,7 @@ async fn test_self_updating_structs() { .gateway .observe_and_into_inner(bundle.guild.clone()) .await; - assert!(guild.channels.is_none()); + assert!(guild.channels.is_empty()); Channel::create( &mut bundle.user, @@ -145,8 +150,8 @@ async fn test_self_updating_structs() { .gateway .observe_and_into_inner(guild.into_shared()) .await; - assert!(guild.channels.is_some()); - assert!(guild.channels.as_ref().unwrap().len() == 1); + assert!(!guild.channels.is_empty()); + assert_eq!(guild.channels.len(), 1); common::teardown(bundle).await } @@ -160,13 +165,12 @@ async fn test_recursive_self_updating_structs() { // Observe Guild, make sure it has no channels let guild = bundle.user.gateway.observe(guild.clone()).await; let inner_guild = guild.read().unwrap().clone(); - assert!(inner_guild.roles.is_none()); + assert!(inner_guild.roles.is_empty()); // Create Role let permissions = types::PermissionFlags::CONNECT | types::PermissionFlags::MANAGE_EVENTS; - let permissions = Some(permissions.to_string()); let mut role_create_schema: types::RoleCreateModifySchema = RoleCreateModifySchema { name: Some("cool person".to_string()), - permissions, + permissions: Some(permissions), hoist: Some(true), icon: None, unicode_emoji: Some("".to_string()), @@ -186,7 +190,7 @@ async fn test_recursive_self_updating_structs() { .await; // Update Guild and check for Guild let inner_guild = guild.read().unwrap().clone(); - assert!(inner_guild.roles.is_some()); + assert!(!inner_guild.roles.is_empty()); // Update the Role role_create_schema.name = Some("yippieee".to_string()); RoleObject::modify(&mut bundle.user, guild_id, role.id, role_create_schema) @@ -202,8 +206,7 @@ async fn test_recursive_self_updating_structs() { let guild = bundle.user.gateway.observe(bundle.guild.clone()).await; let inner_guild = guild.read().unwrap().clone(); let guild_roles = inner_guild.roles; - let guild_role = guild_roles.unwrap(); - let guild_role_inner = guild_role.get(0).unwrap().read().unwrap().clone(); + let guild_role_inner = guild_roles.first().unwrap().read().unwrap().clone(); assert_eq!(guild_role_inner.name, "yippieee".to_string()); common::teardown(bundle).await; } diff --git a/tests/guilds.rs b/tests/guilds.rs index e3a73f5..8399567 100644 --- a/tests/guilds.rs +++ b/tests/guilds.rs @@ -70,15 +70,6 @@ async fn guild_create_ban() { ) .await .unwrap(); - assert!(Guild::create_ban( - guild.id, - other_user_id, - None, - GuildBanCreateSchema::default(), - &mut bundle.user, - ) - .await - .is_err()); common::teardown(bundle).await } diff --git a/tests/messages.rs b/tests/messages.rs index 3ca6e16..c4ceca5 100644 --- a/tests/messages.rs +++ b/tests/messages.rs @@ -114,7 +114,7 @@ async fn search_messages() { .await .unwrap(); assert!(!query_result.is_empty()); - assert_eq!(query_result.get(0).unwrap().id, message.id); + assert_eq!(query_result.first().unwrap().id, message.id); } #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] @@ -139,8 +139,7 @@ async fn test_stickies() { assert_eq!( Message::get_sticky(channel.id, &mut bundle.user) .await - .unwrap() - .get(0) + .unwrap().first() .unwrap() .id, message.id diff --git a/tests/relationships.rs b/tests/relationships.rs index 2eea5b3..4e57b22 100644 --- a/tests/relationships.rs +++ b/tests/relationships.rs @@ -50,7 +50,7 @@ async fn test_get_relationships() { .unwrap(); let relationships = user.get_relationships().await.unwrap(); assert_eq!( - relationships.get(0).unwrap().id, + relationships.first().unwrap().id, other_user.object.read().unwrap().id ); common::teardown(bundle).await @@ -71,20 +71,20 @@ async fn test_modify_relationship_friends() { .unwrap(); let relationships = user.get_relationships().await.unwrap(); assert_eq!( - relationships.get(0).unwrap().id, + relationships.first().unwrap().id, other_user.object.read().unwrap().id ); assert_eq!( - relationships.get(0).unwrap().relationship_type, + relationships.first().unwrap().relationship_type, RelationshipType::Incoming ); let relationships = other_user.get_relationships().await.unwrap(); assert_eq!( - relationships.get(0).unwrap().id, + relationships.first().unwrap().id, user.object.read().unwrap().id ); assert_eq!( - relationships.get(0).unwrap().relationship_type, + relationships.first().unwrap().relationship_type, RelationshipType::Outgoing ); let _ = user @@ -95,7 +95,7 @@ async fn test_modify_relationship_friends() { .get_relationships() .await .unwrap() - .get(0) + .first() .unwrap() .relationship_type, RelationshipType::Friends @@ -124,11 +124,11 @@ async fn test_modify_relationship_block() { assert_eq!(relationships, Vec::::new()); let relationships = other_user.get_relationships().await.unwrap(); assert_eq!( - relationships.get(0).unwrap().id, + relationships.first().unwrap().id, user.object.read().unwrap().id ); assert_eq!( - relationships.get(0).unwrap().relationship_type, + relationships.first().unwrap().relationship_type, RelationshipType::Blocked ); other_user.remove_relationship(user_id).await.unwrap(); diff --git a/tests/roles.rs b/tests/roles.rs index 3246140..faf2719 100644 --- a/tests/roles.rs +++ b/tests/roles.rs @@ -15,10 +15,9 @@ mod common; async fn create_and_get_roles() { let mut bundle = common::setup().await; let permissions = types::PermissionFlags::CONNECT | types::PermissionFlags::MANAGE_EVENTS; - let permissions = Some(permissions.to_string()); let role_create_schema: types::RoleCreateModifySchema = RoleCreateModifySchema { name: Some("cool person".to_string()), - permissions, + permissions: Some(permissions), hoist: Some(true), icon: None, unicode_emoji: Some("".to_string()), diff --git a/tests/types.rs b/tests/types.rs index 8895bb4..27f3239 100644 --- a/tests/types.rs +++ b/tests/types.rs @@ -920,7 +920,7 @@ mod entities { .clone() ); let flags = ApplicationFlags::from_bits(0).unwrap(); - assert!(application.flags() == flags); + assert!(application.flags == flags); } #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] @@ -953,45 +953,7 @@ mod entities { } mod guild { - use std::hash::{Hash, Hasher}; - - use chorus::types::{Guild, GuildInvite}; - - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), test)] - fn guild_hash() { - let id: u64 = 1; - let mut guild1 = Guild::default(); - let mut guild2 = Guild::default(); - guild1.id = id.into(); - guild2.id = id.into(); - let mut hasher1 = std::collections::hash_map::DefaultHasher::new(); - guild1.hash(&mut hasher1); - - let mut hasher2 = std::collections::hash_map::DefaultHasher::new(); - guild2.hash(&mut hasher2); - - assert_eq!(hasher1.finish(), hasher2.finish()); - } - - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), test)] - fn guild_invite_hash() { - let id: u64 = 1; - let mut invite1 = GuildInvite::default(); - let mut invite2 = GuildInvite::default(); - invite1.channel_id = id.into(); - invite2.channel_id = id.into(); - invite1.guild_id = id.into(); - invite2.guild_id = id.into(); - let mut hasher1 = std::collections::hash_map::DefaultHasher::new(); - invite1.hash(&mut hasher1); - - let mut hasher2 = std::collections::hash_map::DefaultHasher::new(); - invite2.hash(&mut hasher2); - - assert_eq!(hasher1.finish(), hasher2.finish()); - } + use chorus::types::Guild; #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), test)] @@ -1006,38 +968,6 @@ mod entities { } } - mod relationship { - use chorus::types::{IntoShared, Relationship, User}; - - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), test)] - fn relationship_partial_eq() { - let user = User::default(); - // These 2 users are different, because they do not have the same Snowflake "id". - let user_2 = User::default(); - let relationship_1 = Relationship { - id: 32_u64.into(), - relationship_type: chorus::types::RelationshipType::Friends, - nickname: Some("Xenia".to_string()), - user: user.into_public_user().into_shared(), - since: None, - }; - - let relationship_2 = Relationship { - id: 32_u64.into(), - relationship_type: chorus::types::RelationshipType::Friends, - nickname: Some("Xenia".to_string()), - user: user_2.into_public_user().into_shared(), - since: None, - }; - - // This should succeed, even though the two users' IDs are different. This is because - // `User` is only `PartialEq`, and the actual user object is not checked, since the - // `RwLock` would have to be locked. - assert_eq!(relationship_1, relationship_2); - } - } - mod message { use chorus::types::{Message, Snowflake};