Skip to content
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

[proc macros]: client implementations with subscriptions doesn't work for HTTP client #448

Closed
Tracked by #492
niklasad1 opened this issue Sep 3, 2021 · 3 comments · Fixed by #563
Closed
Tracked by #492

Comments

@niklasad1
Copy link
Member

niklasad1 commented Sep 3, 2021

For example if a define a API like this:

#[rpc(server, client, namespace = "state")]
pub trait Rpc<Hash: Clone, StorageKey>
where
	Hash: std::fmt::Debug,
{
	/// Async method call example.
	#[method(name = "getKeys")]
	async fn storage_keys(&self, storage_key: StorageKey, hash: Option<Hash>) -> Result<Vec<StorageKey>, Error>;

	/// Subscription that takes a `StorageKey` as input and produces a `Vec<Hash>`.
	#[subscription(name = "subscribeStorage", item = Vec<Hash>)]
	fn subscribe_storage(&self, keys: Option<Vec<StorageKey>>);
}

#[tokio::main]
async fn main() {
    let client = HttpClientBuilder::default().build(url)?;
    Rpc::<(), ()>::storage_keys((), None).await.unwrap();
}

Then if I try to use for the HTTP client it won't work because the client implementation requires SubscriptionClient trait bound when it has at least one subscription in the trait.

For those we should just ignore the subscriptions and require the Client trait bound instead of possible, thoughts or suggestions?

@dvdplm
Copy link
Contributor

dvdplm commented Sep 6, 2021

For those we should just ignore the subscriptions and require the Client bound instead of possible, thought or suggestions?

Not sure I understand what this means, can you elaborate?

@niklasad1
Copy link
Member Author

niklasad1 commented Sep 6, 2021

Alright,

To elaborate a little more, for the clients we have two internal traits to model the capabilities of the client:

  1. Client - basic functionality to perform JSON-RPC method calls and notifications.
  2. SubscriptionClient - client functionality + subscription support.

If I define a trait RPC trait with methods calls, for example as the following:

#[rpc(server, client, namespace = "state")]
pub trait Rpc<Hash: Clone, StorageKey>
where
	Hash: std::fmt::Debug,
{
	/// Async method call example.
	#[method(name = "getKeys")]
	async fn storage_keys(&self, storage_key: StorageKey, hash: Option<Hash>) -> Result<Vec<StorageKey>, Error>;
}

Expands to

pub trait RpcClient<Hash: Clone, StorageKey>: jsonrpsee::types::traits::Client
where
    Hash: Send + Sync + 'static + jsonrpsee::types::Serialize + std::fmt::Debug,
    StorageKey:
        Send + Sync + 'static + jsonrpsee::types::Serialize + jsonrpsee::types::DeserializeOwned,
{
    ///Invokes the RPC method `state_getKeys`.
    #[must_use]
    #[allow(
        clippy::let_unit_value,
        clippy::type_complexity,
        clippy::type_repetition_in_bounds,
        clippy::used_underscore_binding
    )]
    fn storage_keys<'life0, 'async_trait>(
        &'life0 self,
        storage_key: StorageKey,
        hash: Option<Hash>,
    ) -> ::core::pin::Pin<
        Box<
            dyn ::core::future::Future<Output = Result<Vec<StorageKey>, Error>>
                + ::core::marker::Send
                + 'async_trait,
        >,
    >
    where
        'life0: 'async_trait,
        Self: ::core::marker::Sync + 'async_trait,
    {
        Box::pin(async move {
            if let ::core::option::Option::Some(__ret) =
                ::core::option::Option::None::<Result<Vec<StorageKey>, Error>>
            {
                return __ret;
            }
            let __self = self;
            let storage_key = storage_key;
            let hash = hash;
            let __ret: Result<Vec<StorageKey>, Error> = {
                __self
                    .request(
                        "state_getKeys",
                        <[_]>::into_vec(box [
                            jsonrpsee::types::__reexports::serde_json::to_value(&storage_key)?,
                            jsonrpsee::types::__reexports::serde_json::to_value(&hash)?,
                        ])
                        .into(),
                    )
                    .await
            };
            #[allow(unreachable_code)]
            __ret
        })
    }
}
impl<T, Hash: Clone, StorageKey> RpcClient<Hash, StorageKey> for T
where
    T: jsonrpsee::types::traits::Client,
    Hash: Send + Sync + 'static + jsonrpsee::types::Serialize + std::fmt::Debug,
    StorageKey:
        Send + Sync + 'static + jsonrpsee::types::Serialize + jsonrpsee::types::DeserializeOwned,
{
}

If I define a trait RPC trait with at least one subscription, for example as the following:

#[rpc(server, client, namespace = "state")]
pub trait Rpc<Hash: Clone, StorageKey>
where
	Hash: std::fmt::Debug,
{
	/// Async method call example.
	#[method(name = "getKeys")]
	async fn storage_keys(&self, storage_key: StorageKey, hash: Option<Hash>) -> Result<Vec<StorageKey>, Error>;

	/// Subscription that takes a `StorageKey` as input and produces a `Vec<Hash>`.
	#[subscription(name = "subscribeStorage", item = Vec<Hash>)]
	fn subscribe_storage(&self, keys: Option<Vec<StorageKey>>);
}

Expands to

