├── .github
└── workflows
│ └── rust.yml
├── .gitignore
├── .gitmodules
├── .gitpod.yml
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── crates
├── hrpc-build
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ ├── TONIC_LICENSE
│ │ ├── client.rs
│ │ ├── lib.rs
│ │ ├── prost.rs
│ │ └── server.rs
├── hrpc-proc-macro
│ ├── Cargo.toml
│ └── src
│ │ └── lib.rs
└── hrpc
│ ├── Cargo.toml
│ ├── README.md
│ ├── build.rs
│ └── src
│ ├── body.rs
│ ├── client
│ ├── boxed.rs
│ ├── error.rs
│ ├── layer
│ │ ├── backoff.rs
│ │ └── mod.rs
│ ├── mod.rs
│ ├── socket.rs
│ └── transport
│ │ ├── http
│ │ ├── hyper.rs
│ │ ├── mod.rs
│ │ └── wasm.rs
│ │ ├── mock.rs
│ │ └── mod.rs
│ ├── common
│ ├── buf.rs
│ ├── extensions.rs
│ ├── future.rs
│ ├── layer
│ │ ├── mod.rs
│ │ ├── modify.rs
│ │ └── trace.rs
│ ├── mod.rs
│ ├── socket.rs
│ └── transport
│ │ ├── http.rs
│ │ ├── mock.rs
│ │ ├── mod.rs
│ │ ├── tokio_tungstenite.rs
│ │ └── ws_wasm.rs
│ ├── decode.rs
│ ├── encode.rs
│ ├── lib.rs
│ ├── proto.rs
│ ├── request.rs
│ ├── response.rs
│ └── server
│ ├── error.rs
│ ├── layer
│ ├── mod.rs
│ └── ratelimit.rs
│ ├── macros.rs
│ ├── mod.rs
│ ├── router.rs
│ ├── service.rs
│ ├── socket.rs
│ ├── transport
│ ├── http
│ │ ├── impl.rs
│ │ ├── layer
│ │ │ ├── errid_to_status.rs
│ │ │ └── mod.rs
│ │ ├── mod.rs
│ │ └── ws.rs
│ ├── mock.rs
│ └── mod.rs
│ └── utils.rs
├── examples
├── chat
│ ├── README.md
│ ├── client
│ │ ├── Cargo.toml
│ │ └── src
│ │ │ └── main.rs
│ ├── common
│ │ ├── Cargo.toml
│ │ ├── build.rs
│ │ └── src
│ │ │ ├── chat.proto
│ │ │ └── lib.rs
│ ├── server
│ │ ├── Cargo.toml
│ │ └── src
│ │ │ └── main.rs
│ └── wasm_client
│ │ ├── Cargo.toml
│ │ ├── index.html
│ │ └── src
│ │ └── main.rs
├── hello_world
│ ├── Cargo.toml
│ ├── build.rs
│ └── src
│ │ ├── bin
│ │ ├── client.rs
│ │ └── server.rs
│ │ ├── hello.proto
│ │ └── lib.rs
├── interop
│ ├── Cargo.toml
│ ├── build.rs
│ ├── proto
│ │ └── test.proto
│ └── src
│ │ └── main.rs
└── mock
│ ├── Cargo.toml
│ ├── README.md
│ ├── build.rs
│ └── src
│ ├── hello.proto
│ ├── lib.rs
│ └── main.rs
├── flake.lock
├── flake.nix
├── rust-toolchain.toml
└── shell.nix
/.github/workflows/rust.yml:
--------------------------------------------------------------------------------
1 | name: Rust
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | paths-ignore:
7 | - 'README.md'
8 | - '**/*.nix'
9 | - 'nix/envrc'
10 | - 'flake.lock'
11 | pull_request:
12 | branches: [ master ]
13 | paths-ignore:
14 | - 'README.md'
15 | - '**/*.nix'
16 | - 'nix/envrc'
17 | - 'flake.lock'
18 |
19 | env:
20 | CARGO_TERM_COLOR: always
21 |
22 | jobs:
23 | all:
24 | runs-on: ubuntu-latest
25 | steps:
26 | - name: Checkout repo
27 | uses: actions/checkout@v2
28 | with:
29 | submodules: true
30 |
31 | - name: Install dependencies
32 | run: |
33 | sudo apt update -yy
34 | sudo apt install -yy --no-install-recommends protobuf-compiler
35 | - name: Install rust
36 | run: rustup default stable && rustup update && rustup component add rustfmt clippy
37 |
38 | - name: Cache rust
39 | uses: Swatinem/rust-cache@v1
40 |
41 | - name: Build common hrpc
42 | run: cargo build --package hrpc --features server,client --verbose
43 |
44 | - name: Test common hrpc
45 | run: RUST_LOG=info cargo test --package hrpc --features server,client --verbose
46 |
47 | - name: Test all targets
48 | run: RUST_LOG=info cargo test --all-targets --verbose
49 |
50 | - name: Doc
51 | run: cargo doc --no-deps --all-features --verbose
52 |
53 | - name: Finalize documentation
54 | run: |
55 | CRATE_NAME=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]' | cut -f2 -d"/")
56 | echo "" > target/doc/index.html
57 | touch target/doc/.nojekyll
58 |
59 | - name: Publish docs
60 | if: ${{ github.event_name == 'push' }}
61 | uses: peaceiris/actions-gh-pages@v3
62 | with:
63 | github_token: ${{ secrets.GITHUB_TOKEN }}
64 | publish_dir: target/doc/
65 | publish_branch: gh-pages
66 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Rust
2 | /target/
3 | **/*.rs.bk
4 | examples/chat/wasm_client/dist
5 |
6 | # Nix
7 | /result*
8 | /nix/result*
9 |
10 | # Direnv
11 | /.direnv
12 | /.envrc
13 |
14 | # Editor
15 | /.vscode
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "crates/hrpc/hrpc-main"]
2 | path = crates/hrpc/hrpc-main
3 | url = https://github.com/harmony-development/hrpc.git
4 |
--------------------------------------------------------------------------------
/.gitpod.yml:
--------------------------------------------------------------------------------
1 | # This configuration file was automatically generated by Gitpod.
2 | # Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
3 | # and commit this file to your remote git repository to share the goodness with others.
4 |
5 | tasks:
6 | - init: echo "init"
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = [
3 | "crates/*",
4 | "examples/interop",
5 | "examples/hello_world",
6 | "examples/mock",
7 | "examples/chat/*",
8 | ]
9 | resolver = "2"
10 |
11 | [workspace.metadata.nix]
12 | systems = ["x86_64-linux"]
13 | devshell.packages = ["cargo-watch", "trunk"]
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2021 Yusuf Bera Ertan
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://crates.io/crates/hrpc) [](https://docs.rs/hrpc) [](https://harmonyapp.io/hrpc-rs)
2 |
3 | # hrpc-rs
4 |
5 | This repo contains an implementation of [hRPC](https://github.com/harmony-development/hrpc) in Rust:
6 | - `crates/hrpc` contains generic server / client code and transports,
7 | - `crates/hrpc-build` is contains codegen code that generates client or server code,
8 | - `examples/interop` is used to test the implementation against itself and other servers.
9 | - `examples` contains commented examples.
10 | - To run an example's server: `cargo run --package example_name --bin server`
11 | - To run an example's client: `cargo run --package example_name --bin client`
12 |
13 | ## Getting started
14 |
15 | Check out the [chat](./examples/chat) example!
--------------------------------------------------------------------------------
/crates/hrpc-build/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "hrpc-build"
3 | version = "0.33.1"
4 | authors = ["Yusuf Bera Ertan "]
5 | edition = "2021"
6 | license = "MIT"
7 | repository = "https://github.com/harmony-development/hrpc-rs"
8 | homepage = "https://github.com/harmony-development/hrpc-rs"
9 | description = "Code generation for hRPC."
10 | keywords = ["hrpc", "harmony", "rpc", "build", "protobuf"]
11 | categories = ["development-tools::build-utils"]
12 |
13 | [dependencies]
14 | prost-build = "0.10"
15 | syn = "1.0"
16 | quote = "1.0"
17 | proc-macro2 = "1.0"
18 |
19 | [features]
20 | default = ["rustfmt"]
21 | rustfmt = []
22 |
23 | server = []
24 | client = []
25 |
26 | # Shorthand to enable `server_default_transport_http` and `client_default_transport_hyper_http`
27 | default_transport_http = [
28 | "server_default_transport_http",
29 | "client_default_transport_hyper_http",
30 | ]
31 |
32 | server_default_transport_http = []
33 | client_default_transport_hyper_http = []
34 | client_default_transport_wasm_http = []
35 |
36 | [package.metadata.docs.rs]
37 | all-features = true
38 |
39 | [package.metadata.nix]
40 | build = true
41 | library = true
--------------------------------------------------------------------------------
/crates/hrpc-build/README.md:
--------------------------------------------------------------------------------
1 | # hrpc-build
2 |
3 | Contains code generation (protobuf, server and client) for hRPC.
--------------------------------------------------------------------------------
/crates/hrpc-build/src/TONIC_LICENSE:
--------------------------------------------------------------------------------
1 | `hrpc-build` uses code from `tonic-build`. `tonic-build`'s
2 | license is included below:
3 |
4 | Copyright (c) 2020 Lucio Franco
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/crates/hrpc-build/src/client.rs:
--------------------------------------------------------------------------------
1 | use super::{Method, Service};
2 | use crate::{generate_doc_comments, naive_snake_case};
3 | use proc_macro2::TokenStream;
4 | use quote::{format_ident, quote};
5 |
6 | /// Generate service for client.
7 | ///
8 | /// This takes some `Service` and will generate a `TokenStream` that contains
9 | /// a public module with the generated client.
10 | pub fn generate(service: &T, proto_path: &str) -> TokenStream {
11 | let service_ident = quote::format_ident!("{}Client", service.name());
12 | let client_mod = quote::format_ident!("{}_client", naive_snake_case(service.name()));
13 | let methods = generate_methods(service, proto_path);
14 |
15 | let service_doc = generate_doc_comments(service.comment());
16 |
17 | let create_methods = quote! {
18 | impl #service_ident {
19 | /// Create a new client using the provided transport.
20 | pub fn new_transport(transport: Inner) -> Self {
21 | Self {
22 | inner: Client::new(transport)
23 | }
24 | }
25 |
26 | /// Create a new client using the provided generic client.
27 | pub fn new_inner(client: Client) -> Self {
28 | Self {
29 | inner: client,
30 | }
31 | }
32 | }
33 | };
34 |
35 | #[allow(unused_mut)]
36 | let mut def_transport_impl = TokenStream::new();
37 |
38 | #[cfg(feature = "client_default_transport_hyper_http")]
39 | def_transport_impl.extend(quote! {
40 | use hrpc::{client::transport::http::{Hyper, HyperError}, exports::http::Uri};
41 |
42 | impl #service_ident {
43 | /// Create a new client using HTTP transport.
44 | ///
45 | /// Panics if the passed URI is an invalid URI.
46 | pub fn new(server: U) -> ClientResult
47 | where
48 | U: TryInto,
49 | U::Error: Debug,
50 | {
51 | let transport =
52 | Hyper::new(server.try_into().expect("invalid URL"))
53 | .map_err(TransportError::from)
54 | .map_err(ClientError::from)?;
55 | Ok(Self {
56 | inner: Client::new(transport),
57 | })
58 | }
59 | }
60 | });
61 |
62 | #[cfg(feature = "client_default_transport_wasm_http")]
63 | def_transport_impl.extend(quote! {
64 | use hrpc::{client::transport::http::{Wasm, WasmError}, exports::http::Uri};
65 |
66 | impl #service_ident {
67 | /// Create a new client using HTTP transport.
68 | ///
69 | /// Panics if the passed URI is an invalid URI.
70 | pub fn new(server: U) -> ClientResult
71 | where
72 | U: TryInto,
73 | U::Error: Debug,
74 | {
75 | let transport =
76 | Wasm::new(server.try_into().expect("invalid URL"))
77 | .map_err(TransportError::from)
78 | .map_err(ClientError::from)?;
79 | Ok(Self {
80 | inner: Client::new(transport),
81 | })
82 | }
83 | }
84 | });
85 |
86 | quote! {
87 | /// Generated client implementations.
88 | #[allow(dead_code, unused_imports)]
89 | pub mod #client_mod {
90 | use hrpc::client::prelude::*;
91 |
92 | #service_doc
93 | #[derive(Debug, Clone)]
94 | pub struct #service_ident {
95 | inner: Client,
96 | }
97 |
98 | impl #service_ident
99 | where
100 | Inner: Service> + 'static,
101 | InnerErr: 'static,
102 | {
103 | #methods
104 | }
105 |
106 | #create_methods
107 | #def_transport_impl
108 | }
109 | }
110 | }
111 |
112 | fn generate_methods(service: &T, proto_path: &str) -> TokenStream {
113 | let mut stream = TokenStream::new();
114 |
115 | for method in service.methods() {
116 | let path = format!(
117 | "/{}{}{}/{}",
118 | service.package(),
119 | if service.package().is_empty() {
120 | ""
121 | } else {
122 | "."
123 | },
124 | service.identifier(),
125 | method.identifier()
126 | );
127 |
128 | let make_method = match (method.client_streaming(), method.server_streaming()) {
129 | (false, false) => generate_unary,
130 | (true, true) => generate_streaming,
131 | (false, true) => generate_server_streaming,
132 | (true, false) => generate_client_streaming,
133 | };
134 |
135 | stream.extend(generate_doc_comments(method.comment()));
136 | stream.extend(make_method(method, proto_path, path));
137 | }
138 |
139 | stream
140 | }
141 |
142 | fn generate_unary(method: &T, proto_path: &str, path: String) -> TokenStream {
143 | let ident = format_ident!("{}", method.name());
144 | let (request, response) = method.request_response_name(proto_path);
145 |
146 | quote! {
147 | pub fn #ident(&mut self, req: Req) -> impl Future