qrpc 0.1.0

qrpc is a small QUIC + mTLS messaging library
Documentation
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;

use crate::error::QrpcResult;

/// User-defined message abstraction carried by `qrpc`.
///
/// This trait intentionally does not require protobuf. Any binary format is acceptable.
pub trait QrpcMessage: Send + Sync + Sized + 'static {
    /// Returns the command ID used on the wire.
    fn cmd_id(&self) -> u32;
    /// Encodes the message into bytes.
    fn encode_vec(&self) -> Vec<u8>;
    /// Decodes a message from `cmd_id` + bytes.
    fn decode_vec(cmd_id: u32, data: &[u8]) -> QrpcResult<Self>;
}

/// Async callback invoked when a decoded message is received.
pub trait QrpcCallback<S, M>: Send + Sync + 'static
where
    S: Send + Sync + 'static,
    M: QrpcMessage,
{
    /// Handles one inbound message with shared state and source peer ID.
    fn call(
        &self,
        state: Arc<S>,
        peer_id: String,
        message: M,
    ) -> Pin<Box<dyn Future<Output=QrpcResult<()>> + Send>>;
}

impl<S, M, F, Fut> QrpcCallback<S, M> for F
where
    S: Send + Sync + 'static,
    M: QrpcMessage,
    F: Fn(Arc<S>, String, M) -> Fut + Send + Sync + 'static,
    Fut: Future<Output=QrpcResult<()>> + Send + 'static,
{
    fn call(
        &self,
        state: Arc<S>,
        peer_id: String,
        message: M,
    ) -> Pin<Box<dyn Future<Output=QrpcResult<()>> + Send>> {
        Box::pin((self)(state, peer_id, message))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::error::QrpcError;

    #[derive(Clone)]
    struct DemoMsg(Vec<u8>);

    impl QrpcMessage for DemoMsg {
        fn cmd_id(&self) -> u32 {
            42
        }

        fn encode_vec(&self) -> Vec<u8> {
            self.0.clone()
        }

        fn decode_vec(cmd_id: u32, data: &[u8]) -> QrpcResult<Self> {
            if cmd_id != 42 {
                return Err(QrpcError::MessageDecode("bad cmd".to_string()));
            }
            Ok(Self(data.to_vec()))
        }
    }

    #[tokio::test]
    async fn blanket_callback_impl_works() {
        let state = Arc::new(7usize);
        let cb = |s: Arc<usize>, peer: String, msg: DemoMsg| async move {
            assert_eq!(*s, 7);
            assert_eq!(peer, "peer-1");
            assert_eq!(msg.0, b"ok");
            Ok(())
        };

        QrpcCallback::call(&cb, state, "peer-1".to_string(), DemoMsg(b"ok".to_vec()))
            .await
            .expect("callback must succeed");
    }
}