kanamaru 0.1.0

Build typed Tauri plugins with the power of Protobuf Buffers
Documentation
pub mod request;
pub mod response;

use base64::prelude::*;
use prost::Message;
use serde::{Deserialize, Serialize};
use tauri::http::HeaderMap;

#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Default)]
pub struct IpcMessageBase {
    #[serde(with = "self::header_map")]
    pub metadata: HeaderMap,
    pub body: Option<String>,
}

pub(crate) mod header_map {
    use std::{collections::HashMap, str::FromStr};

    use serde::{ser::SerializeMap, Deserialize, Deserializer, Serializer};
    use tauri::http::{HeaderMap, HeaderName, HeaderValue};

    pub fn serialize<S>(metadata: &HeaderMap, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut map = serializer.serialize_map(Some(metadata.len()))?;
        for (k, v) in metadata {
            map.serialize_entry(k.as_str(), v.to_str().map_err(serde::ser::Error::custom)?)?;
        }
        map.end()
    }
    pub fn deserialize<'de, D>(deserializer: D) -> Result<HeaderMap, D::Error>
    where
        D: Deserializer<'de>,
    {
        let map: Option<HashMap<String, String>> = Deserialize::deserialize(deserializer)?;
        let Some(map) = map else {
            return Ok(HeaderMap::new());
        };
        map.into_iter()
            .map(
                |(name, value)| -> Result<(HeaderName, HeaderValue), D::Error> {
                    Ok((
                        HeaderName::from_str(&name).map_err(serde::de::Error::custom)?,
                        HeaderValue::from_str(&value).map_err(serde::de::Error::custom)?,
                    ))
                },
            )
            .collect()
    }
}

impl<M: Message> From<M> for IpcMessageBase {
    fn from(value: M) -> Self {
        let body = Some(BASE64_STANDARD.encode(value.encode_to_vec()));
        IpcMessageBase {
            metadata: HeaderMap::new(),
            body,
        }
    }
}

#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub enum IpcBodyExtractMessageError {
    Base64(#[from] base64::DecodeError),
    Prost(#[from] prost::DecodeError),
}

impl IpcMessageBase {
    pub fn is_empty(&self) -> bool {
        self.body.is_none()
    }
    pub fn extract_message<M: Message + Default>(&self) -> Result<M, IpcBodyExtractMessageError> {
        if let Some(body) = self.body.as_ref() {
            Ok(M::decode(bytes::Bytes::from(
                BASE64_STANDARD.decode(body)?,
            ))?)
        } else {
            Ok(Default::default())
        }
    }
}

#[derive(Debug, Clone, Default)]
pub struct IpcMessage<M> {
    pub metadata: HeaderMap,
    pub body: M,
}

impl<M> IpcMessage<M> {
    pub fn new(body: M) -> Self {
        Self {
            metadata: Default::default(),
            body,
        }
    }
    pub fn new_with_metadata(body: M, metadata: HeaderMap) -> Self {
        Self { metadata, body }
    }
}

impl<M: Message> From<IpcMessage<M>> for IpcMessageBase {
    fn from(value: IpcMessage<M>) -> Self {
        let mut base = Self::from(value.body);
        base.metadata = value.metadata;
        base
    }
}

impl<M> TryFrom<IpcMessageBase> for IpcMessage<M>
where
    M: Message + Default,
{
    type Error = IpcBodyExtractMessageError;
    fn try_from(value: IpcMessageBase) -> Result<Self, Self::Error> {
        let body = value.extract_message::<M>()?;
        Ok(Self::new_with_metadata(body, value.metadata))
    }
}

pub use request::{StreamingRequest, UnaryRequest};

pub use response::{StreamingResponse, UnaryResponse};