Skip to main content

parse_book_source/
error.rs

1//! 分层错误(v2)。fetch/verify 各层错误在对应阶段实现时并入。
2
3use thiserror::Error;
4
5/// 书源 v2 顶层错误。
6#[derive(Debug, Error)]
7pub enum BookSourceError {
8    /// 配置(书源 JSON)解析失败。
9    #[error("book source config error: {0}")]
10    Config(#[from] ConfigError),
11    /// 规则求值失败。
12    #[error("rule eval error: {0}")]
13    Eval(#[from] EvalError),
14    /// 取页失败。
15    #[error("fetch error: {0}")]
16    Fetch(#[from] FetchError),
17    /// 操作所需的配置缺失(如未配置 search/explore)。
18    #[error("book source missing config: {0}")]
19    Missing(&'static str),
20    /// 登录态失效(`loginCheckJs` 在响应期判定),需用户重新登录。
21    #[error("登录态已失效,请重新登录")]
22    LoginExpired,
23}
24
25impl BookSourceError {
26    /// 是否为「被反爬挑战拦截」(如 Cloudflare 托管挑战)。
27    /// 用于诊断/降级:据此给出精确提示而非笼统失败,并决定是否升级浏览器取页。
28    pub fn is_challenge(&self) -> bool {
29        matches!(self, BookSourceError::Fetch(FetchError::Challenged(_)))
30    }
31
32    /// 是否为「登录态失效」(`loginCheckJs` 判定):据此提示用户重新登录。
33    pub fn is_login_expired(&self) -> bool {
34        matches!(self, BookSourceError::LoginExpired)
35    }
36}
37
38/// 取页层错误。
39#[derive(Debug, Error)]
40pub enum FetchError {
41    /// 网络/HTTP 错误。
42    #[error("http error: {0}")]
43    Http(#[from] reqwest::Error),
44    /// 非法请求头。
45    #[error("invalid header: {0}")]
46    Header(String),
47    /// 响应解码失败。
48    #[error("decode error: {0}")]
49    Decode(String),
50    /// 被反爬挑战拦截(如 Cloudflare 托管挑战):拿到的是挑战页而非真实内容。
51    #[error("blocked by anti-bot challenge: {0}")]
52    Challenged(String),
53    /// 浏览器解挑战失败(仅 `browser` feature)。
54    #[cfg(feature = "browser")]
55    #[error("browser solve error: {0}")]
56    Browser(String),
57}
58
59/// 配置层错误。
60#[derive(Debug, Error)]
61pub enum ConfigError {
62    /// 书源 JSON 反序列化失败。
63    #[error("invalid book source json: {0}")]
64    Json(#[from] serde_json::Error),
65    /// 读取本地书源文件失败。
66    #[error("read book source file: {0}")]
67    Io(#[from] std::io::Error),
68}
69
70/// 规则求值层错误。
71#[derive(Debug, Error)]
72pub enum EvalError {
73    /// CSS 选择器非法。
74    #[error("invalid css selector: {0}")]
75    Selector(String),
76    /// 正则非法。
77    #[error("invalid regex: {0}")]
78    Regex(String),
79    /// JSONPath 查询失败。
80    #[error("jsonpath error: {0}")]
81    JsonPath(String),
82    /// XPath 表达式非法或求值失败。
83    #[error("xpath error: {0}")]
84    Xpath(String),
85    /// 待解析内容不是合法 JSON。
86    #[error("invalid json content: {0}")]
87    Json(String),
88    /// 该 via 后端暂未启用(如 xpath)。
89    #[error("extraction backend not enabled: {0}")]
90    Unsupported(&'static str),
91    /// clean 编解码算子失败(非法 base64/hex/url 等)。
92    #[error("codec error: {0}")]
93    Codec(String),
94    /// clean 加解密算子失败(密钥/IV 长度错、padding 错、密文损坏等)。
95    #[error("crypto error: {0}")]
96    Crypto(String),
97    /// JS 脚本求值失败(语法错/运行错;仅 `js` feature)。
98    #[error("js error: {0}")]
99    Js(String),
100    /// clean 字体反爬还原算子失败(未知内置表 / 非法码点 / 缺映射表)。
101    #[error("font map error: {0}")]
102    Font(String),
103    /// JS host 桥失败(网络/cookie/登录态等;仅 `js-host` feature)。
104    /// 以可被 JS `try/catch` 捕获的方式抛出,不使整段求值崩溃。
105    #[cfg(feature = "js-host")]
106    #[error("host bridge error: {0}")]
107    Host(String),
108}
109
110/// v2 结果别名。
111pub type Result<T> = std::result::Result<T, BookSourceError>;