capnproto-rust icon indicating copy to clipboard operation
capnproto-rust copied to clipboard

Improve documentation of capability list

Open MarcelGarus opened this issue 4 years ago • 3 comments

I like this package a lot, but I'm struggling with how to create a capability list. The documentation for the module just says "List of capabilities." I assume it should somehow be done using the Builder, but even then, I can't figure out how to use it to create a capability list from a Vec of RPC clients.

For me and probably other users, some more documentation around how to actually create a capability list would be useful.

MarcelGarus avatar May 31 '21 09:05 MarcelGarus

having the same issue.

jan-br avatar Jul 23 '21 21:07 jan-br

Alright, I managed to find a solution, 2 actually... though this still doesn't feel correct.

Using client and extracting its hook:

It is possible to extract the client hook from a server object by making it into a client first:

let server_implementations = ...; // Some Vec of server object implementations
let mut list = ...; // Your capability list builder, usually obtained by calling results.init_field_name(data.len());

for (index, server) in server_implementations.into_iter().enumerate()  {
  let client_hook = capnp_rpc::new_client::<your_capnp::ty::Client, _>(server).client.hook;
  list.set(index as u32, client_hook);
}

Using your own wrapper type

Alternatively, here is a way which probably has tiny bit less overhead by not constructing a client object:

use std::marker::PhantomData;

use capnp::capability::{FromClientHook, FromServer};
use capnp::private::capability::ClientHook;

pub struct ClientHookProxy<T, S>
where
  T: FromServer<S>,
{
  hook: Box<dyn ClientHook>,
  _data_t: PhantomData<T>,
  _data_s: PhantomData<S>,
}

impl<T, S> ClientHookProxy<T, S>
where
  T: FromServer<S>,
{
  pub fn apply(server: S) -> Box<dyn ClientHook> {
    capnp_rpc::new_client::<Self, _>(server).into()
  }
}

impl<T, S> FromClientHook for ClientHookProxy<T, S>
where
  T: FromServer<S>,
{
  fn new(hook: Box<dyn ClientHook>) -> Self {
    Self {
      hook,
      _data_t: PhantomData,
      _data_s: PhantomData,
    }
  }
}

impl<T, S> FromServer<S> for ClientHookProxy<T, S>
where
  T: FromServer<S>,
{
  type Dispatch = T::Dispatch;

  fn from_server(s: S) -> Self::Dispatch {
    T::from_server(s)
  }
}

impl<T, S> Into<Box<dyn ClientHook>> for ClientHookProxy<T, S>
where
  T: FromServer<S>,
{
  fn into(self) -> Box<dyn ClientHook> {
    self.hook
  }
}

Use such as:

let server_implementations = ...; // Some Vec of server object implementations
let mut list = ...; // Your capability list builder, usually obtained by calling results.init_field_name(data.len());

for (index, server) in server_implementations.into_iter().enumerate()  {
  let client_hook = ClientHookProxy::<your_capnp::ty::Client, _>::apply(server);
  list.set(index as u32, client_hook);
}

Futher work

I don't think thats the intended way. I'm not sure if I'm overlooking something, but in case there is no better way to do that with the current library, improvement is required. I consider both of the approaches above workarounds - it does the job for now, but documentation and usability needs to improve.

Janrupf avatar Jul 27 '21 14:07 Janrupf

One thing that could help is if we add an IntoInternalClientHook trait, similar to the existing IntoInternalStructReader method: https://github.com/capnproto/capnproto-rust/blob/fad4f953194cd1cce9da8cef51234a7608364161/capnp/src/traits.rs#L36

Then capability_list::Builder::set() could have a friendlier type signature.

dwrensha avatar Jul 27 '21 15:07 dwrensha