1use thiserror::Error;
4
5#[derive(Debug, Error)]
7pub enum XtreamError {
8 #[error("auth error: {0}")]
10 Auth(String),
11
12 #[error("session expired: {0}")]
14 SessionExpired(String),
15
16 #[error("rate limited: retry after {retry_after_secs}s")]
18 RateLimited {
19 retry_after_secs: u64,
21 },
22
23 #[error("network error: {0}")]
25 Network(String),
26
27 #[error("timeout: {0}")]
29 Timeout(String),
30
31 #[error("unexpected response: {0}")]
33 UnexpectedResponse(String),
34
35 #[error("not found: {0}")]
37 NotFound(String),
38
39 #[error("invalid url: {0}")]
41 InvalidUrl(String),
42}
43
44impl From<reqwest::Error> for XtreamError {
45 fn from(err: reqwest::Error) -> Self {
46 if err.is_timeout() {
47 return Self::Timeout(err.to_string());
48 }
49 if err.is_connect() {
50 return Self::Network(format!("connection failed: {err}"));
51 }
52 if err.is_decode() {
53 return Self::UnexpectedResponse(format!("decode error: {err}"));
54 }
55 Self::Network(err.to_string())
56 }
57}
58
59impl From<serde_json::Error> for XtreamError {
60 fn from(err: serde_json::Error) -> Self {
61 Self::UnexpectedResponse(format!("json parse error: {err}"))
62 }
63}
64
65impl From<url::ParseError> for XtreamError {
66 fn from(err: url::ParseError) -> Self {
67 Self::InvalidUrl(err.to_string())
68 }
69}
70
71impl From<XtreamError> for crispy_iptv_types::IptvError {
72 fn from(err: XtreamError) -> Self {
73 match err {
74 XtreamError::Auth(msg) => Self::Auth(msg),
75 XtreamError::SessionExpired(msg) => Self::SessionExpired(msg),
76 XtreamError::RateLimited { retry_after_secs } => Self::RateLimited { retry_after_secs },
77 XtreamError::Network(msg) => Self::Network(msg),
78 XtreamError::Timeout(msg) => Self::Timeout(0).into_network(msg),
79 XtreamError::UnexpectedResponse(msg) => Self::UnexpectedResponse(msg),
80 XtreamError::NotFound(msg) => Self::UnexpectedResponse(msg),
81 XtreamError::InvalidUrl(msg) => Self::InvalidUrl(msg),
82 }
83 }
84}
85
86trait IntoNetwork {
88 fn into_network(self, msg: String) -> crispy_iptv_types::IptvError;
89}
90
91impl IntoNetwork for crispy_iptv_types::IptvError {
92 fn into_network(self, msg: String) -> crispy_iptv_types::IptvError {
93 crispy_iptv_types::IptvError::Network(msg)
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn display_auth_error() {
103 let err = XtreamError::Auth("bad credentials".into());
104 assert_eq!(err.to_string(), "auth error: bad credentials");
105 }
106
107 #[test]
108 fn display_rate_limited() {
109 let err = XtreamError::RateLimited {
110 retry_after_secs: 30,
111 };
112 assert_eq!(err.to_string(), "rate limited: retry after 30s");
113 }
114
115 #[test]
116 fn from_serde_json_error() {
117 let raw = serde_json::from_str::<serde_json::Value>("not json").unwrap_err();
118 let err = XtreamError::from(raw);
119 assert!(matches!(err, XtreamError::UnexpectedResponse(_)));
120 }
121
122 #[test]
123 fn from_url_parse_error() {
124 let raw = url::Url::parse("://bad").unwrap_err();
125 let err = XtreamError::from(raw);
126 assert!(matches!(err, XtreamError::InvalidUrl(_)));
127 }
128
129 #[test]
130 fn converts_to_iptv_error() {
131 let err = XtreamError::Auth("test".into());
132 let iptv: crispy_iptv_types::IptvError = err.into();
133 assert!(matches!(iptv, crispy_iptv_types::IptvError::Auth(_)));
134 }
135}