rs-zero 0.2.6

Rust-first microservice framework inspired by go-zero engineering practices
Documentation
use thiserror::Error;

use crate::cache_redis::RedisClusterRedirect;

/// Redis adapter result type.
pub type RedisCacheResult<T> = Result<T, RedisCacheError>;

/// Redis adapter errors.
#[derive(Debug, Error)]
pub enum RedisCacheError {
    /// Redis URL is not usable.
    #[error("invalid redis url `{url}`")]
    InvalidUrl {
        /// Redis URL.
        url: String,
    },

    /// Local Redis cache configuration is invalid.
    #[error("invalid redis cache config: {0}")]
    InvalidConfig(String),

    /// Redis connection could not be established.
    #[error("redis connection error: {0}")]
    Connection(String),

    /// Redis command timed out.
    #[error("redis command timed out: {0}")]
    Timeout(String),

    /// Redis Cluster returned a MOVED or ASK redirection.
    #[error("redis cluster redirect: {0:?}")]
    ClusterRedirect(RedisClusterRedirect),

    /// Redis Cluster redirection exceeded the configured retry limit.
    #[error(
        "too many redis cluster redirects after {attempts} attempts for key fingerprint {key_fingerprint}"
    )]
    TooManyClusterRedirects {
        /// Number of redirect attempts.
        attempts: u32,
        /// Sanitized key fingerprint, never the raw Redis key.
        key_fingerprint: String,
    },

    /// Background retry queue cannot accept more delete tasks.
    #[error("redis delete retry queue is full")]
    RetryQueueFull,

    /// Redis command was rejected by the local circuit breaker.
    #[error("redis circuit breaker rejected command: {0}")]
    BreakerOpen(String),

    /// Serialization failed before or after a Redis operation.
    #[error("redis cache serialization error: {0}")]
    Serde(#[from] serde_json::Error),

    /// Redis backend returned an error.
    #[error("redis backend error: {0}")]
    Backend(String),
}

impl RedisCacheError {
    /// Creates a sanitized Redis Cluster redirect limit error.
    pub fn too_many_cluster_redirects(attempts: u32, key: &str) -> Self {
        Self::TooManyClusterRedirects {
            attempts,
            key_fingerprint: sanitized_key_fingerprint(key),
        }
    }
}

fn sanitized_key_fingerprint(key: &str) -> String {
    let mut hash = 14_695_981_039_346_656_037u64;
    for byte in key.as_bytes() {
        hash ^= u64::from(*byte);
        hash = hash.wrapping_mul(1_099_511_628_211);
    }
    format!("fnv64:{hash:016x}")
}