1use serde::Serialize;
4use ts_rs::TS;
5
6#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, TS)]
7#[serde(rename_all = "snake_case")]
8#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
9pub enum DesktopErrorKind {
10 Input,
11 Config,
12 Routing,
13 Scan,
14 Runtime,
15}
16
17#[derive(Debug, Clone, Serialize, PartialEq, Eq, TS)]
18#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
19pub struct DesktopErrorEnvelope {
20 pub kind: DesktopErrorKind,
21 pub message: String,
22 pub hint: String,
23 pub explain: String,
24}
25
26pub type DesktopResult<T> = Result<T, DesktopErrorEnvelope>;
27
28pub(super) fn input_error(message: String) -> DesktopErrorEnvelope {
29 present_error(
30 DesktopErrorKind::Input,
31 &message,
32 "请先修正输入字段或路径,再重新运行。",
33 )
34}
35
36pub(super) fn runtime_error(error: anyhow::Error) -> DesktopErrorEnvelope {
37 let message = error.to_string();
38 let kind = classify_runtime_error(&message);
39 let hint = match kind {
40 DesktopErrorKind::Input => "请先修正输入字段或路径,再重新运行。",
41 DesktopErrorKind::Config => {
42 "请检查 config 路径、TOML 内容以及 vault.root / note_roots 配置。"
43 }
44 DesktopErrorKind::Routing => {
45 "请确认 CWD 位于某个 project.repo_paths 下,且该项目已配置 note_roots。"
46 }
47 DesktopErrorKind::Scan => {
48 "请检查 vault root、note_roots、Markdown 文件体积限制,以及相关目录是否可读。"
49 }
50 DesktopErrorKind::Runtime => "请查看 explain 中的原始错误,并结合输入与配置继续排查。",
51 };
52 present_error(kind, &message, hint)
53}
54
55fn classify_runtime_error(message: &str) -> DesktopErrorKind {
56 let lowered = message.to_ascii_lowercase();
57 if message.contains("no project matched cwd") {
58 DesktopErrorKind::Routing
59 } else if message.contains("configured note_root")
60 || message.contains("vault scan exceeded")
61 || message.contains("markdown file exceeds")
62 || message.contains("failed to read markdown file")
63 || message.contains("failed to stat markdown file")
64 || message.contains("failed to canonicalize vault root")
65 {
66 DesktopErrorKind::Scan
67 } else if lowered.contains("toml")
68 || lowered.contains("config")
69 || lowered.contains("missing field")
70 || lowered.contains("unknown field")
71 || lowered.contains("invalid type")
72 {
73 DesktopErrorKind::Config
74 } else {
75 DesktopErrorKind::Runtime
76 }
77}
78
79fn present_error(kind: DesktopErrorKind, message: &str, hint: &str) -> DesktopErrorEnvelope {
80 let heading = match kind {
81 DesktopErrorKind::Input => "输入校验失败",
82 DesktopErrorKind::Config => "配置错误",
83 DesktopErrorKind::Routing => "项目路由失败",
84 DesktopErrorKind::Scan => "Vault 扫描失败",
85 DesktopErrorKind::Runtime => "运行时错误",
86 };
87 DesktopErrorEnvelope {
88 kind,
89 message: message.to_string(),
90 hint: hint.to_string(),
91 explain: format!("{heading}\n\n建议:{hint}\n\n原始错误:{message}"),
92 }
93}