-
Notifications
You must be signed in to change notification settings - Fork 2
feat: add support for batched JSON-RPC requests #65
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
0410582
bcc612d
05829f5
dc4d0bb
dce5081
e72299a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,7 +51,7 @@ | |
| //! ``` | ||
| //! | ||
| //! [`Service`]: tower::Service | ||
|
|
||
| use crate::convert::CreateResponseFilter; | ||
| use crate::{ | ||
| convert::{ | ||
| ConvertRequest, ConvertRequestLayer, ConvertResponse, ConvertResponseLayer, | ||
|
|
@@ -61,15 +61,16 @@ use crate::{ | |
| }; | ||
| pub use id::{ConstantSizeId, Id}; | ||
| pub use request::{ | ||
| HttpJsonRpcRequest, JsonRequestConversionError, JsonRequestConverter, JsonRpcRequest, | ||
| BatchJsonRpcRequest, HttpBatchJsonRpcRequest, HttpJsonRpcRequest, JsonRequestConversionError, | ||
| JsonRequestConverter, JsonRpcRequest, | ||
| }; | ||
| pub use response::{ | ||
| ConsistentJsonRpcIdFilter, ConsistentResponseIdFilterError, CreateJsonRpcIdFilter, | ||
| HttpJsonRpcResponse, JsonResponseConversionError, JsonResponseConverter, JsonRpcError, | ||
| JsonRpcResponse, JsonRpcResult, | ||
| BatchJsonRpcResponse, ConsistentJsonRpcIdFilter, ConsistentResponseIdFilterError, | ||
| CreateJsonRpcIdFilter, HttpBatchJsonRpcResponse, HttpJsonRpcResponse, | ||
| JsonResponseConversionError, JsonResponseConverter, JsonRpcError, JsonRpcResponse, | ||
| }; | ||
| use serde::{de::DeserializeOwned, Serialize}; | ||
| use std::marker::PhantomData; | ||
| use std::{fmt::Debug, marker::PhantomData}; | ||
| use tower_layer::{Layer, Stack}; | ||
| pub use version::Version; | ||
|
|
||
|
|
@@ -132,21 +133,77 @@ where | |
| } | ||
| } | ||
|
|
||
| /// Middleware that combines a [`HttpConversionLayer`], a [`JsonConversionLayer`] to create | ||
| /// an JSON-RPC over HTTP [`Service`]. | ||
| /// Middleware that combines an [`HttpConversionLayer`] and a [`JsonConversionLayer`] to create | ||
| /// a JSON-RPC over HTTP [`Service`]. | ||
| /// | ||
| /// This middleware can be used either with regular JSON-RPC requests and responses (i.e. | ||
| /// [`JsonRpcRequest`] and [`JsonRpcResponse`]) or with batch JSON-RPC requests and responses | ||
| /// (i.e. [`BatchJsonRpcRequest`] and [`BatchJsonRpcResponse`]). | ||
| /// | ||
| /// This middleware includes a [`ConsistentJsonRpcIdFilter`], which ensures that each response | ||
| /// carries a valid JSON-RPC ID matching the corresponding request ID. This guarantees that the | ||
| /// [`Service`] complies with the [JSON-RPC 2.0 specification]. | ||
| /// | ||
| /// # Examples | ||
| /// | ||
| /// Create a simple JSON-RPC over HTTP client. | ||
| /// ``` | ||
| /// use canhttp::{ | ||
| /// Client, | ||
| /// http::json::{HttpJsonRpcRequest, HttpJsonRpcResponse, JsonRpcHttpLayer} | ||
| /// }; | ||
| /// use serde::{de::DeserializeOwned, Serialize}; | ||
| /// use std::fmt::Debug; | ||
| /// use tower::{BoxError, Service, ServiceBuilder}; | ||
| /// | ||
| /// fn client<Params, Result>() -> impl Service< | ||
| /// HttpJsonRpcRequest<Params>, | ||
| /// Response = HttpJsonRpcResponse<Result>, | ||
| /// Error = BoxError | ||
| /// > | ||
| /// where | ||
| /// Params: Debug + Serialize, | ||
| /// Result: Debug + DeserializeOwned, | ||
| /// { | ||
| /// ServiceBuilder::new() | ||
| /// .layer(JsonRpcHttpLayer::new()) | ||
| /// .service(Client::new_with_box_error()) | ||
| /// } | ||
| /// ``` | ||
| /// | ||
| /// Create a simple batch JSON-RPC over HTTP client. | ||
| /// ``` | ||
| /// use canhttp::{ | ||
| /// Client, | ||
| /// http::json::{HttpBatchJsonRpcRequest, HttpBatchJsonRpcResponse, JsonRpcHttpLayer} | ||
| /// }; | ||
| /// use serde::{de::DeserializeOwned, Serialize}; | ||
| /// use std::fmt::Debug; | ||
| /// use tower::{BoxError, Service, ServiceBuilder}; | ||
| /// | ||
| /// fn client<Params, Result>() -> impl Service< | ||
| /// HttpBatchJsonRpcRequest<Params>, | ||
| /// Response = HttpBatchJsonRpcResponse<Result>, | ||
| /// Error = BoxError | ||
| /// > | ||
| /// where | ||
| /// Params: Debug + Serialize, | ||
| /// Result: Debug + DeserializeOwned, | ||
| /// { | ||
| /// ServiceBuilder::new() | ||
| /// .layer(JsonRpcHttpLayer::new()) | ||
| /// .service(Client::new_with_box_error()) | ||
| /// } | ||
| /// ``` | ||
| /// | ||
| /// [`Service`]: tower::Service | ||
| /// [JSON-RPC 2.0 specification]: https://www.jsonrpc.org/specification | ||
| #[derive(Debug)] | ||
| pub struct JsonRpcHttpLayer<Params, Result> { | ||
| _marker: PhantomData<(Params, Result)>, | ||
| pub struct JsonRpcHttpLayer<Request, Response> { | ||
| _marker: PhantomData<(Request, Response)>, | ||
| } | ||
|
|
||
| impl<Params, Result> JsonRpcHttpLayer<Params, Result> { | ||
| impl<Request, Response> JsonRpcHttpLayer<Request, Response> { | ||
| /// Returns a new [`JsonRpcHttpLayer`]. | ||
| pub fn new() -> Self { | ||
| Self { | ||
|
|
@@ -155,40 +212,42 @@ impl<Params, Result> JsonRpcHttpLayer<Params, Result> { | |
| } | ||
| } | ||
|
|
||
| impl<Params, Result> Clone for JsonRpcHttpLayer<Params, Result> { | ||
| impl<Request, Response> Clone for JsonRpcHttpLayer<Request, Response> { | ||
| fn clone(&self) -> Self { | ||
| Self { | ||
| _marker: self._marker, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl<Params, Result> Default for JsonRpcHttpLayer<Params, Result> { | ||
| impl<Request, Response> Default for JsonRpcHttpLayer<Request, Response> { | ||
| fn default() -> Self { | ||
| Self::new() | ||
| } | ||
| } | ||
|
|
||
| impl<Params, Result, S> Layer<S> for JsonRpcHttpLayer<Params, Result> | ||
| impl<Request, Response, S> Layer<S> for JsonRpcHttpLayer<Request, Response> | ||
| where | ||
| Params: Serialize, | ||
| Result: DeserializeOwned, | ||
| Request: Serialize, | ||
| Response: DeserializeOwned, | ||
| CreateJsonRpcIdFilter<Request, Response>: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. understanding question: why do we need this new trait bound?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is because the generic changed from just the contents of a |
||
| CreateResponseFilter<http::Request<Request>, http::Response<Response>>, | ||
| { | ||
| type Service = FilterResponse< | ||
| ConvertResponse< | ||
| ConvertRequest< | ||
| ConvertResponse<ConvertRequest<S, HttpRequestConverter>, HttpResponseConverter>, | ||
| JsonRequestConverter<JsonRpcRequest<Params>>, | ||
| JsonRequestConverter<Request>, | ||
| >, | ||
| JsonResponseConverter<JsonRpcResponse<Result>>, | ||
| JsonResponseConverter<Response>, | ||
| >, | ||
| CreateJsonRpcIdFilter<Params, Result>, | ||
| CreateJsonRpcIdFilter<Request, Response>, | ||
| >; | ||
|
|
||
| fn layer(&self, inner: S) -> Self::Service { | ||
| stack( | ||
| HttpConversionLayer, | ||
| JsonConversionLayer::<JsonRpcRequest<Params>, JsonRpcResponse<Result>>::new(), | ||
| JsonConversionLayer::<Request, Response>::new(), | ||
| CreateResponseFilterLayer::new(CreateJsonRpcIdFilter::new()), | ||
| ) | ||
| .layer(inner) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems very similar to the previous example and it's a priori difficult to see what the exact difference is. Maybe we could have a
.batchmethod that transforms a serviceService< HttpJsonRpcRequest<Params>, HttpJsonRpcResponse<Result>, Error >into anService< HttpBatchJsonRpcRequest<Params>, HttpBatchJsonRpcResponse<Result>, Error >?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that would be possible because we would want to modify some of the internal middlewares within the service to achieve that. See this thread, but it's possible to build a generic method for the client that handles both types of requests.