purveyor 0.1.4

Macros to generate server and client code from a simple protocol definition
Documentation
pub use paste;

#[macro_export]
macro_rules! protocol {
    (
        type Error = $err:ident;

        $(
            $module:ident {
                $(
                    fn $handler:ident ( $($arg:ident : $type:ty ),* ) -> $ret:ty;
                )*
            }
        )*
    ) => {
        purveyor::paste::paste! {
            #[derive(Debug, Clone, borsh::BorshSerialize, borsh::BorshDeserialize)]
            pub enum Request {
                $(
                    $([<$module:camel $handler:camel>]( $($type),* )),*
                ),*
            }

            #[derive(Debug, Clone, borsh::BorshSerialize, borsh::BorshDeserialize)]
            pub enum Response {
                $(
                    $([<$module:camel $handler:camel>]( $ret )),*
                ),*
            }

            $(
                #[allow(async_fn_in_trait)]
                pub trait [<$module:camel Server>] {
                    type Error: Into<$err>;

                    $(async fn $handler(&self, $($arg: $type),* ) -> Result<$ret, Self::Error>;)*
                }
            )*

            pub trait Server: $([<$module:camel Server>]<Error = <Self as Server>::Error>+)* {
                type Error: Into<$err>;

                #[allow(async_fn_in_trait)]
                async fn receive(&self, request: Request) -> Result<Response, <Self as Server>::Error> {
                    Ok(match request {
                        $(
                            $(Request::[<$module:camel $handler:camel>]($($arg),*) => {
                                Response::[<$module:camel $handler:camel>](
                                [<$module:camel Server>]::$handler(
                                    self,
                                    $($arg),*
                                ).await?)
                            }),*
                        ),*
                    })
                }
            }

            #[allow(async_fn_in_trait)]
            pub trait Transport {
                async fn send(&self, request: Request) -> Result<Response, $err>;
            }

            $(
                pub struct [<$module:camel Client>]<'a, T: Transport> {
                    transport: &'a T,
                }

                impl<'a, T: Transport> [<$module:camel Client>]<'a, T> {
                    $(async fn $handler(&self, $($arg: $type),* ) -> Result<$ret, $err> {
                        if let Response::[<$module:camel $handler:camel>](out) = self.transport.send( Request::[<$module:camel $handler:camel>]( $($arg),* ) ).await? {
                            Ok(out)
                        } else {
                            unreachable!()
                        }
                    })*
                }
            )*

            pub struct Client<T: Transport> {
                transport: T,
            }

            impl<'a, T: Transport> Client<T> {
                pub fn new(transport: T) -> Self {
                    Self {
                        transport,
                    }
                }

                $(pub fn [<$module:snake>](&'a self) -> [<$module:camel Client>]<'a, T> {
                    [<$module:camel Client>] {
                        transport: &self.transport,
                    }
                })*
            }
        }
    };
}