├── .github ├── codecov.yml ├── dependabot.yml └── workflows │ ├── coverage.yml │ ├── style.yml │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── client ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples │ ├── http │ │ ├── async_std.rs │ │ └── tokio.rs │ └── ws │ │ ├── async_std.rs │ │ └── tokio.rs └── src │ ├── error.rs │ ├── http_client │ ├── builder.rs │ ├── mod.rs │ └── tests.rs │ ├── lib.rs │ ├── transport.rs │ └── ws_client │ ├── builder.rs │ ├── manager.rs │ ├── mod.rs │ ├── task.rs │ └── tests.rs ├── rustfmt.toml ├── server ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md └── src │ └── lib.rs └── types ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md └── src ├── error.rs ├── id.rs ├── lib.rs ├── v1 ├── error.rs ├── mod.rs ├── notification.rs ├── params.rs ├── request.rs └── response.rs └── v2 ├── error.rs ├── mod.rs ├── notification.rs ├── params.rs ├── request.rs └── response.rs /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | patch: off 4 | project: off 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: coverage 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | coverage: 13 | name: Code Coverage 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout Sources 17 | uses: actions/checkout@v3 18 | 19 | - name: Install Rust Toolchain 20 | uses: actions-rs/toolchain@v1 21 | with: 22 | profile: minimal 23 | toolchain: stable 24 | override: true 25 | 26 | - name: Generate Code Coverage 27 | uses: actions-rs/tarpaulin@v0.1 28 | 29 | - name: Upload Code Coverage 30 | uses: codecov/codecov-action@v3 31 | -------------------------------------------------------------------------------- /.github/workflows/style.yml: -------------------------------------------------------------------------------- 1 | name: style 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | style: 13 | name: Code Style 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout Sources 17 | uses: actions/checkout@v3 18 | 19 | - name: Install Rust Toolchain 20 | uses: actions-rs/toolchain@v1 21 | with: 22 | profile: minimal 23 | toolchain: stable 24 | override: true 25 | components: rustfmt, clippy 26 | 27 | - name: Check Code Format 28 | uses: actions-rs/cargo@v1 29 | with: 30 | command: fmt 31 | args: --all -- --check 32 | 33 | - name: Code Lint 34 | uses: actions-rs/cargo@v1 35 | with: 36 | command: clippy 37 | args: --workspace --all-targets -- -D warnings 38 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | 8 | jobs: 9 | types: 10 | name: jsonrpc-types 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | platform: [ubuntu-latest, macos-latest, windows-latest] 15 | toolchain: [stable] 16 | runs-on: ${{ matrix.platform }} 17 | 18 | steps: 19 | - name: Checkout Sources 20 | uses: actions/checkout@v3 21 | 22 | - name: Install Rust Toolchain 23 | uses: actions-rs/toolchain@v1 24 | with: 25 | profile: minimal 26 | toolchain: ${{ matrix.toolchain }} 27 | override: true 28 | target: thumbv6m-none-eabi 29 | 30 | - name: Test 31 | run: cargo test -p jsonrpc-types --all-features 32 | 33 | - name: Build (no_std) 34 | run: cargo build -p jsonrpc-types --no-default-features --features 'v1,v2' --target thumbv6m-none-eabi 35 | 36 | client: 37 | name: async-jsonrpc-client 38 | strategy: 39 | fail-fast: false 40 | matrix: 41 | platform: [ubuntu-latest, macos-latest, windows-latest] 42 | toolchain: [stable] 43 | runs-on: ${{ matrix.platform }} 44 | 45 | steps: 46 | - name: Checkout Sources 47 | uses: actions/checkout@v3 48 | 49 | - name: Install Rust Toolchain 50 | uses: actions-rs/toolchain@v1 51 | with: 52 | profile: minimal 53 | toolchain: ${{ matrix.toolchain }} 54 | override: true 55 | 56 | - name: Cache Dependencies & Build Outputs 57 | uses: actions/cache@v3 58 | with: 59 | path: | 60 | ~/.cargo 61 | target 62 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 63 | 64 | - name: Test 65 | run: | 66 | cargo test --manifest-path client/Cargo.toml --no-default-features --features 'http-async-std' 67 | cargo test --manifest-path client/Cargo.toml --no-default-features --features 'http-tokio' 68 | cargo test --manifest-path client/Cargo.toml --no-default-features --features 'ws-async-std' 69 | cargo test --manifest-path client/Cargo.toml --no-default-features --features 'ws-tokio' 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | .idea 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # `async-jsonrpc` crates 2 | 3 | ## Version [Unreleased] 4 | - `jsonrpc-types` 5 | - Update `async-tungstenite` to 0.14 ([#39]) 6 | - Reorg modules ([#40]) 7 | - Add features `v1` and `v2` ([#40]) 8 | - Add features `std` ([#41]) 9 | 10 | [#39]: https://github.com/koushiro/async-jsonrpc/pull/39 11 | [#40]: https://github.com/koushiro/async-jsonrpc/pull/40 12 | [#41]: https://github.com/koushiro/async-jsonrpc/pull/41 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "types", 4 | "client", 5 | "server" 6 | ] 7 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Qinxuan Chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # async-jsonrpc 2 | 3 | An async JSON-RPC 2.0 crate written in Rust. 4 | 5 | [![ga-svg]][ga-url] 6 | [![codecov-svg]][codecov-url] 7 | [![deps-svg]][deps-url] 8 | 9 | [ga-svg]: https://github.com/koushiro/async-jsonrpc/workflows/test/badge.svg 10 | [ga-url]: https://github.com/koushiro/async-jsonrpc/actions 11 | [codecov-svg]: https://img.shields.io/codecov/c/github/koushiro/async-jsonrpc 12 | [codecov-url]: https://codecov.io/gh/koushiro/async-jsonrpc 13 | [deps-svg]: https://deps.rs/repo/github/koushiro/async-jsonrpc/status.svg 14 | [deps-url]: https://deps.rs/repo/github/koushiro/async-jsonrpc 15 | 16 | | sub-crate | description | label | 17 | | :-------: | :---------: | :---: | 18 | | jsonrpc-types | A general purpose library of JSON-RPC 1.0 and JSON-RPC 2.0 types | [![types-crates-svg]][types-crates-url] [![types-docs-svg]][types-docs-url] | 19 | | async-jsonrpc-client | An asynchronous JSON-RPC 2.0 client library | [![client-crates-svg]][client-crates-url] [![client-docs-svg]][client-docs-url] | 20 | | async-jsonrpc-server | TODO: An asynchronous JSON-RPC 2.0 server library | [![server-crates-svg]][server-crates-url] [![server-docs-svg]][server-docs-url] | 21 | 22 | [types-crates-svg]: https://img.shields.io/crates/v/jsonrpc-types 23 | [types-crates-url]: https://crates.io/crates/jsonrpc-types 24 | [types-docs-svg]: https://docs.rs/jsonrpc-types/badge.svg 25 | [types-docs-url]: https://docs.rs/jsonrpc-types 26 | 27 | [client-crates-svg]: https://img.shields.io/crates/v/async-jsonrpc-client 28 | [client-crates-url]: https://crates.io/crates/async-jsonrpc-client 29 | [client-docs-svg]: https://docs.rs/async-jsonrpc-client/badge.svg 30 | [client-docs-url]: https://docs.rs/async-jsonrpc-client 31 | 32 | [server-crates-svg]: https://img.shields.io/crates/v/async-jsonrpc-server 33 | [server-crates-url]: https://crates.io/crates/async-jsonrpc-server 34 | [server-docs-svg]: https://docs.rs/async-jsonrpc-server/badge.svg 35 | [server-docs-url]: https://docs.rs/async-jsonrpc-server 36 | 37 | ## License 38 | 39 | Licensed under either of 40 | 41 | - [Apache License, Version 2.0](LICENSE-APACHE) 42 | - [MIT License](LICENSE-MIT) 43 | 44 | at your option. 45 | 46 | ## Contribution 47 | 48 | Unless you explicitly state otherwise, any contribution intentionally submitted 49 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 50 | dual licensed as above, without any additional terms or conditions. 51 | -------------------------------------------------------------------------------- /client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "async-jsonrpc-client" 3 | version = "0.4.0-dev" 4 | authors = ["koushiro "] 5 | edition = "2018" 6 | readme = "README.md" 7 | license = "MIT/Apache-2.0" 8 | documentation = "https://docs.rs/async-jsonrpc-client" 9 | repository = "https://github.com/koushiro/async-jsonrpc" 10 | description = "An asynchronous JSON-RPC 2.0 client library" 11 | keywords = ["jsonrpc", "rpc", "async", "client"] 12 | categories = ["network-programming", "web-programming"] 13 | 14 | [features] 15 | default = ["tokio-runtime"] 16 | async-std-runtime = ["ws-async-std"] 17 | tokio-runtime = ["http-tokio", "ws-tokio"] 18 | 19 | # HTTP 20 | http-async-std = ["async-std", "surf", "anyhow"] 21 | http-tokio = ["tokio", "reqwest"] 22 | 23 | # WebSocket 24 | ws-async-std = ["async-std", "async-tungstenite/async-std-runtime", "async-tungstenite/async-tls"] 25 | ws-tokio = ["tokio", "async-tungstenite/tokio-runtime", "async-tungstenite/tokio-native-tls"] 26 | 27 | [dependencies] 28 | async-trait = "0.1" 29 | base64 = "0.13" 30 | futures = "0.3" 31 | log = "0.4" 32 | http = "0.2" 33 | jsonrpc-types = { version = "0.4.0-dev", path = "../types" } 34 | serde = { version = "1.0", features = ["derive"] } 35 | serde_json = "1.0" 36 | thiserror = "1.0" 37 | 38 | # HTTP (async-std) 39 | anyhow = { version = "1.0", optional = true } 40 | surf = { version = "2.1", default-features = false, features = ["curl-client"], optional = true } 41 | # HTTP (tokio) 42 | reqwest = { version = "0.11", features = ["json"], optional = true } 43 | 44 | # WebSocket (async-std / tokio) 45 | async-tungstenite = { version = "0.16", optional = true } 46 | 47 | # Runtime 48 | async-std = { version = "1.9", optional = true } 49 | tokio = { version = "1.2", features = ["time"], optional = true } 50 | 51 | [dev-dependencies] 52 | env_logger = "0.9" 53 | tide = { version = "0.16", default-features = false, features = ["h1-server"] } 54 | async-std = { version = "1.9", features = ["attributes"] } 55 | hyper = { version = "0.14", features = ["server"] } 56 | tokio = { version = "1.2", features = ["macros", "rt-multi-thread"] } 57 | 58 | [[example]] 59 | name = "http-async-std" 60 | path = "examples/http/async_std.rs" 61 | required-features = ["http-async-std"] 62 | 63 | [[example]] 64 | name = "http-tokio" 65 | path = "examples/http/tokio.rs" 66 | required-features = ["http-tokio"] 67 | 68 | [[example]] 69 | name = "ws-async-std" 70 | path = "examples/ws/async_std.rs" 71 | required-features = ["ws-async-std"] 72 | 73 | [[example]] 74 | name = "ws-tokio" 75 | path = "examples/ws/tokio.rs" 76 | required-features = ["ws-tokio"] 77 | -------------------------------------------------------------------------------- /client/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /client/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # async-jsonrpc-client 2 | 3 | [![ga-svg]][ga-url] 4 | [![crates-svg]][crates-url] 5 | [![docs-svg]][docs-url] 6 | 7 | [ga-svg]: https://github.com/koushiro/async-jsonrpc/workflows/test/badge.svg 8 | [ga-url]: https://github.com/koushiro/async-jsonrpc/actions 9 | [crates-svg]: https://img.shields.io/crates/v/async-jsonrpc-client 10 | [crates-url]: https://crates.io/crates/async-jsonrpc-client 11 | [docs-svg]: https://docs.rs/async-jsonrpc-client/badge.svg 12 | [docs-url]: https://docs.rs/async-jsonrpc-client 13 | 14 | An asynchronous JSON-RPC 2.0 client library written in Rust. 15 | 16 | ## Features 17 | 18 | - support HTTP 19 | - support WebSocket 20 | - support batch request 21 | - support subscription (only for WebSocket client) 22 | - support `async-std` and `tokio` runtime 23 | 24 | ## Usage 25 | 26 | See the [examples](examples) for details. 27 | 28 | ## License 29 | 30 | Licensed under either of 31 | 32 | - [Apache License, Version 2.0](LICENSE-APACHE) 33 | - [MIT License](LICENSE-MIT) 34 | 35 | at your option. 36 | 37 | ## Contribution 38 | 39 | Unless you explicitly state otherwise, any contribution intentionally submitted 40 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 41 | dual licensed as above, without any additional terms or conditions. 42 | -------------------------------------------------------------------------------- /client/examples/http/async_std.rs: -------------------------------------------------------------------------------- 1 | use async_jsonrpc_client::{BatchTransport, HttpClient, HttpClientError, ResponseObj, Transport}; 2 | 3 | #[async_std::main] 4 | async fn main() -> Result<(), HttpClientError> { 5 | env_logger::init(); 6 | 7 | let client = HttpClient::new("https://rpc.polkadot.io")?; 8 | 9 | let response = client.request("system_chain", None).await?; 10 | log::info!("Response: {}", ResponseObj::Single(response)); 11 | 12 | let response = client 13 | .request_batch(vec![("system_chain", None), ("system_chainType", None)]) 14 | .await?; 15 | log::info!("Response: {}", ResponseObj::Batch(response)); 16 | 17 | Ok(()) 18 | } 19 | -------------------------------------------------------------------------------- /client/examples/http/tokio.rs: -------------------------------------------------------------------------------- 1 | use async_jsonrpc_client::{BatchTransport, HttpClient, HttpClientError, ResponseObj, Transport}; 2 | 3 | #[tokio::main] 4 | async fn main() -> Result<(), HttpClientError> { 5 | env_logger::init(); 6 | 7 | let client = HttpClient::new("https://rpc.polkadot.io")?; 8 | 9 | let response = client.request("system_chain", None).await?; 10 | log::info!("Response: {}", ResponseObj::Single(response)); 11 | 12 | let response = client 13 | .request_batch(vec![("system_chain", None), ("system_chainType", None)]) 14 | .await?; 15 | log::info!("Response: {}", ResponseObj::Batch(response)); 16 | 17 | Ok(()) 18 | } 19 | -------------------------------------------------------------------------------- /client/examples/ws/async_std.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use async_jsonrpc_client::{BatchTransport, PubsubTransport, ResponseObj, Transport, WsClient, WsClientError}; 4 | 5 | #[async_std::main] 6 | async fn main() -> Result<(), WsClientError> { 7 | env_logger::init(); 8 | 9 | let client = WsClient::new("wss://rpc.polkadot.io").await?; 10 | 11 | let response = client.request("system_chain", None).await?; 12 | log::info!("Response: {}", ResponseObj::Single(response)); 13 | 14 | let response = client 15 | .request_batch(vec![("system_chain", None), ("system_chainType", None)]) 16 | .await?; 17 | log::info!("Response: {}", ResponseObj::Batch(response)); 18 | 19 | let (id, mut rx) = client.subscribe("chain_subscribeNewHead", None).await?; 20 | log::info!("Subscription ID: {}", id); 21 | 22 | let client_clone = client.clone(); 23 | async_std::task::spawn(async move { 24 | async_std::task::sleep(Duration::from_secs(20)).await; 25 | let _ = client_clone.unsubscribe("chain_unsubscribeNewHead", id).await; 26 | }); 27 | 28 | while let Some(notification) = rx.next().await { 29 | log::info!("Subscription Notification: {}", notification); 30 | } 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /client/examples/ws/tokio.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use async_jsonrpc_client::{BatchTransport, PubsubTransport, ResponseObj, Transport, WsClient, WsClientError}; 4 | 5 | #[tokio::main] 6 | async fn main() -> Result<(), WsClientError> { 7 | env_logger::init(); 8 | 9 | let client = WsClient::new("wss://rpc.polkadot.io").await?; 10 | 11 | let response = client.request("system_chain", None).await?; 12 | log::info!("Response: {}", ResponseObj::Single(response)); 13 | 14 | let response = client 15 | .request_batch(vec![("system_chain", None), ("system_chainType", None)]) 16 | .await?; 17 | log::info!("Response: {}", ResponseObj::Batch(response)); 18 | 19 | let (id, mut rx) = client.subscribe("chain_subscribeNewHead", None).await?; 20 | log::info!("Subscription ID: {}", id); 21 | 22 | let client_clone = client.clone(); 23 | tokio::spawn(async move { 24 | tokio::time::sleep(Duration::from_secs(20)).await; 25 | let _ = client_clone.unsubscribe("chain_unsubscribeNewHead", id).await; 26 | }); 27 | 28 | while let Some(notification) = rx.next().await { 29 | log::info!("Subscription Notification: {}", notification); 30 | } 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /client/src/error.rs: -------------------------------------------------------------------------------- 1 | /// The error type for rpc transport. 2 | #[cfg(any(feature = "http-async-std", feature = "http-tokio"))] 3 | #[derive(Debug, thiserror::Error)] 4 | pub enum HttpClientError { 5 | /// Json serialization/deserialization error. 6 | #[error(transparent)] 7 | Json(#[from] serde_json::Error), 8 | 9 | /// HTTP error. 10 | #[cfg(feature = "http-async-std")] 11 | #[error(transparent)] 12 | Http(#[from] anyhow::Error), 13 | 14 | /// HTTP error. 15 | #[cfg(feature = "http-tokio")] 16 | #[error(transparent)] 17 | Http(#[from] reqwest::Error), 18 | } 19 | 20 | /// WebSocket error type. 21 | #[cfg(any(feature = "ws-async-std", feature = "ws-tokio"))] 22 | pub use async_tungstenite::tungstenite::Error as WsError; 23 | 24 | /// The error type for websocket rpc transport. 25 | #[cfg(any(feature = "ws-async-std", feature = "ws-tokio"))] 26 | #[derive(Debug, thiserror::Error)] 27 | pub enum WsClientError { 28 | /// Json serialization/deserialization error. 29 | #[error(transparent)] 30 | Json(#[from] serde_json::Error), 31 | /// WebSocket protocol error. 32 | #[error(transparent)] 33 | WebSocket(#[from] WsError), 34 | /// WebSocket request timeout. 35 | #[error("WebSocket request timeout")] 36 | RequestTimeout, 37 | /// Duplicate request ID. 38 | #[error("Duplicate request ID")] 39 | DuplicateRequestId, 40 | /// Invalid Request ID. 41 | #[error("Invalid request ID")] 42 | InvalidRequestId, 43 | /// Invalid Subscription ID. 44 | #[error("Invalid subscription ID")] 45 | InvalidSubscriptionId, 46 | /// Invalid Unsubscribe request result. 47 | #[error("Invalid Unsubscribe result")] 48 | InvalidUnsubscribeResult, 49 | /// Internal channel error 50 | #[error("Internal channel error")] 51 | InternalChannel, 52 | } 53 | -------------------------------------------------------------------------------- /client/src/http_client/builder.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt, 3 | sync::{atomic::AtomicU64, Arc}, 4 | time::Duration, 5 | }; 6 | 7 | use http::header::{self, HeaderMap, HeaderName, HeaderValue}; 8 | 9 | use crate::{error::HttpClientError, http_client::HttpClient}; 10 | 11 | /// A `HttpClientBuilder` can be used to create a `HttpClient` with custom configuration. 12 | #[derive(Debug)] 13 | pub struct HttpClientBuilder { 14 | pub(crate) headers: HeaderMap, 15 | timeout: Option, 16 | connect_timeout: Option, 17 | } 18 | 19 | impl Default for HttpClientBuilder { 20 | fn default() -> Self { 21 | Self::new() 22 | } 23 | } 24 | 25 | impl HttpClientBuilder { 26 | /// Creates a new `HttpClientBuilder`. 27 | /// 28 | /// This is the same as `HttpClient::builder()`. 29 | pub fn new() -> Self { 30 | Self { 31 | headers: HeaderMap::new(), 32 | timeout: None, 33 | connect_timeout: None, 34 | } 35 | } 36 | 37 | // ======================================================================== 38 | // HTTP header options 39 | // ======================================================================== 40 | 41 | /// Enable basic authentication. 42 | pub fn basic_auth(self, username: U, password: Option

) -> Self 43 | where 44 | U: fmt::Display, 45 | P: fmt::Display, 46 | { 47 | let mut basic_auth = "Basic ".to_string(); 48 | let auth = if let Some(password) = password { 49 | base64::encode(format!("{}:{}", username, password)) 50 | } else { 51 | base64::encode(format!("{}:", username)) 52 | }; 53 | basic_auth.push_str(&auth); 54 | let value = HeaderValue::from_str(&basic_auth).expect("basic auth header value"); 55 | self.header(header::AUTHORIZATION, value) 56 | } 57 | 58 | /// Enable bearer authentication. 59 | pub fn bearer_auth(self, token: T) -> Self 60 | where 61 | T: fmt::Display, 62 | { 63 | let bearer_auth = format!("Bearer {}", token); 64 | let value = HeaderValue::from_str(&bearer_auth).expect("bearer auth header value"); 65 | self.header(header::AUTHORIZATION, value) 66 | } 67 | 68 | /// Adds a `Header` for every request. 69 | pub fn header(mut self, name: HeaderName, value: HeaderValue) -> Self { 70 | self.headers.insert(name, value); 71 | self 72 | } 73 | 74 | /// Adds `Header`s for every request. 75 | pub fn headers(mut self, headers: HeaderMap) -> Self { 76 | self.headers.extend(headers); 77 | self 78 | } 79 | 80 | // ======================================================================== 81 | // Timeout options 82 | // ======================================================================== 83 | 84 | /// Enables a request timeout. 85 | /// 86 | /// The timeout is applied from when the request starts connecting until the 87 | /// response body has finished. 88 | /// 89 | /// Default is no timeout. 90 | pub fn timeout(mut self, timeout: Duration) -> Self { 91 | self.timeout = Some(timeout); 92 | self 93 | } 94 | 95 | /// Set a timeout for only the connect phase of a `Client`. 96 | /// 97 | /// Default is `None`. 98 | #[cfg(feature = "http-tokio")] 99 | pub fn connect_timeout(mut self, timeout: Duration) -> Self { 100 | self.connect_timeout = Some(timeout); 101 | self 102 | } 103 | 104 | // ======================================================================== 105 | 106 | /// Returns a `HttpClient` that uses this `HttpClientBuilder` configuration. 107 | #[cfg(feature = "http-async-std")] 108 | pub fn build>(self, url: U) -> Result { 109 | Ok(HttpClient { 110 | url: url.into(), 111 | id: Arc::new(AtomicU64::new(1)), 112 | client: surf::Client::new(), 113 | headers: self.headers, 114 | timeout: self.timeout, 115 | }) 116 | } 117 | 118 | /// Returns a `HttpClient` that uses this `HttpClientBuilder` configuration. 119 | #[cfg(feature = "http-tokio")] 120 | pub fn build>(self, url: U) -> Result { 121 | let builder = reqwest::Client::builder().default_headers(self.headers); 122 | let builder = if let Some(timeout) = self.timeout { 123 | builder.timeout(timeout) 124 | } else { 125 | builder 126 | }; 127 | let builder = if let Some(timeout) = self.connect_timeout { 128 | builder.connect_timeout(timeout) 129 | } else { 130 | builder 131 | }; 132 | let client = builder.build()?; 133 | Ok(HttpClient { 134 | url: url.into(), 135 | id: Arc::new(AtomicU64::new(1)), 136 | client, 137 | }) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /client/src/http_client/mod.rs: -------------------------------------------------------------------------------- 1 | mod builder; 2 | #[cfg(test)] 3 | mod tests; 4 | 5 | use std::sync::{ 6 | atomic::{AtomicU64, Ordering}, 7 | Arc, 8 | }; 9 | 10 | use jsonrpc_types::v2::*; 11 | use serde::{de::DeserializeOwned, Serialize}; 12 | 13 | pub use self::builder::HttpClientBuilder; 14 | use crate::{ 15 | error::HttpClientError, 16 | transport::{BatchTransport, Transport}, 17 | }; 18 | 19 | /// HTTP JSON-RPC client 20 | #[cfg(feature = "http-async-std")] 21 | #[derive(Clone)] 22 | pub struct HttpClient { 23 | url: String, 24 | id: Arc, 25 | client: surf::Client, 26 | headers: http::header::HeaderMap, 27 | timeout: Option, 28 | } 29 | 30 | /// HTTP JSON-RPC client 31 | #[cfg(feature = "http-tokio")] 32 | #[derive(Clone)] 33 | pub struct HttpClient { 34 | url: String, 35 | id: Arc, 36 | client: reqwest::Client, 37 | } 38 | 39 | impl HttpClient { 40 | /// Creates a new HTTP JSON-RPC client with given `url`. 41 | pub fn new>(url: U) -> Result { 42 | HttpClientBuilder::new().build(url) 43 | } 44 | 45 | /// Creates a `HttpClientBuilder` to configure a `HttpClient`. 46 | /// 47 | /// This is the same as `HttpClientBuilder::new()`. 48 | pub fn builder() -> HttpClientBuilder { 49 | HttpClientBuilder::new() 50 | } 51 | } 52 | 53 | #[cfg(feature = "http-async-std")] 54 | impl HttpClient { 55 | async fn send_request(&self, request: REQ) -> Result 56 | where 57 | REQ: Serialize, 58 | RSP: Serialize + DeserializeOwned, 59 | { 60 | let request = serde_json::to_string(&request).expect("serialize request"); 61 | log::debug!("Request: {}", request); 62 | 63 | let mut builder = self 64 | .client 65 | .post(&self.url) 66 | .content_type(surf::http::mime::JSON) 67 | .body(request); 68 | for (header_name, header_value) in self.headers.iter() { 69 | builder = builder.header( 70 | header_name.as_str(), 71 | header_value.to_str().expect("must be visible ascii"), 72 | ); 73 | } 74 | 75 | let response = builder.send(); 76 | let response = if let Some(duration) = self.timeout { 77 | let timeout = async_std::task::sleep(duration); 78 | futures::pin_mut!(response, timeout); 79 | match futures::future::select(response, timeout).await { 80 | futures::future::Either::Left((response, _)) => response, 81 | futures::future::Either::Right((_, _)) => return Err(anyhow::anyhow!("http request timeout").into()), 82 | } 83 | } else { 84 | response.await 85 | }; 86 | let mut response = response.map_err(|err| err.into_inner())?; 87 | if !response.status().is_success() { 88 | log::debug!("HTTP response status {}", response.status()); 89 | return Err(HttpClientError::Http(anyhow::anyhow!( 90 | "Unexpected response status code: {}", 91 | response.status() 92 | ))); 93 | } 94 | 95 | let response = response.body_string().await.map_err(|err| err.into_inner())?; 96 | log::debug!("Response: {}", response); 97 | 98 | Ok(serde_json::from_str::(&response)?) 99 | } 100 | } 101 | 102 | #[cfg(feature = "http-tokio")] 103 | impl HttpClient { 104 | async fn send_request(&self, request: REQ) -> Result 105 | where 106 | REQ: Serialize, 107 | RSP: Serialize + DeserializeOwned, 108 | { 109 | log::debug!( 110 | "Request: {}", 111 | serde_json::to_string(&request).expect("serialize request") 112 | ); 113 | let builder = self.client.post(&self.url).json(&request); 114 | let response = builder.send().await?; 115 | let response = response.json().await?; 116 | log::debug!( 117 | "Response: {}", 118 | serde_json::to_string(&response).expect("serialize response") 119 | ); 120 | Ok(response) 121 | } 122 | } 123 | 124 | #[async_trait::async_trait] 125 | impl Transport for HttpClient { 126 | type Error = HttpClientError; 127 | 128 | async fn request(&self, method: M, params: Option) -> Result 129 | where 130 | M: Into + Send, 131 | { 132 | let id = self.id.fetch_add(1, Ordering::AcqRel); 133 | let call = Request::new(method, params, Id::Num(id)); 134 | self.send_request(call).await 135 | } 136 | } 137 | 138 | #[async_trait::async_trait] 139 | impl BatchTransport for HttpClient { 140 | async fn request_batch(&self, batch: I) -> Result::Error> 141 | where 142 | I: IntoIterator)> + Send, 143 | I::IntoIter: Send, 144 | M: Into, 145 | { 146 | let calls = batch 147 | .into_iter() 148 | .map(|(method, params)| { 149 | let id = self.id.fetch_add(1, Ordering::AcqRel); 150 | Request::new(method, params, Id::Num(id)) 151 | }) 152 | .collect::>(); 153 | self.send_request(calls).await 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /client/src/http_client/tests.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[test] 4 | fn http_header() { 5 | use http::header::{self, HeaderValue}; 6 | 7 | // basic auth 8 | let builder = HttpClientBuilder::new().basic_auth("username", Some("password")); 9 | let basic_auth = builder.headers.get(header::AUTHORIZATION).unwrap(); 10 | assert_eq!(basic_auth, HeaderValue::from_static("Basic dXNlcm5hbWU6cGFzc3dvcmQ=")); 11 | let builder = HttpClientBuilder::new().basic_auth("username", Option::::None); 12 | let basic_auth = builder.headers.get(header::AUTHORIZATION).unwrap(); 13 | assert_eq!(basic_auth, HeaderValue::from_static("Basic dXNlcm5hbWU6")); 14 | let builder = HttpClientBuilder::new().basic_auth("", Some("password")); 15 | let basic_auth = builder.headers.get(header::AUTHORIZATION).unwrap(); 16 | assert_eq!(basic_auth, HeaderValue::from_static("Basic OnBhc3N3b3Jk")); 17 | 18 | // bearer auth 19 | let builder = HttpClientBuilder::new().bearer_auth("Hold my bear"); 20 | let bearer_auth = builder.headers.get(header::AUTHORIZATION).unwrap(); 21 | assert_eq!(bearer_auth, HeaderValue::from_static("Bearer Hold my bear")); 22 | } 23 | 24 | #[cfg(feature = "http-async-std")] 25 | async fn server(addr: &str) -> std::io::Result<()> { 26 | let mut server = tide::new(); 27 | 28 | async fn v2_no_params(mut req: tide::Request<()>) -> tide::Result { 29 | let got = req.body_string().await.unwrap(); 30 | let expected = r#"{"jsonrpc":"2.0","method":"foo","id":1}"#; 31 | assert_eq!(got, expected); 32 | let response = r#"{"jsonrpc":"2.0","id":1,"result":"x"}"#; 33 | Ok(tide::Response::from(response)) 34 | } 35 | async fn v2_params(mut req: tide::Request<()>) -> tide::Result { 36 | let got = req.body_string().await.unwrap(); 37 | let expected = r#"{"jsonrpc":"2.0","method":"bar","params":[],"id":1}"#; 38 | assert_eq!(got, expected); 39 | let response = r#"{"jsonrpc":"2.0","id":1,"result":"y"}"#; 40 | Ok(tide::Response::from(response)) 41 | } 42 | async fn v2_batch(mut req: tide::Request<()>) -> tide::Result { 43 | let got = req.body_string().await.unwrap(); 44 | let expected = 45 | r#"[{"jsonrpc":"2.0","method":"foo","id":1},{"jsonrpc":"2.0","method":"bar","params":[],"id":2}]"#; 46 | assert_eq!(got, expected); 47 | let response = r#"[{"jsonrpc":"2.0","id":1,"result":"x"},{"jsonrpc":"2.0","id":2,"result":"y"}]"#; 48 | Ok(tide::Response::from(response)) 49 | } 50 | 51 | server.at("/v2_no_params").post(v2_no_params); 52 | server.at("/v2_params").post(v2_params); 53 | server.at("/v2_batch").post(v2_batch); 54 | server.listen(addr).await 55 | } 56 | 57 | #[cfg(feature = "http-async-std")] 58 | #[async_std::test] 59 | async fn make_jsonrpc_request() { 60 | let addr = "127.0.0.1:8080"; 61 | async_std::task::spawn(server(addr)); 62 | 63 | { 64 | let client = HttpClient::new(format!("http://{}/v2_no_params", addr)).unwrap(); 65 | let response = client.request("foo", None).await.unwrap(); 66 | assert_eq!(response, Response::success(Value::String("x".to_string()), 1.into())); 67 | } 68 | 69 | { 70 | let client = HttpClient::new(format!("http://{}/v2_params", addr)).unwrap(); 71 | let response = client.request("bar", Some(Params::Array(vec![]))).await.unwrap(); 72 | assert_eq!(response, Response::success("y".into(), 1.into())); 73 | } 74 | 75 | { 76 | let client = HttpClient::new(format!("http://{}/v2_batch", addr)).unwrap(); 77 | let response = client 78 | .request_batch(vec![("foo", None), ("bar", Some(Params::Array(vec![])))]) 79 | .await 80 | .unwrap(); 81 | assert_eq!( 82 | response, 83 | vec![ 84 | Response::success("x".into(), 1.into()), 85 | Response::success("y".into(), 2.into()), 86 | ] 87 | ); 88 | } 89 | } 90 | 91 | #[cfg(feature = "http-tokio")] 92 | async fn dispatch_fn(req: hyper::Request) -> hyper::Result> { 93 | use hyper::body::HttpBody as _; 94 | assert_eq!(req.method(), &hyper::Method::POST); 95 | 96 | let path = req.uri().path().to_string(); 97 | let mut content = vec![]; 98 | let mut body = req.into_body(); 99 | while let Some(Ok(chunk)) = body.data().await { 100 | content.extend(&*chunk); 101 | } 102 | match path.as_str() { 103 | "/v2_no_params" => { 104 | let expected = r#"{"jsonrpc":"2.0","method":"foo","id":1}"#; 105 | assert_eq!(std::str::from_utf8(&content), Ok(expected)); 106 | let response = r#"{"jsonrpc":"2.0","id":1,"result":"x"}"#; 107 | Ok(hyper::Response::new(response.into())) 108 | } 109 | "/v2_params" => { 110 | let expected = r#"{"jsonrpc":"2.0","method":"bar","params":[],"id":1}"#; 111 | assert_eq!(std::str::from_utf8(&content), Ok(expected)); 112 | let response = r#"{"jsonrpc":"2.0","id":1,"result":"y"}"#; 113 | Ok(hyper::Response::new(response.into())) 114 | } 115 | "/v2_batch" => { 116 | let expected = 117 | r#"[{"jsonrpc":"2.0","method":"foo","id":1},{"jsonrpc":"2.0","method":"bar","params":[],"id":2}]"#; 118 | assert_eq!(std::str::from_utf8(&content), Ok(expected)); 119 | let response = r#"[{"jsonrpc":"2.0","id":1,"result":"x"},{"jsonrpc":"2.0","id":2,"result":"y"}]"#; 120 | Ok(hyper::Response::new(response.into())) 121 | } 122 | _ => unreachable!(), 123 | } 124 | } 125 | 126 | #[cfg(feature = "http-tokio")] 127 | #[tokio::test] 128 | async fn make_jsonrpc_request() { 129 | use hyper::service::{make_service_fn, service_fn}; 130 | 131 | let addr = "127.0.0.1:8080"; 132 | let service = make_service_fn(|_| async { Ok::<_, hyper::Error>(service_fn(dispatch_fn)) }); 133 | let server = hyper::Server::bind(&addr.parse().unwrap()).serve(service); 134 | tokio::spawn(server); 135 | 136 | { 137 | let client = HttpClient::new(format!("http://{}/v2_no_params", addr)).unwrap(); 138 | let response = client.request("foo", None).await.unwrap(); 139 | assert_eq!(response, Response::success(Value::String("x".to_string()), 1.into())); 140 | } 141 | 142 | { 143 | let client = HttpClient::new(format!("http://{}/v2_params", addr)).unwrap(); 144 | let response = client.request("bar", Some(Params::Array(vec![]))).await.unwrap(); 145 | assert_eq!(response, Response::success("y".into(), 1.into())); 146 | } 147 | 148 | { 149 | let client = HttpClient::new(format!("http://{}/v2_batch", addr)).unwrap(); 150 | let response = client 151 | .request_batch(vec![("foo", None), ("bar", Some(Params::Array(vec![])))]) 152 | .await 153 | .unwrap(); 154 | assert_eq!( 155 | response, 156 | vec![ 157 | Response::success("x".into(), 1.into()), 158 | Response::success("y".into(), 2.into()), 159 | ] 160 | ); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /client/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! An async JSON-RPC 2.0 client library. 2 | 3 | #![deny(missing_docs)] 4 | 5 | mod error; 6 | mod transport; 7 | 8 | #[cfg(any(feature = "http-async-std", feature = "http-tokio"))] 9 | mod http_client; 10 | #[cfg(any(feature = "ws-async-std", feature = "ws-tokio"))] 11 | mod ws_client; 12 | 13 | pub use http::header::{self, HeaderName, HeaderValue}; 14 | pub use jsonrpc_types::v2::*; 15 | 16 | pub use self::transport::{BatchTransport, PubsubTransport, Transport}; 17 | #[cfg(any(feature = "http-async-std", feature = "http-tokio"))] 18 | pub use self::{ 19 | error::HttpClientError, 20 | http_client::{HttpClient, HttpClientBuilder}, 21 | }; 22 | #[cfg(any(feature = "ws-async-std", feature = "ws-tokio"))] 23 | pub use self::{ 24 | error::{WsClientError, WsError}, 25 | ws_client::{WsClient, WsClientBuilder, WsSubscription}, 26 | }; 27 | -------------------------------------------------------------------------------- /client/src/transport.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | use futures::stream::Stream; 4 | use jsonrpc_types::v2::*; 5 | 6 | /// A JSON-RPC 2.0 transport. 7 | #[async_trait::async_trait] 8 | pub trait Transport { 9 | /// The transport error type. 10 | type Error: Error; 11 | 12 | /// Send a RPC call with the given method and parameters. 13 | async fn request(&self, method: M, params: Option) -> Result 14 | where 15 | M: Into + Send; 16 | } 17 | 18 | /// A JSON-RPC 2.0 transport supporting batch requests. 19 | #[async_trait::async_trait] 20 | pub trait BatchTransport: Transport { 21 | /// Send a batch of RPC calls with the given method and parameters. 22 | async fn request_batch(&self, batch: I) -> Result 23 | where 24 | I: IntoIterator)> + Send, 25 | I::IntoIter: Send, 26 | M: Into; 27 | } 28 | 29 | /// A JSON-RPC 2.0 transport supporting subscriptions. 30 | #[async_trait::async_trait] 31 | pub trait PubsubTransport: Transport { 32 | /// The subscription stream. 33 | type NotificationStream: Stream; 34 | 35 | /// Add a subscription to this transport. 36 | /// 37 | /// Will send unsubscribe request to the server when drop the notification stream. 38 | async fn subscribe( 39 | &self, 40 | subscribe_method: M, 41 | params: Option, 42 | ) -> Result<(Id, Self::NotificationStream), Self::Error> 43 | where 44 | M: Into + Send; 45 | 46 | /// Send an unsubscribe request to the server manually. 47 | async fn unsubscribe(&self, unsubscribe_method: M, subscription_id: Id) -> Result 48 | where 49 | M: Into + Send; 50 | } 51 | -------------------------------------------------------------------------------- /client/src/ws_client/builder.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, time::Duration}; 2 | 3 | use async_tungstenite::tungstenite::handshake::client::Request as HandShakeRequest; 4 | use futures::channel::mpsc; 5 | use http::header::{self, HeaderMap, HeaderName, HeaderValue}; 6 | 7 | use crate::{ 8 | error::WsError, 9 | ws_client::{task::WsTask, WsClient}, 10 | }; 11 | 12 | /// A `WsClientBuilder` can be used to create a `HttpClient` with custom configuration. 13 | #[derive(Debug)] 14 | pub struct WsClientBuilder { 15 | headers: HeaderMap, 16 | timeout: Option, 17 | max_concurrent_request_capacity: usize, 18 | max_capacity_per_subscription: usize, 19 | } 20 | 21 | impl Default for WsClientBuilder { 22 | fn default() -> Self { 23 | Self::new() 24 | } 25 | } 26 | 27 | impl WsClientBuilder { 28 | /// Creates a new `WsClientBuilder`. 29 | /// 30 | /// This is the same as `WsClient::builder()`. 31 | pub fn new() -> Self { 32 | Self { 33 | headers: HeaderMap::new(), 34 | timeout: None, 35 | max_concurrent_request_capacity: 256, 36 | max_capacity_per_subscription: 64, 37 | } 38 | } 39 | 40 | // ======================================================================== 41 | // HTTP header options 42 | // ======================================================================== 43 | 44 | /// Enables basic authentication. 45 | pub fn basic_auth(self, username: U, password: Option

) -> Self 46 | where 47 | U: fmt::Display, 48 | P: fmt::Display, 49 | { 50 | let mut basic_auth = "Basic ".to_string(); 51 | let auth = if let Some(password) = password { 52 | base64::encode(format!("{}:{}", username, password)) 53 | } else { 54 | base64::encode(format!("{}:", username)) 55 | }; 56 | basic_auth.push_str(&auth); 57 | let value = HeaderValue::from_str(&basic_auth).expect("basic auth header value"); 58 | self.header(header::AUTHORIZATION, value) 59 | } 60 | 61 | /// Enables bearer authentication. 62 | pub fn bearer_auth(self, token: T) -> Self 63 | where 64 | T: fmt::Display, 65 | { 66 | let bearer_auth = format!("Bearer {}", token); 67 | let value = HeaderValue::from_str(&bearer_auth).expect("bearer auth header value"); 68 | self.header(header::AUTHORIZATION, value) 69 | } 70 | 71 | /// Adds a `Header` for handshake request. 72 | pub fn header(mut self, name: HeaderName, value: HeaderValue) -> Self { 73 | self.headers.insert(name, value); 74 | self 75 | } 76 | 77 | /// Adds `Header`s for handshake request. 78 | pub fn headers(mut self, headers: HeaderMap) -> Self { 79 | self.headers.extend(headers); 80 | self 81 | } 82 | 83 | // ======================================================================== 84 | // Channel options 85 | // ======================================================================== 86 | 87 | /// Sets the max channel capacity of sending request concurrently. 88 | /// 89 | /// Default is 256. 90 | pub fn max_concurrent_request_capacity(mut self, capacity: usize) -> Self { 91 | self.max_concurrent_request_capacity = capacity; 92 | self 93 | } 94 | 95 | /// Sets the max channel capacity of every subscription stream. 96 | /// 97 | /// Default is 64. 98 | pub fn max_capacity_per_subscription(mut self, capacity: usize) -> Self { 99 | self.max_capacity_per_subscription = capacity; 100 | self 101 | } 102 | 103 | // ======================================================================== 104 | // Timeout options 105 | // ======================================================================== 106 | 107 | /// Enables a request timeout. 108 | /// 109 | /// The timeout is applied from when the request starts connecting until the 110 | /// response body has finished. 111 | /// 112 | /// Default is no timeout. 113 | pub fn timeout(mut self, timeout: Duration) -> Self { 114 | self.timeout = Some(timeout); 115 | self 116 | } 117 | 118 | // ======================================================================== 119 | 120 | /// Returns a `WsClient` that uses this `WsClientBuilder` configuration. 121 | pub async fn build(self, url: impl Into) -> Result { 122 | let url = url.into(); 123 | let mut handshake_builder = HandShakeRequest::get(&url); 124 | let headers = handshake_builder.headers_mut().expect("handshake request just created"); 125 | headers.extend(self.headers); 126 | let handshake_req = handshake_builder.body(()).map_err(WsError::HttpFormat)?; 127 | 128 | let (to_back, from_front) = mpsc::channel(self.max_concurrent_request_capacity); 129 | log::debug!("Connecting '{}' ...", url); 130 | let task = WsTask::handshake(handshake_req, self.max_capacity_per_subscription).await?; 131 | log::debug!("Connect '{}' successfully", url); 132 | #[cfg(feature = "ws-async-std")] 133 | let _handle = async_std::task::spawn(task.into_task(from_front)); 134 | #[cfg(feature = "ws-tokio")] 135 | let _handle = tokio::spawn(task.into_task(from_front)); 136 | 137 | Ok(WsClient { 138 | to_back, 139 | timeout: self.timeout, 140 | }) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /client/src/ws_client/manager.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::{Entry, HashMap}; 2 | 3 | use futures::channel::{mpsc, oneshot}; 4 | use jsonrpc_types::v2::*; 5 | 6 | use crate::error::WsClientError; 7 | 8 | type PendingMethodCall = oneshot::Sender>; 9 | type PendingBatchMethodCall = oneshot::Sender>; 10 | type PendingSubscription = oneshot::Sender), WsClientError>>; 11 | type ActiveSubscription = mpsc::Sender; 12 | type PendingUnsubscribe = oneshot::Sender>; 13 | 14 | #[derive(Debug)] 15 | enum RequestKind { 16 | PendingMethodCall(PendingMethodCall), 17 | PendingBatchMethodCall(PendingBatchMethodCall), 18 | PendingSubscription(PendingSubscription), 19 | ActiveSubscription(ActiveSubscription), 20 | PendingUnsubscribe((Id, PendingUnsubscribe)), 21 | } 22 | 23 | pub enum RequestStatus { 24 | /// The method call is waiting for a response. 25 | PendingMethodCall, 26 | /// The batch of method calls is waiting for batch of responses. 27 | PendingBatchMethodCall, 28 | /// The subscription is waiting for a response to become an active subscription. 29 | PendingSubscription, 30 | /// An active subscription. 31 | ActiveSubscription, 32 | /// The unsubscribe method call is waiting for a response. 33 | PendingUnsubscribe, 34 | /// Invalid request ID. 35 | Invalid, 36 | } 37 | 38 | /// Manages JSON-RPC 2.0 method calls and subscriptions. 39 | #[derive(Debug)] 40 | pub struct TaskManager { 41 | /// Requests that are waiting for response from the server. 42 | requests: HashMap, 43 | /// Helper to find a request ID by subscription ID instead of looking through all requests. 44 | subscriptions: HashMap, 45 | /// Max capacity of every subscription channel. 46 | pub(crate) max_capacity_per_subscription: usize, 47 | } 48 | 49 | impl TaskManager { 50 | pub fn new(max_capacity_per_subscription: usize) -> Self { 51 | Self { 52 | requests: HashMap::new(), 53 | subscriptions: HashMap::new(), 54 | max_capacity_per_subscription, 55 | } 56 | } 57 | 58 | /// Tries to insert a new pending method call into manager. 59 | pub fn insert_pending_method_call( 60 | &mut self, 61 | request_id: u64, 62 | send_back: PendingMethodCall, 63 | ) -> Result<(), PendingMethodCall> { 64 | match self.requests.entry(request_id) { 65 | Entry::Vacant(request) => { 66 | request.insert(RequestKind::PendingMethodCall(send_back)); 67 | Ok(()) 68 | } 69 | // Duplicate request ID. 70 | Entry::Occupied(_) => Err(send_back), 71 | } 72 | } 73 | 74 | /// Tries to complete a pending method call from manager. 75 | pub fn complete_pending_method_call(&mut self, request_id: u64) -> Option { 76 | match self.requests.entry(request_id) { 77 | Entry::Occupied(request) if matches!(request.get(), RequestKind::PendingMethodCall(_)) => { 78 | if let (_req_id, RequestKind::PendingMethodCall(send_back)) = request.remove_entry() { 79 | Some(send_back) 80 | } else { 81 | unreachable!("Kind must be PendingMethodCall; qed"); 82 | } 83 | } 84 | _ => None, 85 | } 86 | } 87 | 88 | /// Tries to insert a new pending method call into manager. 89 | pub fn insert_pending_batch_method_call( 90 | &mut self, 91 | min_request_id: u64, 92 | send_back: PendingBatchMethodCall, 93 | ) -> Result<(), PendingBatchMethodCall> { 94 | match self.requests.entry(min_request_id) { 95 | Entry::Vacant(request) => { 96 | request.insert(RequestKind::PendingBatchMethodCall(send_back)); 97 | Ok(()) 98 | } 99 | // Duplicate request ID. 100 | Entry::Occupied(_) => Err(send_back), 101 | } 102 | } 103 | 104 | /// Tries to complete a pending batch method call from manager. 105 | pub fn complete_pending_batch_method_call(&mut self, min_request_id: u64) -> Option { 106 | match self.requests.entry(min_request_id) { 107 | Entry::Occupied(request) if matches!(request.get(), RequestKind::PendingBatchMethodCall(_)) => { 108 | if let (_min_req_id, RequestKind::PendingBatchMethodCall(send_back)) = request.remove_entry() { 109 | Some(send_back) 110 | } else { 111 | unreachable!("Kind must be PendingMethodCall; qed"); 112 | } 113 | } 114 | _ => None, 115 | } 116 | } 117 | 118 | /// Tries to insert a new pending subscription into manager. 119 | pub fn insert_pending_subscription( 120 | &mut self, 121 | request_id: u64, 122 | send_back: PendingSubscription, 123 | ) -> Result<(), PendingSubscription> { 124 | match self.requests.entry(request_id) { 125 | Entry::Vacant(request) => { 126 | request.insert(RequestKind::PendingSubscription(send_back)); 127 | Ok(()) 128 | } 129 | // Duplicate request ID. 130 | Entry::Occupied(_) => Err(send_back), 131 | } 132 | } 133 | 134 | /// Tries to complete a pending subscription from manager. 135 | pub fn complete_pending_subscription(&mut self, request_id: u64) -> Option { 136 | match self.requests.entry(request_id) { 137 | Entry::Occupied(request) if matches!(request.get(), RequestKind::PendingSubscription(_)) => { 138 | if let (_id, RequestKind::PendingSubscription(send_back)) = request.remove_entry() { 139 | Some(send_back) 140 | } else { 141 | unreachable!("Kind must be PendingSubscription; qed"); 142 | } 143 | } 144 | _ => None, 145 | } 146 | } 147 | 148 | /// Tries to insert a new active subscription into manager. 149 | pub fn insert_active_subscription( 150 | &mut self, 151 | request_id: u64, 152 | subscription_id: Id, 153 | send_back: ActiveSubscription, 154 | ) -> Result<(), ActiveSubscription> { 155 | match ( 156 | self.requests.entry(request_id), 157 | self.subscriptions.entry(subscription_id), 158 | ) { 159 | (Entry::Vacant(request), Entry::Vacant(subscription)) => { 160 | request.insert(RequestKind::ActiveSubscription(send_back)); 161 | subscription.insert(request_id); 162 | Ok(()) 163 | } 164 | // Duplicate request ID or subscription ID. 165 | _ => Err(send_back), 166 | } 167 | } 168 | 169 | /// Tries to remove an active subscription from manager. 170 | pub fn remove_active_subscription(&mut self, request_id: u64, subscription_id: Id) -> Option { 171 | match ( 172 | self.requests.entry(request_id), 173 | self.subscriptions.entry(subscription_id), 174 | ) { 175 | (Entry::Occupied(request), Entry::Occupied(subscription)) => { 176 | let (_req_id, kind) = request.remove_entry(); 177 | let (_sub_id, _req_id) = subscription.remove_entry(); 178 | if let RequestKind::ActiveSubscription(send_back) = kind { 179 | Some(send_back) 180 | } else { 181 | unreachable!("Kind must be ActiveSubscription; qed"); 182 | } 183 | } 184 | _ => None, 185 | } 186 | } 187 | 188 | /// Tries to insert a new pending unsubscribe method call into manager. 189 | pub fn insert_pending_unsubscribe( 190 | &mut self, 191 | request_id: u64, 192 | subscription_id: Id, 193 | send_back: PendingUnsubscribe, 194 | ) -> Result<(), PendingUnsubscribe> { 195 | match self.requests.entry(request_id) { 196 | Entry::Vacant(request) => { 197 | request.insert(RequestKind::PendingUnsubscribe((subscription_id, send_back))); 198 | Ok(()) 199 | } 200 | // Duplicate request ID. 201 | Entry::Occupied(_) => Err(send_back), 202 | } 203 | } 204 | 205 | /// Tries to complete a pending method call from manager. 206 | pub fn complete_pending_unsubscribe(&mut self, request_id: u64) -> Option<(Id, PendingUnsubscribe)> { 207 | match self.requests.entry(request_id) { 208 | Entry::Occupied(request) if matches!(request.get(), RequestKind::PendingUnsubscribe(_)) => { 209 | if let (_req_id, RequestKind::PendingUnsubscribe(send_back)) = request.remove_entry() { 210 | Some(send_back) 211 | } else { 212 | unreachable!("Kind must be PendingUnsubscribe; qed"); 213 | } 214 | } 215 | _ => None, 216 | } 217 | } 218 | 219 | /// Reverse lookup to get the request ID by a subscription ID. 220 | pub fn get_request_id_by(&self, subscription_id: &Id) -> Option { 221 | self.subscriptions.get(subscription_id).copied() 222 | } 223 | 224 | /// Returns the status of a request ID. 225 | pub fn request_status(&mut self, request_id: &u64) -> RequestStatus { 226 | self.requests 227 | .get(request_id) 228 | .map_or(RequestStatus::Invalid, |kind| match kind { 229 | RequestKind::PendingMethodCall(_) => RequestStatus::PendingMethodCall, 230 | RequestKind::PendingBatchMethodCall(_) => RequestStatus::PendingBatchMethodCall, 231 | RequestKind::PendingSubscription(_) => RequestStatus::PendingSubscription, 232 | RequestKind::ActiveSubscription(_) => RequestStatus::ActiveSubscription, 233 | RequestKind::PendingUnsubscribe(_) => RequestStatus::PendingUnsubscribe, 234 | }) 235 | } 236 | 237 | /// Gets a mutable reference to active subscription sink to send messages back to 238 | /// the subscription channel. 239 | pub fn as_active_subscription_mut(&mut self, request_id: &u64) -> Option<&mut ActiveSubscription> { 240 | let kind = self.requests.get_mut(request_id); 241 | if let Some(RequestKind::ActiveSubscription(sink)) = kind { 242 | Some(sink) 243 | } else { 244 | None 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /client/src/ws_client/mod.rs: -------------------------------------------------------------------------------- 1 | mod builder; 2 | mod manager; 3 | mod task; 4 | #[cfg(test)] 5 | mod tests; 6 | 7 | use std::{ 8 | pin::Pin, 9 | task::{Context, Poll}, 10 | time::Duration, 11 | }; 12 | 13 | use futures::{ 14 | channel::{mpsc, oneshot}, 15 | future, 16 | sink::SinkExt, 17 | stream::{Stream, StreamExt}, 18 | }; 19 | use jsonrpc_types::v2::*; 20 | 21 | pub use self::builder::WsClientBuilder; 22 | use crate::{ 23 | error::WsClientError, 24 | transport::{BatchTransport, PubsubTransport, Transport}, 25 | }; 26 | 27 | /// Message that the client can send to the background task. 28 | pub(crate) enum ToBackTaskMessage { 29 | Request { 30 | method: String, 31 | params: Option, 32 | /// One-shot channel where to send back the response of the request. 33 | send_back: oneshot::Sender>, 34 | }, 35 | BatchRequest { 36 | batch: Vec<(String, Option)>, 37 | /// One-shot channel where to send back the response of the batch request. 38 | send_back: oneshot::Sender>, 39 | }, 40 | Subscribe { 41 | subscribe_method: String, 42 | params: Option, 43 | /// One-shot channel where to send back the response (subscription id) and a `Receiver` 44 | /// that will receive subscription notification when we get a response (subscription id) 45 | /// from the server about the subscription. 46 | send_back: oneshot::Sender), WsClientError>>, 47 | }, 48 | Unsubscribe { 49 | unsubscribe_method: String, 50 | subscription_id: Id, 51 | /// One-shot channel where to send back the response of the unsubscribe request. 52 | send_back: oneshot::Sender>, 53 | }, 54 | } 55 | 56 | /// WebSocket JSON-RPC client 57 | #[derive(Clone)] 58 | pub struct WsClient { 59 | to_back: mpsc::Sender, 60 | /// Request timeout. 61 | timeout: Option, 62 | } 63 | 64 | impl WsClient { 65 | /// Creates a new WebSocket JSON-RPC client. 66 | pub async fn new(url: impl Into) -> Result { 67 | WsClientBuilder::new() 68 | .build(url) 69 | .await 70 | .map_err(WsClientError::WebSocket) 71 | } 72 | 73 | /// Creates a `WsClientBuilder` to configure a `WsClient`. 74 | /// 75 | /// This is the same as `WsClientBuilder::new()`. 76 | pub fn builder() -> WsClientBuilder { 77 | WsClientBuilder::new() 78 | } 79 | 80 | /// Sends a `method call` request to the server. 81 | async fn send_request(&self, method: impl Into, params: Option) -> Result { 82 | let method = method.into(); 83 | log::debug!("[frontend] Send request: method={}, params={:?}", method, params); 84 | 85 | let (tx, rx) = oneshot::channel(); 86 | self.to_back 87 | .clone() 88 | .send(ToBackTaskMessage::Request { 89 | method, 90 | params, 91 | send_back: tx, 92 | }) 93 | .await 94 | .map_err(|_| WsClientError::InternalChannel)?; 95 | 96 | let res = if let Some(duration) = self.timeout { 97 | #[cfg(feature = "ws-async-std")] 98 | let timeout = async_std::task::sleep(duration); 99 | #[cfg(feature = "ws-tokio")] 100 | let timeout = tokio::time::sleep(duration); 101 | futures::pin_mut!(rx, timeout); 102 | match future::select(rx, timeout).await { 103 | future::Either::Left((response, _)) => response, 104 | future::Either::Right((_, _)) => return Err(WsClientError::RequestTimeout), 105 | } 106 | } else { 107 | rx.await 108 | }; 109 | match res { 110 | Ok(Ok(output)) => Ok(output), 111 | Ok(Err(err)) => Err(err), 112 | Err(_) => Err(WsClientError::InternalChannel), 113 | } 114 | } 115 | 116 | /// Sends a batch of `method call` requests to the server. 117 | async fn send_request_batch(&self, batch: I) -> Result 118 | where 119 | I: IntoIterator)>, 120 | M: Into, 121 | { 122 | let batch = batch 123 | .into_iter() 124 | .map(|(method, params)| (method.into(), params)) 125 | .collect::>(); 126 | log::debug!("[frontend] Send a batch of requests: {:?}", batch); 127 | 128 | let (tx, rx) = oneshot::channel(); 129 | self.to_back 130 | .clone() 131 | .send(ToBackTaskMessage::BatchRequest { batch, send_back: tx }) 132 | .await 133 | .map_err(|_| WsClientError::InternalChannel)?; 134 | 135 | let res = if let Some(duration) = self.timeout { 136 | #[cfg(feature = "ws-async-std")] 137 | let timeout = async_std::task::sleep(duration); 138 | #[cfg(feature = "ws-tokio")] 139 | let timeout = tokio::time::sleep(duration); 140 | futures::pin_mut!(rx, timeout); 141 | match future::select(rx, timeout).await { 142 | future::Either::Left((response, _)) => response, 143 | future::Either::Right((_, _)) => return Err(WsClientError::RequestTimeout), 144 | } 145 | } else { 146 | rx.await 147 | }; 148 | match res { 149 | Ok(Ok(outputs)) => Ok(outputs), 150 | Ok(Err(err)) => Err(err), 151 | Err(_) => Err(WsClientError::InternalChannel), 152 | } 153 | } 154 | 155 | /// Sends a subscribe request to the server. 156 | /// 157 | /// `subscribe_method` and `params` are used to ask for the subscription towards the server. 158 | /// `unsubscribe_method` is used to close the subscription. 159 | async fn send_subscribe( 160 | &self, 161 | subscribe_method: impl Into, 162 | params: Option, 163 | ) -> Result, WsClientError> { 164 | let subscribe_method = subscribe_method.into(); 165 | log::debug!("[frontend] Subscribe: method={}, params={:?}", subscribe_method, params); 166 | let (tx, rx) = oneshot::channel(); 167 | self.to_back 168 | .clone() 169 | .send(ToBackTaskMessage::Subscribe { 170 | subscribe_method, 171 | params, 172 | send_back: tx, 173 | }) 174 | .await 175 | .map_err(|_| WsClientError::InternalChannel)?; 176 | 177 | let res = if let Some(duration) = self.timeout { 178 | #[cfg(feature = "ws-async-std")] 179 | let timeout = async_std::task::sleep(duration); 180 | #[cfg(feature = "ws-tokio")] 181 | let timeout = tokio::time::sleep(duration); 182 | futures::pin_mut!(rx, timeout); 183 | match future::select(rx, timeout).await { 184 | future::Either::Left((response, _)) => response, 185 | future::Either::Right((_, _)) => return Err(WsClientError::RequestTimeout), 186 | } 187 | } else { 188 | rx.await 189 | }; 190 | match res { 191 | Ok(Ok((id, notification_rx))) => Ok(WsSubscription { id, notification_rx }), 192 | Ok(Err(err)) => Err(err), 193 | Err(_) => Err(WsClientError::InternalChannel), 194 | } 195 | } 196 | 197 | /// Sends an unsubscribe request to the server. 198 | async fn send_unsubscribe( 199 | &self, 200 | unsubscribe_method: impl Into, 201 | subscription_id: Id, 202 | ) -> Result { 203 | let unsubscribe_method = unsubscribe_method.into(); 204 | log::debug!( 205 | "[frontend] unsubscribe: method={}, id={:?}", 206 | unsubscribe_method, 207 | subscription_id 208 | ); 209 | let (tx, rx) = oneshot::channel(); 210 | self.to_back 211 | .clone() 212 | .send(ToBackTaskMessage::Unsubscribe { 213 | unsubscribe_method, 214 | subscription_id, 215 | send_back: tx, 216 | }) 217 | .await 218 | .map_err(|_| WsClientError::InternalChannel)?; 219 | 220 | let res = if let Some(duration) = self.timeout { 221 | #[cfg(feature = "ws-async-std")] 222 | let timeout = async_std::task::sleep(duration); 223 | #[cfg(feature = "ws-tokio")] 224 | let timeout = tokio::time::sleep(duration); 225 | futures::pin_mut!(rx, timeout); 226 | match future::select(rx, timeout).await { 227 | future::Either::Left((response, _)) => response, 228 | future::Either::Right((_, _)) => return Err(WsClientError::RequestTimeout), 229 | } 230 | } else { 231 | rx.await 232 | }; 233 | 234 | match res { 235 | Ok(Ok(res)) => Ok(res), 236 | Ok(Err(err)) => Err(err), 237 | Err(_) => Err(WsClientError::InternalChannel), 238 | } 239 | } 240 | } 241 | 242 | /// Active subscription on a websocket client. 243 | pub struct WsSubscription { 244 | /// Subscription ID. 245 | pub id: Id, 246 | /// Channel from which we receive notifications from the server. 247 | notification_rx: mpsc::Receiver, 248 | } 249 | 250 | impl WsSubscription { 251 | /// Returns the next notification from the websocket stream. 252 | /// 253 | /// Ignore any malformed packet. 254 | pub async fn next(&mut self) -> Option { 255 | self.notification_rx.next().await 256 | } 257 | } 258 | 259 | impl Stream for WsSubscription { 260 | type Item = Notif; 261 | 262 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 263 | mpsc::Receiver::::poll_next(Pin::new(&mut self.notification_rx), cx) 264 | } 265 | } 266 | 267 | #[async_trait::async_trait] 268 | impl Transport for WsClient { 269 | type Error = WsClientError; 270 | 271 | async fn request(&self, method: M, params: Option) -> Result 272 | where 273 | M: Into + Send, 274 | { 275 | self.send_request(method, params).await 276 | } 277 | } 278 | 279 | #[async_trait::async_trait] 280 | impl BatchTransport for WsClient { 281 | async fn request_batch(&self, batch: I) -> Result::Error> 282 | where 283 | I: IntoIterator)> + Send, 284 | I::IntoIter: Send, 285 | M: Into, 286 | { 287 | self.send_request_batch(batch).await 288 | } 289 | } 290 | 291 | #[async_trait::async_trait] 292 | impl PubsubTransport for WsClient { 293 | type NotificationStream = WsSubscription; 294 | 295 | async fn subscribe( 296 | &self, 297 | subscribe_method: M, 298 | params: Option, 299 | ) -> Result<(Id, Self::NotificationStream), ::Error> 300 | where 301 | M: Into + Send, 302 | { 303 | let notification_stream = self.send_subscribe(subscribe_method, params).await?; 304 | Ok((notification_stream.id.clone(), notification_stream)) 305 | } 306 | 307 | async fn unsubscribe( 308 | &self, 309 | unsubscribe_method: M, 310 | subscription_id: Id, 311 | ) -> Result::Error> 312 | where 313 | M: Into + Send, 314 | { 315 | self.send_unsubscribe(unsubscribe_method, subscription_id).await 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /client/src/ws_client/task.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "ws-async-std")] 2 | use async_tungstenite::async_std::{connect_async, ConnectStream}; 3 | #[cfg(feature = "ws-tokio")] 4 | use async_tungstenite::tokio::{connect_async, ConnectStream}; 5 | use async_tungstenite::{ 6 | tungstenite::{handshake::client::Request as HandShakeRequest, protocol::Message}, 7 | WebSocketStream, 8 | }; 9 | use futures::{ 10 | channel::mpsc, 11 | sink::SinkExt, 12 | stream::{SplitSink, SplitStream, StreamExt}, 13 | }; 14 | use jsonrpc_types::v2::*; 15 | 16 | use crate::{ 17 | error::{WsClientError, WsError}, 18 | ws_client::{ 19 | manager::{RequestStatus, TaskManager}, 20 | ToBackTaskMessage, 21 | }, 22 | }; 23 | 24 | type WsMsgSender = SplitSink, Message>; 25 | type WsMsgReceiver = SplitStream>; 26 | 27 | struct WsSender { 28 | id: u64, 29 | sender: WsMsgSender, 30 | } 31 | 32 | impl WsSender { 33 | fn new(sender: WsMsgSender) -> Self { 34 | Self { id: 1, sender } 35 | } 36 | 37 | async fn send_message(&mut self, msg: Message) -> Result<(), WsError> { 38 | log::trace!("[backend] Send websocket message: {}", msg); 39 | self.sender.feed(msg).await?; 40 | self.sender.flush().await?; 41 | Ok(()) 42 | } 43 | 44 | async fn send_request(&mut self, method: impl Into, params: Option) -> Result { 45 | let method = method.into(); 46 | let id = self.id; 47 | self.id = id.wrapping_add(1); 48 | let call = Request::new(method, params, Id::Num(id)); 49 | let request = serde_json::to_string(&call).expect("serialize call; qed"); 50 | log::debug!("[backend] Send a method call: {}", request); 51 | self.send_message(Message::Text(request)).await?; 52 | Ok(id) 53 | } 54 | 55 | async fn send_batch_request(&mut self, batch: I) -> Result, WsError> 56 | where 57 | I: IntoIterator)>, 58 | M: Into, 59 | { 60 | let mut calls = vec![]; 61 | let mut ids = vec![]; 62 | for (method, params) in batch { 63 | let method = method.into(); 64 | let id = self.id; 65 | self.id = id.wrapping_add(1); 66 | let call = Request::new(method, params, Id::Num(id)); 67 | ids.push(id); 68 | calls.push(call); 69 | } 70 | let request = RequestObj::Batch(calls); 71 | let request = serde_json::to_string(&request).expect("serialize calls; qed"); 72 | log::debug!("[backend] Send a batch of method calls: {}", request); 73 | self.send_message(Message::Text(request)).await?; 74 | Ok(ids) 75 | } 76 | 77 | async fn start_subscription( 78 | &mut self, 79 | subscribe_method: impl Into, 80 | params: Option, 81 | ) -> Result { 82 | self.send_request(subscribe_method, params).await 83 | } 84 | 85 | async fn stop_subscription( 86 | &mut self, 87 | unsubscribe_method: impl Into, 88 | subscription_id: Id, 89 | ) -> Result { 90 | let subscription_id = serde_json::to_value(subscription_id).expect("serialize Id"); 91 | let params = Params::Array(vec![subscription_id]); 92 | self.send_request(unsubscribe_method, Some(params)).await 93 | } 94 | } 95 | 96 | struct WsReceiver(WsMsgReceiver); 97 | impl WsReceiver { 98 | fn new(receiver: WsMsgReceiver) -> Self { 99 | Self(receiver) 100 | } 101 | 102 | async fn recv_message(&mut self) -> Result { 103 | loop { 104 | if let Some(message) = self.0.next().await { 105 | let message = message?; 106 | log::trace!("[backend] Receive websocket message: {}", message); 107 | return Ok(message); 108 | } 109 | } 110 | } 111 | } 112 | 113 | /// Helper struct for managing tasks on a websocket connection. 114 | pub(crate) struct WsTask { 115 | sender: WsSender, 116 | receiver: WsReceiver, 117 | manager: TaskManager, 118 | } 119 | 120 | impl WsTask { 121 | /// Setup websocket connection. 122 | pub(crate) async fn handshake( 123 | request: HandShakeRequest, 124 | max_capacity_per_subscription: usize, 125 | ) -> Result { 126 | let uri = request.uri().clone(); 127 | log::debug!("WebSocket handshake {}, request: {:?}", uri, request); 128 | let (ws_stream, response) = connect_async(request).await?; 129 | log::debug!("WebSocket handshake {}, response: {:?}", uri, response); 130 | let (sink, stream) = ws_stream.split(); 131 | Ok(Self { 132 | sender: WsSender::new(sink), 133 | receiver: WsReceiver::new(stream), 134 | manager: TaskManager::new(max_capacity_per_subscription), 135 | }) 136 | } 137 | 138 | /// Convert self into a spawnable runtime task that processes message sent from the frontend and 139 | /// received from backend. 140 | pub(crate) async fn into_task(self, from_front: mpsc::Receiver) { 141 | let Self { 142 | mut sender, 143 | receiver, 144 | mut manager, 145 | } = self; 146 | 147 | let from_back = futures::stream::unfold(receiver, |mut receiver| async { 148 | let res = receiver.recv_message().await; 149 | Some((res, receiver)) 150 | }); 151 | futures::pin_mut!(from_front, from_back); 152 | 153 | loop { 154 | futures::select! { 155 | msg = from_front.next() => match msg { 156 | Some(msg) => handle_from_front_message(msg, &mut manager, &mut sender).await, 157 | None => { 158 | log::debug!("[backend] Frontend channel dropped; terminate client"); 159 | break; 160 | } 161 | }, 162 | msg = from_back.next() => match msg { 163 | Some(Ok(msg)) => if let Err(err) = handle_from_back_message(msg, &mut manager, &mut sender).await { 164 | log::error!("[backend] Handle websocket message error: {}; terminate client", err); 165 | break; 166 | } 167 | Some(Err(err)) => { 168 | log::error!("[backend] Receive websocket message error: {}; terminate client", err); 169 | break; 170 | } 171 | None => { 172 | log::debug!("[backend] Backend channel dropped; terminate client"); 173 | break; 174 | } 175 | }, 176 | } 177 | } 178 | } 179 | } 180 | 181 | async fn handle_from_front_message(msg: ToBackTaskMessage, manager: &mut TaskManager, sender: &mut WsSender) { 182 | match msg { 183 | ToBackTaskMessage::Request { 184 | method, 185 | params, 186 | send_back, 187 | } => match sender.send_request(method, params).await { 188 | Ok(req_id) => { 189 | if let Err(send_back) = manager.insert_pending_method_call(req_id, send_back) { 190 | send_back 191 | .send(Err(WsClientError::DuplicateRequestId)) 192 | .expect("Send request error back"); 193 | } 194 | } 195 | Err(err) => { 196 | log::warn!("[backend] Send request error: {}", err); 197 | send_back 198 | .send(Err(WsClientError::WebSocket(err))) 199 | .expect("Send request error back"); 200 | } 201 | }, 202 | ToBackTaskMessage::BatchRequest { batch, send_back } => match sender.send_batch_request(batch).await { 203 | Ok(req_ids) => { 204 | let min_request_id = req_ids.into_iter().min().expect("must have one"); 205 | if let Err(send_back) = manager.insert_pending_batch_method_call(min_request_id, send_back) { 206 | send_back 207 | .send(Err(WsClientError::DuplicateRequestId)) 208 | .expect("Send batch request error back"); 209 | } 210 | } 211 | Err(err) => { 212 | log::warn!("[backend] Send a batch of requests error: {}", err); 213 | send_back 214 | .send(Err(WsClientError::WebSocket(err))) 215 | .expect("Send batch request error back"); 216 | } 217 | }, 218 | ToBackTaskMessage::Subscribe { 219 | subscribe_method, 220 | params, 221 | send_back, 222 | } => match sender.start_subscription(subscribe_method, params).await { 223 | Ok(req_id) => { 224 | if let Err(send_back) = manager.insert_pending_subscription(req_id, send_back) { 225 | send_back 226 | .send(Err(WsClientError::DuplicateRequestId)) 227 | .expect("Send subscription request error back"); 228 | } 229 | } 230 | Err(err) => { 231 | log::warn!("[backend] Send subscription request error: {}", err); 232 | send_back 233 | .send(Err(WsClientError::WebSocket(err))) 234 | .expect("Send subscription request error back"); 235 | } 236 | }, 237 | ToBackTaskMessage::Unsubscribe { 238 | unsubscribe_method, 239 | subscription_id, 240 | send_back, 241 | } => match sender 242 | .stop_subscription(unsubscribe_method, subscription_id.clone()) 243 | .await 244 | { 245 | Ok(req_id) => { 246 | if let Err(send_back) = manager.insert_pending_unsubscribe(req_id, subscription_id, send_back) { 247 | send_back 248 | .send(Err(WsClientError::DuplicateRequestId)) 249 | .expect("Send unsubscribe request error back"); 250 | } 251 | } 252 | Err(err) => { 253 | log::warn!("[backend] Send unsubscribe request error: {}", err); 254 | send_back 255 | .send(Err(WsClientError::WebSocket(err))) 256 | .expect("Send unsubscribe request error back"); 257 | } 258 | }, 259 | } 260 | } 261 | 262 | async fn handle_from_back_message( 263 | msg: Message, 264 | manager: &mut TaskManager, 265 | sender: &mut WsSender, 266 | ) -> Result<(), WsClientError> { 267 | match msg { 268 | Message::Text(msg) => { 269 | if let Ok(response) = serde_json::from_str::(&msg) { 270 | handle_response_message(response, manager)? 271 | } else if let Ok(notification) = serde_json::from_str::(&msg) { 272 | handle_subscription_notification_message(notification, manager); 273 | } else { 274 | log::warn!("[backend] Ignore unknown websocket text message: {}", msg); 275 | } 276 | } 277 | Message::Binary(msg) => log::warn!("[backend] Ignore `Binary` message: {:?}", msg), 278 | Message::Ping(msg) => { 279 | log::debug!("[backend] Receive `Ping` message: {:?}", msg); 280 | log::debug!("[backend] Send `Pong` message back, message: {:?}", msg); 281 | sender.send_message(Message::Pong(msg)).await?; 282 | } 283 | Message::Pong(msg) => log::debug!("[backend] Receive `Pong` message: {:?}", msg), 284 | Message::Close(msg) => { 285 | log::error!("[backend] Receive `Close` message: {:?}; terminate client", msg); 286 | return Err(WsClientError::WebSocket(WsError::ConnectionClosed)); 287 | } 288 | } 289 | Ok(()) 290 | } 291 | 292 | fn handle_response_message(response: ResponseObj, manager: &mut TaskManager) -> Result<(), WsClientError> { 293 | match response { 294 | ResponseObj::Single(response) => handle_single_output(response, manager), 295 | ResponseObj::Batch(responses) => handle_batch_output(responses, manager), 296 | } 297 | } 298 | 299 | fn handle_single_output(response: Response, manager: &mut TaskManager) -> Result<(), WsClientError> { 300 | let response_id = response_id_of(&response)?; 301 | match manager.request_status(&response_id) { 302 | RequestStatus::PendingMethodCall => { 303 | log::debug!("[backend] Handle response of method call: id={}", response_id); 304 | let send_back = manager 305 | .complete_pending_method_call(response_id) 306 | .ok_or(WsClientError::InvalidRequestId)?; 307 | send_back.send(Ok(response)).expect("Send single response back"); 308 | Ok(()) 309 | } 310 | RequestStatus::PendingSubscription => { 311 | log::debug!("[backend] Handle response of subscription request: id={}", response_id); 312 | let send_back = manager 313 | .complete_pending_subscription(response_id) 314 | .ok_or(WsClientError::InvalidRequestId)?; 315 | let subscription_id = match response { 316 | Response::Success(success) => match serde_json::from_value::(success.result) { 317 | Ok(id) => id, 318 | Err(err) => { 319 | send_back 320 | .send(Err(WsClientError::Json(err))) 321 | .expect("Send response error back"); 322 | return Ok(()); 323 | } 324 | }, 325 | Response::Failure(_) => { 326 | send_back 327 | .send(Err(WsClientError::InvalidSubscriptionId)) 328 | .expect("Send response error back"); 329 | return Ok(()); 330 | } 331 | }; 332 | 333 | let (subscribe_tx, subscribe_rx) = mpsc::channel(manager.max_capacity_per_subscription); 334 | if manager 335 | .insert_active_subscription(response_id, subscription_id.clone(), subscribe_tx) 336 | .is_ok() 337 | { 338 | send_back 339 | .send(Ok((subscription_id, subscribe_rx))) 340 | .expect("Send subscription stream back"); 341 | } else { 342 | send_back 343 | .send(Err(WsClientError::InvalidSubscriptionId)) 344 | .expect("Send subscription error back"); 345 | } 346 | Ok(()) 347 | } 348 | RequestStatus::PendingUnsubscribe => { 349 | log::debug!("[backend] Handle response of unsubscribe request: id={}", response_id); 350 | let (subscription_id, send_back) = manager 351 | .complete_pending_unsubscribe(response_id) 352 | .ok_or(WsClientError::InvalidRequestId)?; 353 | let result = match response { 354 | Response::Success(success) => match serde_json::from_value::(success.result) { 355 | Ok(result) => result, 356 | Err(err) => { 357 | send_back 358 | .send(Err(WsClientError::Json(err))) 359 | .expect("Send response error back"); 360 | return Ok(()); 361 | } 362 | }, 363 | Response::Failure(failure) => { 364 | log::warn!("[backend] Unexpected response of unsubscribe request: {}", failure); 365 | send_back 366 | .send(Err(WsClientError::InvalidUnsubscribeResult)) 367 | .expect("Send response error back"); 368 | return Ok(()); 369 | } 370 | }; 371 | 372 | send_back.send(Ok(result)).expect("Send single response back"); 373 | 374 | if result { 375 | // clean the subscription of manager according to the subscription id when unsubscribe successfully. 376 | if let Some(request_id) = manager.get_request_id_by(&subscription_id) { 377 | manager.remove_active_subscription(request_id, subscription_id); 378 | } else { 379 | log::error!( 380 | "[backend] Task manager cannot find subscription: id={:?}", 381 | subscription_id 382 | ); 383 | } 384 | } 385 | Ok(()) 386 | } 387 | RequestStatus::ActiveSubscription | RequestStatus::PendingBatchMethodCall | RequestStatus::Invalid => { 388 | Err(WsClientError::InvalidRequestId) 389 | } 390 | } 391 | } 392 | 393 | fn response_id_of(resp: &Response) -> Result { 394 | Ok(*resp 395 | .id() 396 | .ok_or(WsClientError::InvalidRequestId)? 397 | .as_number() 398 | .expect("Response ID must be number")) 399 | } 400 | 401 | fn handle_batch_output(responses: BatchResponse, manager: &mut TaskManager) -> Result<(), WsClientError> { 402 | let (min_response_id, max_response_id) = response_id_range_of(&responses)?; 403 | // use the min id of batch request for managing task 404 | match manager.request_status(&min_response_id) { 405 | RequestStatus::PendingBatchMethodCall => { 406 | log::debug!( 407 | "[backend] Handle batch response of batch request: id=({}~{})", 408 | min_response_id, 409 | max_response_id 410 | ); 411 | let send_back = manager 412 | .complete_pending_batch_method_call(min_response_id) 413 | .ok_or(WsClientError::InvalidRequestId)?; 414 | send_back.send(Ok(responses)).expect("Send batch response back"); 415 | Ok(()) 416 | } 417 | RequestStatus::PendingMethodCall 418 | | RequestStatus::PendingSubscription 419 | | RequestStatus::ActiveSubscription 420 | | RequestStatus::PendingUnsubscribe 421 | | RequestStatus::Invalid => Err(WsClientError::InvalidRequestId), 422 | } 423 | } 424 | 425 | fn response_id_range_of(responses: &[Response]) -> Result<(u64, u64), WsClientError> { 426 | assert!(!responses.is_empty()); 427 | let (mut min, mut max) = (u64::MAX, u64::MIN); 428 | for response in responses { 429 | let id = *response 430 | .id() 431 | .ok_or(WsClientError::InvalidRequestId)? 432 | .as_number() 433 | .expect("Response ID must be number"); 434 | min = std::cmp::min(id, min); 435 | max = std::cmp::max(id, max); 436 | } 437 | Ok((min, max)) 438 | } 439 | 440 | fn handle_subscription_notification_message(notification: SubscriptionNotification, manager: &mut TaskManager) { 441 | let subscription_id = notification.params.subscription.clone(); 442 | let request_id = match manager.get_request_id_by(&subscription_id) { 443 | Some(id) => id, 444 | None => { 445 | log::error!( 446 | "[backend] Task manager cannot find subscription: id={:?}", 447 | subscription_id 448 | ); 449 | return; 450 | } 451 | }; 452 | match manager.as_active_subscription_mut(&request_id) { 453 | Some(send_back) => { 454 | if let Err(err) = send_back.try_send(notification) { 455 | log::error!("[backend] Dropping subscription: id={:?}: {}", subscription_id, err); 456 | manager 457 | .remove_active_subscription(request_id, subscription_id) 458 | .expect("kind is ActiveSubscription; qed"); 459 | } 460 | } 461 | None => log::error!( 462 | "[backend] Subscription id ({:?}) is not an active subscription", 463 | subscription_id 464 | ), 465 | } 466 | } 467 | -------------------------------------------------------------------------------- /client/src/ws_client/tests.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 2 | format_code_in_doc_comments = true 3 | group_imports = "StdExternalCrate" 4 | -------------------------------------------------------------------------------- /server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "async-jsonrpc-server" 3 | version = "0.0.0" 4 | authors = ["koushiro "] 5 | edition = "2018" 6 | readme = "README.md" 7 | license = "MIT/Apache-2.0" 8 | documentation = "https://docs.rs/async-jsonrpc-server" 9 | repository = "https://github.com/koushiro/async-jsonrpc" 10 | description = "An asynchronous JSON-RPC 2.0 server library" 11 | keywords = ["jsonrpc", "rpc", "async", "server"] 12 | categories = ["network-programming", "web-programming"] 13 | 14 | [dependencies] 15 | -------------------------------------------------------------------------------- /server/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /server/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | # async-jsonrpc-server 2 | 3 | [![ga-svg]][ga-url] 4 | [![crates-svg]][crates-url] 5 | [![docs-svg]][docs-url] 6 | 7 | [ga-svg]: https://github.com/koushiro/async-jsonrpc/workflows/build/badge.svg 8 | [ga-url]: https://github.com/koushiro/async-jsonrpc/actions 9 | [crates-svg]: https://img.shields.io/crates/v/async-jsonrpc-server 10 | [crates-url]: https://crates.io/crates/async-jsonrpc-server 11 | [docs-svg]: https://docs.rs/async-jsonrpc-server/badge.svg 12 | [docs-url]: https://docs.rs/async-jsonrpc-server 13 | 14 | TODO: An asynchronous JSON-RPC 2.0 server library written in Rust. 15 | 16 | ## Features 17 | 18 | ## Usage 19 | 20 | ## License 21 | 22 | Licensed under either of 23 | 24 | - [Apache License, Version 2.0](LICENSE-APACHE) 25 | - [MIT License](LICENSE-MIT) 26 | 27 | at your option. 28 | 29 | ## Contribution 30 | 31 | Unless you explicitly state otherwise, any contribution intentionally submitted 32 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 33 | dual licensed as above, without any additional terms or conditions. 34 | -------------------------------------------------------------------------------- /server/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! An async JSON-RPC 2.0 server library. 2 | 3 | #![deny(missing_docs)] 4 | 5 | // TODO 6 | -------------------------------------------------------------------------------- /types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonrpc-types" 3 | version = "0.4.0-dev" 4 | authors = ["koushiro "] 5 | edition = "2018" 6 | readme = "README.md" 7 | license = "MIT/Apache-2.0" 8 | documentation = "https://docs.rs/jsonrpc-types" 9 | repository = "https://github.com/koushiro/async-jsonrpc" 10 | description = "A set of types for representing JSON-RPC requests and responses." 11 | keywords = ["jsonrpc", "rpc", "types"] 12 | categories = ["network-programming", "web-programming"] 13 | 14 | [features] 15 | default = ["std", "v2"] 16 | std = ["serde/std", "serde_json/std"] 17 | v1 = [] 18 | v2 = [] 19 | 20 | [package.metadata.docs.rs] 21 | # RUSTDOCFLAGS="--cfg doc_cfg" 22 | # To build locally: cargo +nightly doc --all-features --no-deps --open 23 | all-features = true 24 | rustdoc-args = ["--cfg", "doc_cfg"] 25 | 26 | [dependencies] 27 | serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } 28 | serde_json = { version = "1.0", default-features = false, features = ["alloc"] } 29 | -------------------------------------------------------------------------------- /types/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /types/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /types/README.md: -------------------------------------------------------------------------------- 1 | # jsonrpc-types 2 | 3 | [![ga-svg]][ga-url] 4 | [![crates-svg]][crates-url] 5 | [![docs-svg]][docs-url] 6 | 7 | [ga-svg]: https://github.com/koushiro/async-jsonrpc/workflows/test/badge.svg 8 | [ga-url]: https://github.com/koushiro/async-jsonrpc/actions 9 | [crates-svg]: https://img.shields.io/crates/v/jsonrpc-types 10 | [crates-url]: https://crates.io/crates/jsonrpc-types 11 | [docs-svg]: https://docs.rs/jsonrpc-types/badge.svg 12 | [docs-url]: https://docs.rs/jsonrpc-types 13 | 14 | A general purpose library of [JSON-RPC 1.0](https://www.jsonrpc.org/specification_v1) and 15 | [JSON-RPC 2.0](https://www.jsonrpc.org/specification) types. 16 | 17 | ## Usage 18 | 19 | See [documentation](https://docs.rs/jsonrpc-types) for details. 20 | 21 | ## License 22 | 23 | Licensed under either of 24 | 25 | - [Apache License, Version 2.0](LICENSE-APACHE) 26 | - [MIT License](LICENSE-MIT) 27 | 28 | at your option. 29 | 30 | ## Contribution 31 | 32 | Unless you explicitly state otherwise, any contribution intentionally submitted 33 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 34 | dual licensed as above, without any additional terms or conditions. 35 | -------------------------------------------------------------------------------- /types/src/error.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "std"))] 2 | use alloc::{ 3 | format, 4 | string::{String, ToString}, 5 | }; 6 | use core::fmt; 7 | 8 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 9 | use serde_json::Value; 10 | 11 | /// Represents JSON-RPC error code. 12 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 13 | pub enum ErrorCode { 14 | /// Invalid JSON was received by the server. 15 | /// An error occurred on the server while parsing the JSON text. 16 | ParseError, 17 | /// The JSON sent is not a valid Request object. 18 | InvalidRequest, 19 | /// The method does not exist / is not available. 20 | MethodNotFound, 21 | /// Invalid method parameter(s). 22 | InvalidParams, 23 | /// Internal JSON-RPC error. 24 | InternalError, 25 | /// Reserved for implementation-defined server-errors. 26 | ServerError(i64), 27 | } 28 | 29 | impl From for ErrorCode { 30 | fn from(code: i64) -> Self { 31 | match code { 32 | -32700 => ErrorCode::ParseError, 33 | -32600 => ErrorCode::InvalidRequest, 34 | -32601 => ErrorCode::MethodNotFound, 35 | -32602 => ErrorCode::InvalidParams, 36 | -32603 => ErrorCode::InternalError, 37 | code => ErrorCode::ServerError(code), 38 | } 39 | } 40 | } 41 | 42 | impl Serialize for ErrorCode { 43 | fn serialize(&self, serializer: S) -> Result 44 | where 45 | S: Serializer, 46 | { 47 | serializer.serialize_i64(self.code()) 48 | } 49 | } 50 | 51 | impl<'de> Deserialize<'de> for ErrorCode { 52 | fn deserialize(deserializer: D) -> Result 53 | where 54 | D: Deserializer<'de>, 55 | { 56 | let code: i64 = Deserialize::deserialize(deserializer)?; 57 | Ok(ErrorCode::from(code)) 58 | } 59 | } 60 | 61 | impl ErrorCode { 62 | /// Returns integer code value. 63 | pub fn code(&self) -> i64 { 64 | match self { 65 | ErrorCode::ParseError => -32700, 66 | ErrorCode::InvalidRequest => -32600, 67 | ErrorCode::MethodNotFound => -32601, 68 | ErrorCode::InvalidParams => -32602, 69 | ErrorCode::InternalError => -32603, 70 | ErrorCode::ServerError(code) => *code, 71 | } 72 | } 73 | 74 | /// Returns human-readable description. 75 | pub fn description(&self) -> String { 76 | let desc = match self { 77 | ErrorCode::ParseError => "Parse error", 78 | ErrorCode::InvalidRequest => "Invalid request", 79 | ErrorCode::MethodNotFound => "Method not found", 80 | ErrorCode::InvalidParams => "Invalid params", 81 | ErrorCode::InternalError => "Internal error", 82 | ErrorCode::ServerError(_) => "Server error", 83 | }; 84 | desc.into() 85 | } 86 | } 87 | 88 | /// Represents JSON-RPC error object. 89 | #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] 90 | #[serde(deny_unknown_fields)] 91 | pub struct Error { 92 | /// A Number that indicates the error type that occurred. 93 | /// This MUST be an integer. 94 | pub code: ErrorCode, 95 | /// A String providing a short description of the error. 96 | /// The message SHOULD be limited to a concise single sentence. 97 | pub message: String, 98 | /// A Primitive or Structured value that contains additional information about the error. 99 | /// This may be omitted. 100 | /// The value of this member is defined by the Server (e.g. detailed error information, nested errors etc.). 101 | #[serde(skip_serializing_if = "Option::is_none")] 102 | pub data: Option, 103 | } 104 | 105 | impl fmt::Display for Error { 106 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 107 | write!(f, "{}: {}", self.code.description(), self.message) 108 | } 109 | } 110 | 111 | #[cfg(feature = "std")] 112 | impl std::error::Error for Error {} 113 | 114 | impl Error { 115 | /// Wraps given `ErrorCode`. 116 | pub fn new(code: ErrorCode) -> Self { 117 | Error { 118 | message: code.description(), 119 | code, 120 | data: None, 121 | } 122 | } 123 | 124 | /// Creates a new `ParseError` error. 125 | pub fn parse_error() -> Self { 126 | Self::new(ErrorCode::ParseError) 127 | } 128 | 129 | /// Creates a new `InvalidRequest` error. 130 | pub fn invalid_request() -> Self { 131 | Self::new(ErrorCode::InvalidRequest) 132 | } 133 | 134 | /// Creates a new `MethodNotFound` error. 135 | pub fn method_not_found() -> Self { 136 | Self::new(ErrorCode::MethodNotFound) 137 | } 138 | 139 | /// Creates a new `InvalidParams` error with given message. 140 | pub fn invalid_params(message: M) -> Self 141 | where 142 | M: fmt::Display, 143 | { 144 | Error { 145 | code: ErrorCode::InvalidParams, 146 | message: format!("Invalid parameters: {}", message), 147 | data: None, 148 | } 149 | } 150 | 151 | /// Creates a new `InvalidParams` error with given message and details. 152 | pub fn invalid_params_with_details(message: M, details: D) -> Self 153 | where 154 | M: fmt::Display, 155 | D: fmt::Display, 156 | { 157 | Error { 158 | code: ErrorCode::InvalidParams, 159 | message: format!("Invalid parameters: {}", message), 160 | data: Some(Value::String(details.to_string())), 161 | } 162 | } 163 | 164 | /// Creates a new `InternalError` error. 165 | pub fn internal_error() -> Self { 166 | Self::new(ErrorCode::InternalError) 167 | } 168 | 169 | /// Creates a new `InvalidRequest` error with invalid version description. 170 | pub fn invalid_version() -> Self { 171 | Error { 172 | code: ErrorCode::InvalidRequest, 173 | message: "Unsupported JSON-RPC protocol version".into(), 174 | data: None, 175 | } 176 | } 177 | } 178 | 179 | #[cfg(test)] 180 | mod tests { 181 | use super::*; 182 | 183 | #[test] 184 | fn error_serialization() { 185 | assert_eq!( 186 | serde_json::to_string(&Error::parse_error()).unwrap(), 187 | r#"{"code":-32700,"message":"Parse error"}"# 188 | ); 189 | assert_eq!( 190 | serde_json::to_string(&Error::invalid_request()).unwrap(), 191 | r#"{"code":-32600,"message":"Invalid request"}"# 192 | ); 193 | assert_eq!( 194 | serde_json::to_string(&Error::method_not_found()).unwrap(), 195 | r#"{"code":-32601,"message":"Method not found"}"# 196 | ); 197 | assert_eq!( 198 | serde_json::to_string(&Error::invalid_params("unexpected params")).unwrap(), 199 | r#"{"code":-32602,"message":"Invalid parameters: unexpected params"}"# 200 | ); 201 | assert_eq!( 202 | serde_json::to_string(&Error::invalid_params_with_details("unexpected params", "details")).unwrap(), 203 | r#"{"code":-32602,"message":"Invalid parameters: unexpected params","data":"details"}"# 204 | ); 205 | assert_eq!( 206 | serde_json::to_string(&Error::internal_error()).unwrap(), 207 | r#"{"code":-32603,"message":"Internal error"}"# 208 | ); 209 | assert_eq!( 210 | serde_json::to_string(&Error::invalid_version()).unwrap(), 211 | r#"{"code":-32600,"message":"Unsupported JSON-RPC protocol version"}"# 212 | ); 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /types/src/id.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "std"))] 2 | use alloc::string::String; 3 | use core::fmt; 4 | 5 | use serde::{Deserialize, Serialize}; 6 | use serde_json::Value; 7 | 8 | /// Represents JSON-RPC request/response id. 9 | /// 10 | /// An identifier established by the Client that MUST contain a String, Number, 11 | /// or NULL value if included, If it is not included it is assumed to be a notification. 12 | /// The value SHOULD normally not be Null and Numbers SHOULD NOT contain fractional parts. 13 | /// 14 | /// The Server **MUST** reply with the same value in the Response object if included. 15 | /// This member is used to correlate the context between the two objects. 16 | #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] 17 | #[serde(deny_unknown_fields)] 18 | #[serde(untagged)] 19 | pub enum Id { 20 | /// Numeric id 21 | Num(u64), 22 | /// String id 23 | Str(String), 24 | } 25 | 26 | impl Id { 27 | /// If the `Id` is an Number, returns the associated number. Returns None 28 | /// otherwise. 29 | pub fn as_number(&self) -> Option<&u64> { 30 | match self { 31 | Self::Num(id) => Some(id), 32 | _ => None, 33 | } 34 | } 35 | 36 | /// If the `Id` is a String, returns the associated str. Returns None 37 | /// otherwise. 38 | pub fn as_str(&self) -> Option<&str> { 39 | match self { 40 | Self::Str(id) => Some(id), 41 | _ => None, 42 | } 43 | } 44 | } 45 | 46 | impl fmt::Display for Id { 47 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 48 | match self { 49 | Self::Num(id) => write!(f, "{}", id), 50 | Self::Str(id) => f.write_str(id), 51 | } 52 | } 53 | } 54 | 55 | impl From for Id { 56 | fn from(id: u64) -> Self { 57 | Self::Num(id) 58 | } 59 | } 60 | 61 | impl From for Id { 62 | fn from(id: String) -> Self { 63 | Self::Str(id) 64 | } 65 | } 66 | 67 | impl From for Value { 68 | fn from(id: Id) -> Self { 69 | match id { 70 | Id::Num(id) => Self::Number(id.into()), 71 | Id::Str(id) => Self::String(id), 72 | } 73 | } 74 | } 75 | 76 | #[cfg(test)] 77 | mod tests { 78 | use super::*; 79 | 80 | #[test] 81 | fn id_serialization() { 82 | let cases = vec![ 83 | (Id::Num(0), r#"0"#), 84 | (Id::Str("1".into()), r#""1""#), 85 | (Id::Str("test".into()), r#""test""#), 86 | ]; 87 | 88 | for (id, expect) in cases { 89 | assert_eq!(serde_json::to_string(&id).unwrap(), expect); 90 | assert_eq!(id, serde_json::from_str(expect).unwrap()); 91 | } 92 | 93 | assert_eq!( 94 | serde_json::to_string(&vec![Id::Num(0), Id::Str("1".into()), Id::Str("test".into()),]).unwrap(), 95 | r#"[0,"1","test"]"# 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /types/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A set of types for representing JSON-RPC requests and responses as defined in 2 | //! the [JSON-RPC 1.0 spec](https://www.jsonrpc.org/specification_v1) and 3 | //! [JSON-RPC 2.0 spec](https://www.jsonrpc.org/specification). 4 | //! 5 | //! # Usage 6 | #![cfg_attr( 7 | feature = "v1", 8 | doc = r##" 9 | ## Creates JSON-RPC 1.0 request 10 | 11 | ```rust 12 | use jsonrpc_types::v1::{Notification, Request, RequestObj}; 13 | 14 | // Creates a JSON-RPC 1.0 request call 15 | let request = Request::new("foo", vec![], 1.into()); 16 | let request = RequestObj::Single(request); 17 | assert_eq!( 18 | serde_json::to_string(&request).unwrap(), 19 | r#"{"method":"foo","params":[],"id":1}"# 20 | ); 21 | 22 | // Creates a JSON-RPC 1.0 notification 23 | let notification = Notification::new("foo", vec![]); 24 | assert_eq!( 25 | serde_json::to_string(¬ification).unwrap(), 26 | r#"{"method":"foo","params":[],"id":null}"# 27 | ); 28 | 29 | // Creates a JSON-RPC 1.0 batch request 30 | let request1 = Request::new("foo", vec![], 1.into()); 31 | let request2 = Request::new("bar", vec![], 2.into()); 32 | let batch_request = RequestObj::Batch(vec![request1, request2]); 33 | assert_eq!( 34 | serde_json::to_string(&batch_request).unwrap(), 35 | r#"[{"method":"foo","params":[],"id":1},{"method":"bar","params":[],"id":2}]"# 36 | ); 37 | ``` 38 | "## 39 | )] 40 | //! 41 | #![cfg_attr( 42 | feature = "v1", 43 | doc = r##" 44 | ## Creates JSON-RPC 1.0 response 45 | 46 | ```rust 47 | use jsonrpc_types::v1::{Value, Error, Response, ResponseObj}; 48 | 49 | // Creates a JSON-RPC 1.0 success response 50 | let success = Response::success(Value::Bool(true), 1.into()); 51 | let response = ResponseObj::Single(success); 52 | assert_eq!( 53 | serde_json::to_string(&response).unwrap(), 54 | r#"{"result":true,"error":null,"id":1}"# 55 | ); 56 | 57 | // Creates a JSON-RPC 1.0 failure response 58 | let failure = Response::::failure(Error::invalid_request(), None); 59 | let response = ResponseObj::Single(failure); 60 | assert_eq!( 61 | serde_json::to_string(&response).unwrap(), 62 | r#"{"result":null,"error":{"code":-32600,"message":"Invalid request"},"id":null}"# 63 | ); 64 | 65 | // Creates a JSON-RPC 1.0 batch response 66 | let success1 = Response::success(Value::Bool(true), 1.into()); 67 | let success2 = Response::success(Value::Bool(false), 2.into()); 68 | let batch_response = ResponseObj::Batch(vec![success1, success2]); 69 | assert_eq!( 70 | serde_json::to_string(&batch_response).unwrap(), 71 | r#"[{"result":true,"error":null,"id":1},{"result":false,"error":null,"id":2}]"# 72 | ); 73 | ``` 74 | "## 75 | )] 76 | //! 77 | #![cfg_attr( 78 | feature = "v2", 79 | doc = r##" 80 | ## Creates JSON-RPC 2.0 request 81 | 82 | ```rust 83 | use jsonrpc_types::v2::{Params, Request, RequestObj, Notification}; 84 | 85 | // Creates a JSON-RPC 2.0 request call 86 | let request = Request::new("foo", Some(Params::Array(vec![])), 1.into()); 87 | let request = RequestObj::Single(request); 88 | assert_eq!( 89 | serde_json::to_string(&request).unwrap(), 90 | r#"{"jsonrpc":"2.0","method":"foo","params":[],"id":1}"# 91 | ); 92 | 93 | // Creates a JSON-RPC 2.0 notification 94 | let notification = Notification::new("foo", Some(Params::Array(vec![]))); 95 | assert_eq!( 96 | serde_json::to_string(¬ification).unwrap(), 97 | r#"{"jsonrpc":"2.0","method":"foo","params":[]}"# 98 | ); 99 | 100 | // Creates a JSON-RPC 2.0 batch request 101 | let request1 = Request::new("foo", Some(Params::Array(vec![])), 1.into()); 102 | let request2 = Request::new("bar", Some(Params::Array(vec![])), 2.into()); 103 | let batch_request = RequestObj::Batch(vec![request1, request2]); 104 | assert_eq!( 105 | serde_json::to_string(&batch_request).unwrap(), 106 | r#"[{"jsonrpc":"2.0","method":"foo","params":[],"id":1},{"jsonrpc":"2.0","method":"bar","params":[],"id":2}]"# 107 | ); 108 | ``` 109 | "## 110 | )] 111 | //! 112 | #![cfg_attr( 113 | feature = "v2", 114 | doc = r##" 115 | ## Creates JSON-RPC 2.0 response 116 | 117 | ```rust 118 | use jsonrpc_types::v2::{Value, Error, Success, Failure, Response, ResponseObj}; 119 | 120 | // Creates a JSON-RPC 2.0 success response 121 | let success = Response::success(Value::Bool(true), 1.into()); 122 | let response = ResponseObj::Single(success); 123 | assert_eq!( 124 | serde_json::to_string(&response).unwrap(), 125 | r#"{"jsonrpc":"2.0","result":true,"id":1}"# 126 | ); 127 | 128 | // Creates a JSON-RPC 2.0 failure response 129 | let failure = Response::::failure(Error::invalid_request(), None); 130 | let response = ResponseObj::Single(failure); 131 | assert_eq!( 132 | serde_json::to_string(&response).unwrap(), 133 | r#"{"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid request"},"id":null}"# 134 | ); 135 | 136 | // Creates a JSON-RPC 2.0 batch response 137 | let success1 = Response::success(Value::Bool(true), 1.into()); 138 | let success2 = Response::success(Value::Bool(false), 2.into()); 139 | let batch_response = ResponseObj::Batch(vec![success1, success2]); 140 | assert_eq!( 141 | serde_json::to_string(&batch_response).unwrap(), 142 | r#"[{"jsonrpc":"2.0","result":true,"id":1},{"jsonrpc":"2.0","result":false,"id":2}]"# 143 | ); 144 | ``` 145 | "## 146 | )] 147 | //! 148 | //! # Crate features 149 | //! 150 | //! **std** and **v2** features are enabled by default. 151 | //! 152 | //! ## Ecosystem features 153 | //! 154 | //! * **std** - 155 | //! When enabled, this crate will use the standard library. 156 | //! Currently, disabling this feature will always use `alloc` library. 157 | //! 158 | //! ## JSON-RPC version features 159 | //! 160 | //! * **v1** - 161 | //! Provide the JSON-RPC 1.0 types. 162 | //! * **v2** - 163 | //! Provide the JSON-RPC 2.0 types. 164 | 165 | #![deny(unused_imports)] 166 | #![deny(missing_docs)] 167 | #![cfg_attr(not(feature = "std"), no_std)] 168 | #![cfg_attr(doc_cfg, feature(doc_cfg))] 169 | 170 | #[cfg(not(feature = "std"))] 171 | extern crate alloc; 172 | 173 | /// JSON-RPC 1.0 types. 174 | #[cfg(feature = "v1")] 175 | #[cfg_attr(doc_cfg, doc(cfg(feature = "v1")))] 176 | pub mod v1; 177 | /// JSON-RPC 2.0 types. 178 | #[cfg(feature = "v2")] 179 | #[cfg_attr(doc_cfg, doc(cfg(feature = "v2")))] 180 | pub mod v2; 181 | 182 | #[cfg(any(feature = "v1", feature = "v2"))] 183 | mod error; 184 | #[cfg(any(feature = "v1", feature = "v2"))] 185 | mod id; 186 | -------------------------------------------------------------------------------- /types/src/v1/error.rs: -------------------------------------------------------------------------------- 1 | pub use crate::error::{Error, ErrorCode}; 2 | -------------------------------------------------------------------------------- /types/src/v1/mod.rs: -------------------------------------------------------------------------------- 1 | /// JSON-RPC 1.0 error objects. 2 | pub mod error; 3 | /// JSON-RPC 1.0 notification. 4 | pub mod notification; 5 | /// JSON-RPC 1.0 request/notification parameters. 6 | pub mod params; 7 | /// JSON-RPC 1.0 request objects. 8 | pub mod request; 9 | /// JSON-RPC 1.0 response objects. 10 | pub mod response; 11 | 12 | // Re-exports 13 | pub use serde_json::Value; 14 | 15 | pub use self::{ 16 | error::{Error, ErrorCode}, 17 | notification::{BatchNotification, BatchNotificationRef, Notification, NotificationRef}, 18 | params::{Id, Params, ParamsRef}, 19 | request::{BatchRequest, BatchRequestRef, Request, RequestObj, RequestRef, RequestRefObj}, 20 | response::{BatchResponse, Response, ResponseObj}, 21 | }; 22 | -------------------------------------------------------------------------------- /types/src/v1/notification.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "std"))] 2 | use alloc::{string::String, vec::Vec}; 3 | use core::{fmt, marker::PhantomData}; 4 | 5 | use serde::{de, ser}; 6 | 7 | use crate::v1::{Id, Params, ParamsRef}; 8 | 9 | /// Represents JSON-RPC 1.0 batch notification. 10 | pub type BatchNotificationRef<'a> = Vec>; 11 | 12 | /// Represents JSON-RPC 1.0 request which is a notification. 13 | /// 14 | /// A Request object that is a Notification signifies the Client's lack of interest in the 15 | /// corresponding Response object, and as such no Response object needs to be returned to the client. 16 | /// As such, the Client would not be aware of any errors (like e.g. "Invalid params","Internal error"). 17 | /// 18 | /// The Server MUST NOT reply to a Notification, including those that are within a batch request. 19 | /// 20 | /// For JSON-RPC 1.0 specification, notification id **MUST** be Null. 21 | #[derive(Debug, PartialEq)] 22 | pub struct NotificationRef<'a> { 23 | /// A String containing the name of the method to be invoked. 24 | /// 25 | /// Method names that begin with the word rpc followed by a period character (U+002E or ASCII 46) 26 | /// are reserved for rpc-internal methods and extensions and MUST NOT be used for anything else. 27 | pub method: &'a str, 28 | /// A Structured value that holds the parameter values to be used 29 | /// during the invocation of the method. 30 | pub params: ParamsRef<'a>, 31 | } 32 | 33 | impl<'a> fmt::Display for NotificationRef<'a> { 34 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 35 | let json = serde_json::to_string(self).expect("`Notification` is serializable"); 36 | write!(f, "{}", json) 37 | } 38 | } 39 | 40 | impl<'a> PartialEq for NotificationRef<'a> { 41 | fn eq(&self, other: &Notification) -> bool { 42 | self.method.eq(&other.method) && self.params.eq(&other.params) 43 | } 44 | } 45 | 46 | impl<'a> ser::Serialize for NotificationRef<'a> { 47 | fn serialize(&self, serializer: S) -> Result 48 | where 49 | S: ser::Serializer, 50 | { 51 | let mut state = ser::Serializer::serialize_struct(serializer, "Notification", 3)?; 52 | ser::SerializeStruct::serialize_field(&mut state, "method", &self.method)?; 53 | ser::SerializeStruct::serialize_field(&mut state, "params", &self.params)?; 54 | ser::SerializeStruct::serialize_field(&mut state, "id", &Option::::None)?; 55 | ser::SerializeStruct::end(state) 56 | } 57 | } 58 | 59 | impl<'a> NotificationRef<'a> { 60 | /// Creates a JSON-RPC 1.0 request which is a notification. 61 | pub fn new(method: &'a str, params: ParamsRef<'a>) -> Self { 62 | Self { method, params } 63 | } 64 | 65 | /// Converts the reference into the owned type. 66 | pub fn to_owned(&self) -> Notification { 67 | Notification { 68 | method: self.method.into(), 69 | params: self.params.to_vec(), 70 | } 71 | } 72 | } 73 | 74 | // ################################################################################################ 75 | 76 | /// Represents JSON-RPC 1.0 batch notification. 77 | pub type BatchNotification = Vec; 78 | 79 | /// Represents JSON-RPC 1.0 request which is a notification. 80 | /// 81 | /// A Request object that is a Notification signifies the Client's lack of interest in the 82 | /// corresponding Response object, and as such no Response object needs to be returned to the client. 83 | /// As such, the Client would not be aware of any errors (like e.g. "Invalid params","Internal error"). 84 | /// 85 | /// The Server MUST NOT reply to a Notification, including those that are within a batch request. 86 | /// 87 | /// For JSON-RPC 1.0 specification, notification id **MUST** be Null. 88 | #[derive(Clone, Debug, PartialEq)] 89 | pub struct Notification { 90 | /// A String containing the name of the method to be invoked. 91 | /// 92 | /// Method names that begin with the word rpc followed by a period character (U+002E or ASCII 46) 93 | /// are reserved for rpc-internal methods and extensions and MUST NOT be used for anything else. 94 | pub method: String, 95 | /// A Structured value that holds the parameter values to be used 96 | /// during the invocation of the method. 97 | pub params: Params, 98 | } 99 | 100 | impl fmt::Display for Notification { 101 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 102 | let json = serde_json::to_string(self).expect("`Notification` is serializable"); 103 | write!(f, "{}", json) 104 | } 105 | } 106 | 107 | impl<'a> PartialEq> for Notification { 108 | fn eq(&self, other: &NotificationRef<'a>) -> bool { 109 | self.method.eq(other.method) && self.params.eq(other.params) 110 | } 111 | } 112 | 113 | impl ser::Serialize for Notification { 114 | fn serialize(&self, serializer: S) -> Result 115 | where 116 | S: ser::Serializer, 117 | { 118 | let mut state = ser::Serializer::serialize_struct(serializer, "Notification", 3)?; 119 | ser::SerializeStruct::serialize_field(&mut state, "method", &self.method)?; 120 | ser::SerializeStruct::serialize_field(&mut state, "params", &self.params)?; 121 | ser::SerializeStruct::serialize_field(&mut state, "id", &Option::::None)?; 122 | ser::SerializeStruct::end(state) 123 | } 124 | } 125 | 126 | impl<'de> de::Deserialize<'de> for Notification { 127 | fn deserialize(deserializer: D) -> Result 128 | where 129 | D: de::Deserializer<'de>, 130 | { 131 | use self::request_field::{Field, FIELDS}; 132 | 133 | struct Visitor<'de> { 134 | marker: PhantomData, 135 | lifetime: PhantomData<&'de ()>, 136 | } 137 | impl<'de> de::Visitor<'de> for Visitor<'de> { 138 | type Value = Notification; 139 | 140 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 141 | formatter.write_str("struct Notification") 142 | } 143 | 144 | fn visit_map(self, mut map: A) -> Result 145 | where 146 | A: de::MapAccess<'de>, 147 | { 148 | let mut method = Option::::None; 149 | let mut params = Option::::None; 150 | let mut id = Option::>::None; 151 | 152 | while let Some(key) = de::MapAccess::next_key::(&mut map)? { 153 | match key { 154 | Field::Method => { 155 | if method.is_some() { 156 | return Err(de::Error::duplicate_field("method")); 157 | } 158 | method = Some(de::MapAccess::next_value::(&mut map)?) 159 | } 160 | Field::Params => { 161 | if params.is_some() { 162 | return Err(de::Error::duplicate_field("params")); 163 | } 164 | params = Some(de::MapAccess::next_value::(&mut map)?) 165 | } 166 | Field::Id => { 167 | if id.is_some() { 168 | return Err(de::Error::duplicate_field("id")); 169 | } 170 | id = Some(de::MapAccess::next_value::>(&mut map)?) 171 | } 172 | } 173 | } 174 | 175 | let method = method.ok_or_else(|| de::Error::missing_field("method"))?; 176 | let params = params.ok_or_else(|| de::Error::missing_field("params"))?; 177 | let id = id.ok_or_else(|| de::Error::missing_field("id"))?; 178 | if id.is_some() { 179 | return Err(de::Error::custom("JSON-RPC 1.0 notification id MUST be Null")); 180 | } 181 | Ok(Notification { method, params }) 182 | } 183 | } 184 | 185 | de::Deserializer::deserialize_struct( 186 | deserializer, 187 | "Notification", 188 | FIELDS, 189 | Visitor { 190 | marker: PhantomData::, 191 | lifetime: PhantomData, 192 | }, 193 | ) 194 | } 195 | } 196 | 197 | impl Notification { 198 | /// Creates a JSON-RPC 1.0 request which is a notification. 199 | pub fn new(method: impl Into, params: impl Into) -> Self { 200 | Self { 201 | method: method.into(), 202 | params: params.into(), 203 | } 204 | } 205 | 206 | /// Borrows from an owned value. 207 | pub fn as_ref(&self) -> NotificationRef<'_> { 208 | NotificationRef { 209 | method: &self.method, 210 | params: &self.params, 211 | } 212 | } 213 | } 214 | 215 | mod request_field { 216 | use super::*; 217 | 218 | pub const FIELDS: &[&str] = &["method", "params", "id"]; 219 | pub enum Field { 220 | Method, 221 | Params, 222 | Id, 223 | } 224 | 225 | impl<'de> de::Deserialize<'de> for Field { 226 | fn deserialize(deserializer: D) -> Result 227 | where 228 | D: de::Deserializer<'de>, 229 | { 230 | de::Deserializer::deserialize_identifier(deserializer, FieldVisitor) 231 | } 232 | } 233 | 234 | struct FieldVisitor; 235 | impl<'de> de::Visitor<'de> for FieldVisitor { 236 | type Value = Field; 237 | 238 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 239 | formatter.write_str("field identifier") 240 | } 241 | 242 | fn visit_str(self, v: &str) -> Result 243 | where 244 | E: de::Error, 245 | { 246 | match v { 247 | "method" => Ok(Field::Method), 248 | "params" => Ok(Field::Params), 249 | "id" => Ok(Field::Id), 250 | _ => Err(de::Error::unknown_field(v, &FIELDS)), 251 | } 252 | } 253 | } 254 | } 255 | 256 | #[cfg(test)] 257 | mod tests { 258 | use serde_json::Value; 259 | 260 | use super::*; 261 | 262 | fn notification_cases() -> Vec<(Notification, &'static str)> { 263 | vec![ 264 | ( 265 | // JSON-RPC 1.0 request notification 266 | Notification::new("foo", vec![Value::from(1), Value::Bool(true)]), 267 | r#"{"method":"foo","params":[1,true],"id":null}"#, 268 | ), 269 | ( 270 | // JSON-RPC 1.0 request notification without parameters 271 | Notification::new("foo", vec![]), 272 | r#"{"method":"foo","params":[],"id":null}"#, 273 | ), 274 | ] 275 | } 276 | 277 | #[test] 278 | fn notification_serialization() { 279 | for (notification, expect) in notification_cases() { 280 | assert_eq!(serde_json::to_string(¬ification.as_ref()).unwrap(), expect); 281 | assert_eq!(serde_json::to_string(¬ification).unwrap(), expect); 282 | 283 | assert_eq!(serde_json::from_str::(expect).unwrap(), notification); 284 | } 285 | 286 | // JSON-RPC 1.0 valid notification 287 | let valid_cases = vec![ 288 | r#"{"method":"foo","params":[1,true],"id":null}"#, 289 | r#"{"method":"foo","params":[],"id":null}"#, 290 | ]; 291 | for case in valid_cases { 292 | assert!(serde_json::from_str::(case).is_ok()); 293 | } 294 | 295 | // JSON-RPC 1.0 invalid notification 296 | let invalid_cases = vec![ 297 | r#"{"method":"foo","params":[1,true],"id":1,"unknown":[]}"#, 298 | r#"{"method":"foo","params":[1,true],"id":1.2}"#, 299 | r#"{"method":"foo","params":[1,true],"id":null,"unknown":[]}"#, 300 | r#"{"method":"foo","params":[1,true],"unknown":[]}"#, 301 | r#"{"method":"foo","params":[1,true]}"#, 302 | r#"{"method":"foo","unknown":[]}"#, 303 | r#"{"method":1,"unknown":[]}"#, 304 | r#"{"unknown":[]}"#, 305 | ]; 306 | for case in invalid_cases { 307 | assert!(serde_json::from_str::(case).is_err()); 308 | } 309 | } 310 | 311 | #[test] 312 | fn batch_notification_serialization() { 313 | let batch_notification = vec![Notification::new("foo", vec![]), Notification::new("bar", vec![])]; 314 | let batch_notification_ref = batch_notification.iter().map(|n| n.as_ref()).collect::>(); 315 | let batch_expect = r#"[{"method":"foo","params":[],"id":null},{"method":"bar","params":[],"id":null}]"#; 316 | 317 | assert_eq!(serde_json::to_string(&batch_notification).unwrap(), batch_expect); 318 | assert_eq!(serde_json::to_string(&batch_notification_ref).unwrap(), batch_expect); 319 | 320 | assert_eq!( 321 | serde_json::from_str::(&batch_expect).unwrap(), 322 | batch_notification, 323 | ); 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /types/src/v1/params.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "std"))] 2 | use alloc::vec::Vec; 3 | 4 | use serde_json::Value; 5 | 6 | /// JSON-RPC 1.0 id object. 7 | pub use crate::id::Id; 8 | 9 | /// Represents JSON-RPC 1.0 request parameters. 10 | pub type Params = Vec; 11 | 12 | /// Represents JSON-RPC 1.0 request parameters. 13 | pub type ParamsRef<'a> = &'a [Value]; 14 | -------------------------------------------------------------------------------- /types/src/v1/request.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "std"))] 2 | use alloc::{string::String, vec::Vec}; 3 | use core::fmt; 4 | 5 | use serde::{Deserialize, Serialize}; 6 | 7 | use crate::v1::{Id, Params, ParamsRef}; 8 | 9 | /// JSON-RPC 2.0 Request Object. 10 | #[derive(Debug, PartialEq, Serialize)] 11 | #[serde(deny_unknown_fields)] 12 | #[serde(untagged)] 13 | pub enum RequestRefObj<'a> { 14 | /// Single request call 15 | Single(RequestRef<'a>), 16 | /// Batch of request calls 17 | Batch(BatchRequestRef<'a>), 18 | } 19 | 20 | impl<'a> fmt::Display for RequestRefObj<'a> { 21 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 22 | let json = serde_json::to_string(self).expect("`RequestRefObj` is serializable"); 23 | write!(f, "{}", json) 24 | } 25 | } 26 | 27 | impl<'a> PartialEq for RequestRefObj<'a> { 28 | fn eq(&self, other: &RequestObj) -> bool { 29 | match (self, other) { 30 | (Self::Single(req1), RequestObj::Single(req2)) => req1.eq(req2), 31 | (Self::Batch(req1), RequestObj::Batch(req2)) => req1.eq(req2), 32 | _ => false, 33 | } 34 | } 35 | } 36 | 37 | /// Represents JSON-RPC 1.0 batch request call. 38 | pub type BatchRequestRef<'a> = Vec>; 39 | 40 | /// Represents JSON-RPC 1.0 request call. 41 | #[derive(Debug, PartialEq, Serialize)] 42 | #[serde(deny_unknown_fields)] 43 | pub struct RequestRef<'a> { 44 | /// A String containing the name of the method to be invoked. 45 | /// 46 | /// Method names that begin with the word rpc followed by a period character (U+002E or ASCII 46) 47 | /// are reserved for rpc-internal methods and extensions and MUST NOT be used for anything else. 48 | pub method: &'a str, 49 | /// A Structured value that holds the parameter values to be used 50 | /// during the invocation of the method. This member MAY be omitted. 51 | pub params: ParamsRef<'a>, 52 | /// An identifier established by the Client. 53 | /// If it is not included it is assumed to be a notification. 54 | pub id: Id, 55 | } 56 | 57 | impl<'a> fmt::Display for RequestRef<'a> { 58 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 59 | let json = serde_json::to_string(self).expect("`RequestRef` is serializable"); 60 | write!(f, "{}", json) 61 | } 62 | } 63 | 64 | impl<'a> PartialEq for RequestRef<'a> { 65 | fn eq(&self, other: &Request) -> bool { 66 | self.method.eq(&other.method) && self.params.eq(&other.params) && self.id.eq(&other.id) 67 | } 68 | } 69 | 70 | impl<'a> RequestRef<'a> { 71 | /// Creates a JSON-RPC 1.0 request which is a call. 72 | pub fn new(method: &'a str, params: ParamsRef<'a>, id: Id) -> Self { 73 | Self { method, params, id } 74 | } 75 | 76 | /// Converts the reference into the owned type. 77 | pub fn to_owned(&self) -> Request { 78 | Request { 79 | method: self.method.into(), 80 | params: self.params.to_vec(), 81 | id: self.id.clone(), 82 | } 83 | } 84 | } 85 | 86 | // ################################################################################################ 87 | 88 | /// JSON-RPC 2.0 Request Object. 89 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 90 | #[serde(deny_unknown_fields)] 91 | #[serde(untagged)] 92 | pub enum RequestObj { 93 | /// Single request call 94 | Single(Request), 95 | /// Batch of request calls 96 | Batch(BatchRequest), 97 | } 98 | 99 | impl fmt::Display for RequestObj { 100 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 101 | let json = serde_json::to_string(self).expect("`RequestObj` is serializable"); 102 | write!(f, "{}", json) 103 | } 104 | } 105 | 106 | impl<'a> PartialEq> for RequestObj { 107 | fn eq(&self, other: &RequestRefObj<'a>) -> bool { 108 | match (self, other) { 109 | (Self::Single(req1), RequestRefObj::Single(req2)) => req1.eq(req2), 110 | (Self::Batch(req1), RequestRefObj::Batch(req2)) => req1.eq(req2), 111 | _ => false, 112 | } 113 | } 114 | } 115 | 116 | /// Represents JSON-RPC 1.0 batch request call. 117 | pub type BatchRequest = Vec; 118 | 119 | /// Represents JSON-RPC 1.0 request call. 120 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 121 | #[serde(deny_unknown_fields)] 122 | pub struct Request { 123 | /// A String containing the name of the method to be invoked. 124 | /// 125 | /// Method names that begin with the word rpc followed by a period character (U+002E or ASCII 46) 126 | /// are reserved for rpc-internal methods and extensions and MUST NOT be used for anything else. 127 | pub method: String, 128 | /// A Structured value that holds the parameter values to be used 129 | /// during the invocation of the method. This member MAY be omitted. 130 | pub params: Params, 131 | /// An identifier established by the Client. 132 | /// If it is not included it is assumed to be a notification. 133 | pub id: Id, 134 | } 135 | 136 | impl fmt::Display for Request { 137 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 138 | let json = serde_json::to_string(self).expect("`Request` is serializable"); 139 | write!(f, "{}", json) 140 | } 141 | } 142 | 143 | impl<'a> PartialEq> for Request { 144 | fn eq(&self, other: &RequestRef<'a>) -> bool { 145 | self.method.eq(other.method) && self.params.eq(other.params) && self.id.eq(&other.id) 146 | } 147 | } 148 | 149 | impl Request { 150 | /// Creates a JSON-RPC 1.0 request which is a call. 151 | pub fn new(method: impl Into, params: impl Into, id: Id) -> Self { 152 | Self { 153 | method: method.into(), 154 | params: params.into(), 155 | id, 156 | } 157 | } 158 | 159 | /// Borrows from an owned value. 160 | pub fn as_ref(&self) -> RequestRef<'_> { 161 | RequestRef { 162 | method: &self.method, 163 | params: &self.params, 164 | id: self.id.clone(), 165 | } 166 | } 167 | } 168 | 169 | #[cfg(test)] 170 | mod tests { 171 | use serde_json::Value; 172 | 173 | use super::*; 174 | 175 | fn request_cases() -> Vec<(Request, &'static str)> { 176 | vec![ 177 | ( 178 | // JSON-RPC 1.0 request 179 | Request::new("foo", vec![Value::from(1), Value::Bool(true)], Id::Num(1)), 180 | r#"{"method":"foo","params":[1,true],"id":1}"#, 181 | ), 182 | ( 183 | // JSON-RPC 1.0 request without parameters 184 | Request::new("foo", vec![], Id::Num(1)), 185 | r#"{"method":"foo","params":[],"id":1}"#, 186 | ), 187 | ] 188 | } 189 | 190 | #[test] 191 | fn request_serialization() { 192 | for (request, expect) in request_cases() { 193 | let request_obj = RequestObj::Single(request.clone()); 194 | let request_ref = RequestRefObj::Single(request.as_ref()); 195 | 196 | assert_eq!(serde_json::to_string(&request).unwrap(), expect); 197 | assert_eq!(serde_json::to_string(&request_obj).unwrap(), expect); 198 | assert_eq!(serde_json::to_string(&request_ref).unwrap(), expect); 199 | 200 | assert_eq!(serde_json::from_str::(expect).unwrap(), request); 201 | assert_eq!(serde_json::from_str::(expect).unwrap(), request_obj); 202 | } 203 | 204 | // JSON-RPC 1.0 valid request 205 | let valid_cases = vec![ 206 | r#"{"method":"foo","params":[1,true],"id":1}"#, 207 | r#"{"method":"foo","params":[],"id":1}"#, 208 | ]; 209 | for case in valid_cases { 210 | assert!(serde_json::from_str::(case).is_ok()); 211 | assert!(serde_json::from_str::(case).is_ok()); 212 | } 213 | 214 | // JSON-RPC 1.0 invalid request 215 | let invalid_cases = vec![ 216 | r#"{"method":"foo","params":[1,true],"id":1,"unknown":[]}"#, 217 | r#"{"method":"foo","params":[1,true],"id":1.2}"#, 218 | r#"{"method":"foo","params":[1,true],"id":null,"unknown":[]}"#, 219 | r#"{"method":"foo","params":[1,true],"unknown":[]}"#, 220 | r#"{"method":"foo","params":[1,true]}"#, 221 | r#"{"method":"foo","unknown":[]}"#, 222 | r#"{"method":1,"unknown":[]}"#, 223 | r#"{"unknown":[]}"#, 224 | ]; 225 | for case in invalid_cases { 226 | assert!(serde_json::from_str::(case).is_err()); 227 | assert!(serde_json::from_str::(case).is_err()); 228 | } 229 | } 230 | 231 | #[test] 232 | fn batch_request_serialization() { 233 | let batch_request = vec![ 234 | Request::new("foo", vec![], Id::Num(1)), 235 | Request::new("bar", vec![], Id::Num(2)), 236 | ]; 237 | let batch_request_obj = RequestObj::Batch(batch_request.clone()); 238 | let batch_request_ref = RequestRefObj::Batch(batch_request.iter().map(|req| req.as_ref()).collect::>()); 239 | let batch_expect = r#"[{"method":"foo","params":[],"id":1},{"method":"bar","params":[],"id":2}]"#; 240 | 241 | assert_eq!(serde_json::to_string(&batch_request).unwrap(), batch_expect); 242 | assert_eq!(serde_json::to_string(&batch_request_obj).unwrap(), batch_expect); 243 | assert_eq!(serde_json::to_string(&batch_request_ref).unwrap(), batch_expect); 244 | 245 | assert_eq!( 246 | serde_json::from_str::(&batch_expect).unwrap(), 247 | batch_request 248 | ); 249 | assert_eq!( 250 | serde_json::from_str::(&batch_expect).unwrap(), 251 | batch_request_obj 252 | ); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /types/src/v1/response.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "std"))] 2 | use alloc::vec::Vec; 3 | use core::{fmt, marker::PhantomData}; 4 | 5 | use serde::{ 6 | de::{self, DeserializeOwned}, 7 | Deserialize, Serialize, 8 | }; 9 | use serde_json::Value; 10 | 11 | use crate::v1::{Error, ErrorCode, Id}; 12 | 13 | /// JSON-RPC 1.0 Response Object. 14 | #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] 15 | #[serde(deny_unknown_fields)] 16 | #[serde(untagged)] 17 | pub enum ResponseObj { 18 | /// Single response 19 | Single(Response), 20 | /// Batch of responses (response to batch request) 21 | Batch(BatchResponse), 22 | } 23 | 24 | impl fmt::Display for ResponseObj { 25 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 26 | let json = serde_json::to_string(self).expect("`ResponseObj` is serializable"); 27 | write!(f, "{}", json) 28 | } 29 | } 30 | 31 | impl From> for ResponseObj { 32 | fn from(response: Response) -> Self { 33 | Self::Single(response) 34 | } 35 | } 36 | 37 | impl From> for ResponseObj { 38 | fn from(batch: BatchResponse) -> Self { 39 | Self::Batch(batch) 40 | } 41 | } 42 | 43 | /// Represents JSON-RPC 1.0 batch response. 44 | pub type BatchResponse = Vec>; 45 | 46 | /// Represents JSON-RPC 1.0 success / failure response. 47 | #[derive(Clone, Debug, Eq, PartialEq, Serialize)] 48 | #[serde(deny_unknown_fields)] 49 | pub struct Response { 50 | /// Successful execution result. 51 | pub result: Option, 52 | /// Failed execution error. 53 | pub error: Option, 54 | /// Correlation id. 55 | /// 56 | /// It **MUST** be the same as the value of the id member in the Request Object. 57 | /// 58 | /// If there was an error in detecting the id in the Request object (e.g. Parse error/Invalid Request), 59 | /// it **MUST** be Null. 60 | pub id: Option, 61 | } 62 | 63 | impl fmt::Display for Response { 64 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 65 | let json = serde_json::to_string(self).expect("`Response` is serializable"); 66 | write!(f, "{}", json) 67 | } 68 | } 69 | 70 | impl<'de, T: Deserialize<'de>> de::Deserialize<'de> for Response { 71 | fn deserialize(deserializer: D) -> Result 72 | where 73 | D: de::Deserializer<'de>, 74 | { 75 | use self::response_field::{Field, FIELDS}; 76 | 77 | struct Visitor<'de, T> { 78 | marker: PhantomData>, 79 | lifetime: PhantomData<&'de ()>, 80 | } 81 | impl<'de, T: Deserialize<'de>> de::Visitor<'de> for Visitor<'de, T> { 82 | type Value = Response; 83 | 84 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 85 | formatter.write_str("struct Response") 86 | } 87 | 88 | fn visit_map(self, mut map: A) -> Result 89 | where 90 | A: de::MapAccess<'de>, 91 | { 92 | let mut result = Option::>::None; 93 | let mut error = Option::>::None; 94 | let mut id = Option::>::None; 95 | 96 | while let Some(key) = de::MapAccess::next_key::(&mut map)? { 97 | match key { 98 | Field::Result => { 99 | if result.is_some() { 100 | return Err(de::Error::duplicate_field("result")); 101 | } 102 | result = Some(de::MapAccess::next_value::>(&mut map)?) 103 | } 104 | Field::Error => { 105 | if error.is_some() { 106 | return Err(de::Error::duplicate_field("error")); 107 | } 108 | error = Some(de::MapAccess::next_value::>(&mut map)?) 109 | } 110 | Field::Id => { 111 | if id.is_some() { 112 | return Err(de::Error::duplicate_field("id")); 113 | } 114 | id = Some(de::MapAccess::next_value::>(&mut map)?) 115 | } 116 | } 117 | } 118 | 119 | let result = result.ok_or_else(|| de::Error::missing_field("result"))?; 120 | let error = error.ok_or_else(|| de::Error::missing_field("error"))?; 121 | let id = id.ok_or_else(|| de::Error::missing_field("id"))?; 122 | let (result, error, id) = match (result, error, id) { 123 | (Some(value), None, Some(id)) => (Some(value), None, Some(id)), 124 | (None, Some(error), id) => (None, Some(error), id), 125 | _ => return Err(de::Error::custom("Invalid JSON-RPC 1.0 response")), 126 | }; 127 | Ok(Response { result, error, id }) 128 | } 129 | } 130 | 131 | de::Deserializer::deserialize_struct( 132 | deserializer, 133 | "Response", 134 | FIELDS, 135 | Visitor { 136 | marker: PhantomData::>, 137 | lifetime: PhantomData, 138 | }, 139 | ) 140 | } 141 | } 142 | 143 | impl Response { 144 | /// Creates a JSON-RPC 1.0 success response. 145 | pub fn success(result: T, id: Id) -> Self { 146 | Self { 147 | result: Some(result), 148 | error: None, 149 | id: Some(id), 150 | } 151 | } 152 | 153 | /// Creates a JSON-RPC 1.0 failure response. 154 | pub fn failure(error: Error, id: Option) -> Self { 155 | Self { 156 | result: None, 157 | error: Some(error), 158 | id, 159 | } 160 | } 161 | 162 | /// Creates a JSON-RPC 1.0 failure response, indicating that the server has an error in parsing the JSON text. 163 | pub fn parse_error(id: Option) -> Self { 164 | Self::failure(Error::parse_error(), id) 165 | } 166 | 167 | /// Creates a JSON-RPC 1.0 failure response, indicating malformed request. 168 | pub fn invalid_request(id: Option) -> Self { 169 | Self::failure(Error::invalid_request(), id) 170 | } 171 | 172 | /// Creates a JSON-RPC 1.0 failure response, indicating that the request's method is not found. 173 | pub fn method_not_found(id: Id) -> Self { 174 | Self::failure(Error::method_not_found(), Some(id)) 175 | } 176 | 177 | /// Creates a JSON-RPC 1.0 failure response, indicating that the request's parameters is invalid. 178 | pub fn invalid_params(id: Id, msg: impl fmt::Display) -> Self { 179 | Self::failure(Error::invalid_params(msg), Some(id)) 180 | } 181 | 182 | /// Creates a JSON-RPC 1.0 failure response, indicating that the internal JSON-RPC error. 183 | pub fn internal_error(id: Id) -> Self { 184 | Self::failure(Error::internal_error(), Some(id)) 185 | } 186 | 187 | /// Creates a JSON-RPC 1.0 failure response, indicating that implementation-defined server error. 188 | pub fn server_error(id: Id, error: i64) -> Self { 189 | Self::failure(Error::new(ErrorCode::ServerError(error)), Some(id)) 190 | } 191 | } 192 | 193 | impl From> for Result { 194 | // Convert into a result. 195 | // Will be `Ok` if it is a `SuccessResponse` and `Err` if `FailureResponse`. 196 | fn from(resp: Response) -> Result { 197 | match (resp.result, resp.error) { 198 | (Some(result), None) => Ok(result), 199 | (None, Some(error)) => Err(error), 200 | _ => unreachable!("Invalid JSON-RPC 1.0 Response"), 201 | } 202 | } 203 | } 204 | 205 | mod response_field { 206 | use super::*; 207 | 208 | pub const FIELDS: &[&str] = &["result", "error", "id"]; 209 | pub enum Field { 210 | Result, 211 | Error, 212 | Id, 213 | } 214 | 215 | impl<'de> de::Deserialize<'de> for Field { 216 | fn deserialize(deserializer: D) -> Result 217 | where 218 | D: de::Deserializer<'de>, 219 | { 220 | de::Deserializer::deserialize_identifier(deserializer, FieldVisitor) 221 | } 222 | } 223 | 224 | struct FieldVisitor; 225 | impl<'de> de::Visitor<'de> for FieldVisitor { 226 | type Value = Field; 227 | 228 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 229 | formatter.write_str("field identifier") 230 | } 231 | 232 | fn visit_str(self, v: &str) -> Result 233 | where 234 | E: de::Error, 235 | { 236 | match v { 237 | "result" => Ok(Field::Result), 238 | "error" => Ok(Field::Error), 239 | "id" => Ok(Field::Id), 240 | _ => Err(de::Error::unknown_field(v, &FIELDS)), 241 | } 242 | } 243 | } 244 | } 245 | 246 | #[cfg(test)] 247 | mod tests { 248 | use super::*; 249 | 250 | fn response_cases() -> Vec<(Response, &'static str)> { 251 | vec![ 252 | ( 253 | // JSON-RPC 1.0 success response 254 | Response::success(Value::Bool(true), Id::Num(1)), 255 | r#"{"result":true,"error":null,"id":1}"#, 256 | ), 257 | ( 258 | // JSON-RPC 1.0 failure response 259 | Response::failure(Error::parse_error(), Some(Id::Num(1))), 260 | r#"{"result":null,"error":{"code":-32700,"message":"Parse error"},"id":1}"#, 261 | ), 262 | ( 263 | // JSON-RPC 1.0 failure response 264 | Response::failure(Error::parse_error(), None), 265 | r#"{"result":null,"error":{"code":-32700,"message":"Parse error"},"id":null}"#, 266 | ), 267 | ] 268 | } 269 | 270 | #[test] 271 | fn response_serialization() { 272 | for (response, expect) in response_cases() { 273 | let response_obj = ResponseObj::Single(response.clone()); 274 | 275 | assert_eq!(serde_json::to_string(&response).unwrap(), expect); 276 | assert_eq!(serde_json::to_string(&response_obj).unwrap(), expect); 277 | 278 | assert_eq!(serde_json::from_str::(expect).unwrap(), response); 279 | assert_eq!(serde_json::from_str::(expect).unwrap(), response_obj); 280 | } 281 | 282 | // JSON-RPC 1.0 valid response 283 | let valid_cases = vec![ 284 | r#"{"result":true,"error":null,"id":1}"#, 285 | r#"{"result":null,"error":{"code": -32700,"message": "Parse error"},"id":1}"#, 286 | ]; 287 | for case in valid_cases { 288 | assert!(serde_json::from_str::(case).is_ok()); 289 | assert!(serde_json::from_str::(case).is_ok()); 290 | } 291 | 292 | // JSON-RPC 1.0 invalid response 293 | let invalid_cases = vec![ 294 | r#"{"result":true,"error":null,"id":1,unknown:[]}"#, 295 | r#"{"result":true,"error":{"code": -32700,"message": "Parse error"},"id":1}"#, 296 | r#"{"result":true,"error":{"code": -32700,"message": "Parse error"}}"#, 297 | r#"{"result":true,"id":1}"#, 298 | r#"{"error":{"code": -32700,"message": "Parse error"},"id":1}"#, 299 | r#"{"unknown":[]}"#, 300 | ]; 301 | for case in invalid_cases { 302 | assert!(serde_json::from_str::(case).is_err()); 303 | assert!(serde_json::from_str::(case).is_err()); 304 | } 305 | } 306 | 307 | #[test] 308 | fn batch_response_serialization() { 309 | let batch_response = vec![ 310 | Response::success(Value::Bool(true), Id::Num(1)), 311 | Response::success(Value::Bool(false), Id::Num(2)), 312 | ]; 313 | let batch_response_obj = ResponseObj::Batch(batch_response.clone()); 314 | let batch_expect = r#"[{"result":true,"error":null,"id":1},{"result":false,"error":null,"id":2}]"#; 315 | 316 | assert_eq!(serde_json::to_string(&batch_response).unwrap(), batch_expect); 317 | assert_eq!(serde_json::to_string(&batch_response_obj).unwrap(), batch_expect); 318 | 319 | assert_eq!( 320 | serde_json::from_str::(&batch_expect).unwrap(), 321 | batch_response 322 | ); 323 | assert_eq!( 324 | serde_json::from_str::(&batch_expect).unwrap(), 325 | batch_response_obj 326 | ); 327 | 328 | // JSON-RPC 1.0 valid batch response 329 | let valid_cases = vec![ 330 | r#"[{"result":true,"error":null,"id":1}]"#, 331 | r#"[{"result":null,"error":{"code": -32700,"message": "Parse error"},"id":1}]"#, 332 | r#"[{"result":true,"error":null,"id":1}, {"result":null,"error":{"code": -32700,"message": "Parse error"},"id":1}]"#, 333 | ]; 334 | for case in valid_cases { 335 | assert!(serde_json::from_str::(case).is_ok()); 336 | assert!(serde_json::from_str::(case).is_ok()); 337 | } 338 | 339 | // JSON-RPC 1.0 invalid batch response 340 | let invalid_cases = vec![ 341 | r#"[{"result":true,"error":null,"id":1}"#, 342 | r#"[{"result":true,"error":{"code": -32700,"message": "Parse error"},"id":1}]"#, 343 | r#"[{"result":true,"error":{"code": -32700,"message": "Parse error"}}]"#, 344 | r#"[{"result":true,"id":1}]"#, 345 | r#"[{"error":{"code": -32700,"message": "Parse error"},"id":1}]"#, 346 | // r#"[]"#, // empty should be invalid 347 | ]; 348 | for case in invalid_cases { 349 | assert!(serde_json::from_str::(case).is_err()); 350 | assert!(serde_json::from_str::(case).is_err()); 351 | } 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /types/src/v2/error.rs: -------------------------------------------------------------------------------- 1 | pub use crate::error::{Error, ErrorCode}; 2 | -------------------------------------------------------------------------------- /types/src/v2/mod.rs: -------------------------------------------------------------------------------- 1 | /// JSON-RPC 2.0 error objects. 2 | pub mod error; 3 | /// JSON-RPC 2.0 notification. 4 | pub mod notification; 5 | /// JSON-RPC 2.0 request/notification parameters. 6 | pub mod params; 7 | /// JSON-RPC 2.0 request objects. 8 | pub mod request; 9 | /// JSON-RPC 2.0 response objects. 10 | pub mod response; 11 | 12 | // Re-exports 13 | pub use serde_json::{Map, Value}; 14 | 15 | pub use self::{ 16 | error::{Error, ErrorCode}, 17 | notification::{Notification, SubscriptionNotification, SubscriptionNotificationParams}, 18 | params::{Id, Params, ParamsRef, Version}, 19 | request::{BatchRequest, Request, RequestObj}, 20 | response::{BatchResponse, Failure, Response, ResponseObj, Success}, 21 | }; 22 | -------------------------------------------------------------------------------- /types/src/v2/notification.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "std"))] 2 | use alloc::{string::String, vec::Vec}; 3 | use core::fmt; 4 | 5 | use serde::{de::DeserializeOwned, Deserialize, Serialize}; 6 | use serde_json::Value; 7 | 8 | use crate::v2::{Id, Params, ParamsRef, Version}; 9 | 10 | /// Represents JSON-RPC 1.0 batch notification. 11 | pub type BatchNotificationRef<'a> = Vec>; 12 | 13 | /// Represents JSON-RPC 2.0 request which is a notification. 14 | /// 15 | /// A Request object that is a Notification signifies the Client's lack of interest in the 16 | /// corresponding Response object, and as such no Response object needs to be returned to the client. 17 | /// As such, the Client would not be aware of any errors (like e.g. "Invalid params","Internal error"). 18 | /// 19 | /// The Server MUST NOT reply to a Notification, including those that are within a batch request. 20 | #[derive(Debug, PartialEq, Serialize)] 21 | #[serde(deny_unknown_fields)] 22 | pub struct NotificationRef<'a> { 23 | /// A String specifying the version of the JSON-RPC protocol. 24 | pub jsonrpc: Version, 25 | /// A String containing the name of the method to be invoked. 26 | /// 27 | /// Method names that begin with the word rpc followed by a period character (U+002E or ASCII 46) 28 | /// are reserved for rpc-internal methods and extensions and MUST NOT be used for anything else. 29 | pub method: &'a str, 30 | /// A Structured value that holds the parameter values to be used 31 | /// during the invocation of the method. This member MAY be omitted. 32 | #[serde(skip_serializing_if = "Option::is_none")] 33 | pub params: Option>, 34 | } 35 | 36 | impl<'a> fmt::Display for NotificationRef<'a> { 37 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 38 | let json = serde_json::to_string(self).expect("`NotificationRef` is serializable"); 39 | write!(f, "{}", json) 40 | } 41 | } 42 | 43 | impl<'a> PartialEq for NotificationRef<'a> { 44 | fn eq(&self, other: &Notification) -> bool { 45 | self.method.eq(&other.method) && self.params.eq(&other.params.as_ref().map(|params| params.as_ref())) 46 | } 47 | } 48 | 49 | impl<'a> NotificationRef<'a> { 50 | /// Creates a JSON-RPC 2.0 request which is a notification. 51 | pub fn new(method: &'a str, params: Option>) -> Self { 52 | Self { 53 | jsonrpc: Version::V2_0, 54 | method, 55 | params, 56 | } 57 | } 58 | 59 | /// Converts the reference into the owned type. 60 | pub fn to_owned(&self) -> Notification { 61 | Notification { 62 | jsonrpc: self.jsonrpc, 63 | method: self.method.into(), 64 | params: self.params.as_ref().map(|params| params.to_owned()), 65 | } 66 | } 67 | } 68 | 69 | // ################################################################################################ 70 | 71 | /// Represents JSON-RPC 1.0 batch notification. 72 | pub type BatchNotification = Vec; 73 | 74 | /// Represents JSON-RPC 2.0 request which is a notification. 75 | /// 76 | /// A Request object that is a Notification signifies the Client's lack of interest in the 77 | /// corresponding Response object, and as such no Response object needs to be returned to the client. 78 | /// As such, the Client would not be aware of any errors (like e.g. "Invalid params","Internal error"). 79 | /// 80 | /// The Server MUST NOT reply to a Notification, including those that are within a batch request. 81 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 82 | #[serde(deny_unknown_fields)] 83 | pub struct Notification { 84 | /// A String specifying the version of the JSON-RPC protocol. 85 | pub jsonrpc: Version, 86 | /// A String containing the name of the method to be invoked. 87 | /// 88 | /// Method names that begin with the word rpc followed by a period character (U+002E or ASCII 46) 89 | /// are reserved for rpc-internal methods and extensions and MUST NOT be used for anything else. 90 | pub method: String, 91 | /// A Structured value that holds the parameter values to be used 92 | /// during the invocation of the method. This member MAY be omitted. 93 | #[serde(skip_serializing_if = "Option::is_none")] 94 | pub params: Option, 95 | } 96 | 97 | impl fmt::Display for Notification { 98 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 99 | let json = serde_json::to_string(self).expect("`Notification` is serializable"); 100 | write!(f, "{}", json) 101 | } 102 | } 103 | 104 | impl<'a> PartialEq> for Notification { 105 | fn eq(&self, other: &NotificationRef<'a>) -> bool { 106 | self.method.eq(other.method) && self.params.as_ref().map(|params| params.as_ref()).eq(&other.params) 107 | } 108 | } 109 | 110 | impl Notification { 111 | /// Creates a JSON-RPC 2.0 request which is a notification. 112 | pub fn new(method: impl Into, params: Option) -> Self { 113 | Self { 114 | jsonrpc: Version::V2_0, 115 | method: method.into(), 116 | params, 117 | } 118 | } 119 | 120 | /// Borrows from an owned value. 121 | pub fn as_ref(&self) -> NotificationRef<'_> { 122 | NotificationRef { 123 | jsonrpc: self.jsonrpc, 124 | method: &self.method, 125 | params: self.params.as_ref().map(|params| params.as_ref()), 126 | } 127 | } 128 | } 129 | 130 | // ################################################################################################ 131 | 132 | /// Parameters of the subscription notification. 133 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 134 | #[serde(deny_unknown_fields)] 135 | pub struct SubscriptionNotificationParams { 136 | /// Subscription id, as communicated during the subscription. 137 | pub subscription: Id, 138 | /// Actual data that the server wants to communicate to the client. 139 | pub result: T, 140 | } 141 | 142 | impl SubscriptionNotificationParams { 143 | /// Creates a JSON-RPC 2.0 notification parameter. 144 | pub fn new(id: Id, result: T) -> Self { 145 | Self { 146 | subscription: id, 147 | result, 148 | } 149 | } 150 | } 151 | 152 | /// Server notification about something the client is subscribed to. 153 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 154 | #[serde(deny_unknown_fields)] 155 | pub struct SubscriptionNotification

