use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum DegradationStrategy {
ReturnCache,
ReturnDefault,
FastFail,
}
impl DegradationStrategy {
pub fn as_str(&self) -> &'static str {
match self {
DegradationStrategy::ReturnCache => "return_cache",
DegradationStrategy::ReturnDefault => "return_default",
DegradationStrategy::FastFail => "fast_fail",
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct DegradationResult {
pub data: Option<Vec<HashMap<String, String>>>,
pub is_degraded: bool,
pub degradation_strategy: DegradationStrategy,
pub mismatch_warning: bool,
}
impl DegradationResult {
pub fn from_cache(data: Vec<HashMap<String, String>>) -> Self {
Self {
data: Some(data),
is_degraded: true,
degradation_strategy: DegradationStrategy::ReturnCache,
mismatch_warning: false,
}
}
pub fn from_default() -> Self {
Self {
data: Some(Vec::new()),
is_degraded: true,
degradation_strategy: DegradationStrategy::ReturnDefault,
mismatch_warning: false,
}
}
pub fn fast_fail() -> Self {
Self {
data: None,
is_degraded: true,
degradation_strategy: DegradationStrategy::FastFail,
mismatch_warning: false,
}
}
pub fn with_mismatch_warning(mut self) -> Self {
self.mismatch_warning = true;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum DegradationError {
#[error("cache miss for query fingerprint: {0}")]
CacheMiss(String),
#[error("fast fail: circuit breaker open")]
FastFail,
#[error("degradation data mismatch for query fingerprint: {0}")]
DataMismatch(String),
}
pub trait DegradationHandler: Send + Sync {
fn handle(&self, query_fingerprint: &str) -> Result<DegradationResult, DegradationError>;
fn strategy(&self) -> DegradationStrategy;
}
pub struct CacheDegradation {
cache: HashMap<String, Vec<HashMap<String, String>>>,
}
impl CacheDegradation {
pub fn new() -> Self {
Self {
cache: HashMap::new(),
}
}
pub fn with_cache(cache: HashMap<String, Vec<HashMap<String, String>>>) -> Self {
Self { cache }
}
pub fn insert(&mut self, fingerprint: String, data: Vec<HashMap<String, String>>) {
self.cache.insert(fingerprint, data);
}
}
impl Default for CacheDegradation {
fn default() -> Self {
Self::new()
}
}
impl DegradationHandler for CacheDegradation {
fn handle(&self, query_fingerprint: &str) -> Result<DegradationResult, DegradationError> {
match self.cache.get(query_fingerprint) {
Some(data) => Ok(DegradationResult::from_cache(data.clone())),
None => Err(DegradationError::CacheMiss(query_fingerprint.to_string())),
}
}
fn strategy(&self) -> DegradationStrategy {
DegradationStrategy::ReturnCache
}
}
pub struct DefaultDegradation;
impl DefaultDegradation {
pub fn new() -> Self {
Self
}
}
impl Default for DefaultDegradation {
fn default() -> Self {
Self
}
}
impl DegradationHandler for DefaultDegradation {
fn handle(&self, _query_fingerprint: &str) -> Result<DegradationResult, DegradationError> {
Ok(DegradationResult::from_default())
}
fn strategy(&self) -> DegradationStrategy {
DegradationStrategy::ReturnDefault
}
}
pub struct FastFailDegradation;
impl FastFailDegradation {
pub fn new() -> Self {
Self
}
}
impl Default for FastFailDegradation {
fn default() -> Self {
Self
}
}
impl DegradationHandler for FastFailDegradation {
fn handle(&self, _query_fingerprint: &str) -> Result<DegradationResult, DegradationError> {
Err(DegradationError::FastFail)
}
fn strategy(&self) -> DegradationStrategy {
DegradationStrategy::FastFail
}
}
pub fn execute_with_degradation(
can_execute: bool,
handler: &dyn DegradationHandler,
query_fingerprint: &str,
normal_query: impl FnOnce() -> Result<Vec<HashMap<String, String>>, String>,
) -> Result<DegradationResult, DegradationError> {
if can_execute {
match normal_query() {
Ok(data) => Ok(DegradationResult {
data: Some(data),
is_degraded: false,
degradation_strategy: handler.strategy(),
mismatch_warning: false,
}),
Err(_) => handler.handle(query_fingerprint),
}
} else {
handler.handle(query_fingerprint)
}
}