use super::builder::{RedisBackendBuilder, RedisMode};
use super::circuit_breaker::CircuitBreaker;
use super::retry::retry_with_backoff;
use crate::core::RedisCommand;
use crate::error::{OxCacheError, OxCacheResult};
use crate::infra::metrics::unified::GLOBAL_UNIFIED_METRICS;
use redis::Client;
use std::future::Future;
use std::sync::Arc;
use std::time::Duration;
#[derive(Clone)]
pub struct RedisBackend {
client: Arc<Client>,
mode: RedisMode,
connection_manager: redis::aio::ConnectionManager,
dangerous_clear_enabled: bool,
retry_count: u32,
retry_delay: Duration,
circuit_breaker: Arc<CircuitBreaker>,
}
impl RedisBackend {
pub(crate) fn from_parts(
client: Arc<Client>,
mode: RedisMode,
connection_manager: redis::aio::ConnectionManager,
dangerous_clear_enabled: bool,
retry_count: u32,
retry_delay: Duration,
circuit_breaker: Arc<CircuitBreaker>,
) -> Self {
Self {
client,
mode,
connection_manager,
dangerous_clear_enabled,
retry_count,
retry_delay,
circuit_breaker,
}
}
pub(crate) fn dangerous_clear_enabled(&self) -> bool {
self.dangerous_clear_enabled
}
pub async fn new(connection_string: &str) -> OxCacheResult<Self> {
Self::builder().connection_string(connection_string).build().await
}
pub async fn with_pool(connection_string: &str, _pool_size: usize) -> OxCacheResult<Self> {
Self::builder().connection_string(connection_string).build().await
}
pub fn builder() -> RedisBackendBuilder {
RedisBackendBuilder::default()
}
pub fn redact_connection_string(conn_str: &str) -> String {
if let Some(start) = conn_str.find("://") {
let protocol = &conn_str[..start + 3];
let rest = &conn_str[start + 3..];
if let Some(at_pos) = rest.find('@') {
let before_at = &rest[..at_pos];
if !before_at.contains('/') {
return format!("{}[REDACTED]@{}", protocol, &rest[at_pos + 1..]);
}
}
}
conn_str.to_string()
}
pub fn mode(&self) -> RedisMode {
self.mode
}
pub fn client(&self) -> &Client {
&self.client
}
pub(crate) fn conn(&self) -> redis::aio::ConnectionManager {
self.connection_manager.clone()
}
pub(crate) fn circuit_breaker(&self) -> &CircuitBreaker {
&self.circuit_breaker
}
pub(crate) async fn execute_with_retry<F, Fut, T>(&self, operation: F) -> OxCacheResult<T>
where
F: Fn() -> Fut + Send + Sync,
Fut: Future<Output = OxCacheResult<T>> + Send,
{
if self.circuit_breaker().is_open() {
return Err(OxCacheError::Degraded("Redis circuit breaker is open".to_string()));
}
let result = retry_with_backoff(operation, self.retry_count, self.retry_delay).await;
match &result {
Ok(_) => self.circuit_breaker.record_success(),
Err(_) => {
if self.circuit_breaker.record_failure() {
GLOBAL_UNIFIED_METRICS.record_l2_degraded();
}
}
}
result
}
pub async fn ping(&self) -> OxCacheResult<String> {
let mut conn = self.conn();
let result: String = redis::cmd(RedisCommand::Ping.as_str())
.query_async(&mut conn)
.await
.map_err(super::error::map_redis_error)?;
Ok(result)
}
}