{ 156 | /// A String specifying the version of the JSON-RPC protocol. 157 | pub jsonrpc: Version, 158 | /// A String containing the name of the method that was used for the subscription. 159 | pub method: String, 160 | /// Parameters of the subscription notification. 161 | pub params: P, 162 | } 163 | 164 | impl fmt::Display for SubscriptionNotification

{ 165 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 166 | let json = serde_json::to_string(self).expect("`SubscriptionNotification` is serializable"); 167 | write!(f, "{}", json) 168 | } 169 | } 170 | 171 | impl SubscriptionNotification

{ 172 | /// Creates a JSON-RPC 2.0 notification which is a subscription notification. 173 | pub fn new(method: impl Into, params: P) -> Self { 174 | Self { 175 | jsonrpc: Version::V2_0, 176 | method: method.into(), 177 | params, 178 | } 179 | } 180 | } 181 | 182 | #[cfg(test)] 183 | mod tests { 184 | use super::*; 185 | 186 | fn notification_cases() -> Vec<(Notification, &'static str)> { 187 | vec![ 188 | ( 189 | // JSON-RPC 2.0 notification 190 | Notification::new("foo", Some(Params::Array(vec![Value::from(1), Value::Bool(true)]))), 191 | r#"{"jsonrpc":"2.0","method":"foo","params":[1,true]}"#, 192 | ), 193 | ( 194 | // JSON-RPC 2.0 notification with an empty array parameters 195 | Notification::new("foo", Some(Params::Array(vec![]))), 196 | r#"{"jsonrpc":"2.0","method":"foo","params":[]}"#, 197 | ), 198 | ( 199 | // JSON-RPC 2.0 notification without parameters 200 | Notification::new("foo", None), 201 | r#"{"jsonrpc":"2.0","method":"foo"}"#, 202 | ), 203 | ] 204 | } 205 | 206 | #[test] 207 | fn notification_serialization() { 208 | for (notification, expect) in notification_cases() { 209 | assert_eq!(serde_json::to_string(¬ification.as_ref()).unwrap(), expect); 210 | assert_eq!(serde_json::to_string(¬ification).unwrap(), expect); 211 | 212 | assert_eq!(serde_json::from_str::(expect).unwrap(), notification); 213 | } 214 | 215 | // JSON-RPC 2.0 valid notification 216 | let valid_cases = vec![ 217 | r#"{"jsonrpc":"2.0","method":"foo","params":[1,true]}"#, 218 | r#"{"jsonrpc":"2.0","method":"foo","params":[]}"#, 219 | r#"{"jsonrpc":"2.0","method":"foo"}"#, 220 | ]; 221 | for case in valid_cases { 222 | assert!(serde_json::from_str::(case).is_ok()); 223 | } 224 | 225 | // JSON-RPC 2.0 invalid notification 226 | let invalid_cases = vec![ 227 | r#"{"jsonrpc":"2.0","method":"foo","params":[1,true],"id":1,"unknown":[]}"#, 228 | r#"{"jsonrpc":"2.0"`,"method":"foo","params":[1,true],"id":1.2}"#, 229 | r#"{"jsonrpc":"2.0","method":"foo","params":[1,true],"id":null,"unknown":[]}"#, 230 | r#"{"jsonrpc":"2.0","method":"foo","params":[1,true],"id":null}"#, 231 | r#"{"jsonrpc":"2.0","method":"foo","params":[1,true],"unknown":[]}"#, 232 | r#"{"jsonrpc":"2.0","method":"foo","unknown":[]}"#, 233 | r#"{"jsonrpc":"2.0","unknown":[]}"#, 234 | ]; 235 | for case in invalid_cases { 236 | assert!(serde_json::from_str::(case).is_err()); 237 | } 238 | } 239 | 240 | #[test] 241 | fn subscription_notification_serialization() { 242 | let params = SubscriptionNotificationParams::new(Id::Str("test1".into()), Value::Bool(true)); 243 | let notification = SubscriptionNotification::new("test_method", params); 244 | let expect = r#"{"jsonrpc":"2.0","method":"test_method","params":{"subscription":"test1","result":true}}"#; 245 | assert_eq!(serde_json::to_string(¬ification).unwrap(), expect); 246 | assert_eq!( 247 | serde_json::from_str::(expect).unwrap(), 248 | notification 249 | ); 250 | 251 | let params = SubscriptionNotificationParams::new(Id::Str("test1".into()), 12345u32); 252 | let notification = SubscriptionNotification::new("test_method", params); 253 | let expect = r#"{"jsonrpc":"2.0","method":"test_method","params":{"subscription":"test1","result":12345}}"#; 254 | assert_eq!(serde_json::to_string(¬ification).unwrap(), expect); 255 | assert_eq!( 256 | serde_json::from_str::>>(expect).unwrap(), 257 | notification 258 | ); 259 | 260 | // ======================================================================================== 261 | 262 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 263 | #[serde(deny_unknown_fields)] 264 | pub struct MySubscriptionNotificationParams((Id, T)); 265 | 266 | let params = MySubscriptionNotificationParams((Id::Str("test1".into()), Value::Bool(true))); 267 | let notification = SubscriptionNotification::new("test_method", params); 268 | let expect = r#"{"jsonrpc":"2.0","method":"test_method","params":["test1",true]}"#; 269 | assert_eq!(serde_json::to_string(¬ification).unwrap(), expect); 270 | assert_eq!( 271 | serde_json::from_str::>(expect).unwrap(), 272 | notification 273 | ); 274 | 275 | let params = MySubscriptionNotificationParams((Id::Str("test1".into()), 12345u32)); 276 | let notification = SubscriptionNotification::new("test_method", params); 277 | let expect = r#"{"jsonrpc":"2.0","method":"test_method","params":["test1",12345]}"#; 278 | assert_eq!(serde_json::to_string(¬ification).unwrap(), expect); 279 | assert_eq!( 280 | serde_json::from_str::>>(expect).unwrap(), 281 | notification 282 | ); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /types/src/v2/params.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "std"))] 2 | use alloc::{collections::BTreeMap, string::String, vec::Vec}; 3 | use core::fmt; 4 | #[cfg(feature = "std")] 5 | use std::collections::BTreeMap; 6 | 7 | use serde::{ 8 | de::{self, DeserializeOwned}, 9 | ser, Deserialize, Serialize, 10 | }; 11 | use serde_json::{from_value, Value}; 12 | 13 | /// JSON-RPC 2.0 id object. 14 | pub use crate::id::Id; 15 | use crate::v2::Error; 16 | 17 | /// Represents JSON-RPC protocol version. 18 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] 19 | pub enum Version { 20 | /// Represents JSON-RPC 2.0 version. 21 | V2_0, 22 | } 23 | 24 | impl ser::Serialize for Version { 25 | fn serialize(&self, serializer: S) -> Result 26 | where 27 | S: ser::Serializer, 28 | { 29 | match self { 30 | Version::V2_0 => serializer.serialize_str("2.0"), 31 | } 32 | } 33 | } 34 | 35 | impl<'a> de::Deserialize<'a> for Version { 36 | fn deserialize(deserializer: D) -> Result 37 | where 38 | D: de::Deserializer<'a>, 39 | { 40 | deserializer.deserialize_identifier(VersionVisitor) 41 | } 42 | } 43 | 44 | struct VersionVisitor; 45 | impl<'a> de::Visitor<'a> for VersionVisitor { 46 | type Value = Version; 47 | 48 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 49 | formatter.write_str("a string") 50 | } 51 | 52 | fn visit_str(self, value: &str) -> Result 53 | where 54 | E: de::Error, 55 | { 56 | match value { 57 | "2.0" => Ok(Version::V2_0), 58 | _ => Err(de::Error::custom("Invalid JSON-RPC protocol version")), 59 | } 60 | } 61 | } 62 | 63 | // ################################################################################################ 64 | 65 | /// Represents JSON-RPC 2.0 request parameters. 66 | /// 67 | /// If present, parameters for the rpc call MUST be provided as a Structured value. 68 | /// Either by-position through an Array or by-name through an Object. 69 | #[derive(Debug, Eq, PartialEq, Serialize)] 70 | #[serde(untagged)] 71 | pub enum ParamsRef<'a> { 72 | /// Positional params (slice). 73 | ArrayRef(&'a [Value]), 74 | /// Params by name. 75 | MapRef(&'a BTreeMap), 76 | } 77 | 78 | impl<'a> Default for ParamsRef<'a> { 79 | fn default() -> Self { 80 | ParamsRef::ArrayRef(&[]) 81 | } 82 | } 83 | 84 | impl<'a> fmt::Display for ParamsRef<'a> { 85 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 86 | let json = serde_json::to_string(self).expect("`ParamsRef` is serializable"); 87 | write!(f, "{}", json) 88 | } 89 | } 90 | 91 | impl<'a> PartialEq for ParamsRef<'a> { 92 | fn eq(&self, other: &Params) -> bool { 93 | match (other, self) { 94 | (Params::Array(arr1), ParamsRef::ArrayRef(arr2)) => arr1.eq(arr2), 95 | (Params::Map(map1), ParamsRef::MapRef(map2)) => map1.eq(map2), 96 | _ => false, 97 | } 98 | } 99 | } 100 | 101 | impl<'a> From<&'a [Value]> for ParamsRef<'a> { 102 | fn from(params: &'a [Value]) -> Self { 103 | Self::ArrayRef(params) 104 | } 105 | } 106 | 107 | impl<'a> From<&'a BTreeMap> for ParamsRef<'a> { 108 | fn from(params: &'a BTreeMap) -> Self { 109 | Self::MapRef(params) 110 | } 111 | } 112 | 113 | impl<'a> ParamsRef<'a> { 114 | /// Checks if the parameters is an empty array of objects. 115 | pub fn is_empty_array(&self) -> bool { 116 | matches!(self, ParamsRef::ArrayRef(array) if array.is_empty()) 117 | } 118 | 119 | /// Checks if the parameters is an array of objects. 120 | pub fn is_array(&self) -> bool { 121 | matches!(self, ParamsRef::ArrayRef(_)) 122 | } 123 | 124 | /// Checks if the parameters is a map of objects. 125 | pub fn is_map(&self) -> bool { 126 | matches!(self, ParamsRef::MapRef(_)) 127 | } 128 | 129 | /// Converts the reference into the owned type. 130 | pub fn to_owned(&self) -> Params { 131 | match *self { 132 | Self::ArrayRef(params) => Params::Array(params.to_vec()), 133 | Self::MapRef(params) => Params::Map(params.clone()), 134 | } 135 | } 136 | } 137 | 138 | // ################################################################################################ 139 | 140 | /// Represents JSON-RPC 2.0 request parameters. 141 | /// 142 | /// If present, parameters for the rpc call MUST be provided as a Structured value. 143 | /// Either by-position through an Array or by-name through an Object. 144 | #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] 145 | #[serde(untagged)] 146 | pub enum Params { 147 | /// Positional params (heap allocated). 148 | Array(Vec), 149 | /// Params by name. 150 | Map(BTreeMap), 151 | } 152 | 153 | impl Default for Params { 154 | fn default() -> Self { 155 | Params::Array(Vec::new()) 156 | } 157 | } 158 | 159 | impl fmt::Display for Params { 160 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 161 | let json = serde_json::to_string(self).expect("`Params` is serializable"); 162 | write!(f, "{}", json) 163 | } 164 | } 165 | 166 | impl<'a> PartialEq> for Params { 167 | fn eq(&self, other: &ParamsRef<'a>) -> bool { 168 | match (self, other) { 169 | (Params::Array(arr1), ParamsRef::ArrayRef(arr2)) => arr1.eq(arr2), 170 | (Params::Map(map1), ParamsRef::MapRef(map2)) => map1.eq(map2), 171 | _ => false, 172 | } 173 | } 174 | } 175 | 176 | impl From> for Params { 177 | fn from(params: Vec) -> Self { 178 | Self::Array(params) 179 | } 180 | } 181 | 182 | impl From> for Params { 183 | fn from(params: BTreeMap) -> Self { 184 | Self::Map(params) 185 | } 186 | } 187 | 188 | impl From for Value { 189 | fn from(params: Params) -> Value { 190 | match params { 191 | Params::Array(array) => Value::Array(array), 192 | Params::Map(object) => Value::Object(object.into_iter().collect()), 193 | } 194 | } 195 | } 196 | 197 | impl Params { 198 | /// Parses incoming `Params` into expected types. 199 | pub fn parse(self) -> Result 200 | where 201 | D: DeserializeOwned, 202 | { 203 | let value = self.into(); 204 | from_value(value).map_err(Error::invalid_params) 205 | } 206 | 207 | /// Checks if the parameters is an empty array of objects. 208 | pub fn is_empty_array(&self) -> bool { 209 | matches!(self, Params::Array(array) if array.is_empty()) 210 | } 211 | 212 | /// Checks if the parameters is an array of objects. 213 | pub fn is_array(&self) -> bool { 214 | matches!(self, Params::Array(_)) 215 | } 216 | 217 | /// Checks if the parameters is a map of objects. 218 | pub fn is_map(&self) -> bool { 219 | matches!(self, Params::Map(_)) 220 | } 221 | 222 | /// Borrows from an owned value. 223 | pub fn as_ref(&self) -> ParamsRef<'_> { 224 | match self { 225 | Self::Array(params) => ParamsRef::ArrayRef(params), 226 | Self::Map(params) => ParamsRef::MapRef(params), 227 | } 228 | } 229 | } 230 | 231 | #[cfg(test)] 232 | mod tests { 233 | use super::*; 234 | 235 | #[test] 236 | fn params_serialization() { 237 | let array = vec![Value::from(1), Value::Bool(true)]; 238 | let params = Params::Array(array.clone()); 239 | assert_eq!(serde_json::to_string(¶ms).unwrap(), r#"[1,true]"#); 240 | assert_eq!(serde_json::to_string(¶ms.as_ref()).unwrap(), r#"[1,true]"#); 241 | assert_eq!(serde_json::from_str::(r#"[1,true]"#).unwrap(), params); 242 | 243 | let object = { 244 | let mut map = BTreeMap::new(); 245 | map.insert("key".into(), Value::String("value".into())); 246 | map 247 | }; 248 | let params = Params::Map(object.clone()); 249 | assert_eq!(serde_json::to_string(¶ms).unwrap(), r#"{"key":"value"}"#); 250 | assert_eq!(serde_json::to_string(¶ms.as_ref()).unwrap(), r#"{"key":"value"}"#); 251 | assert_eq!(serde_json::from_str::(r#"{"key":"value"}"#).unwrap(), params); 252 | 253 | let params = Params::Array(vec![ 254 | Value::Null, 255 | Value::Bool(true), 256 | Value::from(-1), 257 | Value::from(1), 258 | Value::from(1.2), 259 | Value::String("hello".into()), 260 | Value::Array(vec![]), 261 | Value::Array(array), 262 | Value::Object(object.into_iter().collect()), 263 | ]); 264 | assert_eq!( 265 | serde_json::to_string(¶ms).unwrap(), 266 | r#"[null,true,-1,1,1.2,"hello",[],[1,true],{"key":"value"}]"# 267 | ); 268 | assert_eq!( 269 | serde_json::to_string(¶ms.as_ref()).unwrap(), 270 | r#"[null,true,-1,1,1.2,"hello",[],[1,true],{"key":"value"}]"# 271 | ); 272 | assert_eq!( 273 | serde_json::from_str::(r#"[null,true,-1,1,1.2,"hello",[],[1,true],{"key":"value"}]"#).unwrap(), 274 | params 275 | ); 276 | } 277 | 278 | #[test] 279 | fn single_param_parsed_as_tuple() { 280 | let params: (u64,) = Params::Array(vec![Value::from(1)]).parse().unwrap(); 281 | assert_eq!(params, (1,)); 282 | } 283 | 284 | #[test] 285 | fn invalid_params() { 286 | let params = serde_json::from_str::("[1,true]").unwrap(); 287 | assert_eq!( 288 | params.parse::<(u8, bool, String)>().unwrap_err(), 289 | Error::invalid_params("invalid length 2, expected a tuple of size 3") 290 | ); 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /types/src/v2/request.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "std"))] 2 | use alloc::{string::String, vec::Vec}; 3 | use core::fmt; 4 | 5 | use serde::{Deserialize, Serialize}; 6 | 7 | use crate::v2::{Id, Params, ParamsRef, Version}; 8 | 9 | /// JSON-RPC 2.0 Request Object. 10 | #[derive(Debug, PartialEq, Serialize)] 11 | #[serde(deny_unknown_fields)] 12 | #[serde(untagged)] 13 | pub enum RequestRefObj<'a> { 14 | /// Single request call 15 | Single(RequestRef<'a>), 16 | /// Batch of request calls 17 | Batch(BatchRequestRef<'a>), 18 | } 19 | 20 | impl<'a> fmt::Display for RequestRefObj<'a> { 21 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 22 | let json = serde_json::to_string(self).expect("`RequestRefObj` is serializable"); 23 | write!(f, "{}", json) 24 | } 25 | } 26 | 27 | impl<'a> PartialEq for RequestRefObj<'a> { 28 | fn eq(&self, other: &RequestObj) -> bool { 29 | match (self, other) { 30 | (Self::Single(req1), RequestObj::Single(req2)) => req1.eq(req2), 31 | (Self::Batch(req1), RequestObj::Batch(req2)) => req1.eq(req2), 32 | _ => false, 33 | } 34 | } 35 | } 36 | 37 | /// Represents JSON-RPC 2.0 batch request call. 38 | pub type BatchRequestRef<'a> = Vec>; 39 | 40 | /// Represents JSON-RPC 2.0 request call. 41 | #[derive(Debug, PartialEq, Serialize)] 42 | #[serde(deny_unknown_fields)] 43 | pub struct RequestRef<'a> { 44 | /// A String specifying the version of the JSON-RPC protocol. 45 | pub jsonrpc: Version, 46 | /// A String containing the name of the method to be invoked. 47 | /// 48 | /// Method names that begin with the word rpc followed by a period character (U+002E or ASCII 46) 49 | /// are reserved for rpc-internal methods and extensions and MUST NOT be used for anything else. 50 | pub method: &'a str, 51 | /// A Structured value that holds the parameter values to be used 52 | /// during the invocation of the method. This member MAY be omitted. 53 | #[serde(skip_serializing_if = "Option::is_none")] 54 | pub params: Option>, 55 | /// An identifier established by the Client. 56 | /// If it is not included it is assumed to be a notification. 57 | pub id: Id, 58 | } 59 | 60 | impl<'a> fmt::Display for RequestRef<'a> { 61 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 62 | let json = serde_json::to_string(self).expect("`RequestRef` is serializable"); 63 | write!(f, "{}", json) 64 | } 65 | } 66 | 67 | impl<'a> PartialEq for RequestRef<'a> { 68 | fn eq(&self, other: &Request) -> bool { 69 | self.method.eq(&other.method) 70 | && self.params.eq(&other.params.as_ref().map(|params| params.as_ref())) 71 | && self.id.eq(&other.id) 72 | } 73 | } 74 | 75 | impl<'a> RequestRef<'a> { 76 | /// Creates a JSON-RPC 2.0 request call. 77 | pub fn new(method: &'a str, params: Option>, id: Id) -> Self { 78 | Self { 79 | jsonrpc: Version::V2_0, 80 | method, 81 | params, 82 | id, 83 | } 84 | } 85 | 86 | /// Converts the reference into the owned type. 87 | pub fn to_owned(&self) -> Request { 88 | Request { 89 | jsonrpc: self.jsonrpc, 90 | method: self.method.into(), 91 | params: self.params.as_ref().map(|params| params.to_owned()), 92 | id: self.id.clone(), 93 | } 94 | } 95 | } 96 | 97 | // ################################################################################################ 98 | 99 | /// JSON-RPC 2.0 Request Object. 100 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 101 | #[serde(deny_unknown_fields)] 102 | #[serde(untagged)] 103 | pub enum RequestObj { 104 | /// Single request call 105 | Single(Request), 106 | /// Batch of request calls 107 | Batch(BatchRequest), 108 | } 109 | 110 | impl fmt::Display for RequestObj { 111 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 112 | let json = serde_json::to_string(self).expect("`RequestObj` is serializable"); 113 | write!(f, "{}", json) 114 | } 115 | } 116 | 117 | impl<'a> PartialEq> for RequestObj { 118 | fn eq(&self, other: &RequestRefObj<'a>) -> bool { 119 | match (self, other) { 120 | (Self::Single(req1), RequestRefObj::Single(req2)) => req1.eq(req2), 121 | (Self::Batch(req1), RequestRefObj::Batch(req2)) => req1.eq(req2), 122 | _ => false, 123 | } 124 | } 125 | } 126 | 127 | /// Represents JSON-RPC 2.0 batch request call. 128 | pub type BatchRequest = Vec; 129 | 130 | /// Represents JSON-RPC 2.0 request call. 131 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 132 | #[serde(deny_unknown_fields)] 133 | pub struct Request { 134 | /// A String specifying the version of the JSON-RPC protocol. 135 | pub jsonrpc: Version, 136 | /// A String containing the name of the method to be invoked. 137 | /// 138 | /// Method names that begin with the word rpc followed by a period character (U+002E or ASCII 46) 139 | /// are reserved for rpc-internal methods and extensions and MUST NOT be used for anything else. 140 | pub method: String, 141 | /// A Structured value that holds the parameter values to be used 142 | /// during the invocation of the method. This member MAY be omitted. 143 | #[serde(skip_serializing_if = "Option::is_none")] 144 | pub params: Option, 145 | /// An identifier established by the Client. 146 | /// If it is not included it is assumed to be a notification. 147 | pub id: Id, 148 | } 149 | 150 | impl fmt::Display for Request { 151 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 152 | let json = serde_json::to_string(self).expect("`MethodCall` is serializable"); 153 | write!(f, "{}", json) 154 | } 155 | } 156 | 157 | impl<'a> PartialEq> for Request { 158 | fn eq(&self, other: &RequestRef<'a>) -> bool { 159 | self.method.eq(other.method) 160 | && self.params.as_ref().map(|params| params.as_ref()).eq(&other.params) 161 | && self.id.eq(&other.id) 162 | } 163 | } 164 | 165 | impl Request { 166 | /// Creates a JSON-RPC 2.0 request call. 167 | pub fn new(method: impl Into, params: Option, id: Id) -> Self { 168 | Self { 169 | jsonrpc: Version::V2_0, 170 | method: method.into(), 171 | params, 172 | id, 173 | } 174 | } 175 | 176 | /// Borrows from an owned value. 177 | pub fn as_ref(&self) -> RequestRef<'_> { 178 | RequestRef { 179 | jsonrpc: self.jsonrpc, 180 | method: &self.method, 181 | params: self.params.as_ref().map(|params| params.as_ref()), 182 | id: self.id.clone(), 183 | } 184 | } 185 | } 186 | 187 | #[cfg(test)] 188 | mod tests { 189 | use serde_json::Value; 190 | 191 | use super::*; 192 | 193 | fn request_cases() -> Vec<(Request, &'static str)> { 194 | vec![ 195 | ( 196 | // JSON-RPC 2.0 request call 197 | Request::new( 198 | "foo", 199 | Some(Params::Array(vec![Value::from(1), Value::Bool(true)])), 200 | Id::Num(1), 201 | ), 202 | r#"{"jsonrpc":"2.0","method":"foo","params":[1,true],"id":1}"#, 203 | ), 204 | ( 205 | // JSON-RPC 2.0 request method call with an empty array parameters 206 | Request::new("foo", Some(Params::Array(vec![])), Id::Num(1)), 207 | r#"{"jsonrpc":"2.0","method":"foo","params":[],"id":1}"#, 208 | ), 209 | ( 210 | // JSON-RPC 2.0 request method call without parameters 211 | Request::new("foo", None, Id::Num(1)), 212 | r#"{"jsonrpc":"2.0","method":"foo","id":1}"#, 213 | ), 214 | ] 215 | } 216 | 217 | #[test] 218 | fn request_serialization() { 219 | for (request, expect) in request_cases() { 220 | let request_obj = RequestObj::Single(request.clone()); 221 | let request_ref = RequestRefObj::Single(request.as_ref()); 222 | 223 | assert_eq!(serde_json::to_string(&request).unwrap(), expect); 224 | assert_eq!(serde_json::to_string(&request_obj).unwrap(), expect); 225 | assert_eq!(serde_json::to_string(&request_ref).unwrap(), expect); 226 | 227 | assert_eq!(serde_json::from_str::(expect).unwrap(), request); 228 | assert_eq!(serde_json::from_str::(expect).unwrap(), request_obj); 229 | } 230 | 231 | // JSON-RPC 2.0 valid request 232 | let valid_cases = vec![ 233 | r#"{"jsonrpc":"2.0","method":"foo","params":[1,true],"id":1}"#, 234 | r#"{"jsonrpc":"2.0","method":"foo","params":[],"id":1}"#, 235 | r#"{"jsonrpc":"2.0","method":"foo","id":1}"#, 236 | ]; 237 | for case in valid_cases { 238 | assert!(serde_json::from_str::(case).is_ok()); 239 | assert!(serde_json::from_str::(case).is_ok()); 240 | } 241 | 242 | // JSON-RPC 2.0 invalid request 243 | let invalid_cases = vec![ 244 | r#"{"jsonrpc":"2.0","method":"foo","params":[1,true],"id":1,"unknown":[]}"#, 245 | r#"{"jsonrpc":"2.0"`,"method":"foo","params":[1,true],"id":1.2}"#, 246 | r#"{"jsonrpc":"2.0","method":"foo","params":[1,true],"id":null,"unknown":[]}"#, 247 | r#"{"jsonrpc":"2.0","method":"foo","params":[1,true],"id":null}"#, 248 | r#"{"jsonrpc":"2.0","method":"foo","params":[1,true],"unknown":[]}"#, 249 | r#"{"jsonrpc":"2.0","method":"foo","unknown":[]}"#, 250 | r#"{"jsonrpc":"2.0","unknown":[]}"#, 251 | ]; 252 | for case in invalid_cases { 253 | assert!(serde_json::from_str::(case).is_err()); 254 | assert!(serde_json::from_str::(case).is_err()); 255 | } 256 | } 257 | 258 | #[test] 259 | fn batch_request_serialization() { 260 | let batch_request = vec![Request::new("foo", None, 1.into()), Request::new("bar", None, 2.into())]; 261 | let batch_request_obj = RequestObj::Batch(batch_request.clone()); 262 | let batch_request_ref = RequestRefObj::Batch(batch_request.iter().map(|req| req.as_ref()).collect::>()); 263 | let batch_expect = r#"[{"jsonrpc":"2.0","method":"foo","id":1},{"jsonrpc":"2.0","method":"bar","id":2}]"#; 264 | 265 | assert_eq!(serde_json::to_string(&batch_request).unwrap(), batch_expect); 266 | assert_eq!(serde_json::to_string(&batch_request_obj).unwrap(), batch_expect); 267 | assert_eq!(serde_json::to_string(&batch_request_ref).unwrap(), batch_expect); 268 | 269 | assert_eq!( 270 | serde_json::from_str::(batch_expect).unwrap(), 271 | batch_request 272 | ); 273 | assert_eq!( 274 | serde_json::from_str::(batch_expect).unwrap(), 275 | batch_request_obj 276 | ); 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /types/src/v2/response.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "std"))] 2 | use alloc::vec::Vec; 3 | use core::fmt; 4 | 5 | use serde::{de::DeserializeOwned, Deserialize, Serialize}; 6 | use serde_json::Value; 7 | 8 | use crate::v2::{Error, ErrorCode, Id, Version}; 9 | 10 | /// JSON-RPC 2.0 Response Object. 11 | #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] 12 | #[serde(deny_unknown_fields)] 13 | #[serde(untagged)] 14 | pub enum ResponseObj { 15 | /// Single response 16 | Single(Response), 17 | /// Response to batch request (batch of responses) 18 | Batch(Vec>), 19 | } 20 | 21 | impl fmt::Display for ResponseObj { 22 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 23 | let json = serde_json::to_string(self).expect("`Response` is serializable"); 24 | write!(f, "{}", json) 25 | } 26 | } 27 | 28 | impl From> for ResponseObj { 29 | fn from(success: Success) -> Self { 30 | Self::Single(Response::::Success(success)) 31 | } 32 | } 33 | 34 | impl From for ResponseObj { 35 | fn from(failure: Failure) -> Self { 36 | Self::Single(Response::::Failure(failure)) 37 | } 38 | } 39 | 40 | impl From> for ResponseObj { 41 | fn from(batch: BatchResponse) -> Self { 42 | Self::Batch(batch) 43 | } 44 | } 45 | 46 | /// Represents JSON-RPC 2.0 batch response. 47 | pub type BatchResponse = Vec>; 48 | 49 | /// Represents JSON-RPC 2.0 success / failure response. 50 | #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] 51 | #[serde(deny_unknown_fields)] 52 | #[serde(untagged)] 53 | pub enum Response { 54 | /// Success response 55 | Success(Success), 56 | /// Failure response 57 | Failure(Failure), 58 | } 59 | 60 | impl fmt::Display for Response { 61 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 62 | let json = serde_json::to_string(self).expect("`Response` is serializable"); 63 | write!(f, "{}", json) 64 | } 65 | } 66 | 67 | impl From> for Response { 68 | fn from(success: Success) -> Self { 69 | Self::Success(success) 70 | } 71 | } 72 | 73 | impl From for Response { 74 | fn from(failure: Failure) -> Self { 75 | Self::Failure(failure) 76 | } 77 | } 78 | 79 | impl Response { 80 | /// Creates a JSON-RPC 2.0 success response. 81 | pub fn success(result: T, id: Id) -> Self { 82 | Self::Success(Success::new(result, id)) 83 | } 84 | 85 | /// Creates a JSON-RPC 2.0 failure response. 86 | pub fn failure(error: Error, id: Option) -> Self { 87 | Self::Failure(Failure::new(error, id)) 88 | } 89 | 90 | /// Gets the JSON-RPC protocol version. 91 | pub fn version(&self) -> Version { 92 | match self { 93 | Self::Success(s) => s.jsonrpc, 94 | Self::Failure(f) => f.jsonrpc, 95 | } 96 | } 97 | 98 | /// Gets the correlation id. 99 | pub fn id(&self) -> Option { 100 | match self { 101 | Self::Success(s) => Some(s.id.clone()), 102 | Self::Failure(f) => f.id.clone(), 103 | } 104 | } 105 | } 106 | 107 | impl From> for Result { 108 | // Convert into a result. 109 | // Will be `Ok` if it is a `SuccessResponse` and `Err` if `FailureResponse`. 110 | fn from(response: Response) -> Result { 111 | match response { 112 | Response::Success(s) => Ok(s.result), 113 | Response::Failure(f) => Err(f.error), 114 | } 115 | } 116 | } 117 | 118 | /// Represents JSON-RPC 2.0 success response. 119 | #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] 120 | #[serde(deny_unknown_fields)] 121 | pub struct Success { 122 | /// A String specifying the version of the JSON-RPC protocol. 123 | pub jsonrpc: Version, 124 | /// Successful execution result. 125 | pub result: T, 126 | /// Correlation id. 127 | /// 128 | /// It **MUST** be the same as the value of the id member in the Request Object. 129 | pub id: Id, 130 | } 131 | 132 | impl fmt::Display for Success { 133 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 134 | let json = serde_json::to_string(self).expect("`Success` is serializable"); 135 | write!(f, "{}", json) 136 | } 137 | } 138 | 139 | impl Success { 140 | /// Creates a JSON-RPC 2.0 success response. 141 | pub fn new(result: T, id: Id) -> Self { 142 | Self { 143 | jsonrpc: Version::V2_0, 144 | result, 145 | id, 146 | } 147 | } 148 | } 149 | 150 | /// Represents JSON-RPC 2.0 failure response. 151 | #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] 152 | #[serde(deny_unknown_fields)] 153 | pub struct Failure { 154 | /// A String specifying the version of the JSON-RPC protocol. 155 | pub jsonrpc: Version, 156 | /// Failed execution error. 157 | pub error: Error, 158 | /// Correlation id. 159 | /// 160 | /// It **MUST** be the same as the value of the id member in the Request Object. 161 | /// 162 | /// If there was an error in detecting the id in the Request object (e.g. Parse error/Invalid Request), 163 | /// it **MUST** be Null. 164 | pub id: Option, 165 | } 166 | 167 | impl fmt::Display for Failure { 168 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 169 | let json = serde_json::to_string(self).expect("`Failure` is serializable"); 170 | write!(f, "{}", json) 171 | } 172 | } 173 | 174 | impl Failure { 175 | /// Creates a JSON-RPC 2.0 failure response. 176 | pub fn new(error: Error, id: Option) -> Self { 177 | Self { 178 | jsonrpc: Version::V2_0, 179 | error, 180 | id, 181 | } 182 | } 183 | 184 | /// Creates a JSON-RPC 2.0 failure response, indicating that the server has an error in parsing the JSON text. 185 | pub fn parse_error(id: Option) -> Self { 186 | Self::new(Error::parse_error(), id) 187 | } 188 | 189 | /// Creates a JSON-RPC 2.0 failure response, indicating malformed request. 190 | pub fn invalid_request(id: Option) -> Self { 191 | Self::new(Error::invalid_request(), id) 192 | } 193 | 194 | /// Creates a JSON-RPC 2.0 failure response, indicating that the request's method is not found. 195 | pub fn method_not_found(id: Id) -> Self { 196 | Self::new(Error::method_not_found(), Some(id)) 197 | } 198 | 199 | /// Creates a JSON-RPC 2.0 failure response, indicating that the request's parameters is invalid. 200 | pub fn invalid_params(id: Id, msg: impl fmt::Display) -> Self { 201 | Self::new(Error::invalid_params(msg), Some(id)) 202 | } 203 | 204 | /// Creates a JSON-RPC 2.0 failure response, indicating that the internal JSON-RPC error. 205 | pub fn internal_error(id: Id) -> Self { 206 | Self::new(Error::internal_error(), Some(id)) 207 | } 208 | 209 | /// Creates a JSON-RPC 2.0 failure response, indicating that implementation-defined server error. 210 | pub fn server_error(id: Id, error: i64) -> Self { 211 | Self::new(Error::new(ErrorCode::ServerError(error)), Some(id)) 212 | } 213 | } 214 | 215 | #[cfg(test)] 216 | mod tests { 217 | use super::*; 218 | 219 | // JSON-RPC 2.0 success response 220 | fn success_response_cases() -> Vec<(Success, &'static str)> { 221 | vec![( 222 | Success::new(Value::Bool(true), Id::Num(1)), 223 | r#"{"jsonrpc":"2.0","result":true,"id":1}"#, 224 | )] 225 | } 226 | 227 | // JSON-RPC 2.0 failure response 228 | fn failure_response_cases() -> Vec<(Failure, &'static str)> { 229 | vec![ 230 | ( 231 | Failure::parse_error(Some(Id::Num(1))), 232 | r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":1}"#, 233 | ), 234 | ( 235 | Failure::parse_error(None), 236 | r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":null}"#, 237 | ), 238 | ] 239 | } 240 | 241 | #[test] 242 | fn response_serialization() { 243 | for (success_response, expect) in success_response_cases() { 244 | assert_eq!(serde_json::to_string(&success_response).unwrap(), expect); 245 | assert_eq!(serde_json::from_str::(expect).unwrap(), success_response); 246 | 247 | let response = Response::Success(success_response); 248 | assert_eq!(serde_json::to_string(&response).unwrap(), expect); 249 | assert_eq!(serde_json::from_str::(expect).unwrap(), response); 250 | 251 | let response = ResponseObj::Single(response.clone()); 252 | assert_eq!(serde_json::to_string(&response).unwrap(), expect); 253 | assert_eq!(serde_json::from_str::(expect).unwrap(), response); 254 | } 255 | 256 | for (failure_response, expect) in failure_response_cases() { 257 | assert_eq!(serde_json::to_string(&failure_response).unwrap(), expect); 258 | assert_eq!(serde_json::from_str::(expect).unwrap(), failure_response); 259 | 260 | let response = Response::Failure(failure_response); 261 | assert_eq!(serde_json::to_string(&response).unwrap(), expect); 262 | assert_eq!(serde_json::from_str::(expect).unwrap(), response); 263 | 264 | let response = ResponseObj::Single(response.clone()); 265 | assert_eq!(serde_json::to_string(&response).unwrap(), expect); 266 | assert_eq!(serde_json::from_str::(expect).unwrap(), response); 267 | } 268 | 269 | // JSON-RPC 2.0 valid response 270 | let valid_cases = vec![ 271 | r#"{"jsonrpc":"2.0","result":true,"id":1}"#, 272 | r#"{"jsonrpc":"2.0","error":{"code": -32700,"message": "Parse error"},"id":1}"#, 273 | r#"{"jsonrpc":"2.0","error":{"code": -32700,"message": "Parse error"},"id":null}"#, 274 | ]; 275 | for case in valid_cases { 276 | assert!(serde_json::from_str::(case).is_ok()); 277 | assert!(serde_json::from_str::(case).is_ok()); 278 | } 279 | 280 | // JSON-RPC 2.0 invalid response 281 | let invalid_cases = vec![ 282 | r#"{"jsonrpc":"2.0","result":true,"id":1,"unknown":[]}"#, 283 | r#"{"jsonrpc":"2.0","error":{"code": -32700,"message": "Parse error"},"id":1,"unknown":[]}"#, 284 | r#"{"jsonrpc":"2.0","result":true,"error":{"code": -32700,"message": "Parse error"},"id":1}"#, 285 | r#"{"jsonrpc":"2.0","id":1}"#, 286 | r#"{"jsonrpc":"2.0","unknown":[]}"#, 287 | ]; 288 | for case in invalid_cases { 289 | assert!(serde_json::from_str::(case).is_err()); 290 | assert!(serde_json::from_str::(case).is_err()); 291 | } 292 | } 293 | 294 | #[test] 295 | fn batch_response_serialization() { 296 | for ((success_resp, success_expect), (failure_resp, failure_expect)) in 297 | success_response_cases().into_iter().zip(failure_response_cases()) 298 | { 299 | let response = vec![Response::Success(success_resp), Response::Failure(failure_resp)]; 300 | let response_obj = ResponseObj::Batch(response.clone()); 301 | let expect = format!("[{},{}]", success_expect, failure_expect); 302 | 303 | assert_eq!(serde_json::to_string(&response).unwrap(), expect); 304 | assert_eq!(serde_json::to_string(&response_obj).unwrap(), expect); 305 | 306 | assert_eq!(serde_json::from_str::(&expect).unwrap(), response); 307 | assert_eq!(serde_json::from_str::(&expect).unwrap(), response_obj); 308 | } 309 | } 310 | } 311 | --------------------------------------------------------------------------------