1use crate::model::ErrorObj;
4
5pub mod exit {
7 pub const OK: i32 = 0;
9 pub const UNEXPECTED: i32 = 1;
11 pub const USAGE: i32 = 2;
13 pub const PARTIAL: i32 = 3;
15 pub const ALL_FAILED: i32 = 4;
17}
18
19#[derive(Debug, thiserror::Error)]
21pub enum RssError {
22 #[error("usage error: {0}")]
23 Usage(String),
24
25 #[error("invalid URL: {0}")]
26 InvalidUrl(String),
27
28 #[error("network error: {0}")]
29 Network(String),
30
31 #[error("HTTP status {status}: {url}")]
32 Http {
33 status: u16,
34 url: String,
35 retry_after: Option<String>,
37 },
38
39 #[error("feed parse error: {0}")]
40 Parse(String),
41
42 #[error("no feeds discovered at {0}")]
43 NotFound(String),
44
45 #[error("cache error: {0}")]
46 Cache(String),
47
48 #[error(
49 "response too large: ~{estimated_tokens} tokens exceeds the {budget_tokens}-token \
50 budget; retry with limit={suggested_limit} or max_content_chars={suggested_max_content_chars}"
51 )]
52 ResponseTooLarge {
53 estimated_tokens: usize,
54 budget_tokens: usize,
55 suggested_limit: usize,
56 suggested_max_content_chars: usize,
57 },
58
59 #[error(transparent)]
60 Io(#[from] std::io::Error),
61
62 #[error("{0}")]
63 Other(String),
64}
65
66impl RssError {
67 pub fn code(&self) -> &'static str {
69 match self {
70 RssError::Usage(_) => "USAGE_ERROR",
71 RssError::InvalidUrl(_) => "INVALID_URL",
72 RssError::Network(_) => "NETWORK_ERROR",
73 RssError::Http { .. } => "FEED_FETCH_FAILED",
74 RssError::Parse(_) => "FEED_PARSE_FAILED",
75 RssError::NotFound(_) => "NOT_FOUND",
76 RssError::Cache(_) => "CACHE_ERROR",
77 RssError::ResponseTooLarge { .. } => "RESPONSE_TOO_LARGE",
78 RssError::Io(_) => "IO_ERROR",
79 RssError::Other(_) => "INTERNAL_ERROR",
80 }
81 }
82
83 pub fn to_error_obj(&self, feed_url: Option<&str>) -> ErrorObj {
85 let mut obj = ErrorObj::new(self.code(), self.to_string());
86 if let Some(u) = feed_url {
87 obj.feed_url = Some(u.to_string());
88 }
89 match self {
90 RssError::Http {
91 status,
92 retry_after,
93 ..
94 } => {
95 obj.details = serde_json::json!({
96 "http_status": status,
97 "retry_after": retry_after,
98 });
99 }
100 RssError::ResponseTooLarge {
101 estimated_tokens,
102 budget_tokens,
103 suggested_limit,
104 suggested_max_content_chars,
105 } => {
106 obj.details = serde_json::json!({
108 "estimated_tokens": estimated_tokens,
109 "budget_tokens": budget_tokens,
110 "suggested_limit": suggested_limit,
111 "suggested_max_content_chars": suggested_max_content_chars,
112 });
113 }
114 _ => {}
115 }
116 obj
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn http_error_surfaces_retry_after_when_present() {
126 let e = RssError::Http {
127 status: 429,
128 url: "https://x".into(),
129 retry_after: Some("2".into()),
130 };
131 let obj = e.to_error_obj(None);
132 assert_eq!(obj.code, "FEED_FETCH_FAILED");
133 assert_eq!(obj.details["http_status"], 429);
134 assert_eq!(obj.details["retry_after"], "2");
135 }
136}