Skip to main content

tail_fin_core/
error.rs

1//! Error types for site lifecycle operations.
2
3#[cfg(feature = "browser")]
4use night_fury_core::NightFuryError;
5
6/// Errors specific to site lifecycle operations (refresh, validate, login).
7#[derive(Debug, thiserror::Error)]
8pub enum SiteError {
9    #[error("refresh failed on site '{site}': {reason}")]
10    RefreshFailed { site: &'static str, reason: String },
11
12    #[error("validation failed on site '{site}': {reason}")]
13    ValidationFailed { site: &'static str, reason: String },
14
15    #[error("manual login required for site '{site}' — automated login not supported")]
16    ManualLoginRequired { site: &'static str },
17
18    #[error("login failed: {0:?}")]
19    LoginFailed(AuthFailureKind),
20
21    #[cfg(feature = "browser")]
22    #[error("browser error during site operation: {0}")]
23    Browser(#[from] NightFuryError),
24}
25
26/// Why a site operation failed authentication.
27#[derive(Debug, Clone)]
28pub enum AuthFailureKind {
29    /// Cookie expired — refresh should help.
30    CookieExpired,
31    /// Credentials (password / token) were rejected by the server.
32    CredentialsRejected,
33    /// Rate limited — caller should wait and retry.
34    RateLimited { retry_after: std::time::Duration },
35    /// Anti-bot challenge (captcha, fingerprint check, etc.).
36    AntibotBlock { challenge_type: String },
37    /// Account suspended / banned.
38    AccountSuspended,
39    /// Other — free-form string.
40    Other(String),
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46
47    #[test]
48    fn site_error_displays_with_site_id() {
49        let err = SiteError::RefreshFailed {
50            site: "twitter",
51            reason: "auth_token missing".into(),
52        };
53        let msg = err.to_string();
54        assert!(msg.contains("twitter"));
55        assert!(msg.contains("auth_token missing"));
56    }
57
58    #[test]
59    fn auth_failure_kind_debug() {
60        let kind = AuthFailureKind::RateLimited {
61            retry_after: std::time::Duration::from_secs(60),
62        };
63        let debug = format!("{kind:?}");
64        assert!(debug.contains("RateLimited"));
65    }
66
67    #[test]
68    fn auth_failure_kind_variants_exist() {
69        let _ = AuthFailureKind::CookieExpired;
70        let _ = AuthFailureKind::CredentialsRejected;
71        let _ = AuthFailureKind::AccountSuspended;
72        let _ = AuthFailureKind::AntibotBlock {
73            challenge_type: "captcha".into(),
74        };
75        let _ = AuthFailureKind::Other("custom".into());
76    }
77}