wslink-rs 0.0.2

A wslink-compatible WebSocket RPC server runtime for Rust using MessagePack transport.
Documentation
use downcast_rs::{Downcast, impl_downcast};
use log::{error, warn};
use serde::Serialize;
use tokio::sync::mpsc;
use tokio::sync::mpsc::error::TrySendError;

use crate::server::ServerArgument;
use crate::wslink::{Local2Async, WsLinkRpc, WsRsp};

pub trait ServerProtocol: Downcast + Send {
    fn on_connect(&mut self) {}
    fn on_close(&mut self) {}
    fn initialize(&mut self, _args: &ServerArgument, _config: &mut ServerConfig) {}
}

impl_downcast!(ServerProtocol);

pub struct ServerConfig {
    pub(crate) secret: String,
    pub(crate) rpcs: Vec<WsLinkRpc>,
}

impl Default for ServerConfig {
    fn default() -> Self {
        Self {
            secret: "wslink-secret".to_string(),
            rpcs: Vec::new(),
        }
    }
}

impl ServerConfig {
    pub fn register_rpc(&mut self, rpc: WsLinkRpc) -> &mut Self {
        self.rpcs.push(rpc);
        self
    }

    pub fn register_rpcs<I>(&mut self, rpcs: I) -> &mut Self
    where
        I: IntoIterator<Item = WsLinkRpc>,
    {
        self.rpcs.extend(rpcs);
        self
    }

    pub fn update_secret(&mut self, secret: impl Into<String>) -> &mut Self {
        self.secret = secret.into();
        self
    }

    pub(crate) fn into_parts(self) -> (String, Vec<WsLinkRpc>) {
        (self.secret, self.rpcs)
    }
}

#[derive(Clone)]
pub struct Publisher {
    tx: mpsc::Sender<Local2Async>,
}

impl Publisher {
    pub(crate) fn new(tx: mpsc::Sender<Local2Async>) -> Self {
        Self { tx }
    }

    pub fn publish<T: Serialize + Send + Sync + 'static>(&self, topic: &str, payload: T, client_id: Option<usize>) {
        let f = Box::new(move |rpc_id: &str| -> anyhow::Result<Vec<u8>> {
            let res = WsRsp {
                wslink: "1.0".into(),
                id: rpc_id.into(),
                result: payload,
            };

            let data = crate::rmp::to_vec_named(&res)?;
            Ok(data)
        });

        match self.tx.try_send(Local2Async {
            topic: Some(topic.to_string()),
            rpc_id: String::new(),
            client_id,
            f,
        }) {
            Ok(()) => {}
            Err(TrySendError::Full(_)) => {
                warn!("publish '{}' dropped because async queue is full", topic);
            }
            Err(TrySendError::Closed(_)) => {
                error!("publish ignored because async context is closed");
            }
        }
    }
}