1#[cfg(feature = "browser")]
2use night_fury_core::NightFuryError;
3#[cfg(feature = "core-api")]
4use tail_fin_core::SiteError;
5
6#[derive(Debug, thiserror::Error)]
7pub enum TailFinError {
8 #[error("not logged in: auth cookie not found")]
9 AuthRequired,
10
11 #[cfg(feature = "browser")]
12 #[error("browser error: {0}")]
13 Browser(#[from] NightFuryError),
14
15 #[error("API error: {0}")]
16 Api(String),
17
18 #[error("HTTP {status} {status_text} — body: {body}")]
19 Http {
20 status: u16,
21 status_text: String,
22 body: String,
23 },
24
25 #[error("IO error: {0}")]
26 Io(String),
27
28 #[error("parse error: {0}")]
29 Parse(String),
30
31 #[cfg(feature = "core-api")]
32 #[error("site error: {0}")]
33 Site(#[from] SiteError),
34}
35
36impl From<serde_json::Error> for TailFinError {
37 fn from(e: serde_json::Error) -> Self {
38 TailFinError::Parse(e.to_string())
39 }
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45
46 #[test]
47 fn serde_json_error_converts_to_parse_variant() {
48 let bad_json = "not json at all";
49 let serde_err: serde_json::Error =
50 serde_json::from_str::<serde_json::Value>(bad_json).unwrap_err();
51 let tail_err: TailFinError = serde_err.into();
52 match &tail_err {
53 TailFinError::Parse(msg) => assert!(!msg.is_empty()),
54 other => panic!("expected Parse variant, got {:?}", other),
55 }
56 }
57
58 #[test]
59 fn display_auth_required() {
60 let err = TailFinError::AuthRequired;
61 assert_eq!(err.to_string(), "not logged in: auth cookie not found");
62 }
63
64 #[test]
65 fn display_api_error() {
66 let err = TailFinError::Api("rate limited".into());
67 assert_eq!(err.to_string(), "API error: rate limited");
68 }
69
70 #[test]
71 fn display_http_error_includes_status_and_body() {
72 let err = TailFinError::Http {
73 status: 403,
74 status_text: "Forbidden".into(),
75 body: "invalid token".into(),
76 };
77 let s = err.to_string();
78 assert!(s.contains("403"), "expected status in: {s}");
79 assert!(s.contains("Forbidden"), "expected status text in: {s}");
80 assert!(s.contains("invalid token"), "expected body in: {s}");
81 }
82
83 #[test]
84 fn display_io_error() {
85 let err = TailFinError::Io("file not found".into());
86 assert_eq!(err.to_string(), "IO error: file not found");
87 }
88
89 #[test]
90 fn display_parse_error() {
91 let err = TailFinError::Parse("unexpected token".into());
92 assert_eq!(err.to_string(), "parse error: unexpected token");
93 }
94
95 #[test]
96 fn debug_output_is_not_empty() {
97 let err = TailFinError::AuthRequired;
98 let debug = format!("{:?}", err);
99 assert!(!debug.is_empty());
100 }
101
102 #[cfg(feature = "core-api")]
103 #[test]
104 fn tail_fin_error_from_site_error() {
105 let site_err = SiteError::ManualLoginRequired { site: "shopee" };
108 let tail_err: TailFinError = site_err.into();
109 match tail_err {
110 TailFinError::Site(_) => {}
111 other => panic!("expected Site variant, got {other:?}"),
112 }
113 }
114}