ezrpc 0.1.1

Ergonomic, flexible and Zero-cost RPC framework
Documentation
use std::sync::Arc;

use crate::*;
use gloo_utils::format::JsValueSerdeExt;
use serde_wasm_bindgen::{Deserializer, Serializer};
use wasm_bindgen::UnwrapThrowExt;
use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue};

#[wasm_bindgen]
pub struct EZRpc {
    session: Arc<Session>,
}

pub struct AnyErr(anyhow::Error);

impl<T: Into<anyhow::Error>> From<T> for AnyErr {
    fn from(value: T) -> Self {
        Self(value.into())
    }
}

impl From<AnyErr> for JsValue {
    fn from(value: AnyErr) -> Self {
        JsValue::from_str(&format!("{:#?}", value.0))
    }
}

impl AnyErr {
    fn msg<T: core::fmt::Debug>(err: T) -> Self {
        Self(anyhow::anyhow!("{err:?}"))
    }
}

pub type AnyRes<T> = Result<T, AnyErr>;

pub struct JsService(js_sys::Object);

unsafe impl Send for JsService {}
unsafe impl Sync for JsService {}

impl Service for JsService {
    fn handle<'a>(&'a self, ss: &'a Arc<Session>, pack: Packet) -> ServiceResult<'a> {
        let key = JsValue::from_serde(pack.method().unwrap()).unwrap_throw();
        let func = js_sys::Reflect::get(&self.0, &key).unwrap_throw();
        let func: js_sys::Function = func.dyn_into().unwrap_throw();
        let args = pack
            .decode_to(
                &Serializer::new()
                    .serialize_missing_as_null(true)
                    .serialize_maps_as_objects(true),
            )
            .unwrap_throw();
        let res = match func.length() {
            0 => func.call0(&self.0),
            1 => func.call1(&self.0, &args),
            _ => func.apply(&self.0, args.dyn_ref().unwrap_throw()),
        };
        match res {
            Ok(ret) => {
                let ss = ss.clone();
                return Box::pin(async move {
                    let val = match ret.dyn_into::<js_sys::Promise>() {
                        Ok(prom) => match wasm_bindgen_futures::JsFuture::from(prom).await {
                            Ok(val) => val,
                            Err(err) => {
                                gloo_console::error!("ezrpc service error: {:?}", err);
                                JsValue::NULL
                            }
                        },
                        Err(val) => val,
                    };
                    if let Some(id) = pack.request_id() {
                        ss.response_de(id, Deserializer::from(val)).await.ok();
                    }

                    Ok(())
                });
            }
            Err(err) => {
                gloo_console::error!("ezrpc service error: {:?}", err);
            }
        }
        Box::pin(async { Ok(()) })
    }
}

#[wasm_bindgen]
impl EZRpc {
    #[wasm_bindgen(constructor)]
    pub async fn new(addr: &str, service: js_sys::Object) -> AnyRes<EZRpc> {
        let a = ws::WsAdapter::connect(addr).await?;
        let session = Arc::new(Session::new(Arc::new(a), Arc::new(JsService(service))));

        Ok(Self { session })
    }

    pub async fn notify(&self, method: JsValue, args: JsValue) -> AnyRes<()> {
        self.session
            .notify_de(Deserializer::from(method), Deserializer::from(args))
            .await?;
        Ok(())
    }

    pub async fn request(&self, method: JsValue, args: JsValue) -> AnyRes<JsValue> {
        Ok(self
            .session
            .request_de(
                Deserializer::from(method),
                Deserializer::from(args),
                &Serializer::new()
                    .serialize_missing_as_null(true)
                    .serialize_maps_as_objects(true),
            )
            .await?)
    }

    pub async fn request_recver(&self, method: JsValue, args: JsValue) -> AnyRes<RespRecver> {
        Ok(self
            .session
            .request_recver_de(Deserializer::from(method), Deserializer::from(args))
            .await
            .map(RespRecver)?)
    }

    pub async fn loop_dispatch(&self) -> AnyRes<()> {
        Ok(self.session.loop_dispatch().await?)
    }
}

#[wasm_bindgen]
pub struct RespRecver(StreamReceiver);

#[wasm_bindgen]
impl RespRecver {
    pub async fn next(&mut self) -> AnyRes<JsValue> {
        if let Some(p) = self.0.recv().await? {
            Ok(p.decode_to(
                &Serializer::new()
                    .serialize_missing_as_null(true)
                    .serialize_maps_as_objects(true),
            )
            .map_err(AnyErr::msg)?)
        } else {
            Ok(JsValue::NULL)
        }
    }
}