Skip to main content

tail_fin_core/
error.rs

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