tauri-plugin-deeprespond-im 1.0.0-alpha

深应智能 IM 的 Tauri 插件:本地存储、安全存储、窗口间的锁与消息、系统事件、通知、角标、文件
use serde::Serialize;

/// 插件命令的错误:以 `{ code, reason, message }` 交给 JavaScript,由适配层转成 DRError(《3.接口设计》第 7 节)。
#[derive(Debug, Clone, Serialize)]
pub struct Error {
    pub code: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reason: Option<String>,
    pub message: String,
}

pub type Result<T> = std::result::Result<T, Error>;

impl Error {
    pub fn new(code: &str, reason: Option<&str>, message: impl Into<String>) -> Self {
        Self { code: code.into(), reason: reason.map(Into::into), message: message.into() }
    }

    pub fn permission_denied(reason: &str) -> Self {
        Self::new("permission_denied", Some(reason), reason)
    }

    pub fn validation(reason: &str, message: impl Into<String>) -> Self {
        Self::new("local_validation", Some(reason), message)
    }

    pub fn invalid_state(reason: &str) -> Self {
        Self::new("invalid_state", Some(reason), reason)
    }

    pub fn unsupported(reason: &str) -> Self {
        Self::new("unsupported", Some(reason), reason)
    }

    pub fn storage(reason: &str, message: impl Into<String>) -> Self {
        Self::new("storage_error", Some(reason), message)
    }

    pub fn aborted() -> Self {
        Self::new("aborted", None, "aborted")
    }

    pub fn network(message: impl Into<String>) -> Self {
        Self::new("network_error", None, message)
    }

    pub fn internal(message: impl Into<String>) -> Self {
        Self::new("internal", None, message)
    }

    /// 只能由引擎窗口调用的命令被其他窗口调用(即使误授了 deeprespond-im:engine,《3.接口设计》第 2 节)。
    pub fn not_engine() -> Self {
        Self::permission_denied("not_engine_window")
    }

    /// 读文件时的 IO 错误:不存在、不能读、磁盘满分别给出原因。
    pub fn from_io_read(err: &std::io::Error) -> Self {
        match err.kind() {
            std::io::ErrorKind::NotFound => Self::validation("file_not_found", err.to_string()),
            std::io::ErrorKind::PermissionDenied => Self::validation("file_unreadable", err.to_string()),
            _ => Self::from_io(err),
        }
    }

    pub fn from_io(err: &std::io::Error) -> Self {
        if is_disk_full(err) {
            return Self::storage("disk_full", err.to_string());
        }
        match err.kind() {
            std::io::ErrorKind::NotFound => Self::validation("file_not_found", err.to_string()),
            _ => Self::storage("io", err.to_string()),
        }
    }
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match &self.reason {
            Some(reason) => write!(f, "{}({}):{}", self.code, reason, self.message),
            None => write!(f, "{}:{}", self.code, self.message),
        }
    }
}

impl std::error::Error for Error {}

impl From<std::io::Error> for Error {
    fn from(err: std::io::Error) -> Self {
        Self::from_io(&err)
    }
}

impl From<serde_json::Error> for Error {
    fn from(err: serde_json::Error) -> Self {
        Self::new("local_validation", Some("invalid_json"), err.to_string())
    }
}

impl From<rusqlite::Error> for Error {
    fn from(err: rusqlite::Error) -> Self {
        if let rusqlite::Error::SqliteFailure(e, _) = &err {
            if e.code == rusqlite::ErrorCode::DiskFull {
                return Self::storage("quota_exceeded", err.to_string());
            }
        }
        Self::storage("sqlite", err.to_string())
    }
}

impl From<tauri::Error> for Error {
    fn from(err: tauri::Error) -> Self {
        Self::internal(err.to_string())
    }
}

/// 磁盘空间不足(ENOSPC、ERROR_DISK_FULL、ERROR_HANDLE_DISK_FULL)。
pub fn is_disk_full(err: &std::io::Error) -> bool {
    if err.kind() == std::io::ErrorKind::StorageFull {
        return true;
    }
    match err.raw_os_error() {
        #[cfg(unix)]
        Some(28) => true,
        #[cfg(windows)]
        Some(39) | Some(112) => true,
        _ => false,
    }
}