pub trait RpcClient<Hash: Clone, StorageKey>: jsonrpsee::types::traits::SubscriptionClient
where
    Hash: Send
        + Sync
        + 'static
        + jsonrpsee::types::Serialize
        + jsonrpsee::types::DeserializeOwned
        + std::fmt::Debug,
    StorageKey:
        Send + Sync + 'static + jsonrpsee::types::Serialize + jsonrpsee::types::DeserializeOwned,
{
    ///Invokes the RPC method `state_getKeys`.
    #[must_use]
    #[allow(
        clippy::let_unit_value,
        clippy::type_complexity,
        clippy::type_repetition_in_bounds,
        clippy::used_underscore_binding
    )]
    fn storage_keys<'life0, 'async_trait>(
        &'life0 self,
        storage_key: StorageKey,
        hash: Option<Hash>,
    ) -> ::core::pin::Pin<
        Box<
            dyn ::core::future::Future<Output = Result<Vec<StorageKey>, Error>>
                + ::core::marker::Send
                + 'async_trait,
        >,
    >
    where
        'life0: 'async_trait,
        Self: ::core::marker::Sync + 'async_trait,
    {
        Box::pin(async move {
            if let ::core::option::Option::Some(__ret) =
                ::core::option::Option::None::<Result<Vec<StorageKey>, Error>>
            {
                return __ret;
            }
            let __self = self;
            let storage_key = storage_key;
            let hash = hash;
            let __ret: Result<Vec<StorageKey>, Error> = {
                __self
                    .request(
                        "state_getKeys",
                        <[_]>::into_vec(box [
                            jsonrpsee::types::__reexports::serde_json::to_value(&storage_key)?,
                            jsonrpsee::types::__reexports::serde_json::to_value(&hash)?,
                        ])
                        .into(),
                    )
                    .await
            };
            #[allow(unreachable_code)]
            __ret
        })
    }
    ///Subscribes to the RPC method `state_subscribeStorage`.
    #[must_use]
    #[allow(
        clippy::let_unit_value,
        clippy::type_complexity,
        clippy::type_repetition_in_bounds,
        clippy::used_underscore_binding
    )]
    fn subscribe_storage<'life0, 'async_trait>(
        &'life0 self,
        keys: Option<Vec<StorageKey>>,
    ) -> ::core::pin::Pin<
        Box<
            dyn ::core::future::Future<
                    Output = Result<
                        jsonrpsee::types::Subscription<Vec<Hash>>,
                        jsonrpsee::types::Error,
                    >,
                > + ::core::marker::Send
                + 'async_trait,
        >,
    >
    where
        'life0: 'async_trait,
        Self: ::core::marker::Sync + 'async_trait,
    {
        Box::pin(async move {
            if let ::core::option::Option::Some(__ret) = ::core::option::Option::None::<
                Result<jsonrpsee::types::Subscription<Vec<Hash>>, jsonrpsee::types::Error>,
            > {
                return __ret;
            }
            let __self = self;
            let keys = keys;
            let __ret: Result<jsonrpsee::types::Subscription<Vec<Hash>>, jsonrpsee::types::Error> = {
                __self
                    .subscribe(
                        "state_subscribeStorage",
                        <[_]>::into_vec(box [jsonrpsee::types::__reexports::serde_json::to_value(
                            &keys,
                        )?])
                        .into(),
                        "state_unsubscribeStorage",
                    )
                    .await
            };
            #[allow(unreachable_code)]
            __ret
        })
    }
}
impl<T, Hash: Clone, StorageKey> RpcClient<Hash, StorageKey> for T
where
    T: jsonrpsee::types::traits::SubscriptionClient,
    Hash: Send
        + Sync
        + 'static
        + jsonrpsee::types::Serialize
        + jsonrpsee::types::DeserializeOwned
        + std::fmt::Debug,
    StorageKey:
        Send + Sync + 'static + jsonrpsee::types::Serialize + jsonrpsee::types::DeserializeOwned,
{
}

-> This causes the HTTP client not work for RPC API that has at least one subscription, as it requires the SubscriptionClient bound (see example above) which is not implemented for the HTTP client

@dvdplm
Copy link
Contributor

dvdplm commented Sep 6, 2021

which is not implemented for the HTTP client

Ah, that was the piece I was missing. Ok, clear now!

For those we should just ignore the subscriptions and require the Client trait bound instead of possible, thoughts or suggestions?

It's either that or:

  1. implement subscriptions for http (awkward, not a good idea imo)
  2. change the macro api to replace client with ws-client and http-client (and throw error if an http-client uses subscriptions)
  3. like you say, ignore any subscriptions for http clients and only implement the Client trait for them.

/cc @maciejhirsz

@dvdplm dvdplm mentioned this issue Oct 1, 2021
10 tasks
dvdplm added a commit that referenced this issue Nov 15, 2021
Closes #448

This PR adds an implementation for `SubscriptionClient` to the `HttpClient` struct, which makes it possible for http clients to use macro-generated RPC servers. If an http client tries to set up a subscription it will fail with a `HttpNotImplemented` error.
dvdplm added a commit that referenced this issue Nov 17, 2021
Closes #448

This PR adds an implementation for `SubscriptionClient` to the `HttpClient` struct, which makes it possible for http clients to use macro-generated RPC servers. If an http client tries to set up a subscription it will fail with a `HttpNotImplemented` error.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants