use serde::Serialize;
use ts_rs::TS;
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
pub enum DesktopErrorKind {
Input,
Config,
Routing,
Scan,
Runtime,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq, TS)]
#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
pub struct DesktopErrorEnvelope {
pub kind: DesktopErrorKind,
pub message: String,
pub hint: String,
pub explain: String,
}
pub type DesktopResult<T> = Result<T, DesktopErrorEnvelope>;
pub(super) fn input_error(message: String) -> DesktopErrorEnvelope {
present_error(
DesktopErrorKind::Input,
&message,
"请先修正输入字段或路径,再重新运行。",
)
}
pub(super) fn runtime_error(error: anyhow::Error) -> DesktopErrorEnvelope {
let message = error.to_string();
let kind = classify_runtime_error(&message);
let hint = match kind {
DesktopErrorKind::Input => "请先修正输入字段或路径,再重新运行。",
DesktopErrorKind::Config => {
"请检查 config 路径、TOML 内容以及 vault.root / note_roots 配置。"
}
DesktopErrorKind::Routing => {
"请确认 CWD 位于某个 project.repo_paths 下,且该项目已配置 note_roots。"
}
DesktopErrorKind::Scan => {
"请检查 vault root、note_roots、Markdown 文件体积限制,以及相关目录是否可读。"
}
DesktopErrorKind::Runtime => "请查看 explain 中的原始错误,并结合输入与配置继续排查。",
};
present_error(kind, &message, hint)
}
fn classify_runtime_error(message: &str) -> DesktopErrorKind {
let lowered = message.to_ascii_lowercase();
if message.contains("no project matched cwd") {
DesktopErrorKind::Routing
} else if message.contains("configured note_root")
|| message.contains("vault scan exceeded")
|| message.contains("markdown file exceeds")
|| message.contains("failed to read markdown file")
|| message.contains("failed to stat markdown file")
|| message.contains("failed to canonicalize vault root")
{
DesktopErrorKind::Scan
} else if lowered.contains("toml")
|| lowered.contains("config")
|| lowered.contains("missing field")
|| lowered.contains("unknown field")
|| lowered.contains("invalid type")
{
DesktopErrorKind::Config
} else {
DesktopErrorKind::Runtime
}
}
fn present_error(kind: DesktopErrorKind, message: &str, hint: &str) -> DesktopErrorEnvelope {
let heading = match kind {
DesktopErrorKind::Input => "输入校验失败",
DesktopErrorKind::Config => "配置错误",
DesktopErrorKind::Routing => "项目路由失败",
DesktopErrorKind::Scan => "Vault 扫描失败",
DesktopErrorKind::Runtime => "运行时错误",
};
DesktopErrorEnvelope {
kind,
message: message.to_string(),
hint: hint.to_string(),
explain: format!("{heading}\n\n建议:{hint}\n\n原始错误:{message}"),
}
}