-
Notifications
You must be signed in to change notification settings - Fork 189
add IntoResponse
trait for method calls
#1057
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
Conversation
module.register_method("say_hello", |_, _| Ok("hello")).unwrap(); | ||
module.register_method("notif", |_, _| Ok("")).unwrap(); | ||
module.register_method("say_hello", |_, _| "hello").unwrap(); | ||
module.register_method("notif", |_, _| "").unwrap(); |
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.
so yeah, no need to return Result
here anymore
221da19
to
4f9567d
Compare
/// | ||
/// If the value couldn't be serialized/encoded, jsonrpsee will sent out an error | ||
/// to the client `response could not be serialized`. | ||
pub trait IntoResponse { |
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 is the essential part of this PR
core/src/server/mod.rs
Outdated
type Output: serde::Serialize; | ||
|
||
/// Something that can be converted into a JSON-RPC method call response. | ||
fn into_response(self) -> PartialResponse<Self::Output>; |
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.
We may want to return Result
here and serialize the response in into_response
but I don't recall why I did it this way. Might be better to have the users do this explicitly
Currently the entire Response
is serialized in a helper function which will emit a log and sent out an error to the method call.
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 guess I'm wondering ; can we justify the differences between this and
pub trait IntoSubscriptionCloseResponse {
fn into_response(self) -> SubscriptionCloseResponse;
}
Right now IntoResponse
is really quite different (more generic, for instance with its associated param, and so on). Should these traits be aiming to be as consistent with eachother as makes sense, or are there good reasons for the differences that we can explain?
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 would say that they are fundamentally different mainly because IntoResponse
is something that converts a response to a JSON-RPC response or JSON-RPC error object (a response with the result
or error
entry). IntoSubscriptionClose
is really just something that is converted into a subscription notification.
In practice, users would not need to implement IntoResponse
except some rare edge-cases for custom types that are not Result<T, E>
.
For example you could return:
enum MyResponse {
Success(String),
Error1,
Error2 }
where one would need to implement this trait
Thus, is in practice the only trait that folks needs to implement is IntoErrorObject
where some state/error is converted into a JSON-RPC error code, message and similar. As long folks returns Result<T, IntoErrorObject
or primitive types IntoResponse
should already be implemented (perhaps we should add it for sequences and tuple as well)
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 would say that they are fundamentally different mainly because IntoResponse is something that converts a response to a JSON-RPC response or JSON-RPC error object (a response with the result or error entry). IntoSubscriptionClose is really just something that is converted into a subscription notification.
I guess what I meant was that they both basically take some type and return some part of a JSON response, but this one comes with an associated type and is more generic, and this isn't. It's not a huge deal for me; just wondering whether there is anything that should be made more consistent between these traits, but happy if not :)
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.
we want to enforce these formats:
{"jsonrpc": "2.0", "result": -19, "id": 2}
{"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": "1"}
and be flexible enough for folks to use so we could serialize the response in the IntoResponse impl
then remove the generic stuff which probably is better
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 guess efficiency is the main thing; if we can be less generic ie have something like:
pub trait IntoResponse {
fn into_response(self) -> Response; // returns some hardcoded Response type
}
Where into_response
is serializing into some non-generic type, and if that doesn't lose out in efficiency (because we have to serializie stuff anyway or whatever) then I geuss it'd be easier to udnerstand and more consistent with the subscription version.
But if having the generic PartialResponse<Self::Output>
type thing makes stuff more efficient then I get it :)
IntroResponse
trait for method callsIntoResponse
trait for method calls
IntoResponse
trait for method callsIntoResponse
trait for method calls
.register_async_method(ASYNC_FAST_CALL, |_, _| async { Result::<_, jsonrpsee::core::Error>::Ok("lo") }) | ||
.unwrap(); | ||
module.register_method(SYNC_FAST_CALL, |_, _| "lo").unwrap(); | ||
module.register_async_method(ASYNC_FAST_CALL, |_, _| async { "lo" }).unwrap(); |
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'm surprised but happy that you can remove the type annotations!
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.
it's cheating I implemented the IntoResponse trait
for the primitive types so that can go away :P
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.4.0 to 3.5.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](actions/checkout@v3.4.0...v3.5.0) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [baptiste0928/cargo-install](https://github.com/baptiste0928/cargo-install) from 1 to 2. - [Release notes](https://github.com/baptiste0928/cargo-install/releases) - [Changelog](https://github.com/baptiste0928/cargo-install/blob/main/CHANGELOG.md) - [Commits](baptiste0928/cargo-install@v1...v2) --- updated-dependencies: - dependency-name: baptiste0928/cargo-install dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* fix: tokio v1.27 * Update server/src/transport/ws.rs * fix rustdoc * Update server/src/transport/ws.rs * Update server/src/transport/ws.rs * no more futuredriver for incoming conns * add comment for unclear code
} | ||
} | ||
|
||
impl_into_response!( |
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.
we could remove these because it doesn't work to implement these for [T], Vec<T>
and other sequence types because of the result impl above
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 guess these are just convenience wrappers for when people want to return these basic types, so I don't see any issue keeping them around personally :) Makes it easier to write examples and such anyway!
Possible an impl for Option<T> where T: Serialize
would be nice, as much as anything to let people write Some(arbitrary_thing_that_can_be_serialized)
and not need to provide a type for E
, but I guess mostly people will be returning Results.
} | ||
} | ||
|
||
impl<'a> ResponsePayload<'a, ()> { |
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.
Nit: Maybe it doesn't matter but couldn't ()
be T
instead and these methods part of the above impl block? (T
is never used, so downside is you may need to specify it, upside is that you can create an error_borrowed()
etc that would be compatible with any other ResponsePayload<T>
is that ever mattered)
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 did it like that because specifying the ()
all over the place for error stuff is annoying
core/src/server/mod.rs
Outdated
/// to the client `response could not be serialized`. | ||
pub trait IntoResponse { | ||
/// Output. | ||
type Output: serde::Serialize + ToOwned<Owned = Self::Output>; |
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.
Aah I see; it's a shame about the ToOwned
but I see why!
ToOwned
is impl for all T where T: Clone
, so I wonder whether there type Output: serde::Serialize + Clone
would be possible also and maybe a touch cleaner looking?. Hmm but the current approach is more "correct" though. Will have to ponder this, but yeah maybe it's best as it is here :)
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 know so I was strict and went with ToOwned
and I don't really care.
Seems like it's equal to Clone
in practice anyway.
// TODO: hack around the lifetime on the `SubscriptionId` by deserialize first to serde_json::Value. | ||
let as_success: ResponseSuccess<serde_json::Value> = serde_json::from_str::<Response<_>>(&resp.result)? | ||
.try_into() | ||
.map_err(|e| Error::Call(CallError::Custom(e)))?; |
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.
Ah so ResponseSuccess<SubscriptionId>
wouldn't work? SubscriptionId
looks like it would be able to borrow the string OK from the input (and then you could into_owned()
on it to make it 'static
); I wonder what didn't work here basically :)
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.
so if I deserialize to to SubscriptionId<'a>
then later calls sub_id.into_owned()
then serde complains about static lifetime issues
proc-macros/src/helpers.rs
Outdated
@@ -87,7 +87,7 @@ fn find_jsonrpsee_crate(crate_names: &[&str]) -> Result<proc_macro2::TokenStream | |||
/// use jsonrpsee::core::{RpcResult, SubscriptionResult}; | |||
/// | |||
/// #[rpc(client, server)] | |||
/// pub trait RpcTrait<A, B, C> { | |||
/// pub trait RpcTrait<A, B: Clone, C> { |
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.
Out of interest, why did this need adding?
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.
(some question for some other Clone
's. Is it to do with the ToOwned
bound on the response? Will this make using the macro more painful?)
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.
Every response type for the server needs to implement Clone/ToOwned
such as
fn call() -> Result<T, Error>;
T will be used in IntoResponse trait
and thus must implement Clone.
Yes, it will be more annoying to use but Alex added these custom bounds on the traits which is okay I guess.
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.
it's a trade-off all these annoyance can away with separate serialize/deserialize types :)
That's why I skipped these complicated stuff in the first place but reduces some technical debt with duplicate types and hard to read.
This PR adds a similar trait as we introduced for RPC subscriptions but for RPC method calls.
The benefit is that the return types are more flexible and any type that implements
IntoResponse
can be defined in the RPC proc macro API after this change but it only works for the `proc macro API for the server-only (see #1067 for futher information)