├── .github └── workflows │ ├── release.yml │ └── rust.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── examples-wasm ├── Cargo.toml └── examples │ └── subscriptions.rs ├── examples ├── Cargo.toml ├── examples │ ├── cynic-mulitiple-subscriptions.rs │ ├── cynic-single-subscription.rs │ ├── graphql-client-single-subscription.rs │ ├── graphql-client-subscription.graphql │ └── tokio.rs └── src │ └── lib.rs ├── release-plz.toml ├── schemas └── books.graphql ├── src ├── doc_utils.rs ├── error.rs ├── graphql.rs ├── lib.rs ├── logging.rs ├── native.rs ├── next │ ├── actor.rs │ ├── builder.rs │ ├── connection.rs │ ├── keepalive.rs │ ├── mod.rs │ ├── production_future.rs │ └── stream.rs ├── protocol.rs ├── sink_ext.rs └── ws_stream_wasm.rs └── tests ├── cynic-tests.rs ├── graphql-client-subscription.graphql ├── graphql-client-tests.rs └── subscription_server └── mod.rs /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release-plz 2 | 3 | permissions: 4 | pull-requests: write 5 | contents: write 6 | 7 | on: 8 | push: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | release-plz: 14 | name: Release-plz 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Generate GitHub token 18 | uses: actions/create-github-app-token@v1 19 | id: generate-token 20 | with: 21 | app-id: ${{ secrets.RELEASE_APP_ID }} 22 | private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} 23 | - name: Checkout repository 24 | uses: actions/checkout@v4 25 | with: 26 | fetch-depth: 0 27 | token: ${{ steps.generate-token.outputs.token }} 28 | - name: Install Rust toolchain 29 | uses: dtolnay/rust-toolchain@stable 30 | - name: Run release-plz 31 | uses: MarcoIeni/release-plz-action@7fe60ae5d741fc80fa624aef172aee9de2b98747 32 | env: 33 | GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} 34 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 35 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: rust 2 | 3 | on: 4 | push: 5 | branches: [main, staging, trying] 6 | pull_request: 7 | branches: [main] 8 | 9 | env: 10 | CARGO_INCREMENTAL: 0 11 | CARGO_TERM_COLOR: always 12 | CI: 1 13 | RUSTFLAGS: "-W rust-2021-compatibility -D warnings" 14 | RUST_BACKTRACE: short 15 | 16 | jobs: 17 | check-format: 18 | # Skip draft release PRs 19 | if: github.actor_id != '166155226' || github.event_name != 'pull_request' || github.event.pull_request.draft == false 20 | 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v4 24 | with: 25 | fetch-depth: 0 26 | - uses: dtolnay/rust-toolchain@1.76.0 27 | with: 28 | components: rustfmt 29 | - name: Check format 30 | run: cargo fmt --all --check 31 | 32 | build: 33 | # Skip draft release PRs 34 | if: github.actor_id != '166155226' || github.event_name != 'pull_request' || github.event.pull_request.draft == false 35 | 36 | runs-on: ubuntu-latest 37 | 38 | steps: 39 | - uses: actions/checkout@v4 40 | with: 41 | fetch-depth: 0 42 | - uses: dtolnay/rust-toolchain@1.76.0 43 | with: 44 | components: clippy 45 | - uses: Swatinem/rust-cache@v2 46 | - name: Build 47 | run: cargo build --workspace --all-features 48 | - name: Build tests 49 | run: cargo test --workspace --all-features --no-run 50 | - name: Run tests 51 | run: cargo test --workspace --all-features 52 | - name: Build examples 53 | run: cargo build --workspace --all-features --examples 54 | - name: Build examples tests 55 | run: cargo test --workspace --all-features --examples --no-run 56 | - name: Run examples tests 57 | run: cargo test --workspace --all-features --examples 58 | - name: Run clippy 59 | run: cargo clippy --all --all-features 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo will have compiled files and executables 2 | /debug/ 3 | /target/ 4 | 5 | # These are backup files generated by rustfmt 6 | **/*.rs.bk 7 | 8 | # MSVC Windows builds of rustc generate these, which store debugging information 9 | *.pdb 10 | 11 | # Log 12 | *.log 13 | 14 | # JetBrains 15 | /.idea/ 16 | 17 | # Visual Studio Code 18 | /.vscode/ 19 | 20 | # Local History for Visual Studio Code 21 | /.history/ 22 | 23 | # Converage 24 | /coverage/ 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is roughly based on [Keep a 6 | Changelog](http://keepachangelog.com/en/1.0.0/). 7 | 8 | This project intends to inhere to [Semantic 9 | Versioning](http://semver.org/spec/v2.0.0.html), but has not yet reached 1.0 so 10 | all APIs might be changed. 11 | 12 | ## Unreleased 13 | 14 | ## v0.11.1 - 2024-10-29 15 | 16 | ### Bug Fixes 17 | 18 | - Fixed an infinite loop in the actor that could occur when all `Client` 19 | instances are dropped and there are still ongoing operations ([#124](https://github.com/obmarg/graphql-ws-client/pull/124)) 20 | 21 | ### Contributors 22 | 23 | Thanks to the people who contributed to this release: 24 | 25 | - @Sytten 26 | 27 | ## v0.11.0 - 2024-10-25 28 | 29 | ### Changes 30 | 31 | - Updated tungstenite bounds to support 0.24 as well as 0.23. ([#121](https://github.com/obmarg/graphql-ws-client/pull/121)) 32 | 33 | ### Contributors 34 | 35 | Thanks to the people who contributed to this release: 36 | 37 | - @Sytten 38 | 39 | ## v0.10.2 - 2024-08-21 40 | 41 | ### Bug Fixes 42 | 43 | - send graphql-specific ping instead of ws ping frame 44 | ([#117](https://github.com/obmarg/graphql-ws-client/pull/117)) 45 | 46 | ### Changes 47 | 48 | - Tidied up some documentation ([#114](https://github.com/obmarg/graphql-ws-client/pull/114)) 49 | - Handled some clippy lints ([#114](https://github.com/obmarg/graphql-ws-client/pull/114)) 50 | 51 | ### Bug Fixes 52 | 53 | - graphql-transport-ws pings are now responded to with graphql-tranport-ws pongs, 54 | rather than websocket pongs ([#116](https://github.com/obmarg/graphql-ws-client/pull/116)) 55 | - Keep alives now send `graphql-transport-ws` ping messages instead of websocket ping 56 | frames ([#117](https://github.com/obmarg/graphql-ws-client/pull/117)) 57 | 58 | ### Contributors 59 | 60 | Thanks to the people who contributed to this release: 61 | 62 | - @vorporeal 63 | - @szgupta 64 | - @carlocorradini 65 | 66 | ## v0.10.1 - 2024-06-08 67 | 68 | ### Bug Fixes 69 | 70 | - Fixed some compile errors when `ws_stream_wasm` was enabled and `tungstenite` 71 | was not ([#111](https://github.com/obmarg/graphql-ws-client/pull/111)) 72 | 73 | ## v0.10.0 - 2024-06-08 74 | 75 | ### Breaking Changes 76 | 77 | - All Connection trait functions now return impl Future instead of BoxFuture 78 | ([#108](https://github.com/obmarg/graphql-ws-client/pull/108)) 79 | - Removed the legacy API that was deprecated in v0.8.0 80 | ([#81](https://github.com/obmarg/graphql-ws-client/pull/81)) 81 | - The deprecated `async-tungstenite` feature has been removed. Use the 82 | `tungstenite` feature instead, which works with `async-tungtenite`, 83 | `tokio-tungstenite` and any other library that provides a 84 | `futures::{Stream, Sink}` based tungsetenite interface. 85 | ([#106](https://github.com/obmarg/graphql-ws-client/pull/106)) 86 | 87 | ### Changes 88 | 89 | - MSRV is now 1.76 90 | - Updated dependencies ([#100](https://github.com/obmarg/graphql-ws-client/pull/100)) 91 | - `tungstenite` `0.23` 92 | - `graphql_client` `0.14` 93 | - Removed unused dependencies ([#105](https://github.com/obmarg/graphql-ws-client/pull/105)) 94 | - `async-trait` 95 | - `pin-project-lite` 96 | 97 | ### Contributors 98 | 99 | Thanks to the people who contributed to this release: 100 | 101 | - @carlocorradini 102 | 103 | ## v0.9.0 - 2024-06-08 104 | 105 | ### Breaking Changes 106 | 107 | - The `no-logging` feature has been removed in favour of a default `logging` 108 | feature ([#97](https://github.com/obmarg/graphql-ws-client/pull/97)) 109 | 110 | ### New Features 111 | 112 | - Added keep-alive functionality. When enabled this will send periodic pings 113 | when the connection appears inactive. If these pings are not replied to, the 114 | connection will be considered broken. 115 | ([#93](https://github.com/obmarg/graphql-ws-client/pull/93), 116 | [#94](https://github.com/obmarg/graphql-ws-client/pull/94), 117 | [#103](https://github.com/obmarg/graphql-ws-client/pull/103)) 118 | - Client is now Debug ([#101](https://github.com/obmarg/graphql-ws-client/pull/101)) 119 | 120 | ### Changes 121 | 122 | - simplify keep alive implementation 123 | - pin release-plz version ([#91](https://github.com/obmarg/graphql-ws-client/pull/91)) 124 | 125 | ### Contributors 126 | 127 | Thanks to the people who contributed to this release: 128 | 129 | - @rhishikeshj 130 | - @carlocorradini 131 | 132 | ## [0.8.2](https://github.com/obmarg/graphql-ws-client/compare/v0.8.1...v0.8.2) - 2024-04-09 133 | 134 | ### Changes 135 | 136 | - `Client::subscribe` now takes `&self` instead of `&mut self`, to make sharing a 137 | `Client` among threads easier ([#88](https://github.com/obmarg/graphql-ws-client/pull/88)) 138 | - `Client` is now clone, again to make sharing among threads easier 139 | ([#88](https://github.com/obmarg/graphql-ws-client/pull/88)) 140 | 141 | ## v0.8.1 - 2024-03-21 142 | 143 | ### Fixes 144 | 145 | - Hopefully fixed the docs.rs build 146 | 147 | ## v0.8.0 - 2024-03-17 148 | 149 | ### Breaking Changes 150 | 151 | - `async_tungstenite` is no longer a default feautre, you should explicitly 152 | enable it if you need it. 153 | - Updated to `tungstenite` 0.21 154 | - MSRV is now 1.69 (there was no official MSRV before) 155 | - Subscription IDs sent to the server are now just monotonic numbers rather 156 | than uuids. 157 | 158 | ### Deprecations 159 | 160 | These will be removed in a future version, probably in v0.9.0 161 | 162 | - `AsyncWebsocketClient` and all its supporting traits and structs are now 163 | deprecated. 164 | - The `async-tungstenite` feature flag is deprecated and will be removed in 165 | favour of `tungstenite` eventually. 166 | 167 | ### New Features 168 | 169 | - Added an entirely new client API as a replacement for the old API. 170 | - Added a `subscribe` function to `next::ClientBuilder` to make 171 | creating a single subscription on a given connection easier. 172 | 173 | ### Changes 174 | 175 | - `graphql-ws-client` now depends only on `tungstenite` and not directly on 176 | `async-tungstenite` (or `tokio-tungstenite`). This should allow it to work 177 | with more versions of the async libraries (provided they support the same 178 | `tungstenite` version). 179 | 180 | ## v0.8.0-rc.2 - 2024-02-13 181 | 182 | ### Breaking Changes 183 | 184 | - `async-tungstenite` is no longer automatically enabled when adding any of the 185 | client feature flags. 186 | 187 | ### Changes 188 | 189 | - `graphql-ws-client` now depends only on `tungstenite` and not directly on 190 | `async-tungstenite` (or `tokio-tungstenite`) This should allow it to work 191 | with more versions of the async libraries (provided they support the same 192 | `tungstenite` version). 193 | 194 | ### Bug Fixes 195 | 196 | - Fixed `tokio-tungstenite` support by switching the `async_tungstenite` 197 | `Connection` impl to a generic impl on any `tungstenite` compatible `Stream` 198 | & `Sink`. 199 | 200 | ## v0.8.0-rc.1 - 2024-02-10 201 | 202 | ### Breaking Changes 203 | 204 | - The `next` api is now available at the top level rather than the `next` 205 | module. 206 | - `async_tungstenite` is no longer a default feautre, you should explicitly 207 | enable it if you need it 208 | - Updated to `async_tungstenite` 0.25 209 | - Renamed `Client::streaming_operation` to subscribe in `next` api. 210 | - MSRV is now 1.69 (there was no official MSRV before) 211 | 212 | ### Deprecations 213 | 214 | These will be removed in a future version, probably in v0.9.0 215 | 216 | - `AsyncWebsocketClient` and all its supporting traits and structs are now 217 | deprecated. 218 | 219 | ### New Features 220 | 221 | - Added a `subscribe` function to `next::ClientBuilder` to make 222 | creating a single subscription on a given connection easier. 223 | 224 | ## v0.8.0-alpha.2 - 2024-01-30 225 | 226 | ### Breaking Changes 227 | 228 | - `Error::Close` now has a code as well as a reason. 229 | 230 | ### New Features 231 | 232 | - Added a `next` module with a significant re-work of the API 233 | 234 | ## v0.8.0-alpha.1 - 2024-01-19 235 | 236 | ### Breaking Changes 237 | 238 | - Subscription IDs sent to the server are now just monotonic numbers rather 239 | than uuids. 240 | - `SubscriptionStream` no longer takes `GraphqlClient` as a generic parameter 241 | - Removed the `GraphqlClient` trait and all its uses 242 | - Changed the `StreamingOperation` trait: 243 | - Removed the `GenericResponse` associated type 244 | - `decode_response` now always takes a `serde_json::value` - I expect most 245 | implementations of this will now just be a call to `serde_json::from_value` 246 | 247 | ## v0.7.0 - 2024-01-03 248 | 249 | ### Breaking Changes 250 | 251 | - `async_tungstenite` dependency has been updated to 0.24 252 | 253 | ## v0.6.0 - 2023-10-01 254 | 255 | ### Breaking Changes 256 | 257 | - `async_tungstenite` dependency has been updated to 0.23, pulling in a tungstenite security fix 258 | 259 | ## v0.5.0 - 2023-07-13 260 | 261 | ### Breaking Changes 262 | 263 | - `cynic` dependency has been updated to 3 264 | - `graphql_client` dependency has been updated to 0.13 265 | - `async_tungstenite` dependency has been updated to 0.22 266 | 267 | ### Bug Fixes 268 | 269 | - Updated the cynic code to support operations with variables. 270 | 271 | ## v0.4.0 - 2023-04-02 272 | 273 | ### Breaking Changes 274 | 275 | - `cynic` dependency has been updated to 2.2 276 | - `async_tungstenite` dependency has been updated to 0.19 277 | - `graphql_client` dependency has been updated to 0.12 278 | 279 | ### Bug Fixes 280 | 281 | - The examples now compile. 282 | 283 | ## v0.3.0 - 2022-12-26 284 | 285 | ### New Features 286 | 287 | - Added support for wasm with the `ws_stream_wasm` library. 288 | 289 | ### Changes 290 | 291 | - Updated some dependency versions 292 | 293 | ### Bug Fixes 294 | 295 | - `graphql-ws-client` will no longer panic when it receives a `Ping` event. 296 | - The `AsyncWebsocketClientBuilder` type is now `Send`. 297 | 298 | ## v0.2.0 - 2022-01-27 299 | 300 | ### Breaking Changes 301 | 302 | - Clients are now created through builder types rather than directly. See the 303 | `AsyncWebsocketClientBuilder` type (or it's `CynicClientBuilder` alias) 304 | - `cynic` support is now behind the `client-cynic` feature. 305 | - It's now recommended to use a custom impl of `futures::task::Spawn` for tokio 306 | rather than the `async_executors` crate, as `async_executors` is not 307 | compatible with `#[tokio::main]`. An example impl is provided for `tokio` in 308 | the examples folder. 309 | 310 | ### New Features 311 | 312 | - `graphql_client` is now supported, behind the `client-graphql-client` feature. 313 | - `graphql-ws-client` now has an example 314 | - `streaming_operation` now returns a `SubscriptionStream` type. This is still 315 | a `Stream` but also exposes a `stop_operation` function that can be used to 316 | tell the server to end the stream. 317 | - `cynic` no longer requires the use of `async_executors` - it now only 318 | requires an `impl futures::task::Spawn`. An example is included for tokio. 319 | Old code using the `AsyncStd` executor should continue to work but tokio 320 | users are encouraged to provide their own using the example. 321 | 322 | ### Bug Fixes 323 | 324 | - `graphql-ws-client` has better support for running inside `#[tokio::main]` 325 | - Cynic will now use the `log` crate rather than printing to stdout. 326 | 327 | ## v0.1.0 - 2021-04-04 328 | 329 | - Initial release 330 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at cynic@polyandglot.dev. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | If you want to contribute to graphql-ws-client, that's great! There's plenty 4 | of improvements to make, features that still need built and probably a bug or 5 | two hiding in there. 6 | 7 | Please note we have a [code of conduct][COC], please follow it in all your 8 | interactions with the project. 9 | 10 | There's a few different ways to contribute: 11 | 12 | - If you've found a bug or have an idea for a feature you'd like to implement, 13 | please [create an issue][NewIssue] to discuss or send a message [on 14 | discord][Discord]. 15 | - If you'd like to contribute to an existing issue feel free to comment on the 16 | issue and let us know. If anything isn't clear someone will be happy to 17 | explain (the project is still fairly new, and I have treated the issue 18 | tracker like a notepad occasionally, sorry about that 😬). 19 | - If you'd like to contribute but you're not sure how: 20 | - We have [Good First Issues][GFI] labelled. 21 | - We [use milestones][Milestones] to plan what's going into the next release, 22 | any free work in there would be great to pick up. 23 | - The documentation (on [docs.rs](https://docs.rs/graphql-ws-client)) could 24 | always be improved. 25 | - You can just try to use graphql-ws-client in a project - we'd appreciate 26 | any bug reports or feedback on the API, and it's always nice seeing what 27 | people build. 28 | 29 | ## Getting Help 30 | 31 | If you have any questions about how to do anything or otherwise need help, please just ask. 32 | You can do this by: 33 | 34 | 1. [Joining the discord server][Discord] 35 | 2. [Asking in Discussions][Discussions] 36 | 37 | ## Coding Guidelines 38 | 39 | 1. `cargo fmt` everything 40 | 2. Don't use unsafe 41 | 3. Don't leave warnings in the codebase. 42 | 43 | ## Pull Request Process 44 | 45 | 1. Ensure the build passes and your code meets the above guidelines. 46 | 2. Ideally make sure you have some tests for any new functionality you've added. 47 | 3. Update CHANGELOG.md with a new entry for your changes (under unreleased) 48 | 4. Please write a reasonably detailed pull request message to help reviewer 49 | understand the context around & reason for the changes. 50 | 5. A maintainer will review the PR as soon as possible, and once it is approved 51 | will merge and make a release. 52 | 53 | [COC]: ./CODE_OF_CONDUCT.md 54 | [Discord]: https://discord.gg/Y5xDmDP 55 | [Discussions]: https://github.com/obmarg/graphql-ws-client/discussions/new 56 | [GFI]: https://github.com/obmarg/graphql-ws-client/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22 57 | [Milestones]: https://github.com/obmarg/graphql-ws-client/milestones 58 | [NewIssue]: https://github.com/obmarg/graphql-ws-client/issues/new/choose 59 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "Inflector" 7 | version = "0.11.4" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" 10 | dependencies = [ 11 | "lazy_static", 12 | "regex", 13 | ] 14 | 15 | [[package]] 16 | name = "addr2line" 17 | version = "0.21.0" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 20 | dependencies = [ 21 | "gimli", 22 | ] 23 | 24 | [[package]] 25 | name = "adler" 26 | version = "1.0.2" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 29 | 30 | [[package]] 31 | name = "aho-corasick" 32 | version = "1.1.1" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" 35 | dependencies = [ 36 | "memchr", 37 | ] 38 | 39 | [[package]] 40 | name = "aliasable" 41 | version = "0.1.3" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" 44 | 45 | [[package]] 46 | name = "ascii" 47 | version = "0.9.3" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" 50 | 51 | [[package]] 52 | name = "ascii_utils" 53 | version = "0.9.3" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" 56 | 57 | [[package]] 58 | name = "assert_matches" 59 | version = "1.5.0" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" 62 | 63 | [[package]] 64 | name = "async-attributes" 65 | version = "1.1.2" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" 68 | dependencies = [ 69 | "quote", 70 | "syn 1.0.109", 71 | ] 72 | 73 | [[package]] 74 | name = "async-channel" 75 | version = "1.9.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" 78 | dependencies = [ 79 | "concurrent-queue", 80 | "event-listener 2.5.3", 81 | "futures-core", 82 | ] 83 | 84 | [[package]] 85 | name = "async-channel" 86 | version = "2.3.1" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" 89 | dependencies = [ 90 | "concurrent-queue", 91 | "event-listener-strategy", 92 | "futures-core", 93 | "pin-project-lite", 94 | ] 95 | 96 | [[package]] 97 | name = "async-executor" 98 | version = "1.5.4" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "2c1da3ae8dabd9c00f453a329dfe1fb28da3c0a72e2478cdcd93171740c20499" 101 | dependencies = [ 102 | "async-lock", 103 | "async-task", 104 | "concurrent-queue", 105 | "fastrand 2.0.1", 106 | "futures-lite 1.13.0", 107 | "slab", 108 | ] 109 | 110 | [[package]] 111 | name = "async-global-executor" 112 | version = "2.3.1" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" 115 | dependencies = [ 116 | "async-channel 1.9.0", 117 | "async-executor", 118 | "async-io", 119 | "async-lock", 120 | "blocking", 121 | "futures-lite 1.13.0", 122 | "once_cell", 123 | ] 124 | 125 | [[package]] 126 | name = "async-graphql" 127 | version = "7.0.1" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "b16926f97f683ff3b47b035cc79622f3d6a374730b07a5d9051e81e88b5f1904" 130 | dependencies = [ 131 | "async-graphql-derive", 132 | "async-graphql-parser", 133 | "async-graphql-value", 134 | "async-stream", 135 | "async-trait", 136 | "base64 0.13.1", 137 | "bytes", 138 | "fast_chemail", 139 | "fnv", 140 | "futures-util", 141 | "handlebars", 142 | "http", 143 | "indexmap", 144 | "mime", 145 | "multer", 146 | "num-traits", 147 | "once_cell", 148 | "pin-project-lite", 149 | "regex", 150 | "serde", 151 | "serde_json", 152 | "serde_urlencoded", 153 | "static_assertions_next", 154 | "tempfile", 155 | "thiserror", 156 | ] 157 | 158 | [[package]] 159 | name = "async-graphql-axum" 160 | version = "7.0.1" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "de3415c9dbaf54397292da0bb81a907e2b989661ce068e4ccfebac33dc9e245e" 163 | dependencies = [ 164 | "async-graphql", 165 | "async-trait", 166 | "axum", 167 | "bytes", 168 | "futures-util", 169 | "serde_json", 170 | "tokio", 171 | "tokio-stream", 172 | "tokio-util", 173 | "tower-service", 174 | ] 175 | 176 | [[package]] 177 | name = "async-graphql-derive" 178 | version = "7.0.1" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "a6a7349168b79030e3172a620f4f0e0062268a954604e41475eff082380fe505" 181 | dependencies = [ 182 | "Inflector", 183 | "async-graphql-parser", 184 | "darling 0.20.5", 185 | "proc-macro-crate", 186 | "proc-macro2", 187 | "quote", 188 | "strum", 189 | "syn 2.0.37", 190 | "thiserror", 191 | ] 192 | 193 | [[package]] 194 | name = "async-graphql-parser" 195 | version = "7.0.1" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "58fdc0adf9f53c2b65bb0ff5170cba1912299f248d0e48266f444b6f005deb1d" 198 | dependencies = [ 199 | "async-graphql-value", 200 | "pest", 201 | "serde", 202 | "serde_json", 203 | ] 204 | 205 | [[package]] 206 | name = "async-graphql-value" 207 | version = "7.0.1" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "7cf4d4e86208f4f9b81a503943c07e6e7f29ad3505e6c9ce6431fe64dc241681" 210 | dependencies = [ 211 | "bytes", 212 | "indexmap", 213 | "serde", 214 | "serde_json", 215 | ] 216 | 217 | [[package]] 218 | name = "async-io" 219 | version = "1.13.0" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" 222 | dependencies = [ 223 | "async-lock", 224 | "autocfg", 225 | "cfg-if", 226 | "concurrent-queue", 227 | "futures-lite 1.13.0", 228 | "log", 229 | "parking", 230 | "polling", 231 | "rustix 0.37.24", 232 | "slab", 233 | "socket2 0.4.9", 234 | "waker-fn", 235 | ] 236 | 237 | [[package]] 238 | name = "async-lock" 239 | version = "2.8.0" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" 242 | dependencies = [ 243 | "event-listener 2.5.3", 244 | ] 245 | 246 | [[package]] 247 | name = "async-std" 248 | version = "1.12.0" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" 251 | dependencies = [ 252 | "async-attributes", 253 | "async-channel 1.9.0", 254 | "async-global-executor", 255 | "async-io", 256 | "async-lock", 257 | "crossbeam-utils", 258 | "futures-channel", 259 | "futures-core", 260 | "futures-io", 261 | "futures-lite 1.13.0", 262 | "gloo-timers", 263 | "kv-log-macro", 264 | "log", 265 | "memchr", 266 | "once_cell", 267 | "pin-project-lite", 268 | "pin-utils", 269 | "slab", 270 | "wasm-bindgen-futures", 271 | ] 272 | 273 | [[package]] 274 | name = "async-stream" 275 | version = "0.3.5" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" 278 | dependencies = [ 279 | "async-stream-impl", 280 | "futures-core", 281 | "pin-project-lite", 282 | ] 283 | 284 | [[package]] 285 | name = "async-stream-impl" 286 | version = "0.3.5" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" 289 | dependencies = [ 290 | "proc-macro2", 291 | "quote", 292 | "syn 2.0.37", 293 | ] 294 | 295 | [[package]] 296 | name = "async-task" 297 | version = "4.4.1" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "b9441c6b2fe128a7c2bf680a44c34d0df31ce09e5b7e401fcca3faa483dbc921" 300 | 301 | [[package]] 302 | name = "async-trait" 303 | version = "0.1.73" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" 306 | dependencies = [ 307 | "proc-macro2", 308 | "quote", 309 | "syn 2.0.37", 310 | ] 311 | 312 | [[package]] 313 | name = "async-tungstenite" 314 | version = "0.28.0" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "90e661b6cb0a6eb34d02c520b052daa3aa9ac0cc02495c9d066bbce13ead132b" 317 | dependencies = [ 318 | "async-std", 319 | "futures-io", 320 | "futures-util", 321 | "log", 322 | "pin-project-lite", 323 | "tokio", 324 | "tungstenite 0.24.0", 325 | ] 326 | 327 | [[package]] 328 | name = "async_io_stream" 329 | version = "0.3.3" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" 332 | dependencies = [ 333 | "futures", 334 | "pharos", 335 | "rustc_version", 336 | ] 337 | 338 | [[package]] 339 | name = "atomic-waker" 340 | version = "1.1.2" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 343 | 344 | [[package]] 345 | name = "autocfg" 346 | version = "1.1.0" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 349 | 350 | [[package]] 351 | name = "axum" 352 | version = "0.7.4" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "1236b4b292f6c4d6dc34604bb5120d85c3fe1d1aa596bd5cc52ca054d13e7b9e" 355 | dependencies = [ 356 | "async-trait", 357 | "axum-core", 358 | "base64 0.21.4", 359 | "bytes", 360 | "futures-util", 361 | "http", 362 | "http-body", 363 | "http-body-util", 364 | "hyper", 365 | "hyper-util", 366 | "itoa", 367 | "matchit", 368 | "memchr", 369 | "mime", 370 | "percent-encoding", 371 | "pin-project-lite", 372 | "rustversion", 373 | "serde", 374 | "serde_json", 375 | "serde_path_to_error", 376 | "serde_urlencoded", 377 | "sha1", 378 | "sync_wrapper", 379 | "tokio", 380 | "tokio-tungstenite", 381 | "tower", 382 | "tower-layer", 383 | "tower-service", 384 | "tracing", 385 | ] 386 | 387 | [[package]] 388 | name = "axum-core" 389 | version = "0.4.3" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" 392 | dependencies = [ 393 | "async-trait", 394 | "bytes", 395 | "futures-util", 396 | "http", 397 | "http-body", 398 | "http-body-util", 399 | "mime", 400 | "pin-project-lite", 401 | "rustversion", 402 | "sync_wrapper", 403 | "tower-layer", 404 | "tower-service", 405 | "tracing", 406 | ] 407 | 408 | [[package]] 409 | name = "axum-macros" 410 | version = "0.4.1" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "00c055ee2d014ae5981ce1016374e8213682aa14d9bf40e48ab48b5f3ef20eaa" 413 | dependencies = [ 414 | "heck", 415 | "proc-macro2", 416 | "quote", 417 | "syn 2.0.37", 418 | ] 419 | 420 | [[package]] 421 | name = "backtrace" 422 | version = "0.3.69" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 425 | dependencies = [ 426 | "addr2line", 427 | "cc", 428 | "cfg-if", 429 | "libc", 430 | "miniz_oxide", 431 | "object", 432 | "rustc-demangle", 433 | ] 434 | 435 | [[package]] 436 | name = "base64" 437 | version = "0.13.1" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 440 | 441 | [[package]] 442 | name = "base64" 443 | version = "0.21.4" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" 446 | 447 | [[package]] 448 | name = "bitflags" 449 | version = "1.3.2" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 452 | 453 | [[package]] 454 | name = "bitflags" 455 | version = "2.4.0" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" 458 | 459 | [[package]] 460 | name = "block-buffer" 461 | version = "0.10.4" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 464 | dependencies = [ 465 | "generic-array", 466 | ] 467 | 468 | [[package]] 469 | name = "blocking" 470 | version = "1.4.0" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "94c4ef1f913d78636d78d538eec1f18de81e481f44b1be0a81060090530846e1" 473 | dependencies = [ 474 | "async-channel 1.9.0", 475 | "async-lock", 476 | "async-task", 477 | "fastrand 2.0.1", 478 | "futures-io", 479 | "futures-lite 1.13.0", 480 | "piper", 481 | "tracing", 482 | ] 483 | 484 | [[package]] 485 | name = "bumpalo" 486 | version = "3.14.0" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" 489 | 490 | [[package]] 491 | name = "byteorder" 492 | version = "1.4.3" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 495 | 496 | [[package]] 497 | name = "bytes" 498 | version = "1.5.0" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" 501 | dependencies = [ 502 | "serde", 503 | ] 504 | 505 | [[package]] 506 | name = "cc" 507 | version = "1.0.83" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 510 | dependencies = [ 511 | "libc", 512 | ] 513 | 514 | [[package]] 515 | name = "cfg-if" 516 | version = "1.0.0" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 519 | 520 | [[package]] 521 | name = "combine" 522 | version = "3.8.1" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680" 525 | dependencies = [ 526 | "ascii", 527 | "byteorder", 528 | "either", 529 | "memchr", 530 | "unreachable", 531 | ] 532 | 533 | [[package]] 534 | name = "concurrent-queue" 535 | version = "2.5.0" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" 538 | dependencies = [ 539 | "crossbeam-utils", 540 | ] 541 | 542 | [[package]] 543 | name = "console" 544 | version = "0.15.7" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" 547 | dependencies = [ 548 | "encode_unicode", 549 | "lazy_static", 550 | "libc", 551 | "windows-sys 0.45.0", 552 | ] 553 | 554 | [[package]] 555 | name = "console_log" 556 | version = "1.0.0" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "be8aed40e4edbf4d3b4431ab260b63fdc40f5780a4766824329ea0f1eefe3c0f" 559 | dependencies = [ 560 | "log", 561 | "web-sys", 562 | ] 563 | 564 | [[package]] 565 | name = "counter" 566 | version = "0.5.7" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "2d458e66999348f56fd3ffcfbb7f7951542075ca8359687c703de6500c1ddccd" 569 | dependencies = [ 570 | "num-traits", 571 | ] 572 | 573 | [[package]] 574 | name = "cpufeatures" 575 | version = "0.2.9" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" 578 | dependencies = [ 579 | "libc", 580 | ] 581 | 582 | [[package]] 583 | name = "crossbeam-utils" 584 | version = "0.8.16" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" 587 | dependencies = [ 588 | "cfg-if", 589 | ] 590 | 591 | [[package]] 592 | name = "crypto-common" 593 | version = "0.1.6" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 596 | dependencies = [ 597 | "generic-array", 598 | "typenum", 599 | ] 600 | 601 | [[package]] 602 | name = "cynic" 603 | version = "3.2.2" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "bf035d785657f3621eee03fdfeefab48127d8b1643b6f9edf8b3cd66cbd86e9b" 606 | dependencies = [ 607 | "cynic-proc-macros", 608 | "ref-cast", 609 | "serde", 610 | "static_assertions", 611 | "thiserror", 612 | ] 613 | 614 | [[package]] 615 | name = "cynic-codegen" 616 | version = "3.2.2" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "4e8e65b71a8bd2751712ab38326b73a5f98405b6b5f8fd4dae658e58c1576d09" 619 | dependencies = [ 620 | "counter", 621 | "darling 0.14.4", 622 | "graphql-parser", 623 | "once_cell", 624 | "ouroboros", 625 | "proc-macro2", 626 | "quote", 627 | "strsim", 628 | "syn 1.0.109", 629 | "thiserror", 630 | ] 631 | 632 | [[package]] 633 | name = "cynic-proc-macros" 634 | version = "3.2.2" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "4a933ea1f357cbd48f2068c59457631696ae58d554f89290e4da272b1f69ebf1" 637 | dependencies = [ 638 | "cynic-codegen", 639 | "quote", 640 | "syn 1.0.109", 641 | ] 642 | 643 | [[package]] 644 | name = "darling" 645 | version = "0.14.4" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" 648 | dependencies = [ 649 | "darling_core 0.14.4", 650 | "darling_macro 0.14.4", 651 | ] 652 | 653 | [[package]] 654 | name = "darling" 655 | version = "0.20.5" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "fc5d6b04b3fd0ba9926f945895de7d806260a2d7431ba82e7edaecb043c4c6b8" 658 | dependencies = [ 659 | "darling_core 0.20.5", 660 | "darling_macro 0.20.5", 661 | ] 662 | 663 | [[package]] 664 | name = "darling_core" 665 | version = "0.14.4" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" 668 | dependencies = [ 669 | "fnv", 670 | "ident_case", 671 | "proc-macro2", 672 | "quote", 673 | "strsim", 674 | "syn 1.0.109", 675 | ] 676 | 677 | [[package]] 678 | name = "darling_core" 679 | version = "0.20.5" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "04e48a959bcd5c761246f5d090ebc2fbf7b9cd527a492b07a67510c108f1e7e3" 682 | dependencies = [ 683 | "fnv", 684 | "ident_case", 685 | "proc-macro2", 686 | "quote", 687 | "strsim", 688 | "syn 2.0.37", 689 | ] 690 | 691 | [[package]] 692 | name = "darling_macro" 693 | version = "0.14.4" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" 696 | dependencies = [ 697 | "darling_core 0.14.4", 698 | "quote", 699 | "syn 1.0.109", 700 | ] 701 | 702 | [[package]] 703 | name = "darling_macro" 704 | version = "0.20.5" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "1d1545d67a2149e1d93b7e5c7752dce5a7426eb5d1357ddcfd89336b94444f77" 707 | dependencies = [ 708 | "darling_core 0.20.5", 709 | "quote", 710 | "syn 2.0.37", 711 | ] 712 | 713 | [[package]] 714 | name = "data-encoding" 715 | version = "2.4.0" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" 718 | 719 | [[package]] 720 | name = "digest" 721 | version = "0.10.7" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 724 | dependencies = [ 725 | "block-buffer", 726 | "crypto-common", 727 | ] 728 | 729 | [[package]] 730 | name = "either" 731 | version = "1.9.0" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" 734 | 735 | [[package]] 736 | name = "encode_unicode" 737 | version = "0.3.6" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 740 | 741 | [[package]] 742 | name = "encoding_rs" 743 | version = "0.8.33" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" 746 | dependencies = [ 747 | "cfg-if", 748 | ] 749 | 750 | [[package]] 751 | name = "equivalent" 752 | version = "1.0.1" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 755 | 756 | [[package]] 757 | name = "errno" 758 | version = "0.3.9" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 761 | dependencies = [ 762 | "libc", 763 | "windows-sys 0.52.0", 764 | ] 765 | 766 | [[package]] 767 | name = "event-listener" 768 | version = "2.5.3" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" 771 | 772 | [[package]] 773 | name = "event-listener" 774 | version = "5.3.1" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" 777 | dependencies = [ 778 | "concurrent-queue", 779 | "parking", 780 | "pin-project-lite", 781 | ] 782 | 783 | [[package]] 784 | name = "event-listener-strategy" 785 | version = "0.5.2" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" 788 | dependencies = [ 789 | "event-listener 5.3.1", 790 | "pin-project-lite", 791 | ] 792 | 793 | [[package]] 794 | name = "examples" 795 | version = "0.11.0" 796 | dependencies = [ 797 | "async-std", 798 | "async-tungstenite", 799 | "cynic", 800 | "futures", 801 | "graphql-ws-client", 802 | "graphql_client", 803 | "insta", 804 | "serde", 805 | "tokio", 806 | ] 807 | 808 | [[package]] 809 | name = "examples-wasm" 810 | version = "0.11.0" 811 | dependencies = [ 812 | "async-std", 813 | "console_log", 814 | "cynic", 815 | "futures", 816 | "graphql-ws-client", 817 | "insta", 818 | "log", 819 | "wasm-bindgen", 820 | "wasm-bindgen-futures", 821 | "ws_stream_wasm", 822 | ] 823 | 824 | [[package]] 825 | name = "fast_chemail" 826 | version = "0.9.6" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4" 829 | dependencies = [ 830 | "ascii_utils", 831 | ] 832 | 833 | [[package]] 834 | name = "fastrand" 835 | version = "1.9.0" 836 | source = "registry+https://github.com/rust-lang/crates.io-index" 837 | checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" 838 | dependencies = [ 839 | "instant", 840 | ] 841 | 842 | [[package]] 843 | name = "fastrand" 844 | version = "2.0.1" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" 847 | 848 | [[package]] 849 | name = "fnv" 850 | version = "1.0.7" 851 | source = "registry+https://github.com/rust-lang/crates.io-index" 852 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 853 | 854 | [[package]] 855 | name = "form_urlencoded" 856 | version = "1.2.0" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" 859 | dependencies = [ 860 | "percent-encoding", 861 | ] 862 | 863 | [[package]] 864 | name = "futures" 865 | version = "0.3.28" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" 868 | dependencies = [ 869 | "futures-channel", 870 | "futures-core", 871 | "futures-executor", 872 | "futures-io", 873 | "futures-sink", 874 | "futures-task", 875 | "futures-util", 876 | ] 877 | 878 | [[package]] 879 | name = "futures-channel" 880 | version = "0.3.28" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" 883 | dependencies = [ 884 | "futures-core", 885 | "futures-sink", 886 | ] 887 | 888 | [[package]] 889 | name = "futures-core" 890 | version = "0.3.28" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" 893 | 894 | [[package]] 895 | name = "futures-executor" 896 | version = "0.3.28" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" 899 | dependencies = [ 900 | "futures-core", 901 | "futures-task", 902 | "futures-util", 903 | ] 904 | 905 | [[package]] 906 | name = "futures-io" 907 | version = "0.3.28" 908 | source = "registry+https://github.com/rust-lang/crates.io-index" 909 | checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" 910 | 911 | [[package]] 912 | name = "futures-lite" 913 | version = "1.13.0" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" 916 | dependencies = [ 917 | "fastrand 1.9.0", 918 | "futures-core", 919 | "futures-io", 920 | "memchr", 921 | "parking", 922 | "pin-project-lite", 923 | "waker-fn", 924 | ] 925 | 926 | [[package]] 927 | name = "futures-lite" 928 | version = "2.3.0" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" 931 | dependencies = [ 932 | "fastrand 2.0.1", 933 | "futures-core", 934 | "futures-io", 935 | "parking", 936 | "pin-project-lite", 937 | ] 938 | 939 | [[package]] 940 | name = "futures-macro" 941 | version = "0.3.28" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" 944 | dependencies = [ 945 | "proc-macro2", 946 | "quote", 947 | "syn 2.0.37", 948 | ] 949 | 950 | [[package]] 951 | name = "futures-sink" 952 | version = "0.3.28" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" 955 | 956 | [[package]] 957 | name = "futures-task" 958 | version = "0.3.28" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" 961 | 962 | [[package]] 963 | name = "futures-timer" 964 | version = "3.0.3" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" 967 | 968 | [[package]] 969 | name = "futures-util" 970 | version = "0.3.28" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" 973 | dependencies = [ 974 | "futures-channel", 975 | "futures-core", 976 | "futures-io", 977 | "futures-macro", 978 | "futures-sink", 979 | "futures-task", 980 | "memchr", 981 | "pin-project-lite", 982 | "pin-utils", 983 | "slab", 984 | ] 985 | 986 | [[package]] 987 | name = "generic-array" 988 | version = "0.14.7" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 991 | dependencies = [ 992 | "typenum", 993 | "version_check", 994 | ] 995 | 996 | [[package]] 997 | name = "getrandom" 998 | version = "0.2.10" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" 1001 | dependencies = [ 1002 | "cfg-if", 1003 | "libc", 1004 | "wasi", 1005 | ] 1006 | 1007 | [[package]] 1008 | name = "gimli" 1009 | version = "0.28.0" 1010 | source = "registry+https://github.com/rust-lang/crates.io-index" 1011 | checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" 1012 | 1013 | [[package]] 1014 | name = "gloo-timers" 1015 | version = "0.2.6" 1016 | source = "registry+https://github.com/rust-lang/crates.io-index" 1017 | checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" 1018 | dependencies = [ 1019 | "futures-channel", 1020 | "futures-core", 1021 | "js-sys", 1022 | "wasm-bindgen", 1023 | ] 1024 | 1025 | [[package]] 1026 | name = "graphql-introspection-query" 1027 | version = "0.2.0" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "7f2a4732cf5140bd6c082434494f785a19cfb566ab07d1382c3671f5812fed6d" 1030 | dependencies = [ 1031 | "serde", 1032 | ] 1033 | 1034 | [[package]] 1035 | name = "graphql-parser" 1036 | version = "0.4.0" 1037 | source = "registry+https://github.com/rust-lang/crates.io-index" 1038 | checksum = "d2ebc8013b4426d5b81a4364c419a95ed0b404af2b82e2457de52d9348f0e474" 1039 | dependencies = [ 1040 | "combine", 1041 | "thiserror", 1042 | ] 1043 | 1044 | [[package]] 1045 | name = "graphql-ws-client" 1046 | version = "0.11.1" 1047 | dependencies = [ 1048 | "assert_matches", 1049 | "async-channel 2.3.1", 1050 | "async-graphql", 1051 | "async-graphql-axum", 1052 | "async-tungstenite", 1053 | "axum", 1054 | "axum-macros", 1055 | "cynic", 1056 | "futures-lite 2.3.0", 1057 | "futures-sink", 1058 | "futures-timer", 1059 | "graphql-ws-client", 1060 | "graphql_client", 1061 | "insta", 1062 | "log", 1063 | "pharos", 1064 | "pin-project", 1065 | "serde", 1066 | "serde_json", 1067 | "thiserror", 1068 | "tokio", 1069 | "tokio-stream", 1070 | "tungstenite 0.24.0", 1071 | "ws_stream_wasm", 1072 | ] 1073 | 1074 | [[package]] 1075 | name = "graphql_client" 1076 | version = "0.14.0" 1077 | source = "registry+https://github.com/rust-lang/crates.io-index" 1078 | checksum = "a50cfdc7f34b7f01909d55c2dcb71d4c13cbcbb4a1605d6c8bd760d654c1144b" 1079 | dependencies = [ 1080 | "graphql_query_derive", 1081 | "serde", 1082 | "serde_json", 1083 | ] 1084 | 1085 | [[package]] 1086 | name = "graphql_client_codegen" 1087 | version = "0.14.0" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "5e27ed0c2cf0c0cc52c6bcf3b45c907f433015e580879d14005386251842fb0a" 1090 | dependencies = [ 1091 | "graphql-introspection-query", 1092 | "graphql-parser", 1093 | "heck", 1094 | "lazy_static", 1095 | "proc-macro2", 1096 | "quote", 1097 | "serde", 1098 | "serde_json", 1099 | "syn 1.0.109", 1100 | ] 1101 | 1102 | [[package]] 1103 | name = "graphql_query_derive" 1104 | version = "0.14.0" 1105 | source = "registry+https://github.com/rust-lang/crates.io-index" 1106 | checksum = "83febfa838f898cfa73dfaa7a8eb69ff3409021ac06ee94cfb3d622f6eeb1a97" 1107 | dependencies = [ 1108 | "graphql_client_codegen", 1109 | "proc-macro2", 1110 | "syn 1.0.109", 1111 | ] 1112 | 1113 | [[package]] 1114 | name = "h2" 1115 | version = "0.4.2" 1116 | source = "registry+https://github.com/rust-lang/crates.io-index" 1117 | checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943" 1118 | dependencies = [ 1119 | "bytes", 1120 | "fnv", 1121 | "futures-core", 1122 | "futures-sink", 1123 | "futures-util", 1124 | "http", 1125 | "indexmap", 1126 | "slab", 1127 | "tokio", 1128 | "tokio-util", 1129 | "tracing", 1130 | ] 1131 | 1132 | [[package]] 1133 | name = "handlebars" 1134 | version = "4.4.0" 1135 | source = "registry+https://github.com/rust-lang/crates.io-index" 1136 | checksum = "c39b3bc2a8f715298032cf5087e58573809374b08160aa7d750582bdb82d2683" 1137 | dependencies = [ 1138 | "log", 1139 | "pest", 1140 | "pest_derive", 1141 | "serde", 1142 | "serde_json", 1143 | "thiserror", 1144 | ] 1145 | 1146 | [[package]] 1147 | name = "hashbrown" 1148 | version = "0.14.1" 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" 1150 | checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" 1151 | 1152 | [[package]] 1153 | name = "heck" 1154 | version = "0.4.1" 1155 | source = "registry+https://github.com/rust-lang/crates.io-index" 1156 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 1157 | 1158 | [[package]] 1159 | name = "hermit-abi" 1160 | version = "0.3.9" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 1163 | 1164 | [[package]] 1165 | name = "http" 1166 | version = "1.0.0" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" 1169 | dependencies = [ 1170 | "bytes", 1171 | "fnv", 1172 | "itoa", 1173 | ] 1174 | 1175 | [[package]] 1176 | name = "http-body" 1177 | version = "1.0.0" 1178 | source = "registry+https://github.com/rust-lang/crates.io-index" 1179 | checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" 1180 | dependencies = [ 1181 | "bytes", 1182 | "http", 1183 | ] 1184 | 1185 | [[package]] 1186 | name = "http-body-util" 1187 | version = "0.1.0" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" 1190 | dependencies = [ 1191 | "bytes", 1192 | "futures-util", 1193 | "http", 1194 | "http-body", 1195 | "pin-project-lite", 1196 | ] 1197 | 1198 | [[package]] 1199 | name = "httparse" 1200 | version = "1.8.0" 1201 | source = "registry+https://github.com/rust-lang/crates.io-index" 1202 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 1203 | 1204 | [[package]] 1205 | name = "httpdate" 1206 | version = "1.0.3" 1207 | source = "registry+https://github.com/rust-lang/crates.io-index" 1208 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 1209 | 1210 | [[package]] 1211 | name = "hyper" 1212 | version = "1.1.0" 1213 | source = "registry+https://github.com/rust-lang/crates.io-index" 1214 | checksum = "fb5aa53871fc917b1a9ed87b683a5d86db645e23acb32c2e0785a353e522fb75" 1215 | dependencies = [ 1216 | "bytes", 1217 | "futures-channel", 1218 | "futures-util", 1219 | "h2", 1220 | "http", 1221 | "http-body", 1222 | "httparse", 1223 | "httpdate", 1224 | "itoa", 1225 | "pin-project-lite", 1226 | "tokio", 1227 | ] 1228 | 1229 | [[package]] 1230 | name = "hyper-util" 1231 | version = "0.1.3" 1232 | source = "registry+https://github.com/rust-lang/crates.io-index" 1233 | checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" 1234 | dependencies = [ 1235 | "bytes", 1236 | "futures-util", 1237 | "http", 1238 | "http-body", 1239 | "hyper", 1240 | "pin-project-lite", 1241 | "socket2 0.5.4", 1242 | "tokio", 1243 | ] 1244 | 1245 | [[package]] 1246 | name = "ident_case" 1247 | version = "1.0.1" 1248 | source = "registry+https://github.com/rust-lang/crates.io-index" 1249 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 1250 | 1251 | [[package]] 1252 | name = "idna" 1253 | version = "0.4.0" 1254 | source = "registry+https://github.com/rust-lang/crates.io-index" 1255 | checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" 1256 | dependencies = [ 1257 | "unicode-bidi", 1258 | "unicode-normalization", 1259 | ] 1260 | 1261 | [[package]] 1262 | name = "indexmap" 1263 | version = "2.0.2" 1264 | source = "registry+https://github.com/rust-lang/crates.io-index" 1265 | checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" 1266 | dependencies = [ 1267 | "equivalent", 1268 | "hashbrown", 1269 | "serde", 1270 | ] 1271 | 1272 | [[package]] 1273 | name = "insta" 1274 | version = "1.33.0" 1275 | source = "registry+https://github.com/rust-lang/crates.io-index" 1276 | checksum = "1aa511b2e298cd49b1856746f6bb73e17036bcd66b25f5e92cdcdbec9bd75686" 1277 | dependencies = [ 1278 | "console", 1279 | "lazy_static", 1280 | "linked-hash-map", 1281 | "similar", 1282 | "yaml-rust", 1283 | ] 1284 | 1285 | [[package]] 1286 | name = "instant" 1287 | version = "0.1.12" 1288 | source = "registry+https://github.com/rust-lang/crates.io-index" 1289 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 1290 | dependencies = [ 1291 | "cfg-if", 1292 | ] 1293 | 1294 | [[package]] 1295 | name = "io-lifetimes" 1296 | version = "1.0.11" 1297 | source = "registry+https://github.com/rust-lang/crates.io-index" 1298 | checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" 1299 | dependencies = [ 1300 | "hermit-abi", 1301 | "libc", 1302 | "windows-sys 0.48.0", 1303 | ] 1304 | 1305 | [[package]] 1306 | name = "itoa" 1307 | version = "1.0.9" 1308 | source = "registry+https://github.com/rust-lang/crates.io-index" 1309 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 1310 | 1311 | [[package]] 1312 | name = "js-sys" 1313 | version = "0.3.64" 1314 | source = "registry+https://github.com/rust-lang/crates.io-index" 1315 | checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" 1316 | dependencies = [ 1317 | "wasm-bindgen", 1318 | ] 1319 | 1320 | [[package]] 1321 | name = "kv-log-macro" 1322 | version = "1.0.7" 1323 | source = "registry+https://github.com/rust-lang/crates.io-index" 1324 | checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" 1325 | dependencies = [ 1326 | "log", 1327 | ] 1328 | 1329 | [[package]] 1330 | name = "lazy_static" 1331 | version = "1.4.0" 1332 | source = "registry+https://github.com/rust-lang/crates.io-index" 1333 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 1334 | 1335 | [[package]] 1336 | name = "libc" 1337 | version = "0.2.155" 1338 | source = "registry+https://github.com/rust-lang/crates.io-index" 1339 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 1340 | 1341 | [[package]] 1342 | name = "linked-hash-map" 1343 | version = "0.5.6" 1344 | source = "registry+https://github.com/rust-lang/crates.io-index" 1345 | checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" 1346 | 1347 | [[package]] 1348 | name = "linux-raw-sys" 1349 | version = "0.3.8" 1350 | source = "registry+https://github.com/rust-lang/crates.io-index" 1351 | checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" 1352 | 1353 | [[package]] 1354 | name = "linux-raw-sys" 1355 | version = "0.4.14" 1356 | source = "registry+https://github.com/rust-lang/crates.io-index" 1357 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 1358 | 1359 | [[package]] 1360 | name = "log" 1361 | version = "0.4.20" 1362 | source = "registry+https://github.com/rust-lang/crates.io-index" 1363 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 1364 | dependencies = [ 1365 | "value-bag", 1366 | ] 1367 | 1368 | [[package]] 1369 | name = "matchit" 1370 | version = "0.7.3" 1371 | source = "registry+https://github.com/rust-lang/crates.io-index" 1372 | checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" 1373 | 1374 | [[package]] 1375 | name = "memchr" 1376 | version = "2.6.3" 1377 | source = "registry+https://github.com/rust-lang/crates.io-index" 1378 | checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" 1379 | 1380 | [[package]] 1381 | name = "mime" 1382 | version = "0.3.17" 1383 | source = "registry+https://github.com/rust-lang/crates.io-index" 1384 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1385 | 1386 | [[package]] 1387 | name = "miniz_oxide" 1388 | version = "0.7.1" 1389 | source = "registry+https://github.com/rust-lang/crates.io-index" 1390 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 1391 | dependencies = [ 1392 | "adler", 1393 | ] 1394 | 1395 | [[package]] 1396 | name = "mio" 1397 | version = "0.8.8" 1398 | source = "registry+https://github.com/rust-lang/crates.io-index" 1399 | checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" 1400 | dependencies = [ 1401 | "libc", 1402 | "wasi", 1403 | "windows-sys 0.48.0", 1404 | ] 1405 | 1406 | [[package]] 1407 | name = "multer" 1408 | version = "3.0.0" 1409 | source = "registry+https://github.com/rust-lang/crates.io-index" 1410 | checksum = "a15d522be0a9c3e46fd2632e272d178f56387bdb5c9fbb3a36c649062e9b5219" 1411 | dependencies = [ 1412 | "bytes", 1413 | "encoding_rs", 1414 | "futures-util", 1415 | "http", 1416 | "httparse", 1417 | "log", 1418 | "memchr", 1419 | "mime", 1420 | "spin", 1421 | "version_check", 1422 | ] 1423 | 1424 | [[package]] 1425 | name = "num-traits" 1426 | version = "0.2.16" 1427 | source = "registry+https://github.com/rust-lang/crates.io-index" 1428 | checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" 1429 | dependencies = [ 1430 | "autocfg", 1431 | ] 1432 | 1433 | [[package]] 1434 | name = "num_cpus" 1435 | version = "1.16.0" 1436 | source = "registry+https://github.com/rust-lang/crates.io-index" 1437 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 1438 | dependencies = [ 1439 | "hermit-abi", 1440 | "libc", 1441 | ] 1442 | 1443 | [[package]] 1444 | name = "object" 1445 | version = "0.32.1" 1446 | source = "registry+https://github.com/rust-lang/crates.io-index" 1447 | checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" 1448 | dependencies = [ 1449 | "memchr", 1450 | ] 1451 | 1452 | [[package]] 1453 | name = "once_cell" 1454 | version = "1.18.0" 1455 | source = "registry+https://github.com/rust-lang/crates.io-index" 1456 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 1457 | 1458 | [[package]] 1459 | name = "ouroboros" 1460 | version = "0.15.6" 1461 | source = "registry+https://github.com/rust-lang/crates.io-index" 1462 | checksum = "e1358bd1558bd2a083fed428ffeda486fbfb323e698cdda7794259d592ca72db" 1463 | dependencies = [ 1464 | "aliasable", 1465 | "ouroboros_macro", 1466 | ] 1467 | 1468 | [[package]] 1469 | name = "ouroboros_macro" 1470 | version = "0.15.6" 1471 | source = "registry+https://github.com/rust-lang/crates.io-index" 1472 | checksum = "5f7d21ccd03305a674437ee1248f3ab5d4b1db095cf1caf49f1713ddf61956b7" 1473 | dependencies = [ 1474 | "Inflector", 1475 | "proc-macro-error", 1476 | "proc-macro2", 1477 | "quote", 1478 | "syn 1.0.109", 1479 | ] 1480 | 1481 | [[package]] 1482 | name = "parking" 1483 | version = "2.2.0" 1484 | source = "registry+https://github.com/rust-lang/crates.io-index" 1485 | checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" 1486 | 1487 | [[package]] 1488 | name = "percent-encoding" 1489 | version = "2.3.0" 1490 | source = "registry+https://github.com/rust-lang/crates.io-index" 1491 | checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" 1492 | 1493 | [[package]] 1494 | name = "pest" 1495 | version = "2.7.4" 1496 | source = "registry+https://github.com/rust-lang/crates.io-index" 1497 | checksum = "c022f1e7b65d6a24c0dbbd5fb344c66881bc01f3e5ae74a1c8100f2f985d98a4" 1498 | dependencies = [ 1499 | "memchr", 1500 | "thiserror", 1501 | "ucd-trie", 1502 | ] 1503 | 1504 | [[package]] 1505 | name = "pest_derive" 1506 | version = "2.7.4" 1507 | source = "registry+https://github.com/rust-lang/crates.io-index" 1508 | checksum = "35513f630d46400a977c4cb58f78e1bfbe01434316e60c37d27b9ad6139c66d8" 1509 | dependencies = [ 1510 | "pest", 1511 | "pest_generator", 1512 | ] 1513 | 1514 | [[package]] 1515 | name = "pest_generator" 1516 | version = "2.7.4" 1517 | source = "registry+https://github.com/rust-lang/crates.io-index" 1518 | checksum = "bc9fc1b9e7057baba189b5c626e2d6f40681ae5b6eb064dc7c7834101ec8123a" 1519 | dependencies = [ 1520 | "pest", 1521 | "pest_meta", 1522 | "proc-macro2", 1523 | "quote", 1524 | "syn 2.0.37", 1525 | ] 1526 | 1527 | [[package]] 1528 | name = "pest_meta" 1529 | version = "2.7.4" 1530 | source = "registry+https://github.com/rust-lang/crates.io-index" 1531 | checksum = "1df74e9e7ec4053ceb980e7c0c8bd3594e977fde1af91daba9c928e8e8c6708d" 1532 | dependencies = [ 1533 | "once_cell", 1534 | "pest", 1535 | "sha2", 1536 | ] 1537 | 1538 | [[package]] 1539 | name = "pharos" 1540 | version = "0.5.3" 1541 | source = "registry+https://github.com/rust-lang/crates.io-index" 1542 | checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" 1543 | dependencies = [ 1544 | "futures", 1545 | "rustc_version", 1546 | ] 1547 | 1548 | [[package]] 1549 | name = "pin-project" 1550 | version = "1.1.3" 1551 | source = "registry+https://github.com/rust-lang/crates.io-index" 1552 | checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" 1553 | dependencies = [ 1554 | "pin-project-internal", 1555 | ] 1556 | 1557 | [[package]] 1558 | name = "pin-project-internal" 1559 | version = "1.1.3" 1560 | source = "registry+https://github.com/rust-lang/crates.io-index" 1561 | checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" 1562 | dependencies = [ 1563 | "proc-macro2", 1564 | "quote", 1565 | "syn 2.0.37", 1566 | ] 1567 | 1568 | [[package]] 1569 | name = "pin-project-lite" 1570 | version = "0.2.13" 1571 | source = "registry+https://github.com/rust-lang/crates.io-index" 1572 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 1573 | 1574 | [[package]] 1575 | name = "pin-utils" 1576 | version = "0.1.0" 1577 | source = "registry+https://github.com/rust-lang/crates.io-index" 1578 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1579 | 1580 | [[package]] 1581 | name = "piper" 1582 | version = "0.2.1" 1583 | source = "registry+https://github.com/rust-lang/crates.io-index" 1584 | checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" 1585 | dependencies = [ 1586 | "atomic-waker", 1587 | "fastrand 2.0.1", 1588 | "futures-io", 1589 | ] 1590 | 1591 | [[package]] 1592 | name = "polling" 1593 | version = "2.8.0" 1594 | source = "registry+https://github.com/rust-lang/crates.io-index" 1595 | checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" 1596 | dependencies = [ 1597 | "autocfg", 1598 | "bitflags 1.3.2", 1599 | "cfg-if", 1600 | "concurrent-queue", 1601 | "libc", 1602 | "log", 1603 | "pin-project-lite", 1604 | "windows-sys 0.48.0", 1605 | ] 1606 | 1607 | [[package]] 1608 | name = "ppv-lite86" 1609 | version = "0.2.17" 1610 | source = "registry+https://github.com/rust-lang/crates.io-index" 1611 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 1612 | 1613 | [[package]] 1614 | name = "proc-macro-crate" 1615 | version = "1.3.1" 1616 | source = "registry+https://github.com/rust-lang/crates.io-index" 1617 | checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" 1618 | dependencies = [ 1619 | "once_cell", 1620 | "toml_edit", 1621 | ] 1622 | 1623 | [[package]] 1624 | name = "proc-macro-error" 1625 | version = "1.0.4" 1626 | source = "registry+https://github.com/rust-lang/crates.io-index" 1627 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 1628 | dependencies = [ 1629 | "proc-macro-error-attr", 1630 | "proc-macro2", 1631 | "quote", 1632 | "syn 1.0.109", 1633 | "version_check", 1634 | ] 1635 | 1636 | [[package]] 1637 | name = "proc-macro-error-attr" 1638 | version = "1.0.4" 1639 | source = "registry+https://github.com/rust-lang/crates.io-index" 1640 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 1641 | dependencies = [ 1642 | "proc-macro2", 1643 | "quote", 1644 | "version_check", 1645 | ] 1646 | 1647 | [[package]] 1648 | name = "proc-macro2" 1649 | version = "1.0.67" 1650 | source = "registry+https://github.com/rust-lang/crates.io-index" 1651 | checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" 1652 | dependencies = [ 1653 | "unicode-ident", 1654 | ] 1655 | 1656 | [[package]] 1657 | name = "quote" 1658 | version = "1.0.33" 1659 | source = "registry+https://github.com/rust-lang/crates.io-index" 1660 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 1661 | dependencies = [ 1662 | "proc-macro2", 1663 | ] 1664 | 1665 | [[package]] 1666 | name = "rand" 1667 | version = "0.8.5" 1668 | source = "registry+https://github.com/rust-lang/crates.io-index" 1669 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1670 | dependencies = [ 1671 | "libc", 1672 | "rand_chacha", 1673 | "rand_core", 1674 | ] 1675 | 1676 | [[package]] 1677 | name = "rand_chacha" 1678 | version = "0.3.1" 1679 | source = "registry+https://github.com/rust-lang/crates.io-index" 1680 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1681 | dependencies = [ 1682 | "ppv-lite86", 1683 | "rand_core", 1684 | ] 1685 | 1686 | [[package]] 1687 | name = "rand_core" 1688 | version = "0.6.4" 1689 | source = "registry+https://github.com/rust-lang/crates.io-index" 1690 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1691 | dependencies = [ 1692 | "getrandom", 1693 | ] 1694 | 1695 | [[package]] 1696 | name = "redox_syscall" 1697 | version = "0.3.5" 1698 | source = "registry+https://github.com/rust-lang/crates.io-index" 1699 | checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" 1700 | dependencies = [ 1701 | "bitflags 1.3.2", 1702 | ] 1703 | 1704 | [[package]] 1705 | name = "ref-cast" 1706 | version = "1.0.20" 1707 | source = "registry+https://github.com/rust-lang/crates.io-index" 1708 | checksum = "acde58d073e9c79da00f2b5b84eed919c8326832648a5b109b3fce1bb1175280" 1709 | dependencies = [ 1710 | "ref-cast-impl", 1711 | ] 1712 | 1713 | [[package]] 1714 | name = "ref-cast-impl" 1715 | version = "1.0.20" 1716 | source = "registry+https://github.com/rust-lang/crates.io-index" 1717 | checksum = "7f7473c2cfcf90008193dd0e3e16599455cb601a9fce322b5bb55de799664925" 1718 | dependencies = [ 1719 | "proc-macro2", 1720 | "quote", 1721 | "syn 2.0.37", 1722 | ] 1723 | 1724 | [[package]] 1725 | name = "regex" 1726 | version = "1.9.6" 1727 | source = "registry+https://github.com/rust-lang/crates.io-index" 1728 | checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" 1729 | dependencies = [ 1730 | "aho-corasick", 1731 | "memchr", 1732 | "regex-automata", 1733 | "regex-syntax", 1734 | ] 1735 | 1736 | [[package]] 1737 | name = "regex-automata" 1738 | version = "0.3.9" 1739 | source = "registry+https://github.com/rust-lang/crates.io-index" 1740 | checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" 1741 | dependencies = [ 1742 | "aho-corasick", 1743 | "memchr", 1744 | "regex-syntax", 1745 | ] 1746 | 1747 | [[package]] 1748 | name = "regex-syntax" 1749 | version = "0.7.5" 1750 | source = "registry+https://github.com/rust-lang/crates.io-index" 1751 | checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" 1752 | 1753 | [[package]] 1754 | name = "rustc-demangle" 1755 | version = "0.1.23" 1756 | source = "registry+https://github.com/rust-lang/crates.io-index" 1757 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 1758 | 1759 | [[package]] 1760 | name = "rustc_version" 1761 | version = "0.4.0" 1762 | source = "registry+https://github.com/rust-lang/crates.io-index" 1763 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 1764 | dependencies = [ 1765 | "semver", 1766 | ] 1767 | 1768 | [[package]] 1769 | name = "rustix" 1770 | version = "0.37.24" 1771 | source = "registry+https://github.com/rust-lang/crates.io-index" 1772 | checksum = "4279d76516df406a8bd37e7dff53fd37d1a093f997a3c34a5c21658c126db06d" 1773 | dependencies = [ 1774 | "bitflags 1.3.2", 1775 | "errno", 1776 | "io-lifetimes", 1777 | "libc", 1778 | "linux-raw-sys 0.3.8", 1779 | "windows-sys 0.48.0", 1780 | ] 1781 | 1782 | [[package]] 1783 | name = "rustix" 1784 | version = "0.38.34" 1785 | source = "registry+https://github.com/rust-lang/crates.io-index" 1786 | checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" 1787 | dependencies = [ 1788 | "bitflags 2.4.0", 1789 | "errno", 1790 | "libc", 1791 | "linux-raw-sys 0.4.14", 1792 | "windows-sys 0.52.0", 1793 | ] 1794 | 1795 | [[package]] 1796 | name = "rustversion" 1797 | version = "1.0.14" 1798 | source = "registry+https://github.com/rust-lang/crates.io-index" 1799 | checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" 1800 | 1801 | [[package]] 1802 | name = "ryu" 1803 | version = "1.0.15" 1804 | source = "registry+https://github.com/rust-lang/crates.io-index" 1805 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 1806 | 1807 | [[package]] 1808 | name = "semver" 1809 | version = "1.0.19" 1810 | source = "registry+https://github.com/rust-lang/crates.io-index" 1811 | checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0" 1812 | 1813 | [[package]] 1814 | name = "send_wrapper" 1815 | version = "0.6.0" 1816 | source = "registry+https://github.com/rust-lang/crates.io-index" 1817 | checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" 1818 | 1819 | [[package]] 1820 | name = "serde" 1821 | version = "1.0.188" 1822 | source = "registry+https://github.com/rust-lang/crates.io-index" 1823 | checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" 1824 | dependencies = [ 1825 | "serde_derive", 1826 | ] 1827 | 1828 | [[package]] 1829 | name = "serde_derive" 1830 | version = "1.0.188" 1831 | source = "registry+https://github.com/rust-lang/crates.io-index" 1832 | checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" 1833 | dependencies = [ 1834 | "proc-macro2", 1835 | "quote", 1836 | "syn 2.0.37", 1837 | ] 1838 | 1839 | [[package]] 1840 | name = "serde_json" 1841 | version = "1.0.107" 1842 | source = "registry+https://github.com/rust-lang/crates.io-index" 1843 | checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" 1844 | dependencies = [ 1845 | "itoa", 1846 | "ryu", 1847 | "serde", 1848 | ] 1849 | 1850 | [[package]] 1851 | name = "serde_path_to_error" 1852 | version = "0.1.14" 1853 | source = "registry+https://github.com/rust-lang/crates.io-index" 1854 | checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" 1855 | dependencies = [ 1856 | "itoa", 1857 | "serde", 1858 | ] 1859 | 1860 | [[package]] 1861 | name = "serde_urlencoded" 1862 | version = "0.7.1" 1863 | source = "registry+https://github.com/rust-lang/crates.io-index" 1864 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1865 | dependencies = [ 1866 | "form_urlencoded", 1867 | "itoa", 1868 | "ryu", 1869 | "serde", 1870 | ] 1871 | 1872 | [[package]] 1873 | name = "sha1" 1874 | version = "0.10.6" 1875 | source = "registry+https://github.com/rust-lang/crates.io-index" 1876 | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 1877 | dependencies = [ 1878 | "cfg-if", 1879 | "cpufeatures", 1880 | "digest", 1881 | ] 1882 | 1883 | [[package]] 1884 | name = "sha2" 1885 | version = "0.10.8" 1886 | source = "registry+https://github.com/rust-lang/crates.io-index" 1887 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 1888 | dependencies = [ 1889 | "cfg-if", 1890 | "cpufeatures", 1891 | "digest", 1892 | ] 1893 | 1894 | [[package]] 1895 | name = "similar" 1896 | version = "2.2.1" 1897 | source = "registry+https://github.com/rust-lang/crates.io-index" 1898 | checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" 1899 | 1900 | [[package]] 1901 | name = "slab" 1902 | version = "0.4.9" 1903 | source = "registry+https://github.com/rust-lang/crates.io-index" 1904 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1905 | dependencies = [ 1906 | "autocfg", 1907 | ] 1908 | 1909 | [[package]] 1910 | name = "socket2" 1911 | version = "0.4.9" 1912 | source = "registry+https://github.com/rust-lang/crates.io-index" 1913 | checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" 1914 | dependencies = [ 1915 | "libc", 1916 | "winapi", 1917 | ] 1918 | 1919 | [[package]] 1920 | name = "socket2" 1921 | version = "0.5.4" 1922 | source = "registry+https://github.com/rust-lang/crates.io-index" 1923 | checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" 1924 | dependencies = [ 1925 | "libc", 1926 | "windows-sys 0.48.0", 1927 | ] 1928 | 1929 | [[package]] 1930 | name = "spin" 1931 | version = "0.9.8" 1932 | source = "registry+https://github.com/rust-lang/crates.io-index" 1933 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1934 | 1935 | [[package]] 1936 | name = "static_assertions" 1937 | version = "1.1.0" 1938 | source = "registry+https://github.com/rust-lang/crates.io-index" 1939 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1940 | 1941 | [[package]] 1942 | name = "static_assertions_next" 1943 | version = "1.1.2" 1944 | source = "registry+https://github.com/rust-lang/crates.io-index" 1945 | checksum = "d7beae5182595e9a8b683fa98c4317f956c9a2dec3b9716990d20023cc60c766" 1946 | 1947 | [[package]] 1948 | name = "strsim" 1949 | version = "0.10.0" 1950 | source = "registry+https://github.com/rust-lang/crates.io-index" 1951 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1952 | 1953 | [[package]] 1954 | name = "strum" 1955 | version = "0.25.0" 1956 | source = "registry+https://github.com/rust-lang/crates.io-index" 1957 | checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" 1958 | dependencies = [ 1959 | "strum_macros", 1960 | ] 1961 | 1962 | [[package]] 1963 | name = "strum_macros" 1964 | version = "0.25.3" 1965 | source = "registry+https://github.com/rust-lang/crates.io-index" 1966 | checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" 1967 | dependencies = [ 1968 | "heck", 1969 | "proc-macro2", 1970 | "quote", 1971 | "rustversion", 1972 | "syn 2.0.37", 1973 | ] 1974 | 1975 | [[package]] 1976 | name = "syn" 1977 | version = "1.0.109" 1978 | source = "registry+https://github.com/rust-lang/crates.io-index" 1979 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1980 | dependencies = [ 1981 | "proc-macro2", 1982 | "quote", 1983 | "unicode-ident", 1984 | ] 1985 | 1986 | [[package]] 1987 | name = "syn" 1988 | version = "2.0.37" 1989 | source = "registry+https://github.com/rust-lang/crates.io-index" 1990 | checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" 1991 | dependencies = [ 1992 | "proc-macro2", 1993 | "quote", 1994 | "unicode-ident", 1995 | ] 1996 | 1997 | [[package]] 1998 | name = "sync_wrapper" 1999 | version = "0.1.2" 2000 | source = "registry+https://github.com/rust-lang/crates.io-index" 2001 | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 2002 | 2003 | [[package]] 2004 | name = "tempfile" 2005 | version = "3.8.0" 2006 | source = "registry+https://github.com/rust-lang/crates.io-index" 2007 | checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" 2008 | dependencies = [ 2009 | "cfg-if", 2010 | "fastrand 2.0.1", 2011 | "redox_syscall", 2012 | "rustix 0.38.34", 2013 | "windows-sys 0.48.0", 2014 | ] 2015 | 2016 | [[package]] 2017 | name = "thiserror" 2018 | version = "1.0.49" 2019 | source = "registry+https://github.com/rust-lang/crates.io-index" 2020 | checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" 2021 | dependencies = [ 2022 | "thiserror-impl", 2023 | ] 2024 | 2025 | [[package]] 2026 | name = "thiserror-impl" 2027 | version = "1.0.49" 2028 | source = "registry+https://github.com/rust-lang/crates.io-index" 2029 | checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" 2030 | dependencies = [ 2031 | "proc-macro2", 2032 | "quote", 2033 | "syn 2.0.37", 2034 | ] 2035 | 2036 | [[package]] 2037 | name = "tinyvec" 2038 | version = "1.6.0" 2039 | source = "registry+https://github.com/rust-lang/crates.io-index" 2040 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 2041 | dependencies = [ 2042 | "tinyvec_macros", 2043 | ] 2044 | 2045 | [[package]] 2046 | name = "tinyvec_macros" 2047 | version = "0.1.1" 2048 | source = "registry+https://github.com/rust-lang/crates.io-index" 2049 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 2050 | 2051 | [[package]] 2052 | name = "tokio" 2053 | version = "1.32.0" 2054 | source = "registry+https://github.com/rust-lang/crates.io-index" 2055 | checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" 2056 | dependencies = [ 2057 | "backtrace", 2058 | "bytes", 2059 | "libc", 2060 | "mio", 2061 | "num_cpus", 2062 | "pin-project-lite", 2063 | "socket2 0.5.4", 2064 | "tokio-macros", 2065 | "windows-sys 0.48.0", 2066 | ] 2067 | 2068 | [[package]] 2069 | name = "tokio-macros" 2070 | version = "2.1.0" 2071 | source = "registry+https://github.com/rust-lang/crates.io-index" 2072 | checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" 2073 | dependencies = [ 2074 | "proc-macro2", 2075 | "quote", 2076 | "syn 2.0.37", 2077 | ] 2078 | 2079 | [[package]] 2080 | name = "tokio-stream" 2081 | version = "0.1.14" 2082 | source = "registry+https://github.com/rust-lang/crates.io-index" 2083 | checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" 2084 | dependencies = [ 2085 | "futures-core", 2086 | "pin-project-lite", 2087 | "tokio", 2088 | "tokio-util", 2089 | ] 2090 | 2091 | [[package]] 2092 | name = "tokio-tungstenite" 2093 | version = "0.21.0" 2094 | source = "registry+https://github.com/rust-lang/crates.io-index" 2095 | checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" 2096 | dependencies = [ 2097 | "futures-util", 2098 | "log", 2099 | "tokio", 2100 | "tungstenite 0.21.0", 2101 | ] 2102 | 2103 | [[package]] 2104 | name = "tokio-util" 2105 | version = "0.7.9" 2106 | source = "registry+https://github.com/rust-lang/crates.io-index" 2107 | checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" 2108 | dependencies = [ 2109 | "bytes", 2110 | "futures-core", 2111 | "futures-io", 2112 | "futures-sink", 2113 | "pin-project-lite", 2114 | "tokio", 2115 | "tracing", 2116 | ] 2117 | 2118 | [[package]] 2119 | name = "toml_datetime" 2120 | version = "0.6.3" 2121 | source = "registry+https://github.com/rust-lang/crates.io-index" 2122 | checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" 2123 | 2124 | [[package]] 2125 | name = "toml_edit" 2126 | version = "0.19.15" 2127 | source = "registry+https://github.com/rust-lang/crates.io-index" 2128 | checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" 2129 | dependencies = [ 2130 | "indexmap", 2131 | "toml_datetime", 2132 | "winnow", 2133 | ] 2134 | 2135 | [[package]] 2136 | name = "tower" 2137 | version = "0.4.13" 2138 | source = "registry+https://github.com/rust-lang/crates.io-index" 2139 | checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 2140 | dependencies = [ 2141 | "futures-core", 2142 | "futures-util", 2143 | "pin-project", 2144 | "pin-project-lite", 2145 | "tokio", 2146 | "tower-layer", 2147 | "tower-service", 2148 | "tracing", 2149 | ] 2150 | 2151 | [[package]] 2152 | name = "tower-layer" 2153 | version = "0.3.2" 2154 | source = "registry+https://github.com/rust-lang/crates.io-index" 2155 | checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" 2156 | 2157 | [[package]] 2158 | name = "tower-service" 2159 | version = "0.3.2" 2160 | source = "registry+https://github.com/rust-lang/crates.io-index" 2161 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 2162 | 2163 | [[package]] 2164 | name = "tracing" 2165 | version = "0.1.37" 2166 | source = "registry+https://github.com/rust-lang/crates.io-index" 2167 | checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 2168 | dependencies = [ 2169 | "cfg-if", 2170 | "log", 2171 | "pin-project-lite", 2172 | "tracing-core", 2173 | ] 2174 | 2175 | [[package]] 2176 | name = "tracing-core" 2177 | version = "0.1.31" 2178 | source = "registry+https://github.com/rust-lang/crates.io-index" 2179 | checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" 2180 | dependencies = [ 2181 | "once_cell", 2182 | ] 2183 | 2184 | [[package]] 2185 | name = "tungstenite" 2186 | version = "0.21.0" 2187 | source = "registry+https://github.com/rust-lang/crates.io-index" 2188 | checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" 2189 | dependencies = [ 2190 | "byteorder", 2191 | "bytes", 2192 | "data-encoding", 2193 | "http", 2194 | "httparse", 2195 | "log", 2196 | "rand", 2197 | "sha1", 2198 | "thiserror", 2199 | "url", 2200 | "utf-8", 2201 | ] 2202 | 2203 | [[package]] 2204 | name = "tungstenite" 2205 | version = "0.24.0" 2206 | source = "registry+https://github.com/rust-lang/crates.io-index" 2207 | checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" 2208 | dependencies = [ 2209 | "byteorder", 2210 | "bytes", 2211 | "data-encoding", 2212 | "http", 2213 | "httparse", 2214 | "log", 2215 | "rand", 2216 | "sha1", 2217 | "thiserror", 2218 | "utf-8", 2219 | ] 2220 | 2221 | [[package]] 2222 | name = "typenum" 2223 | version = "1.17.0" 2224 | source = "registry+https://github.com/rust-lang/crates.io-index" 2225 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 2226 | 2227 | [[package]] 2228 | name = "ucd-trie" 2229 | version = "0.1.6" 2230 | source = "registry+https://github.com/rust-lang/crates.io-index" 2231 | checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" 2232 | 2233 | [[package]] 2234 | name = "unicode-bidi" 2235 | version = "0.3.13" 2236 | source = "registry+https://github.com/rust-lang/crates.io-index" 2237 | checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" 2238 | 2239 | [[package]] 2240 | name = "unicode-ident" 2241 | version = "1.0.12" 2242 | source = "registry+https://github.com/rust-lang/crates.io-index" 2243 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 2244 | 2245 | [[package]] 2246 | name = "unicode-normalization" 2247 | version = "0.1.22" 2248 | source = "registry+https://github.com/rust-lang/crates.io-index" 2249 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 2250 | dependencies = [ 2251 | "tinyvec", 2252 | ] 2253 | 2254 | [[package]] 2255 | name = "unreachable" 2256 | version = "1.0.0" 2257 | source = "registry+https://github.com/rust-lang/crates.io-index" 2258 | checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" 2259 | dependencies = [ 2260 | "void", 2261 | ] 2262 | 2263 | [[package]] 2264 | name = "url" 2265 | version = "2.4.1" 2266 | source = "registry+https://github.com/rust-lang/crates.io-index" 2267 | checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" 2268 | dependencies = [ 2269 | "form_urlencoded", 2270 | "idna", 2271 | "percent-encoding", 2272 | ] 2273 | 2274 | [[package]] 2275 | name = "utf-8" 2276 | version = "0.7.6" 2277 | source = "registry+https://github.com/rust-lang/crates.io-index" 2278 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 2279 | 2280 | [[package]] 2281 | name = "value-bag" 2282 | version = "1.4.1" 2283 | source = "registry+https://github.com/rust-lang/crates.io-index" 2284 | checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3" 2285 | 2286 | [[package]] 2287 | name = "version_check" 2288 | version = "0.9.4" 2289 | source = "registry+https://github.com/rust-lang/crates.io-index" 2290 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 2291 | 2292 | [[package]] 2293 | name = "void" 2294 | version = "1.0.2" 2295 | source = "registry+https://github.com/rust-lang/crates.io-index" 2296 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 2297 | 2298 | [[package]] 2299 | name = "waker-fn" 2300 | version = "1.1.1" 2301 | source = "registry+https://github.com/rust-lang/crates.io-index" 2302 | checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" 2303 | 2304 | [[package]] 2305 | name = "wasi" 2306 | version = "0.11.0+wasi-snapshot-preview1" 2307 | source = "registry+https://github.com/rust-lang/crates.io-index" 2308 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2309 | 2310 | [[package]] 2311 | name = "wasm-bindgen" 2312 | version = "0.2.87" 2313 | source = "registry+https://github.com/rust-lang/crates.io-index" 2314 | checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" 2315 | dependencies = [ 2316 | "cfg-if", 2317 | "wasm-bindgen-macro", 2318 | ] 2319 | 2320 | [[package]] 2321 | name = "wasm-bindgen-backend" 2322 | version = "0.2.87" 2323 | source = "registry+https://github.com/rust-lang/crates.io-index" 2324 | checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" 2325 | dependencies = [ 2326 | "bumpalo", 2327 | "log", 2328 | "once_cell", 2329 | "proc-macro2", 2330 | "quote", 2331 | "syn 2.0.37", 2332 | "wasm-bindgen-shared", 2333 | ] 2334 | 2335 | [[package]] 2336 | name = "wasm-bindgen-futures" 2337 | version = "0.4.37" 2338 | source = "registry+https://github.com/rust-lang/crates.io-index" 2339 | checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" 2340 | dependencies = [ 2341 | "cfg-if", 2342 | "js-sys", 2343 | "wasm-bindgen", 2344 | "web-sys", 2345 | ] 2346 | 2347 | [[package]] 2348 | name = "wasm-bindgen-macro" 2349 | version = "0.2.87" 2350 | source = "registry+https://github.com/rust-lang/crates.io-index" 2351 | checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" 2352 | dependencies = [ 2353 | "quote", 2354 | "wasm-bindgen-macro-support", 2355 | ] 2356 | 2357 | [[package]] 2358 | name = "wasm-bindgen-macro-support" 2359 | version = "0.2.87" 2360 | source = "registry+https://github.com/rust-lang/crates.io-index" 2361 | checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" 2362 | dependencies = [ 2363 | "proc-macro2", 2364 | "quote", 2365 | "syn 2.0.37", 2366 | "wasm-bindgen-backend", 2367 | "wasm-bindgen-shared", 2368 | ] 2369 | 2370 | [[package]] 2371 | name = "wasm-bindgen-shared" 2372 | version = "0.2.87" 2373 | source = "registry+https://github.com/rust-lang/crates.io-index" 2374 | checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" 2375 | 2376 | [[package]] 2377 | name = "web-sys" 2378 | version = "0.3.64" 2379 | source = "registry+https://github.com/rust-lang/crates.io-index" 2380 | checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" 2381 | dependencies = [ 2382 | "js-sys", 2383 | "wasm-bindgen", 2384 | ] 2385 | 2386 | [[package]] 2387 | name = "winapi" 2388 | version = "0.3.9" 2389 | source = "registry+https://github.com/rust-lang/crates.io-index" 2390 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2391 | dependencies = [ 2392 | "winapi-i686-pc-windows-gnu", 2393 | "winapi-x86_64-pc-windows-gnu", 2394 | ] 2395 | 2396 | [[package]] 2397 | name = "winapi-i686-pc-windows-gnu" 2398 | version = "0.4.0" 2399 | source = "registry+https://github.com/rust-lang/crates.io-index" 2400 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2401 | 2402 | [[package]] 2403 | name = "winapi-x86_64-pc-windows-gnu" 2404 | version = "0.4.0" 2405 | source = "registry+https://github.com/rust-lang/crates.io-index" 2406 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2407 | 2408 | [[package]] 2409 | name = "windows-sys" 2410 | version = "0.45.0" 2411 | source = "registry+https://github.com/rust-lang/crates.io-index" 2412 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 2413 | dependencies = [ 2414 | "windows-targets 0.42.2", 2415 | ] 2416 | 2417 | [[package]] 2418 | name = "windows-sys" 2419 | version = "0.48.0" 2420 | source = "registry+https://github.com/rust-lang/crates.io-index" 2421 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 2422 | dependencies = [ 2423 | "windows-targets 0.48.5", 2424 | ] 2425 | 2426 | [[package]] 2427 | name = "windows-sys" 2428 | version = "0.52.0" 2429 | source = "registry+https://github.com/rust-lang/crates.io-index" 2430 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2431 | dependencies = [ 2432 | "windows-targets 0.52.5", 2433 | ] 2434 | 2435 | [[package]] 2436 | name = "windows-targets" 2437 | version = "0.42.2" 2438 | source = "registry+https://github.com/rust-lang/crates.io-index" 2439 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 2440 | dependencies = [ 2441 | "windows_aarch64_gnullvm 0.42.2", 2442 | "windows_aarch64_msvc 0.42.2", 2443 | "windows_i686_gnu 0.42.2", 2444 | "windows_i686_msvc 0.42.2", 2445 | "windows_x86_64_gnu 0.42.2", 2446 | "windows_x86_64_gnullvm 0.42.2", 2447 | "windows_x86_64_msvc 0.42.2", 2448 | ] 2449 | 2450 | [[package]] 2451 | name = "windows-targets" 2452 | version = "0.48.5" 2453 | source = "registry+https://github.com/rust-lang/crates.io-index" 2454 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 2455 | dependencies = [ 2456 | "windows_aarch64_gnullvm 0.48.5", 2457 | "windows_aarch64_msvc 0.48.5", 2458 | "windows_i686_gnu 0.48.5", 2459 | "windows_i686_msvc 0.48.5", 2460 | "windows_x86_64_gnu 0.48.5", 2461 | "windows_x86_64_gnullvm 0.48.5", 2462 | "windows_x86_64_msvc 0.48.5", 2463 | ] 2464 | 2465 | [[package]] 2466 | name = "windows-targets" 2467 | version = "0.52.5" 2468 | source = "registry+https://github.com/rust-lang/crates.io-index" 2469 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 2470 | dependencies = [ 2471 | "windows_aarch64_gnullvm 0.52.5", 2472 | "windows_aarch64_msvc 0.52.5", 2473 | "windows_i686_gnu 0.52.5", 2474 | "windows_i686_gnullvm", 2475 | "windows_i686_msvc 0.52.5", 2476 | "windows_x86_64_gnu 0.52.5", 2477 | "windows_x86_64_gnullvm 0.52.5", 2478 | "windows_x86_64_msvc 0.52.5", 2479 | ] 2480 | 2481 | [[package]] 2482 | name = "windows_aarch64_gnullvm" 2483 | version = "0.42.2" 2484 | source = "registry+https://github.com/rust-lang/crates.io-index" 2485 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 2486 | 2487 | [[package]] 2488 | name = "windows_aarch64_gnullvm" 2489 | version = "0.48.5" 2490 | source = "registry+https://github.com/rust-lang/crates.io-index" 2491 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 2492 | 2493 | [[package]] 2494 | name = "windows_aarch64_gnullvm" 2495 | version = "0.52.5" 2496 | source = "registry+https://github.com/rust-lang/crates.io-index" 2497 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 2498 | 2499 | [[package]] 2500 | name = "windows_aarch64_msvc" 2501 | version = "0.42.2" 2502 | source = "registry+https://github.com/rust-lang/crates.io-index" 2503 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 2504 | 2505 | [[package]] 2506 | name = "windows_aarch64_msvc" 2507 | version = "0.48.5" 2508 | source = "registry+https://github.com/rust-lang/crates.io-index" 2509 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2510 | 2511 | [[package]] 2512 | name = "windows_aarch64_msvc" 2513 | version = "0.52.5" 2514 | source = "registry+https://github.com/rust-lang/crates.io-index" 2515 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 2516 | 2517 | [[package]] 2518 | name = "windows_i686_gnu" 2519 | version = "0.42.2" 2520 | source = "registry+https://github.com/rust-lang/crates.io-index" 2521 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 2522 | 2523 | [[package]] 2524 | name = "windows_i686_gnu" 2525 | version = "0.48.5" 2526 | source = "registry+https://github.com/rust-lang/crates.io-index" 2527 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2528 | 2529 | [[package]] 2530 | name = "windows_i686_gnu" 2531 | version = "0.52.5" 2532 | source = "registry+https://github.com/rust-lang/crates.io-index" 2533 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 2534 | 2535 | [[package]] 2536 | name = "windows_i686_gnullvm" 2537 | version = "0.52.5" 2538 | source = "registry+https://github.com/rust-lang/crates.io-index" 2539 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 2540 | 2541 | [[package]] 2542 | name = "windows_i686_msvc" 2543 | version = "0.42.2" 2544 | source = "registry+https://github.com/rust-lang/crates.io-index" 2545 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 2546 | 2547 | [[package]] 2548 | name = "windows_i686_msvc" 2549 | version = "0.48.5" 2550 | source = "registry+https://github.com/rust-lang/crates.io-index" 2551 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2552 | 2553 | [[package]] 2554 | name = "windows_i686_msvc" 2555 | version = "0.52.5" 2556 | source = "registry+https://github.com/rust-lang/crates.io-index" 2557 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 2558 | 2559 | [[package]] 2560 | name = "windows_x86_64_gnu" 2561 | version = "0.42.2" 2562 | source = "registry+https://github.com/rust-lang/crates.io-index" 2563 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 2564 | 2565 | [[package]] 2566 | name = "windows_x86_64_gnu" 2567 | version = "0.48.5" 2568 | source = "registry+https://github.com/rust-lang/crates.io-index" 2569 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2570 | 2571 | [[package]] 2572 | name = "windows_x86_64_gnu" 2573 | version = "0.52.5" 2574 | source = "registry+https://github.com/rust-lang/crates.io-index" 2575 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 2576 | 2577 | [[package]] 2578 | name = "windows_x86_64_gnullvm" 2579 | version = "0.42.2" 2580 | source = "registry+https://github.com/rust-lang/crates.io-index" 2581 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 2582 | 2583 | [[package]] 2584 | name = "windows_x86_64_gnullvm" 2585 | version = "0.48.5" 2586 | source = "registry+https://github.com/rust-lang/crates.io-index" 2587 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2588 | 2589 | [[package]] 2590 | name = "windows_x86_64_gnullvm" 2591 | version = "0.52.5" 2592 | source = "registry+https://github.com/rust-lang/crates.io-index" 2593 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 2594 | 2595 | [[package]] 2596 | name = "windows_x86_64_msvc" 2597 | version = "0.42.2" 2598 | source = "registry+https://github.com/rust-lang/crates.io-index" 2599 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 2600 | 2601 | [[package]] 2602 | name = "windows_x86_64_msvc" 2603 | version = "0.48.5" 2604 | source = "registry+https://github.com/rust-lang/crates.io-index" 2605 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2606 | 2607 | [[package]] 2608 | name = "windows_x86_64_msvc" 2609 | version = "0.52.5" 2610 | source = "registry+https://github.com/rust-lang/crates.io-index" 2611 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 2612 | 2613 | [[package]] 2614 | name = "winnow" 2615 | version = "0.5.15" 2616 | source = "registry+https://github.com/rust-lang/crates.io-index" 2617 | checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" 2618 | dependencies = [ 2619 | "memchr", 2620 | ] 2621 | 2622 | [[package]] 2623 | name = "ws_stream_wasm" 2624 | version = "0.7.4" 2625 | source = "registry+https://github.com/rust-lang/crates.io-index" 2626 | checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5" 2627 | dependencies = [ 2628 | "async_io_stream", 2629 | "futures", 2630 | "js-sys", 2631 | "log", 2632 | "pharos", 2633 | "rustc_version", 2634 | "send_wrapper", 2635 | "thiserror", 2636 | "wasm-bindgen", 2637 | "wasm-bindgen-futures", 2638 | "web-sys", 2639 | ] 2640 | 2641 | [[package]] 2642 | name = "yaml-rust" 2643 | version = "0.4.5" 2644 | source = "registry+https://github.com/rust-lang/crates.io-index" 2645 | checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" 2646 | dependencies = [ 2647 | "linked-hash-map", 2648 | ] 2649 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "graphql-ws-client" 3 | version = "0.11.1" 4 | authors = ["Graeme Coupar "] 5 | edition = "2021" 6 | resolver = "2" 7 | description = "A graphql over websockets client" 8 | keywords = ["graphql", "client", "api", "websockets", "subscriptions"] 9 | categories = [ 10 | "asynchronous", 11 | "network-programming", 12 | "wasm", 13 | "web-programming", 14 | "web-programming::websocket", 15 | ] 16 | license = "Apache-2.0" 17 | autoexamples = false 18 | documentation = "https://docs.rs/graphql-ws-client" 19 | readme = "README.md" 20 | repository = "https://github.com/obmarg/graphql-ws-client" 21 | rust-version = "1.76" 22 | 23 | [workspace] 24 | members = ["examples", "examples-wasm"] 25 | 26 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 27 | 28 | [features] 29 | default = ["logging"] 30 | logging = ["dep:log"] 31 | tungstenite = ["dep:tungstenite"] 32 | client-cynic = ["cynic"] 33 | client-graphql-client = ["graphql_client"] 34 | ws_stream_wasm = ["dep:ws_stream_wasm", "dep:pharos"] 35 | 36 | [dependencies] 37 | async-channel = "2" 38 | futures-lite = "2" 39 | futures-sink = "0.3" 40 | futures-timer = "3" 41 | log = { version = "0.4", optional = true } 42 | pin-project = "1" 43 | serde = { version = "1.0", features = ["derive"] } 44 | serde_json = "1.0" 45 | thiserror = "1.0" 46 | 47 | cynic = { version = "3", optional = true } 48 | tungstenite = { version = ">=0.23, <=0.24", optional = true } 49 | graphql_client = { version = "0.14.0", optional = true } 50 | 51 | ws_stream_wasm = { version = "0.7", optional = true } 52 | pharos = { version = "0.5.2", optional = true } 53 | 54 | [dev-dependencies] 55 | assert_matches = "1.5" 56 | async-graphql = "7.0.1" 57 | async-graphql-axum = "7" 58 | async-tungstenite = { version = "0.28", features = ["tokio-runtime"] } 59 | axum = "0.7" 60 | axum-macros = "0.4" 61 | cynic = { version = "3" } 62 | insta = "1.11" 63 | tokio = { version = "1", features = ["macros"] } 64 | tokio-stream = { version = "0.1", features = ["sync"] } 65 | 66 | graphql-ws-client.path = "." 67 | 68 | graphql-ws-client.features = [ 69 | "client-cynic", 70 | "client-graphql-client", 71 | "tungstenite", 72 | ] 73 | 74 | [package.metadata.docs.rs] 75 | all-features = true 76 | rustdoc-args = ["--cfg", "docsrs"] 77 | 78 | [workspace.lints.rust] 79 | unsafe_code = "forbid" 80 | 81 | [workspace.lints.clippy] 82 | cargo = { level = "warn", priority = -1 } 83 | multiple_crate_versions = "allow" 84 | 85 | [lints] 86 | workspace = true 87 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

