fraiseql_auth/oauth/
failover.rs1use std::sync::Arc;
4
5use chrono::{DateTime, Duration, Utc};
6
7use super::super::error::AuthError;
8
9#[derive(Debug, Clone)]
11pub struct ProviderFailoverManager {
12 primary_provider: String,
14 fallback_providers: Vec<String>,
16 unavailable: Arc<std::sync::Mutex<Vec<(String, DateTime<Utc>)>>>,
20}
21
22impl ProviderFailoverManager {
23 #[must_use]
25 pub fn new(primary: String, fallbacks: Vec<String>) -> Self {
26 Self {
27 primary_provider: primary,
28 fallback_providers: fallbacks,
29 unavailable: Arc::new(std::sync::Mutex::new(Vec::new())),
30 }
31 }
32
33 pub fn get_available_provider(&self) -> std::result::Result<String, AuthError> {
39 let unavailable = self.unavailable.lock().map_err(|_| AuthError::Internal {
40 message: "failover manager mutex poisoned".to_string(),
41 })?;
42 let now = Utc::now();
43
44 if !unavailable
46 .iter()
47 .any(|(name, exp)| name == &self.primary_provider && *exp > now)
48 {
49 return Ok(self.primary_provider.clone());
50 }
51
52 for fallback in &self.fallback_providers {
54 if !unavailable.iter().any(|(name, exp)| name == fallback && *exp > now) {
55 return Ok(fallback.clone());
56 }
57 }
58
59 Err(AuthError::Internal {
60 message: "no OAuth providers available".to_string(),
61 })
62 }
63
64 pub fn mark_unavailable(
70 &self,
71 provider: String,
72 duration_seconds: u64,
73 ) -> std::result::Result<(), AuthError> {
74 let mut unavailable = self.unavailable.lock().map_err(|_| AuthError::Internal {
75 message: "failover manager mutex poisoned".to_string(),
76 })?;
77 unavailable
78 .push((provider, Utc::now() + Duration::seconds(duration_seconds.cast_signed())));
79 Ok(())
80 }
81
82 pub fn mark_available(&self, provider: &str) -> std::result::Result<(), AuthError> {
88 let mut unavailable = self.unavailable.lock().map_err(|_| AuthError::Internal {
89 message: "failover manager mutex poisoned".to_string(),
90 })?;
91 unavailable.retain(|(name, _)| name != provider);
92 Ok(())
93 }
94}