GraphQL Websocket Client

3 | 4 |

5 | Runtime agnostic graphql websocket client 6 |

7 | 8 |

9 | Crate Info 10 | API Docs 11 | Discord Chat 12 |

13 | 14 |

15 | Examples 16 | | 17 | Changelog 18 |

19 |
20 | 21 | # Overview 22 | 23 | The goal of this library is to provide a runtime agnostic implementation for 24 | [GraphQL-over-Websockets](https://github.com/graphql/graphql-over-http/blob/main/rfcs/GraphQLOverWebSocket.md). 25 | 26 | The library only supports subscriptions for now but will eventually support queries and mutations. 27 | 28 | It supports the websocket libraries 29 | [async-tungstenite](https://github.com/sdroege/async-tungstenite), 30 | [tokio-tungstenite](https://github.com/snapview/tokio-tungstenite) and 31 | [ws-stream-wasm](https://github.com/najamelan/ws_stream_wasm), and 32 | 33 | ## Integrations 34 | 35 | The library offers integrations with some popular GraphQL clients with feature flags: 36 | 37 | - [graphql-client](https://github.com/graphql-rust/graphql-client): `features = ["client-graphql-client"]` 38 | - [cynic](https://github.com/obmarg/cynic): `features = ["client-cynic"]` 39 | 40 | ## Documentation 41 | 42 | The documentation is quite limited at the moment, here are some sources: 43 | 44 | 1. The provided [examples](https://github.com/obmarg/graphql-ws-client/tree/main/examples/examples) 45 | 2. The reference documentation on [docs.rs](https://docs.rs/graphql-ws-client) 46 | 47 | ## Logging 48 | 49 | By default, the library will log some messages at the `trace` level to help you debug. 50 | It is possible to turn off the logging entirely by using the `no-logging` feature. 51 | -------------------------------------------------------------------------------- /examples-wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "examples-wasm" 3 | version = "0.11.0" 4 | authors = ["Graeme Coupar "] 5 | edition = "2018" 6 | publish = false 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | async-std = { version = "1.9", features = ["attributes"] } 12 | cynic = { version = "3" } 13 | futures = { version = "0.3" } 14 | log = "0.4" 15 | 16 | # wasm-specific 17 | ws_stream_wasm = "0.7" 18 | wasm-bindgen = "0.2" 19 | wasm-bindgen-futures = "0.4" 20 | console_log = "1" 21 | 22 | [dependencies.graphql-ws-client] 23 | path = "../" 24 | version = "0.11.1" 25 | default-features = false 26 | features = ["cynic", "ws_stream_wasm"] 27 | 28 | [dev-dependencies] 29 | insta = "1.11" 30 | 31 | [lints] 32 | workspace = true 33 | -------------------------------------------------------------------------------- /examples-wasm/examples/subscriptions.rs: -------------------------------------------------------------------------------- 1 | //! An example of using subscriptions with `graphql-ws-client` and 2 | //! `ws_stream_wasm` 3 | //! 4 | //! Talks to the the tide subscription example in `async-graphql` 5 | 6 | use std::future::IntoFuture; 7 | 8 | use graphql_ws_client::{ws_stream_wasm::Connection, Client}; 9 | 10 | mod schema { 11 | cynic::use_schema!("../schemas/books.graphql"); 12 | } 13 | 14 | #[derive(cynic::QueryFragment, Debug)] 15 | #[cynic(schema_path = "../schemas/books.graphql", graphql_type = "Book")] 16 | #[allow(dead_code)] 17 | struct Book { 18 | id: String, 19 | name: String, 20 | author: String, 21 | } 22 | 23 | #[derive(cynic::QueryFragment, Debug)] 24 | #[cynic(schema_path = "../schemas/books.graphql", graphql_type = "BookChanged")] 25 | #[allow(dead_code)] 26 | struct BookChanged { 27 | id: cynic::Id, 28 | book: Option, 29 | } 30 | 31 | #[derive(cynic::QueryFragment, Debug)] 32 | #[cynic( 33 | schema_path = "../schemas/books.graphql", 34 | graphql_type = "SubscriptionRoot" 35 | )] 36 | #[allow(dead_code)] 37 | struct BooksChangedSubscription { 38 | books: BookChanged, 39 | } 40 | 41 | #[async_std::main] 42 | async fn main() { 43 | use futures::StreamExt; 44 | use log::info; 45 | use wasm_bindgen::UnwrapThrowExt; 46 | 47 | console_log::init_with_level(log::Level::Info).expect("init logging"); 48 | 49 | let ws_conn = ws_stream_wasm::WsMeta::connect( 50 | "ws://localhost:8000/graphql", 51 | Some(vec!["graphql-transport-ws"]), 52 | ) 53 | .await 54 | .expect_throw("assume the connection succeeds"); 55 | 56 | info!("Connected"); 57 | 58 | let connection = Connection::new(ws_conn).await; 59 | 60 | let (client, actor) = Client::build(connection).await.unwrap(); 61 | wasm_bindgen_futures::spawn_local(actor.into_future()); 62 | 63 | let mut stream = client.subscribe(build_query()).await.unwrap(); 64 | info!("Running subscription"); 65 | while let Some(item) = stream.next().await { 66 | info!("{:?}", item); 67 | } 68 | } 69 | 70 | fn build_query() -> cynic::StreamingOperation { 71 | use cynic::SubscriptionBuilder; 72 | 73 | BooksChangedSubscription::build(()) 74 | } 75 | -------------------------------------------------------------------------------- /examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "examples" 3 | version = "0.11.0" 4 | authors = ["Graeme Coupar "] 5 | edition = "2018" 6 | publish = false 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | async-std = { version = "1.9", features = ["attributes"] } 12 | async-tungstenite = { version = "0.28", features = [ 13 | "async-std-runtime", 14 | "tokio-runtime", 15 | ] } 16 | cynic = { version = "3" } 17 | futures = { version = "0.3" } 18 | graphql_client = { version = "0.14" } 19 | serde = "1" 20 | tokio = { version = "1.15", features = ["rt-multi-thread", "macros"] } 21 | 22 | [dependencies.graphql-ws-client] 23 | path = "../" 24 | version = "0.11.1" 25 | default-features = false 26 | features = ["cynic", "tungstenite"] 27 | 28 | [dev-dependencies] 29 | insta = "1.11" 30 | 31 | [lints] 32 | workspace = true 33 | -------------------------------------------------------------------------------- /examples/examples/cynic-mulitiple-subscriptions.rs: -------------------------------------------------------------------------------- 1 | //! An example of running a multiple subscriptions on a single connection 2 | //! using `cynic`, `async-tungstenite` & the `async_std` 3 | //! executor. 4 | //! 5 | //! Talks to the the tide subscription example in `async-graphql` 6 | 7 | use std::future::IntoFuture; 8 | 9 | mod schema { 10 | cynic::use_schema!("../schemas/books.graphql"); 11 | } 12 | 13 | #[derive(cynic::QueryFragment, Debug)] 14 | #[cynic(schema_path = "../schemas/books.graphql", graphql_type = "Book")] 15 | #[allow(dead_code)] 16 | struct Book { 17 | id: String, 18 | name: String, 19 | author: String, 20 | } 21 | 22 | #[derive(cynic::QueryFragment, Debug)] 23 | #[cynic(schema_path = "../schemas/books.graphql", graphql_type = "BookChanged")] 24 | #[allow(dead_code)] 25 | struct BookChanged { 26 | id: cynic::Id, 27 | book: Option, 28 | } 29 | 30 | #[derive(cynic::QueryFragment, Debug)] 31 | #[cynic( 32 | schema_path = "../schemas/books.graphql", 33 | graphql_type = "SubscriptionRoot" 34 | )] 35 | #[allow(dead_code)] 36 | struct BooksChangedSubscription { 37 | books: BookChanged, 38 | } 39 | 40 | #[async_std::main] 41 | async fn main() { 42 | use async_tungstenite::tungstenite::{client::IntoClientRequest, http::HeaderValue}; 43 | use futures::StreamExt; 44 | use graphql_ws_client::Client; 45 | 46 | let mut request = "ws://localhost:8000/graphql".into_client_request().unwrap(); 47 | request.headers_mut().insert( 48 | "Sec-WebSocket-Protocol", 49 | HeaderValue::from_str("graphql-transport-ws").unwrap(), 50 | ); 51 | 52 | let (connection, _) = async_tungstenite::async_std::connect_async(request) 53 | .await 54 | .unwrap(); 55 | 56 | println!("Connected"); 57 | 58 | let (client, actor) = Client::build(connection).await.unwrap(); 59 | async_std::task::spawn(actor.into_future()); 60 | 61 | // In reality you'd probably want to different subscriptions, but for the sake of this example 62 | // these are the same subscriptions 63 | let mut first_subscription = client.subscribe(build_query()).await.unwrap(); 64 | let mut second_subscription = client.subscribe(build_query()).await.unwrap(); 65 | 66 | futures::join!( 67 | async move { 68 | while let Some(item) = first_subscription.next().await { 69 | println!("{item:?}"); 70 | } 71 | }, 72 | async move { 73 | while let Some(item) = second_subscription.next().await { 74 | println!("{item:?}"); 75 | } 76 | } 77 | ); 78 | } 79 | 80 | fn build_query() -> cynic::StreamingOperation { 81 | use cynic::SubscriptionBuilder; 82 | 83 | BooksChangedSubscription::build(()) 84 | } 85 | -------------------------------------------------------------------------------- /examples/examples/cynic-single-subscription.rs: -------------------------------------------------------------------------------- 1 | //! An example of creating a connection and running a single subscription on it 2 | //! using `cynic` and `async-tungstenite` 3 | //! 4 | //! Talks to the the tide subscription example in `async-graphql` 5 | 6 | mod schema { 7 | cynic::use_schema!("../schemas/books.graphql"); 8 | } 9 | 10 | #[derive(cynic::QueryFragment, Debug)] 11 | #[cynic(schema_path = "../schemas/books.graphql", graphql_type = "Book")] 12 | #[allow(dead_code)] 13 | struct Book { 14 | id: String, 15 | name: String, 16 | author: String, 17 | } 18 | 19 | #[derive(cynic::QueryFragment, Debug)] 20 | #[cynic(schema_path = "../schemas/books.graphql", graphql_type = "BookChanged")] 21 | #[allow(dead_code)] 22 | struct BookChanged { 23 | id: cynic::Id, 24 | book: Option, 25 | } 26 | 27 | #[derive(cynic::QueryFragment, Debug)] 28 | #[cynic( 29 | schema_path = "../schemas/books.graphql", 30 | graphql_type = "SubscriptionRoot" 31 | )] 32 | #[allow(dead_code)] 33 | struct BooksChangedSubscription { 34 | books: BookChanged, 35 | } 36 | 37 | #[async_std::main] 38 | async fn main() { 39 | use async_tungstenite::tungstenite::{client::IntoClientRequest, http::HeaderValue}; 40 | use futures::StreamExt; 41 | use graphql_ws_client::Client; 42 | 43 | let mut request = "ws://localhost:8000/graphql".into_client_request().unwrap(); 44 | request.headers_mut().insert( 45 | "Sec-WebSocket-Protocol", 46 | HeaderValue::from_str("graphql-transport-ws").unwrap(), 47 | ); 48 | 49 | let (connection, _) = async_tungstenite::async_std::connect_async(request) 50 | .await 51 | .unwrap(); 52 | 53 | println!("Connected"); 54 | 55 | let mut subscription = Client::build(connection) 56 | .subscribe(build_query()) 57 | .await 58 | .unwrap(); 59 | 60 | while let Some(item) = subscription.next().await { 61 | println!("{item:?}"); 62 | } 63 | } 64 | 65 | fn build_query() -> cynic::StreamingOperation { 66 | use cynic::SubscriptionBuilder; 67 | 68 | BooksChangedSubscription::build(()) 69 | } 70 | -------------------------------------------------------------------------------- /examples/examples/graphql-client-single-subscription.rs: -------------------------------------------------------------------------------- 1 | //! An example of creating a connection and running a single subscription on it 2 | //! using `graphql-client` and `async-tungstenite` 3 | //! 4 | //! Talks to the the tide subscription example in `async-graphql` 5 | 6 | use futures::StreamExt; 7 | use graphql_client::GraphQLQuery; 8 | use graphql_ws_client::graphql::StreamingOperation; 9 | 10 | #[derive(GraphQLQuery)] 11 | #[graphql( 12 | query_path = "examples/graphql-client-subscription.graphql", 13 | schema_path = "../schemas/books.graphql", 14 | response_derives = "Debug" 15 | )] 16 | struct BooksChanged; 17 | 18 | #[async_std::main] 19 | async fn main() { 20 | use async_tungstenite::tungstenite::{client::IntoClientRequest, http::HeaderValue}; 21 | use graphql_ws_client::Client; 22 | 23 | let mut request = "ws://localhost:8000/graphql".into_client_request().unwrap(); 24 | request.headers_mut().insert( 25 | "Sec-WebSocket-Protocol", 26 | HeaderValue::from_str("graphql-transport-ws").unwrap(), 27 | ); 28 | 29 | let (connection, _) = async_tungstenite::async_std::connect_async(request) 30 | .await 31 | .unwrap(); 32 | 33 | println!("Connected"); 34 | 35 | let mut subscription = Client::build(connection) 36 | .subscribe(StreamingOperation::::new( 37 | books_changed::Variables, 38 | )) 39 | .await 40 | .unwrap(); 41 | 42 | while let Some(item) = subscription.next().await { 43 | println!("{item:?}"); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/examples/graphql-client-subscription.graphql: -------------------------------------------------------------------------------- 1 | subscription BooksChanged { 2 | books(mutationType: CREATED) { 3 | id 4 | book { 5 | id 6 | name 7 | author 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/examples/tokio.rs: -------------------------------------------------------------------------------- 1 | //! An example of using subscriptions with `graphql-ws-client` and 2 | //! `async-tungstenite` 3 | //! 4 | //! Talks to the the tide subscription example in `async-graphql` 5 | 6 | use std::future::IntoFuture; 7 | 8 | mod schema { 9 | cynic::use_schema!("../schemas/books.graphql"); 10 | } 11 | 12 | #[derive(cynic::QueryFragment, Debug)] 13 | #[cynic(schema_path = "../schemas/books.graphql", graphql_type = "Book")] 14 | #[allow(dead_code)] 15 | struct Book { 16 | id: String, 17 | name: String, 18 | author: String, 19 | } 20 | 21 | #[derive(cynic::QueryFragment, Debug)] 22 | #[cynic(schema_path = "../schemas/books.graphql", graphql_type = "BookChanged")] 23 | #[allow(dead_code)] 24 | struct BookChanged { 25 | id: cynic::Id, 26 | book: Option, 27 | } 28 | 29 | #[derive(cynic::QueryFragment, Debug)] 30 | #[cynic( 31 | schema_path = "../schemas/books.graphql", 32 | graphql_type = "SubscriptionRoot" 33 | )] 34 | #[allow(dead_code)] 35 | struct BooksChangedSubscription { 36 | books: BookChanged, 37 | } 38 | 39 | #[tokio::main] 40 | async fn main() { 41 | use async_tungstenite::tungstenite::{client::IntoClientRequest, http::HeaderValue}; 42 | use futures::StreamExt; 43 | use graphql_ws_client::Client; 44 | 45 | let mut request = "ws://localhost:8000".into_client_request().unwrap(); 46 | request.headers_mut().insert( 47 | "Sec-WebSocket-Protocol", 48 | HeaderValue::from_str("graphql-transport-ws").unwrap(), 49 | ); 50 | 51 | let (connection, _) = async_tungstenite::tokio::connect_async(request) 52 | .await 53 | .unwrap(); 54 | 55 | println!("Connected"); 56 | 57 | let (client, actor) = Client::build(connection).await.unwrap(); 58 | tokio::spawn(actor.into_future()); 59 | 60 | let mut stream = client.subscribe(build_query()).await.unwrap(); 61 | println!("Running subscription apparently?"); 62 | while let Some(item) = stream.next().await { 63 | println!("{item:?}"); 64 | } 65 | } 66 | 67 | fn build_query() -> cynic::StreamingOperation { 68 | use cynic::SubscriptionBuilder; 69 | 70 | BooksChangedSubscription::build(()) 71 | } 72 | -------------------------------------------------------------------------------- /examples/src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /release-plz.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | changelog_update = false 3 | git_release_enable = false 4 | git_tag_enable = false 5 | 6 | # create the release PR in draft to avoid running CI till we're ready 7 | pr_draft = true 8 | 9 | [changelog] 10 | commit_parsers = [ 11 | { message = "^feat!", group = "Breaking Changes" }, 12 | { message = "^feat", group = "New Features" }, 13 | { message = "^fix", group = "Bug Fixes" }, 14 | { message = "^chore", group = "Changes" }, 15 | ] 16 | body = """ 17 | 18 | ## v{{ version }} - {{ timestamp | date(format="%Y-%m-%d") }} 19 | {% for group, commits in commits | group_by(attribute="group") %} 20 | ### {{ group | striptags | trim | upper_first }} 21 | 22 | {% for commit in commits -%} 23 | - {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message }} 24 | {% endfor -%} 25 | {% endfor -%} 26 | """ 27 | 28 | [[package]] 29 | name = "graphql-ws-client" 30 | 31 | git_tag_enable = true 32 | git_tag_name = "v{{version}}" 33 | 34 | git_release_enable = true 35 | git_release_name = "v{{version}}" 36 | 37 | changelog_update = true 38 | changelog_path = "./CHANGELOG.md" 39 | -------------------------------------------------------------------------------- /schemas/books.graphql: -------------------------------------------------------------------------------- 1 | schema { 2 | query: QueryRoot 3 | mutation: MutationRoot 4 | subscription: SubscriptionRoot 5 | } 6 | 7 | # Directs the executor to query only when the field exists. 8 | directive @ifdef on FIELD 9 | 10 | type Book { 11 | id: String! 12 | name: String! 13 | author: String! 14 | } 15 | 16 | type BookChanged { 17 | mutationType: MutationType! 18 | id: ID! 19 | book: Book 20 | } 21 | 22 | type MutationRoot { 23 | createBook(name: String!, author: String!): ID! 24 | deleteBook(id: ID!): Boolean! 25 | } 26 | 27 | enum MutationType { 28 | CREATED 29 | DELETED 30 | } 31 | 32 | type QueryRoot { 33 | books: [Book!]! 34 | } 35 | 36 | type SubscriptionRoot { 37 | interval(n: Int! = 1): Int! 38 | books(mutationType: MutationType): BookChanged! 39 | } 40 | -------------------------------------------------------------------------------- /src/doc_utils.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | 3 | use crate::{next::Message, Error}; 4 | 5 | pub struct Conn; 6 | 7 | impl crate::next::Connection for Conn { 8 | async fn receive(&mut self) -> Option { 9 | unimplemented!() 10 | } 11 | 12 | async fn send(&mut self, _: Message) -> Result<(), Error> { 13 | unimplemented!() 14 | } 15 | } 16 | 17 | #[derive(serde::Serialize)] 18 | pub struct Subscription; 19 | 20 | impl crate::graphql::GraphqlOperation for Subscription { 21 | type Response = (); 22 | 23 | type Error = crate::Error; 24 | 25 | fn decode(&self, _data: serde_json::Value) -> Result { 26 | unimplemented!() 27 | } 28 | } 29 | 30 | pub fn spawn(_: impl Future + Send + 'static) {} 31 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | #[derive(thiserror::Error, Debug)] 2 | /// Error type 3 | pub enum Error { 4 | /// Unknown error 5 | #[error("unknown: {0}")] 6 | Unknown(String), 7 | /// Custom error 8 | #[error("{0}: {1}")] 9 | Custom(String, String), 10 | /// Unexpected close frame 11 | #[error("got close frame. code: {0}, reason: {1}")] 12 | Close(u16, String), 13 | /// Decoding / parsing error 14 | #[error("message decode error, reason: {0}")] 15 | Decode(String), 16 | /// Serializing error 17 | #[error("couldn't serialize message, reason: {0}")] 18 | Serializing(String), 19 | /// Sending error 20 | #[error("message sending error, reason: {0}")] 21 | Send(String), 22 | /// Futures spawn error 23 | #[error("futures spawn error, reason: {0}")] 24 | SpawnHandle(String), 25 | /// Sender shutdown error 26 | #[error("sender shutdown error, reason: {0}")] 27 | SenderShutdown(String), 28 | } 29 | -------------------------------------------------------------------------------- /src/graphql.rs: -------------------------------------------------------------------------------- 1 | //! This module contains traits that abstract over GraphQL implementations, 2 | //! allowing this library to be used with different GraphQL client libraries. 3 | //! 4 | //! Support is provided for [`cynic`][cynic] & [`graphql_client`][graphql-client], 5 | //! but other client libraries can be added by implementing these traits for 6 | //! those libraries. 7 | //! 8 | //! [cynic]: https://cynic-rs.dev 9 | //! [graphql-client]: https://github.com/graphql-rust/graphql-client 10 | 11 | /// An abstraction over GraphQL operations. 12 | pub trait GraphqlOperation: serde::Serialize { 13 | /// The actual response & error type of this operation. 14 | type Response; 15 | 16 | /// The error that will be returned from failed attempts to decode a `Response`. 17 | type Error: std::error::Error; 18 | 19 | /// Decodes a `GenericResponse` into the actual response that will be returned 20 | /// to users for this operation. 21 | fn decode(&self, data: serde_json::Value) -> Result; 22 | } 23 | 24 | #[cfg(feature = "client-cynic")] 25 | mod cynic { 26 | use super::GraphqlOperation; 27 | 28 | #[cfg_attr(docsrs, doc(cfg(feature = "client-cynic")))] 29 | impl GraphqlOperation 30 | for ::cynic::StreamingOperation 31 | where 32 | ResponseData: serde::de::DeserializeOwned, 33 | Variables: serde::Serialize, 34 | { 35 | type Response = ::cynic::GraphQlResponse; 36 | 37 | type Error = serde_json::Error; 38 | 39 | fn decode(&self, response: serde_json::Value) -> Result { 40 | serde_json::from_value(response) 41 | } 42 | } 43 | } 44 | 45 | #[cfg(feature = "client-graphql-client")] 46 | pub use self::graphql_client::StreamingOperation; 47 | 48 | #[cfg(feature = "client-graphql-client")] 49 | mod graphql_client { 50 | use super::GraphqlOperation; 51 | use ::graphql_client::{GraphQLQuery, QueryBody, Response}; 52 | use std::marker::PhantomData; 53 | 54 | /// A streaming operation for a [`GraphQLQuery`] 55 | #[cfg_attr(docsrs, doc(cfg(feature = "client-graphql-client")))] 56 | pub struct StreamingOperation { 57 | inner: QueryBody, 58 | phantom: PhantomData, 59 | } 60 | 61 | impl StreamingOperation { 62 | /// Constructs a [`StreamingOperation`] 63 | pub fn new(variables: Q::Variables) -> Self { 64 | Self { 65 | inner: Q::build_query(variables), 66 | phantom: PhantomData, 67 | } 68 | } 69 | } 70 | 71 | impl serde::Serialize for StreamingOperation { 72 | fn serialize(&self, serializer: S) -> Result 73 | where 74 | S: serde::Serializer, 75 | { 76 | self.inner.serialize(serializer) 77 | } 78 | } 79 | 80 | impl GraphqlOperation for StreamingOperation { 81 | type Response = Response; 82 | 83 | type Error = serde_json::Error; 84 | 85 | fn decode(&self, response: serde_json::Value) -> Result { 86 | serde_json::from_value(response) 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # graphql-ws-client 2 | //! 3 | //! graphql-ws-client implements asynchronous GraphQL-over-Websocket using the 4 | //! [graphql-transport-ws protocol][protocol]. It is websocket client, graphql 5 | //! client _and_ async runtime agnostic. Built in support is provided for: 6 | //! 7 | //! - [Cynic][cynic] & [Graphql-Client][graphql-client] GraphQL clients. 8 | //! - [async-tungstenite][async-tungstenite], [tokio-tungstenite][tokio-tungstenite] 9 | //! & [ws-stream-wasm][ws-stream-wasm] Websocket Clients . 10 | //! - Any async runtime. 11 | //! 12 | //! If you'd like to use another client or adding support should be trivial. 13 | //! 14 | //! ```rust 15 | //! use graphql_ws_client::Client; 16 | //! use std::future::IntoFuture; 17 | //! use futures_lite::StreamExt; 18 | //! # async fn example() -> Result<(), graphql_ws_client::Error> { 19 | //! # let connection = graphql_ws_client::__doc_utils::Conn; 20 | //! # let subscription = graphql_ws_client::__doc_utils::Subscription; 21 | //! 22 | //! let mut stream = Client::build(connection).subscribe(subscription).await?; 23 | //! 24 | //! while let Some(response) = stream.next().await { 25 | //! // Do something with response 26 | //! } 27 | //! # Ok(()) 28 | //! # } 29 | //! ``` 30 | //! 31 | //! See the [examples][examples] for more thorough usage details. 32 | //! 33 | //! [protocol]: https://github.com/graphql/graphql-over-http/blob/main/rfcs/GraphQLOverWebSocket.md 34 | //! [cynic]: https://cynic-rs.dev 35 | //! [graphql-client]: https://github.com/graphql-rust/graphql-client 36 | //! [async-tungstenite]: https://github.com/sdroege/async-tungstenite 37 | //! [tokio-tungstenite]: https://github.com/snapview/tokio-tungstenite 38 | //! [ws-stream-wasm]: https://github.com/najamelan/ws_stream_wasm 39 | //! [examples]: https://github.com/obmarg/graphql-ws-client/tree/main/examples/examples 40 | 41 | #![cfg_attr(docsrs, feature(doc_cfg))] 42 | #![warn(missing_docs)] 43 | 44 | mod error; 45 | mod logging; 46 | mod protocol; 47 | mod sink_ext; 48 | 49 | #[doc(hidden)] 50 | #[path = "doc_utils.rs"] 51 | pub mod __doc_utils; 52 | 53 | pub mod graphql; 54 | 55 | mod next; 56 | 57 | #[cfg(feature = "ws_stream_wasm")] 58 | #[cfg_attr(docsrs, doc(cfg(feature = "ws_stream_wasm")))] 59 | /// Integration with the [ws_stream_wasm][1] library 60 | /// 61 | /// [1]: https://docs.rs/ws_stream/latest/ws_stream 62 | pub mod ws_stream_wasm; 63 | 64 | #[cfg(feature = "tungstenite")] 65 | #[cfg_attr(docsrs, doc(cfg(feature = "tungstenite")))] 66 | mod native; 67 | 68 | pub use next::*; 69 | 70 | pub use error::Error; 71 | -------------------------------------------------------------------------------- /src/logging.rs: -------------------------------------------------------------------------------- 1 | #[cfg(all(feature = "logging", not(target_arch = "wasm32")))] 2 | macro_rules! trace { 3 | ($($arg:tt)+) => ( 4 | log::trace!(target: "graphql-ws-client", $($arg)+) 5 | ) 6 | } 7 | 8 | #[cfg(any(not(feature = "logging"), target_arch = "wasm32"))] 9 | macro_rules! trace { 10 | ($($t:tt)*) => {}; 11 | } 12 | 13 | #[cfg(all(feature = "logging", not(target_arch = "wasm32")))] 14 | #[allow(unused_macros)] 15 | macro_rules! warning { 16 | ($($arg:tt)+) => ( 17 | log::warn!(target: "graphql-ws-client", $($arg)+) 18 | ) 19 | } 20 | 21 | #[cfg(any(not(feature = "logging"), target_arch = "wasm32"))] 22 | #[allow(unused_macros)] 23 | macro_rules! warning { 24 | ($($t:tt)*) => {}; 25 | } 26 | 27 | pub(crate) use trace; 28 | 29 | #[allow(unused_imports)] 30 | pub(crate) use warning; 31 | -------------------------------------------------------------------------------- /src/native.rs: -------------------------------------------------------------------------------- 1 | use futures_lite::{Stream, StreamExt}; 2 | use futures_sink::Sink; 3 | use tungstenite::{self, protocol::CloseFrame}; 4 | 5 | use crate::{sink_ext::SinkExt, Error, Message}; 6 | 7 | #[cfg_attr(docsrs, doc(cfg(feature = "tungstenite")))] 8 | impl crate::next::Connection for T 9 | where 10 | T: Stream> 11 | + Sink 12 | + Send 13 | + Sync 14 | + Unpin, 15 | >::Error: std::fmt::Display, 16 | { 17 | async fn receive(&mut self) -> Option { 18 | loop { 19 | match self.next().await? { 20 | Ok(tungstenite::Message::Text(text)) => { 21 | return Some(crate::next::Message::Text(text)) 22 | } 23 | Ok(tungstenite::Message::Ping(_)) => return Some(crate::next::Message::Ping), 24 | Ok(tungstenite::Message::Pong(_)) => return Some(crate::next::Message::Pong), 25 | Ok(tungstenite::Message::Close(frame)) => { 26 | return Some(crate::next::Message::Close { 27 | code: frame.as_ref().map(|frame| frame.code.into()), 28 | reason: frame.map(|frame| frame.reason.to_string()), 29 | }) 30 | } 31 | Ok(tungstenite::Message::Frame(_) | tungstenite::Message::Binary(_)) => continue, 32 | Err(error) => { 33 | #[allow(unused)] 34 | let error = error; 35 | crate::logging::warning!("error receiving message: {error:?}"); 36 | return None; 37 | } 38 | } 39 | } 40 | } 41 | 42 | async fn send(&mut self, message: crate::next::Message) -> Result<(), Error> { 43 | >::send( 44 | self, 45 | match message { 46 | crate::next::Message::Text(text) => tungstenite::Message::Text(text), 47 | crate::next::Message::Close { code, reason } => { 48 | tungstenite::Message::Close(code.zip(reason).map(|(code, reason)| CloseFrame { 49 | code: code.into(), 50 | reason: reason.into(), 51 | })) 52 | } 53 | crate::next::Message::Ping => tungstenite::Message::Ping(vec![]), 54 | crate::next::Message::Pong => tungstenite::Message::Pong(vec![]), 55 | }, 56 | ) 57 | .await 58 | .map_err(|error| Error::Send(error.to_string())) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/next/actor.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::{hash_map::Entry, HashMap}, 3 | future::IntoFuture, 4 | }; 5 | 6 | use futures_lite::{future, stream, FutureExt, StreamExt}; 7 | use serde_json::{json, Value}; 8 | 9 | use crate::{ 10 | logging::{trace, warning}, 11 | protocol::Event, 12 | Error, 13 | }; 14 | 15 | use super::{ 16 | connection::{Message, ObjectSafeConnection}, 17 | keepalive::KeepAliveSettings, 18 | ConnectionCommand, 19 | }; 20 | 21 | #[must_use] 22 | /// The `ConnectionActor` contains the main loop for handling incoming 23 | /// & outgoing messages for a Client. 24 | /// 25 | /// This type implements `IntoFuture` and should usually be spawned 26 | /// with an async runtime. 27 | pub struct ConnectionActor { 28 | client: async_channel::Receiver, 29 | connection: Box, 30 | operations: HashMap>, 31 | keep_alive: KeepAliveSettings, 32 | keep_alive_actor: stream::Boxed, 33 | } 34 | 35 | impl ConnectionActor { 36 | pub(super) fn new( 37 | connection: Box, 38 | client: async_channel::Receiver, 39 | keep_alive: KeepAliveSettings, 40 | ) -> Self { 41 | ConnectionActor { 42 | client, 43 | connection, 44 | operations: HashMap::new(), 45 | keep_alive_actor: Box::pin(keep_alive.run()), 46 | keep_alive, 47 | } 48 | } 49 | 50 | async fn run(mut self) { 51 | while let Some(next) = self.next().await { 52 | let response = match next { 53 | Next::Command(cmd) => self.handle_command(cmd), 54 | Next::Message(message) => self.handle_message(message).await, 55 | }; 56 | 57 | let Some(response) = response else { continue }; 58 | 59 | if matches!(response, Message::Close { .. }) { 60 | self.connection.send(response).await.ok(); 61 | return; 62 | } 63 | 64 | if self.connection.send(response).await.is_err() { 65 | return; 66 | } 67 | } 68 | 69 | self.connection 70 | .send(Message::Close { 71 | code: Some(100), 72 | reason: None, 73 | }) 74 | .await 75 | .ok(); 76 | } 77 | 78 | fn handle_command(&mut self, cmd: ConnectionCommand) -> Option { 79 | match cmd { 80 | ConnectionCommand::Subscribe { 81 | request, 82 | sender, 83 | id, 84 | } => { 85 | assert!(self.operations.insert(id, sender).is_none()); 86 | 87 | Some(Message::Text(request)) 88 | } 89 | ConnectionCommand::Cancel(id) => { 90 | if self.operations.remove(&id).is_some() { 91 | return Some(Message::complete(id)); 92 | } 93 | None 94 | } 95 | ConnectionCommand::Close(code, reason) => Some(Message::Close { 96 | code: Some(code), 97 | reason: Some(reason), 98 | }), 99 | ConnectionCommand::Ping => Some(Message::graphql_ping()), 100 | } 101 | } 102 | 103 | async fn handle_message(&mut self, message: Message) -> Option { 104 | let event = match extract_event(message) { 105 | Ok(event) => event?, 106 | Err(Error::Close(code, reason)) => { 107 | return Some(Message::Close { 108 | code: Some(code), 109 | reason: Some(reason), 110 | }) 111 | } 112 | Err(other) => { 113 | return Some(Message::Close { 114 | code: Some(4857), 115 | reason: Some(format!("Error while decoding event: {other}")), 116 | }) 117 | } 118 | }; 119 | 120 | match event { 121 | event @ (Event::Next { .. } | Event::Error { .. }) => { 122 | let Some(id) = event.id().unwrap().parse::().ok() else { 123 | return Some(Message::close(Reason::UnknownSubscription)); 124 | }; 125 | 126 | let sender = self.operations.entry(id); 127 | 128 | let Entry::Occupied(mut sender) = sender else { 129 | return None; 130 | }; 131 | 132 | let payload = event.forwarding_payload().unwrap(); 133 | 134 | if sender.get_mut().send(payload).await.is_err() { 135 | sender.remove(); 136 | return Some(Message::complete(id)); 137 | } 138 | 139 | None 140 | } 141 | Event::Complete { id } => { 142 | let Some(id) = id.parse::().ok() else { 143 | return Some(Message::close(Reason::UnknownSubscription)); 144 | }; 145 | 146 | trace!("Stream complete"); 147 | 148 | self.operations.remove(&id); 149 | None 150 | } 151 | Event::ConnectionAck { .. } => Some(Message::close(Reason::UnexpectedAck)), 152 | Event::Ping { .. } => Some(Message::graphql_pong()), 153 | Event::Pong { .. } => None, 154 | } 155 | } 156 | 157 | async fn next(&mut self) -> Option { 158 | enum Select { 159 | Command(Option), 160 | Message(Option), 161 | KeepAlive(Option), 162 | } 163 | 164 | let command = async { Select::Command(self.client.recv().await.ok()) }; 165 | let message = async { Select::Message(self.connection.receive().await) }; 166 | let keep_alive = async { Select::KeepAlive(self.keep_alive_actor.next().await) }; 167 | 168 | match command.or(message).or(keep_alive).await { 169 | Select::Command(Some(command)) | Select::KeepAlive(Some(command)) => { 170 | Some(Next::Command(command)) 171 | } 172 | Select::Command(None) => { 173 | // All clients have disconnected 174 | None 175 | } 176 | Select::Message(message) => { 177 | self.keep_alive_actor = Box::pin(self.keep_alive.run()); 178 | Some(Next::Message(message?)) 179 | } 180 | Select::KeepAlive(None) => Some(self.keep_alive.report_timeout()), 181 | } 182 | } 183 | } 184 | 185 | enum Next { 186 | Command(ConnectionCommand), 187 | Message(Message), 188 | } 189 | 190 | impl IntoFuture for ConnectionActor { 191 | type Output = (); 192 | 193 | type IntoFuture = future::Boxed<()>; 194 | 195 | fn into_future(self) -> Self::IntoFuture { 196 | Box::pin(self.run()) 197 | } 198 | } 199 | 200 | fn extract_event(message: Message) -> Result, Error> { 201 | match message { 202 | Message::Text(s) => { 203 | trace!("Decoding message: {}", s); 204 | Ok(Some( 205 | serde_json::from_str(&s).map_err(|err| Error::Decode(err.to_string()))?, 206 | )) 207 | } 208 | Message::Close { code, reason } => Err(Error::Close( 209 | code.unwrap_or_default(), 210 | reason.unwrap_or_default(), 211 | )), 212 | Message::Ping | Message::Pong => Ok(None), 213 | } 214 | } 215 | 216 | enum Reason { 217 | UnexpectedAck, 218 | UnknownSubscription, 219 | } 220 | 221 | impl Message { 222 | fn close(reason: Reason) -> Self { 223 | match reason { 224 | Reason::UnexpectedAck => Message::Close { 225 | code: Some(4855), 226 | reason: Some("too many acknowledges".into()), 227 | }, 228 | Reason::UnknownSubscription => Message::Close { 229 | code: Some(4856), 230 | reason: Some("unknown subscription".into()), 231 | }, 232 | } 233 | } 234 | } 235 | 236 | impl Event { 237 | fn forwarding_payload(self) -> Option { 238 | match self { 239 | Event::Next { payload, .. } => Some(payload), 240 | Event::Error { payload, .. } => Some(json!({"errors": payload})), 241 | _ => None, 242 | } 243 | } 244 | } 245 | 246 | impl KeepAliveSettings { 247 | fn report_timeout(&self) -> Next { 248 | warning!( 249 | "No messages received within keep-alive ({:?}s) from server. Closing the connection", 250 | self.interval.unwrap() 251 | ); 252 | Next::Command(ConnectionCommand::Close( 253 | 4503, 254 | "Service unavailable. keep-alive failure".to_string(), 255 | )) 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /src/next/builder.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | future::{Future, IntoFuture}, 3 | time::Duration, 4 | }; 5 | 6 | use futures_lite::future; 7 | use serde::Serialize; 8 | 9 | use crate::{graphql::GraphqlOperation, logging::trace, protocol::Event, Error}; 10 | 11 | use super::{ 12 | actor::ConnectionActor, 13 | connection::{Connection, Message, ObjectSafeConnection}, 14 | keepalive::KeepAliveSettings, 15 | production_future::read_from_producer, 16 | Client, Subscription, 17 | }; 18 | 19 | /// Builder for Graphql over Websocket clients 20 | /// 21 | /// This can be used to configure the client prior to construction, but can also create 22 | /// subscriptions directly in the case where users only need to run one per connection. 23 | /// 24 | /// ```rust 25 | /// use graphql_ws_client::Client; 26 | /// use std::future::IntoFuture; 27 | /// # 28 | /// # async fn example() -> Result<(), graphql_ws_client::Error> { 29 | /// # let connection = graphql_ws_client::__doc_utils::Conn; 30 | /// let (client, actor) = Client::build(connection).await?; 31 | /// # Ok(()) 32 | /// # } 33 | /// ``` 34 | #[must_use] 35 | pub struct ClientBuilder { 36 | payload: Option, 37 | subscription_buffer_size: Option, 38 | connection: Box, 39 | keep_alive: KeepAliveSettings, 40 | } 41 | 42 | impl super::Client { 43 | /// Creates a `ClientBuilder` with the given connection. 44 | /// 45 | /// ```rust 46 | /// use graphql_ws_client::Client; 47 | /// use std::future::IntoFuture; 48 | /// # async fn example() -> Result<(), graphql_ws_client::Error> { 49 | /// # let connection = graphql_ws_client::__doc_utils::Conn; 50 | /// let (client, actor) = Client::build(connection).await?; 51 | /// # Ok(()) 52 | /// # } 53 | /// ``` 54 | pub fn build(connection: Conn) -> ClientBuilder 55 | where 56 | Conn: Connection + Send + 'static, 57 | { 58 | ClientBuilder { 59 | payload: None, 60 | subscription_buffer_size: None, 61 | connection: Box::new(connection), 62 | keep_alive: KeepAliveSettings::default(), 63 | } 64 | } 65 | } 66 | 67 | impl ClientBuilder { 68 | /// Add payload to `connection_init` 69 | /// 70 | /// # Errors 71 | /// 72 | /// Will return `Err` if `payload` serialization fails. 73 | pub fn payload(self, payload: NewPayload) -> Result 74 | where 75 | NewPayload: Serialize, 76 | { 77 | Ok(ClientBuilder { 78 | payload: Some( 79 | serde_json::to_value(payload) 80 | .map_err(|error| Error::Serializing(error.to_string()))?, 81 | ), 82 | ..self 83 | }) 84 | } 85 | 86 | /// Sets the size of the incoming message buffer that subscriptions created by this client will 87 | /// use 88 | pub fn subscription_buffer_size(self, new: usize) -> Self { 89 | ClientBuilder { 90 | subscription_buffer_size: Some(new), 91 | ..self 92 | } 93 | } 94 | 95 | /// Sets the interval between keep alives. 96 | /// 97 | /// Any incoming messages automatically reset this interval so keep alives may not be sent 98 | /// on busy connections even if this is set. 99 | pub fn keep_alive_interval(mut self, new: Duration) -> Self { 100 | self.keep_alive.interval = Some(new); 101 | self 102 | } 103 | 104 | /// The number of keepalive retries before a connection is considered broken. 105 | /// 106 | /// This defaults to 3, but has no effect if `keep_alive_interval` is not called. 107 | pub fn keep_alive_retries(mut self, count: usize) -> Self { 108 | self.keep_alive.retries = count; 109 | self 110 | } 111 | 112 | /// Initialise a Client and use it to run a single subscription 113 | /// 114 | /// ```rust 115 | /// use graphql_ws_client::Client; 116 | /// use std::future::IntoFuture; 117 | /// # async fn example() -> Result<(), graphql_ws_client::Error> { 118 | /// # let connection = graphql_ws_client::__doc_utils::Conn; 119 | /// # let subscription = graphql_ws_client::__doc_utils::Subscription; 120 | /// let stream = Client::build(connection).subscribe(subscription).await?; 121 | /// # Ok(()) 122 | /// # } 123 | /// ``` 124 | /// 125 | /// Note that this takes ownership of the client, so it cannot be 126 | /// used to run any more operations. 127 | /// 128 | /// If users want to run multiple operations on a connection they 129 | /// should use the `IntoFuture` impl to construct a `Client` 130 | pub async fn subscribe<'a, Operation>( 131 | self, 132 | op: Operation, 133 | ) -> Result, Error> 134 | where 135 | Operation: GraphqlOperation + Unpin + Send + 'static, 136 | { 137 | let (client, actor) = self.await?; 138 | 139 | let actor_future = actor.into_future(); 140 | let subscribe_future = client.subscribe(op); 141 | 142 | let (stream, actor_future) = run_startup(subscribe_future, actor_future).await?; 143 | 144 | Ok(stream.join(actor_future)) 145 | } 146 | } 147 | 148 | impl IntoFuture for ClientBuilder { 149 | type Output = Result<(Client, ConnectionActor), Error>; 150 | 151 | type IntoFuture = future::Boxed; 152 | 153 | fn into_future(self) -> Self::IntoFuture { 154 | Box::pin(self.build()) 155 | } 156 | } 157 | 158 | impl ClientBuilder { 159 | /// Constructs a Client 160 | /// 161 | /// Accepts an already built websocket connection, and returns the connection 162 | /// and a future that must be awaited somewhere - if the future is dropped the 163 | /// connection will also drop. 164 | pub async fn build(self) -> Result<(Client, ConnectionActor), Error> { 165 | let Self { 166 | payload, 167 | subscription_buffer_size, 168 | mut connection, 169 | keep_alive, 170 | } = self; 171 | 172 | connection.send(Message::init(payload)).await?; 173 | 174 | // wait for ack before entering receiver loop: 175 | loop { 176 | match connection.receive().await { 177 | None => return Err(Error::Unknown("connection dropped".into())), 178 | Some(Message::Close { code, reason }) => { 179 | return Err(Error::Close( 180 | code.unwrap_or_default(), 181 | reason.unwrap_or_default(), 182 | )) 183 | } 184 | Some(Message::Ping | Message::Pong) => {} 185 | Some(message @ Message::Text(_)) => { 186 | let event = message.deserialize::()?; 187 | match event { 188 | // pings can be sent at any time 189 | Event::Ping { .. } => { 190 | connection.send(Message::graphql_pong()).await?; 191 | } 192 | Event::Pong { .. } => {} 193 | Event::ConnectionAck { .. } => { 194 | // handshake completed, ready to enter main receiver loop 195 | trace!("connection_ack received, handshake completed"); 196 | break; 197 | } 198 | event => { 199 | connection 200 | .send(Message::Close { 201 | code: Some(4950), 202 | reason: Some("Unexpected message while waiting for ack".into()), 203 | }) 204 | .await 205 | .ok(); 206 | return Err(Error::Decode(format!( 207 | "expected a connection_ack or ping, got {}", 208 | event.r#type() 209 | ))); 210 | } 211 | } 212 | } 213 | } 214 | } 215 | 216 | let (command_sender, command_receiver) = async_channel::bounded(5); 217 | 218 | let actor = ConnectionActor::new(connection, command_receiver, keep_alive); 219 | 220 | let client = Client::new_internal(command_sender, subscription_buffer_size.unwrap_or(5)); 221 | 222 | Ok((client, actor)) 223 | } 224 | } 225 | 226 | async fn run_startup( 227 | subscribe: SubscribeFut, 228 | actor: future::Boxed<()>, 229 | ) -> Result<(Subscription, future::Boxed<()>), Error> 230 | where 231 | SubscribeFut: Future, Error>>, 232 | Operation: GraphqlOperation, 233 | { 234 | match read_from_producer(subscribe, actor).await { 235 | Some((Ok(subscription), actor)) => Ok((subscription, actor)), 236 | Some((Err(err), _)) => Err(err), 237 | None => Err(Error::Unknown( 238 | "actor ended before subscription started".into(), 239 | )), 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/next/connection.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::pin::Pin; 3 | 4 | use crate::Error; 5 | 6 | /// Abstraction around a websocket connection. 7 | /// 8 | /// Built in implementations are provided for `ws_stream_wasm` & `async_tungstenite`. 9 | /// 10 | /// If users wish to add support for a new client they should implement this trait. 11 | pub trait Connection { 12 | /// Receive the next message on this connection. 13 | fn receive(&mut self) -> impl Future> + Send; 14 | 15 | /// Send a message with on connection 16 | fn send(&mut self, message: Message) -> impl Future> + Send; 17 | } 18 | 19 | /// A websocket message 20 | /// 21 | /// Websocket client libraries usually provide their own version of this struct. 22 | /// The [Connection] trait for a given client should handle translation to & from this enum. 23 | pub enum Message { 24 | /// A message containing the given text payload 25 | Text(String), 26 | /// A message that closes the connection with the given code & reason 27 | Close { 28 | /// The status code for this close message 29 | code: Option, 30 | /// Some text explaining the reason the connection is being closed 31 | reason: Option, 32 | }, 33 | /// A ping 34 | Ping, 35 | /// A reply to a ping 36 | Pong, 37 | } 38 | 39 | impl Message { 40 | pub(crate) fn deserialize(self) -> Result 41 | where 42 | T: serde::de::DeserializeOwned, 43 | { 44 | let Message::Text(text) = self else { 45 | panic!("Don't call deserialize on non-text messages"); 46 | }; 47 | 48 | serde_json::from_str(&text).map_err(|error| Error::Decode(error.to_string())) 49 | } 50 | 51 | pub(crate) fn init(payload: Option) -> Self { 52 | Self::Text( 53 | serde_json::to_string(&crate::protocol::ConnectionInit::new(payload)) 54 | .expect("payload is already serialized so this shouldn't fail"), 55 | ) 56 | } 57 | 58 | pub(crate) fn graphql_pong() -> Self { 59 | Self::Text(serde_json::to_string(&crate::protocol::Message::Pong::<()>).unwrap()) 60 | } 61 | 62 | pub(crate) fn graphql_ping() -> Self { 63 | Self::Text(serde_json::to_string(&crate::protocol::Message::Ping::<()>).unwrap()) 64 | } 65 | 66 | pub(crate) fn complete(id: usize) -> Self { 67 | Self::Text( 68 | serde_json::to_string(&crate::protocol::Message::Complete::<()> { id: id.to_string() }) 69 | .unwrap(), 70 | ) 71 | } 72 | } 73 | 74 | /// An object safe wrapper around the Connection trait, allowing us 75 | /// to use it dynamically 76 | pub(crate) trait ObjectSafeConnection: Send { 77 | fn receive(&mut self) -> Pin> + Send + '_>>; 78 | 79 | fn send( 80 | &mut self, 81 | message: Message, 82 | ) -> Pin> + Send + '_>>; 83 | } 84 | 85 | impl ObjectSafeConnection for T { 86 | fn receive(&mut self) -> Pin> + Send + '_>> { 87 | Box::pin(Connection::receive(self)) 88 | } 89 | 90 | fn send( 91 | &mut self, 92 | message: Message, 93 | ) -> Pin> + Send + '_>> { 94 | Box::pin(Connection::send(self, message)) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/next/keepalive.rs: -------------------------------------------------------------------------------- 1 | use std::{future::pending, time::Duration}; 2 | 3 | use futures_lite::{stream, Stream}; 4 | 5 | use crate::ConnectionCommand; 6 | 7 | #[derive(Clone)] 8 | pub(super) struct KeepAliveSettings { 9 | /// How often to send a keep alive ping 10 | pub(super) interval: Option, 11 | 12 | /// How many pings can be sent without receiving a reply before 13 | /// the connection is considered dropped 14 | pub(super) retries: usize, 15 | } 16 | 17 | impl Default for KeepAliveSettings { 18 | fn default() -> Self { 19 | Self { 20 | interval: None, 21 | retries: 3, 22 | } 23 | } 24 | } 25 | 26 | enum KeepAliveState { 27 | Running, 28 | StartedKeepAlive, 29 | TimingOut { failure_count: usize }, 30 | } 31 | 32 | impl KeepAliveSettings { 33 | pub(super) fn run(&self) -> impl Stream + 'static { 34 | let settings = self.clone(); 35 | 36 | stream::unfold(KeepAliveState::Running, move |mut state| async move { 37 | match settings.interval { 38 | Some(duration) => futures_timer::Delay::new(duration).await, 39 | None => pending::<()>().await, 40 | } 41 | 42 | match state { 43 | KeepAliveState::Running => { 44 | state = KeepAliveState::StartedKeepAlive; 45 | } 46 | KeepAliveState::StartedKeepAlive => { 47 | state = KeepAliveState::TimingOut { failure_count: 0 }; 48 | } 49 | KeepAliveState::TimingOut { failure_count } => { 50 | state = KeepAliveState::TimingOut { 51 | failure_count: failure_count + 1, 52 | }; 53 | } 54 | } 55 | 56 | if state.failure_count() > settings.retries { 57 | // returning None aborts 58 | return None; 59 | } 60 | 61 | Some((ConnectionCommand::Ping, state)) 62 | }) 63 | } 64 | } 65 | 66 | impl KeepAliveState { 67 | pub fn failure_count(&self) -> usize { 68 | match self { 69 | KeepAliveState::Running | KeepAliveState::StartedKeepAlive => 0, 70 | KeepAliveState::TimingOut { failure_count } => *failure_count, 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/next/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt, 3 | sync::{ 4 | atomic::{AtomicUsize, Ordering}, 5 | Arc, 6 | }, 7 | }; 8 | 9 | use futures_lite::StreamExt; 10 | use serde_json::Value; 11 | 12 | use crate::{ 13 | graphql::GraphqlOperation, 14 | protocol::{self}, 15 | Error, 16 | }; 17 | 18 | mod actor; 19 | mod builder; 20 | mod connection; 21 | mod keepalive; 22 | mod production_future; 23 | mod stream; 24 | 25 | pub use self::{ 26 | actor::ConnectionActor, 27 | builder::ClientBuilder, 28 | connection::{Connection, Message}, 29 | stream::Subscription, 30 | }; 31 | 32 | /// A GraphQL over Websocket client 33 | /// 34 | /// ```rust,no_run 35 | /// use graphql_ws_client::Client; 36 | /// use std::future::IntoFuture; 37 | /// use futures_lite::StreamExt; 38 | /// # use graphql_ws_client::__doc_utils::spawn; 39 | /// # async fn example() -> Result<(), graphql_ws_client::Error> { 40 | /// # let connection = graphql_ws_client::__doc_utils::Conn; 41 | /// # let subscription = graphql_ws_client::__doc_utils::Subscription; 42 | /// 43 | /// let (mut client, actor) = Client::build(connection).await?; 44 | /// 45 | /// // Spawn the actor onto an async runtime 46 | /// spawn(actor.into_future()); 47 | /// 48 | /// let mut subscription = client.subscribe(subscription).await?; 49 | /// 50 | /// while let Some(response) = subscription.next().await { 51 | /// // Do something with response 52 | /// } 53 | /// # Ok(()) 54 | /// # } 55 | #[derive(Clone)] 56 | pub struct Client { 57 | actor: async_channel::Sender, 58 | subscription_buffer_size: usize, 59 | next_id: Arc, 60 | } 61 | 62 | impl Client { 63 | pub(super) fn new_internal( 64 | actor: async_channel::Sender, 65 | subscription_buffer_size: usize, 66 | ) -> Self { 67 | Client { 68 | actor, 69 | subscription_buffer_size, 70 | next_id: Arc::new(AtomicUsize::new(0)), 71 | } 72 | } 73 | 74 | /// Starts a streaming operation on this client. 75 | /// 76 | /// Returns a `Stream` of responses. 77 | pub async fn subscribe<'a, Operation>( 78 | &self, 79 | op: Operation, 80 | ) -> Result, Error> 81 | where 82 | Operation: GraphqlOperation + Unpin + Send + 'static, 83 | { 84 | let (sender, receiver) = async_channel::bounded(self.subscription_buffer_size); 85 | 86 | let id = self.next_id.fetch_add(1, Ordering::Relaxed); 87 | 88 | let message = protocol::Message::Subscribe { 89 | id: id.to_string(), 90 | payload: &op, 91 | }; 92 | 93 | let request = serde_json::to_string(&message) 94 | .map_err(|error| Error::Serializing(error.to_string()))?; 95 | 96 | let actor = self.actor.clone(); 97 | actor 98 | .send(ConnectionCommand::Subscribe { 99 | request, 100 | sender, 101 | id, 102 | }) 103 | .await 104 | .map_err(|error| Error::Send(error.to_string()))?; 105 | 106 | Ok(Subscription:: { 107 | id, 108 | stream: Box::pin(receiver.map(move |response| { 109 | op.decode(response) 110 | .map_err(|err| Error::Decode(err.to_string())) 111 | })), 112 | actor, 113 | }) 114 | } 115 | 116 | /// Gracefully closes the connection 117 | /// 118 | /// This will stop all running subscriptions and shut down the [`ConnectionActor`] wherever 119 | /// it is running. 120 | pub async fn close(self, code: u16, description: impl Into) { 121 | self.actor 122 | .send(ConnectionCommand::Close(code, description.into())) 123 | .await 124 | .ok(); 125 | } 126 | } 127 | 128 | pub(super) enum ConnectionCommand { 129 | Subscribe { 130 | /// The full subscribe request as a JSON encoded string. 131 | request: String, 132 | sender: async_channel::Sender, 133 | id: usize, 134 | }, 135 | Ping, 136 | Cancel(usize), 137 | Close(u16, String), 138 | } 139 | 140 | impl fmt::Debug for Client { 141 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 142 | f.debug_struct("Client") 143 | .field("subscription_buffer_size", &self.subscription_buffer_size) 144 | .finish_non_exhaustive() 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/next/production_future.rs: -------------------------------------------------------------------------------- 1 | use std::{future::Future, task::Poll}; 2 | 3 | use futures_lite::{future, FutureExt}; 4 | 5 | /// A future that runs a long running producer future while also polling a future 6 | /// that should be reading a stream generated by that producer future. 7 | /// 8 | /// This will resolve to Some(Out, future::Boxed<()>) if the reader resolves, 9 | /// and None if the producer fails. 10 | #[pin_project::pin_project] 11 | pub struct ProductionFuture 12 | where 13 | ReadFut: Future, 14 | { 15 | #[pin] 16 | reader: ReadFut, 17 | pub producer: Option>, 18 | } 19 | 20 | impl Future for ProductionFuture 21 | where 22 | ReadFut: Future, 23 | { 24 | type Output = Option<(Out, future::Boxed<()>)>; 25 | 26 | fn poll( 27 | self: std::pin::Pin<&mut Self>, 28 | cx: &mut std::task::Context<'_>, 29 | ) -> std::task::Poll { 30 | let this = self.project(); 31 | 32 | let Some(producer) = this.producer else { 33 | return Poll::Ready(None); 34 | }; 35 | 36 | match this.reader.poll(cx) { 37 | Poll::Ready(read_output) => { 38 | return Poll::Ready(Some((read_output, this.producer.take().unwrap()))); 39 | } 40 | Poll::Pending => {} 41 | } 42 | 43 | if producer.poll(cx).is_ready() { 44 | return Poll::Ready(None); 45 | } 46 | 47 | Poll::Pending 48 | } 49 | } 50 | 51 | pub fn read_from_producer( 52 | reader: ReadFut, 53 | producer: future::Boxed<()>, 54 | ) -> ProductionFuture 55 | where 56 | ReadFut: Future, 57 | { 58 | ProductionFuture { 59 | reader, 60 | producer: Some(producer), 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/next/stream.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | pin::Pin, 3 | task::{Context, Poll}, 4 | }; 5 | 6 | use futures_lite::{future, stream, Stream, StreamExt}; 7 | 8 | use crate::{graphql::GraphqlOperation, next::production_future::read_from_producer, Error}; 9 | 10 | use super::ConnectionCommand; 11 | 12 | /// A `futures::Stream` for a subscription. 13 | /// 14 | /// Emits an item for each message received by the subscription. 15 | #[pin_project::pin_project] 16 | pub struct Subscription 17 | where 18 | Operation: GraphqlOperation, 19 | { 20 | pub(super) id: usize, 21 | pub(super) stream: stream::Boxed>, 22 | pub(super) actor: async_channel::Sender, 23 | } 24 | 25 | impl Subscription 26 | where 27 | Operation: GraphqlOperation + Send, 28 | { 29 | /// Stops the subscription by sending a Complete message to the server. 30 | /// 31 | /// # Errors 32 | /// 33 | /// Will return `Err` if the stop operation fails. 34 | pub async fn stop(self) -> Result<(), Error> { 35 | self.actor 36 | .send(ConnectionCommand::Cancel(self.id)) 37 | .await 38 | .map_err(|error| Error::Send(error.to_string())) 39 | } 40 | 41 | pub(super) fn join(self, future: future::Boxed<()>) -> Self 42 | where 43 | Operation::Response: 'static, 44 | { 45 | Self { 46 | stream: join_stream(self.stream, future).boxed(), 47 | ..self 48 | } 49 | } 50 | } 51 | 52 | impl Stream for Subscription 53 | where 54 | Operation: GraphqlOperation + Unpin, 55 | { 56 | type Item = Result; 57 | 58 | fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 59 | self.project().stream.as_mut().poll_next(cx) 60 | } 61 | } 62 | 63 | /// Joins a future onto the execution of a stream returning a stream that also polls 64 | /// the given future. 65 | /// 66 | /// If the future ends the stream will still continue till completion but if the stream 67 | /// ends the future will be cancelled. 68 | /// 69 | /// This can be used when you have the receivng side of a channel and a future that sends 70 | /// on that channel - combining the two into a single stream that'll run till the channel 71 | /// is exhausted. If you drop the stream you also cancel the underlying process. 72 | fn join_stream( 73 | stream: stream::Boxed, 74 | future: future::Boxed<()>, 75 | ) -> impl Stream { 76 | stream::unfold(ProducerState::Running(stream, future), producer_handler) 77 | } 78 | 79 | enum ProducerState<'a, Item> { 80 | Running( 81 | Pin + Send + 'a>>, 82 | future::Boxed<()>, 83 | ), 84 | Draining(Pin + Send + 'a>>), 85 | } 86 | 87 | async fn producer_handler( 88 | mut state: ProducerState<'_, Item>, 89 | ) -> Option<(Item, ProducerState<'_, Item>)> { 90 | loop { 91 | match state { 92 | ProducerState::Running(mut stream, producer) => { 93 | match read_from_producer(stream.next(), producer).await { 94 | Some((item, producer)) => { 95 | return Some((item?, ProducerState::Running(stream, producer))); 96 | } 97 | None => state = ProducerState::Draining(stream), 98 | } 99 | } 100 | ProducerState::Draining(mut stream) => { 101 | return Some((stream.next().await?, ProducerState::Draining(stream))); 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/protocol.rs: -------------------------------------------------------------------------------- 1 | //! Message definitions for the [graphql-transport-ws protocol][1] 2 | //! 3 | //! [1]: https://github.com/enisdenjo/graphql-ws/blob/HEAD/PROTOCOL.md 4 | 5 | #[derive(Default, Debug)] 6 | pub struct ConnectionInit { 7 | payload: Option, 8 | } 9 | 10 | impl ConnectionInit { 11 | pub fn new(payload: Option) -> Self { 12 | ConnectionInit { payload } 13 | } 14 | } 15 | 16 | impl serde::Serialize for ConnectionInit 17 | where 18 | Payload: serde::Serialize, 19 | { 20 | fn serialize(&self, serializer: S) -> Result 21 | where 22 | S: serde::Serializer, 23 | { 24 | use serde::ser::SerializeMap; 25 | 26 | let mut map = serializer.serialize_map(Some(2))?; 27 | map.serialize_entry("type", "connection_init")?; 28 | if self.payload.is_some() { 29 | map.serialize_entry("payload", &self.payload)?; 30 | } 31 | map.end() 32 | } 33 | } 34 | 35 | #[derive(serde::Serialize)] 36 | #[serde(tag = "type")] 37 | pub enum Message<'a, Operation> { 38 | #[serde(rename = "subscribe")] 39 | Subscribe { id: String, payload: &'a Operation }, 40 | #[serde(rename = "complete")] 41 | Complete { id: String }, 42 | #[serde(rename = "ping")] 43 | Ping, 44 | #[serde(rename = "pong")] 45 | Pong, 46 | } 47 | 48 | #[allow(dead_code)] 49 | #[derive(serde::Deserialize, Debug)] 50 | #[serde(tag = "type")] 51 | pub enum Event { 52 | #[serde(rename = "next")] 53 | Next { 54 | id: String, 55 | payload: serde_json::Value, 56 | }, 57 | #[serde(rename = "error")] 58 | Error { 59 | id: String, 60 | payload: Vec, 61 | }, 62 | #[serde(rename = "complete")] 63 | Complete { id: String }, 64 | #[serde(rename = "connection_ack")] 65 | ConnectionAck { payload: Option }, 66 | #[serde(rename = "ping")] 67 | Ping { payload: Option }, 68 | #[serde(rename = "pong")] 69 | Pong { payload: Option }, 70 | } 71 | 72 | impl Event { 73 | pub fn id(&self) -> Option<&str> { 74 | match self { 75 | Event::Next { id, .. } | Event::Complete { id, .. } | Event::Error { id, .. } => { 76 | Some(id.as_ref()) 77 | } 78 | Event::Ping { .. } | Event::Pong { .. } | Event::ConnectionAck { .. } => None, 79 | } 80 | } 81 | 82 | pub fn r#type(&self) -> &'static str { 83 | match self { 84 | Event::Next { .. } => "next", 85 | Event::Complete { .. } => "complete", 86 | Event::Error { .. } => "error", 87 | Event::Ping { .. } => "ping", 88 | Event::Pong { .. } => "pong", 89 | Event::ConnectionAck { .. } => "connection_ack", 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/sink_ext.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | future::Future, 3 | pin::Pin, 4 | task::{Context, Poll}, 5 | }; 6 | 7 | use futures_lite::ready; 8 | use futures_sink::Sink; 9 | 10 | /// A very limited clone of [futures::sink::SinkExt](1) to avoid having to 11 | /// pull the original in 12 | /// 13 | /// [1]: https://docs.rs/futures/latest/futures/sink/trait.SinkExt.html 14 | pub trait SinkExt: Sink { 15 | fn send(&mut self, item: Item) -> Send<'_, Self, Item> 16 | where 17 | Self: Unpin, 18 | { 19 | Send::new(self, item) 20 | } 21 | } 22 | 23 | impl SinkExt for T where T: Sink {} 24 | 25 | #[derive(Debug)] 26 | #[must_use = "futures do nothing unless you `.await` or poll them"] 27 | pub struct Send<'a, Si: ?Sized, Item> { 28 | sink: &'a mut Si, 29 | item: Option, 30 | } 31 | 32 | // Pinning is never projected to children 33 | impl Unpin for Send<'_, Si, Item> {} 34 | 35 | impl<'a, Si: Sink + Unpin + ?Sized, Item> Send<'a, Si, Item> { 36 | pub(super) fn new(sink: &'a mut Si, item: Item) -> Self { 37 | Self { 38 | sink, 39 | item: Some(item), 40 | } 41 | } 42 | } 43 | 44 | impl + Unpin + ?Sized, Item> Future for Send<'_, Si, Item> { 45 | type Output = Result<(), Si::Error>; 46 | 47 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 48 | let this = self.get_mut(); 49 | let mut sink = Pin::new(&mut this.sink); 50 | 51 | if let Some(item) = this.item.take() { 52 | ready!(sink.as_mut().poll_ready(cx))?; 53 | sink.as_mut().start_send(item)?; 54 | } 55 | 56 | // we're done sending the item, but want to block on flushing the 57 | // sink 58 | ready!(sink.poll_flush(cx))?; 59 | 60 | Poll::Ready(Ok(())) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/ws_stream_wasm.rs: -------------------------------------------------------------------------------- 1 | use futures_lite::{FutureExt, StreamExt}; 2 | use pharos::{Observable, ObserveConfig}; 3 | use ws_stream_wasm::{WsEvent, WsMessage, WsMeta, WsStream}; 4 | 5 | use crate::{sink_ext::SinkExt, Error}; 6 | 7 | /// A websocket connection for [ws_stream_wasm][1] 8 | /// 9 | /// [1]: https://docs.rs/ws_stream/latest/ws_stream 10 | #[cfg_attr(docsrs, doc(cfg(feature = "ws_stream_wasm")))] 11 | pub struct Connection { 12 | messages: WsStream, 13 | event_stream: pharos::Events, 14 | meta: WsMeta, 15 | } 16 | 17 | impl Connection { 18 | /// Creates a new Connection from a [`WsMeta`] and [`WsStream`] combo 19 | /// 20 | /// # Panics 21 | /// 22 | /// Will panic if `meta.observe` fails. 23 | pub async fn new((mut meta, messages): (WsMeta, WsStream)) -> Self { 24 | let event_stream = meta.observe(ObserveConfig::default()).await.unwrap(); 25 | 26 | Connection { 27 | messages, 28 | event_stream, 29 | meta, 30 | } 31 | } 32 | } 33 | 34 | impl crate::next::Connection for Connection { 35 | async fn receive(&mut self) -> Option { 36 | use crate::next::Message; 37 | loop { 38 | match self.next().await? { 39 | EventOrMessage::Event(WsEvent::Closed(close)) => { 40 | return Some(Message::Close { 41 | code: Some(close.code), 42 | reason: Some(close.reason), 43 | }); 44 | } 45 | EventOrMessage::Event(WsEvent::Error | WsEvent::WsErr(_)) => { 46 | return None; 47 | } 48 | EventOrMessage::Event(WsEvent::Open | WsEvent::Closing) => { 49 | continue; 50 | } 51 | EventOrMessage::Message(WsMessage::Text(text)) => return Some(Message::Text(text)), 52 | EventOrMessage::Message(WsMessage::Binary(_)) => { 53 | // We shouldn't receive binary messages, but ignore them if we do 54 | continue; 55 | } 56 | } 57 | } 58 | } 59 | 60 | async fn send(&mut self, message: crate::next::Message) -> Result<(), Error> { 61 | use crate::next::Message; 62 | 63 | match message { 64 | Message::Text(text) => self.messages.send(WsMessage::Text(text)).await, 65 | Message::Close { code, reason } => match (code, reason) { 66 | (Some(code), Some(reason)) => self.meta.close_reason(code, reason).await, 67 | (Some(code), _) => self.meta.close_code(code).await, 68 | _ => self.meta.close().await, 69 | } 70 | .map(|_| ()), 71 | Message::Ping | Message::Pong => return Ok(()), 72 | } 73 | .map_err(|error| Error::Send(error.to_string())) 74 | } 75 | } 76 | 77 | impl Connection { 78 | async fn next(&mut self) -> Option { 79 | let event = async { self.event_stream.next().await.map(EventOrMessage::Event) }; 80 | let message = async { self.messages.next().await.map(EventOrMessage::Message) }; 81 | 82 | event.race(message).await 83 | } 84 | } 85 | 86 | enum EventOrMessage { 87 | Event(WsEvent), 88 | Message(WsMessage), 89 | } 90 | -------------------------------------------------------------------------------- /tests/cynic-tests.rs: -------------------------------------------------------------------------------- 1 | use std::{future::IntoFuture, time::Duration}; 2 | 3 | use assert_matches::assert_matches; 4 | use futures_lite::{future, StreamExt}; 5 | use subscription_server::SubscriptionServer; 6 | use tokio::time::sleep; 7 | 8 | mod subscription_server; 9 | 10 | mod schema { 11 | cynic::use_schema!("schemas/books.graphql"); 12 | } 13 | 14 | #[derive(cynic::QueryFragment, Debug, Clone)] 15 | #[cynic(schema_path = "schemas/books.graphql")] 16 | #[allow(dead_code)] 17 | struct Book { 18 | id: String, 19 | name: String, 20 | author: String, 21 | } 22 | 23 | #[derive(cynic::QueryFragment, Debug, Clone)] 24 | #[cynic(schema_path = "schemas/books.graphql")] 25 | #[allow(dead_code)] 26 | struct BookChanged { 27 | id: cynic::Id, 28 | book: Option, 29 | } 30 | 31 | #[derive(cynic::QueryVariables)] 32 | struct BooksChangedVariables { 33 | mutation_type: MutationType, 34 | } 35 | 36 | #[derive(cynic::Enum)] 37 | #[cynic(schema_path = "schemas/books.graphql")] 38 | enum MutationType { 39 | Created, 40 | Deleted, 41 | } 42 | 43 | #[derive(cynic::QueryFragment, Debug)] 44 | #[cynic( 45 | schema_path = "schemas/books.graphql", 46 | graphql_type = "SubscriptionRoot", 47 | variables = "BooksChangedVariables" 48 | )] 49 | #[allow(dead_code)] 50 | struct BooksChangedSubscription { 51 | #[arguments(mutationType: $mutation_type)] 52 | books: BookChanged, 53 | } 54 | 55 | #[tokio::test] 56 | async fn main_test() { 57 | use async_tungstenite::tungstenite::{client::IntoClientRequest, http::HeaderValue}; 58 | 59 | let server = SubscriptionServer::start().await; 60 | 61 | sleep(Duration::from_millis(20)).await; 62 | 63 | let mut request = server.websocket_url().into_client_request().unwrap(); 64 | request.headers_mut().insert( 65 | "Sec-WebSocket-Protocol", 66 | HeaderValue::from_str("graphql-transport-ws").unwrap(), 67 | ); 68 | 69 | let (connection, _) = async_tungstenite::tokio::connect_async(request) 70 | .await 71 | .unwrap(); 72 | 73 | println!("Connected"); 74 | 75 | let (client, actor) = graphql_ws_client::Client::build(connection).await.unwrap(); 76 | 77 | tokio::spawn(actor.into_future()); 78 | 79 | let stream = client.subscribe(build_query()).await.unwrap(); 80 | 81 | sleep(Duration::from_millis(100)).await; 82 | 83 | let updates = [ 84 | subscription_server::BookChanged { 85 | id: "123".into(), 86 | book: None, 87 | }, 88 | subscription_server::BookChanged { 89 | id: "456".into(), 90 | book: None, 91 | }, 92 | subscription_server::BookChanged { 93 | id: "789".into(), 94 | book: None, 95 | }, 96 | ]; 97 | 98 | future::zip( 99 | async { 100 | for update in &updates { 101 | server.send(update.to_owned()).unwrap(); 102 | } 103 | }, 104 | async { 105 | let received_updates = stream.take(updates.len()).collect::>().await; 106 | 107 | for (expected, update) in updates.iter().zip(received_updates) { 108 | let update = update.unwrap(); 109 | assert_matches!(update.errors, None); 110 | let data = update.data.unwrap(); 111 | assert_eq!(data.books.id.inner(), expected.id.0); 112 | } 113 | }, 114 | ) 115 | .await; 116 | } 117 | 118 | #[tokio::test] 119 | async fn oneshot_operation_test() { 120 | use async_tungstenite::tungstenite::{client::IntoClientRequest, http::HeaderValue}; 121 | 122 | let server = SubscriptionServer::start().await; 123 | 124 | sleep(Duration::from_millis(20)).await; 125 | 126 | let mut request = server.websocket_url().into_client_request().unwrap(); 127 | request.headers_mut().insert( 128 | "Sec-WebSocket-Protocol", 129 | HeaderValue::from_str("graphql-transport-ws").unwrap(), 130 | ); 131 | 132 | let (connection, _) = async_tungstenite::tokio::connect_async(request) 133 | .await 134 | .unwrap(); 135 | 136 | println!("Connected"); 137 | 138 | let stream = graphql_ws_client::Client::build(connection) 139 | .subscribe(build_query()) 140 | .await 141 | .unwrap(); 142 | 143 | let updates = [ 144 | subscription_server::BookChanged { 145 | id: "123".into(), 146 | book: None, 147 | }, 148 | subscription_server::BookChanged { 149 | id: "456".into(), 150 | book: None, 151 | }, 152 | subscription_server::BookChanged { 153 | id: "789".into(), 154 | book: None, 155 | }, 156 | ]; 157 | 158 | future::zip( 159 | async { 160 | sleep(Duration::from_millis(10)).await; 161 | for update in &updates { 162 | server.send(update.to_owned()).unwrap(); 163 | } 164 | }, 165 | async { 166 | let received_updates = stream.take(updates.len()).collect::>().await; 167 | 168 | for (expected, update) in updates.iter().zip(received_updates) { 169 | let update = update.unwrap(); 170 | assert_matches!(update.errors, None); 171 | let data = update.data.unwrap(); 172 | assert_eq!(data.books.id.inner(), expected.id.0); 173 | } 174 | }, 175 | ) 176 | .await; 177 | } 178 | 179 | fn build_query() -> cynic::StreamingOperation { 180 | use cynic::SubscriptionBuilder; 181 | 182 | BooksChangedSubscription::build(BooksChangedVariables { 183 | mutation_type: MutationType::Created, 184 | }) 185 | } 186 | 187 | impl PartialEq for Book { 188 | fn eq(&self, other: &subscription_server::Book) -> bool { 189 | self.id == other.id.0 && self.name == other.name && self.author == other.author 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /tests/graphql-client-subscription.graphql: -------------------------------------------------------------------------------- 1 | subscription BooksChanged { 2 | books(mutationType: CREATED) { 3 | id 4 | book { 5 | id 6 | name 7 | author 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/graphql-client-tests.rs: -------------------------------------------------------------------------------- 1 | use std::{future::IntoFuture, time::Duration}; 2 | 3 | use assert_matches::assert_matches; 4 | use async_tungstenite::tungstenite::{client::IntoClientRequest, http::HeaderValue}; 5 | use futures_lite::{future, StreamExt}; 6 | use graphql_client::GraphQLQuery; 7 | use graphql_ws_client::graphql::StreamingOperation; 8 | use subscription_server::SubscriptionServer; 9 | use tokio::time::sleep; 10 | 11 | mod subscription_server; 12 | 13 | #[derive(GraphQLQuery)] 14 | #[graphql( 15 | query_path = "tests/graphql-client-subscription.graphql", 16 | schema_path = "schemas/books.graphql", 17 | response_derives = "Debug" 18 | )] 19 | struct BooksChanged; 20 | 21 | #[tokio::test] 22 | async fn main_test() { 23 | let server = SubscriptionServer::start().await; 24 | 25 | sleep(Duration::from_millis(20)).await; 26 | 27 | let mut request = server.websocket_url().into_client_request().unwrap(); 28 | request.headers_mut().insert( 29 | "Sec-WebSocket-Protocol", 30 | HeaderValue::from_str("graphql-transport-ws").unwrap(), 31 | ); 32 | 33 | let (connection, _) = async_tungstenite::tokio::connect_async(request) 34 | .await 35 | .unwrap(); 36 | 37 | println!("Connected"); 38 | 39 | let (client, actor) = graphql_ws_client::Client::build(connection).await.unwrap(); 40 | 41 | tokio::spawn(actor.into_future()); 42 | 43 | let stream = client.subscribe(build_query()).await.unwrap(); 44 | 45 | sleep(Duration::from_millis(100)).await; 46 | 47 | let updates = [ 48 | subscription_server::BookChanged { 49 | id: "123".into(), 50 | book: None, 51 | }, 52 | subscription_server::BookChanged { 53 | id: "456".into(), 54 | book: None, 55 | }, 56 | subscription_server::BookChanged { 57 | id: "789".into(), 58 | book: None, 59 | }, 60 | ]; 61 | 62 | future::zip( 63 | async { 64 | for update in &updates { 65 | server.send(update.to_owned()).unwrap(); 66 | } 67 | }, 68 | async { 69 | let received_updates = stream.take(updates.len()).collect::>().await; 70 | 71 | for (expected, update) in updates.iter().zip(received_updates) { 72 | let update = update.unwrap(); 73 | assert_matches!(update.errors, None); 74 | let data = update.data.unwrap(); 75 | assert_eq!(data.books.id, expected.id.0); 76 | } 77 | }, 78 | ) 79 | .await; 80 | } 81 | 82 | #[tokio::test] 83 | async fn oneshot_operation_test() { 84 | let server = SubscriptionServer::start().await; 85 | 86 | sleep(Duration::from_millis(20)).await; 87 | 88 | let mut request = server.websocket_url().into_client_request().unwrap(); 89 | request.headers_mut().insert( 90 | "Sec-WebSocket-Protocol", 91 | HeaderValue::from_str("graphql-transport-ws").unwrap(), 92 | ); 93 | 94 | let (connection, _) = async_tungstenite::tokio::connect_async(request) 95 | .await 96 | .unwrap(); 97 | 98 | println!("Connected"); 99 | 100 | let stream = graphql_ws_client::Client::build(connection) 101 | .subscribe(build_query()) 102 | .await 103 | .unwrap(); 104 | 105 | let updates = [ 106 | subscription_server::BookChanged { 107 | id: "123".into(), 108 | book: None, 109 | }, 110 | subscription_server::BookChanged { 111 | id: "456".into(), 112 | book: None, 113 | }, 114 | subscription_server::BookChanged { 115 | id: "789".into(), 116 | book: None, 117 | }, 118 | ]; 119 | 120 | future::zip( 121 | async { 122 | sleep(Duration::from_millis(10)).await; 123 | for update in &updates { 124 | server.send(update.to_owned()).unwrap(); 125 | } 126 | }, 127 | async { 128 | let received_updates = stream.take(updates.len()).collect::>().await; 129 | 130 | for (expected, update) in updates.iter().zip(received_updates) { 131 | let update = update.unwrap(); 132 | assert_matches!(update.errors, None); 133 | let data = update.data.unwrap(); 134 | assert_eq!(data.books.id, expected.id.0); 135 | } 136 | }, 137 | ) 138 | .await; 139 | } 140 | 141 | #[tokio::test] 142 | async fn multiple_clients_test() { 143 | async fn inner(server: &SubscriptionServer) { 144 | // Open connection 145 | let mut request = server.websocket_url().into_client_request().unwrap(); 146 | request.headers_mut().insert( 147 | "Sec-WebSocket-Protocol", 148 | HeaderValue::from_str("graphql-transport-ws").unwrap(), 149 | ); 150 | let (connection, _) = async_tungstenite::tokio::connect_async(request) 151 | .await 152 | .unwrap(); 153 | 154 | // Connect / Subscribe 155 | let (client, actor) = graphql_ws_client::Client::build(connection).await.unwrap(); 156 | tokio::spawn(actor.into_future()); 157 | let mut stream = client.subscribe(build_query()).await.unwrap(); 158 | 159 | sleep(Duration::from_millis(20)).await; 160 | 161 | // Send / Receive 162 | server 163 | .send(subscription_server::BookChanged { 164 | id: "123".into(), 165 | book: None, 166 | }) 167 | .unwrap(); 168 | let update = stream.next().await.unwrap().unwrap(); 169 | assert_eq!(update.data.unwrap().books.id, "123"); 170 | } 171 | 172 | // Start server 173 | let server = SubscriptionServer::start().await; 174 | sleep(Duration::from_millis(20)).await; 175 | 176 | // Open connection 177 | let mut request = server.websocket_url().into_client_request().unwrap(); 178 | request.headers_mut().insert( 179 | "Sec-WebSocket-Protocol", 180 | HeaderValue::from_str("graphql-transport-ws").unwrap(), 181 | ); 182 | let (connection, _) = async_tungstenite::tokio::connect_async(request) 183 | .await 184 | .unwrap(); 185 | 186 | // Connect / Subscribe 187 | let (client, actor) = graphql_ws_client::Client::build(connection).await.unwrap(); 188 | tokio::spawn(actor.into_future()); 189 | let mut stream = client.subscribe(build_query()).await.unwrap(); 190 | 191 | // Spawn another client 192 | inner(&server).await; 193 | 194 | // Receive 195 | let update = stream.next().await.unwrap().unwrap(); 196 | assert_eq!(update.data.unwrap().books.id, "123"); 197 | 198 | let res = tokio::time::timeout(Duration::from_millis(100), stream.next()).await; 199 | assert!(res.is_err()) 200 | } 201 | 202 | fn build_query() -> graphql_ws_client::graphql::StreamingOperation { 203 | StreamingOperation::new(books_changed::Variables) 204 | } 205 | 206 | impl PartialEq for books_changed::BooksChangedBooksBook { 207 | fn eq(&self, other: &subscription_server::Book) -> bool { 208 | self.id == other.id.0 && self.name == other.name && self.author == other.author 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /tests/subscription_server/mod.rs: -------------------------------------------------------------------------------- 1 | // TODO: Start a server w/ async-graphql. 2 | // Query that server... 3 | 4 | use async_graphql::{EmptyMutation, Object, Schema, SimpleObject, Subscription, ID}; 5 | use async_graphql_axum::{GraphQLRequest, GraphQLResponse, GraphQLSubscription}; 6 | use axum::{extract::Extension, routing::post, Router}; 7 | use futures_lite::{Stream, StreamExt}; 8 | use tokio::sync::broadcast::Sender; 9 | use tokio_stream::wrappers::BroadcastStream; 10 | 11 | pub type BooksSchema = Schema; 12 | 13 | pub struct SubscriptionServer { 14 | shutdown: Option>, 15 | port: u16, 16 | sender: Sender, 17 | } 18 | 19 | impl Drop for SubscriptionServer { 20 | fn drop(&mut self) { 21 | if let Some(shutdown) = self.shutdown.take() { 22 | shutdown.send(()).ok(); 23 | } 24 | } 25 | } 26 | 27 | impl SubscriptionServer { 28 | pub async fn start() -> SubscriptionServer { 29 | let (channel, _) = tokio::sync::broadcast::channel(16); 30 | 31 | let schema = Schema::build( 32 | QueryRoot, 33 | EmptyMutation, 34 | SubscriptionRoot { 35 | channel: channel.clone(), 36 | }, 37 | ) 38 | .finish(); 39 | 40 | let app = Router::new() 41 | .route("/", post(graphql_handler)) 42 | .route_service("/ws", GraphQLSubscription::new(schema.clone())) 43 | .layer(Extension(schema)); 44 | 45 | let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); 46 | let port = listener.local_addr().unwrap().port(); 47 | 48 | let (shutdown_sender, shutdown_receiver) = tokio::sync::oneshot::channel::<()>(); 49 | 50 | tokio::spawn(async move { 51 | axum::serve(listener, app.with_state(())) 52 | .with_graceful_shutdown(async move { 53 | shutdown_receiver.await.ok(); 54 | }) 55 | .await 56 | .unwrap(); 57 | }); 58 | 59 | SubscriptionServer { 60 | port, 61 | shutdown: Some(shutdown_sender), 62 | sender: channel, 63 | } 64 | } 65 | 66 | pub fn websocket_url(&self) -> String { 67 | format!("ws://localhost:{}/ws", self.port) 68 | } 69 | 70 | pub fn send( 71 | &self, 72 | change: BookChanged, 73 | ) -> Result<(), tokio::sync::broadcast::error::SendError> { 74 | self.sender.send(change).map(|_| ()) 75 | } 76 | } 77 | 78 | #[axum_macros::debug_handler] 79 | async fn graphql_handler(schema: Extension, req: GraphQLRequest) -> GraphQLResponse { 80 | schema.execute(req.into_inner()).await.into() 81 | } 82 | 83 | #[derive(SimpleObject, Debug, Clone)] 84 | pub struct Book { 85 | pub id: ID, 86 | pub name: String, 87 | pub author: String, 88 | } 89 | 90 | pub struct QueryRoot; 91 | 92 | #[Object] 93 | impl QueryRoot { 94 | pub async fn id(&self) -> ID { 95 | "123".into() 96 | } 97 | } 98 | 99 | #[derive(Clone, Debug, SimpleObject)] 100 | pub struct BookChanged { 101 | pub book: Option, 102 | pub id: ID, 103 | } 104 | 105 | #[derive(Clone, Copy, PartialEq, Eq, Debug, async_graphql::Enum)] 106 | enum MutationType { 107 | Created, 108 | Deleted, 109 | } 110 | 111 | pub struct SubscriptionRoot { 112 | channel: Sender, 113 | } 114 | 115 | #[Subscription] 116 | impl SubscriptionRoot { 117 | async fn books(&self, _mutation_type: MutationType) -> impl Stream { 118 | println!("Subscription received"); 119 | BroadcastStream::new(self.channel.subscribe()).filter_map(Result::ok) 120 | } 121 | } 122 | --------------------------------------------------------------------------------