coil-cache 0.1.1

Caching primitives for the Coil framework.
Documentation
use std::fmt;
use std::sync::Arc;

use crate::{
    CacheEntry, CacheInstant, CacheKey, CacheLookup, CacheMetrics, CacheModelError, FillDecision,
    FillLease, InvalidationSet, RequestCoalescingMode,
};

use super::CacheBackendKind;

pub trait DistributedCacheRuntime: Send + Sync + 'static {
    fn insert(&self, entry: CacheEntry);
    fn lookup(&self, key: &CacheKey, now: CacheInstant) -> CacheLookup;
    fn invalidate(&self, tags: &InvalidationSet) -> Vec<CacheKey>;
    fn begin_fill(
        &self,
        key: &CacheKey,
        mode: RequestCoalescingMode,
        holder: String,
    ) -> FillDecision;
    fn complete_fill(&self, lease: &FillLease) -> Result<(), CacheModelError>;
    fn metrics(&self) -> CacheMetrics;
    fn is_shared_backend(&self) -> bool {
        true
    }
    fn supports_live_shared_state(&self) -> bool {
        false
    }
}

#[derive(Clone)]
pub struct DistributedCacheClient {
    kind: CacheBackendKind,
    shared: bool,
    runtime: Arc<dyn DistributedCacheRuntime>,
}

impl DistributedCacheClient {
    pub fn new(kind: CacheBackendKind, runtime: Arc<dyn DistributedCacheRuntime>) -> Self {
        Self::with_runtime(kind, runtime)
    }

    pub fn with_runtime(kind: CacheBackendKind, runtime: Arc<dyn DistributedCacheRuntime>) -> Self {
        Self::with_shared_runtime(kind, runtime)
    }

    pub fn with_shared_runtime(
        kind: CacheBackendKind,
        runtime: Arc<dyn DistributedCacheRuntime>,
    ) -> Self {
        Self {
            kind,
            shared: runtime.is_shared_backend(),
            runtime,
        }
    }

    pub fn live_shared_runtime(
        kind: CacheBackendKind,
        namespace: impl Into<String>,
        root: impl Into<std::path::PathBuf>,
    ) -> Arc<dyn DistributedCacheRuntime> {
        super::live::live_shared_runtime(kind, namespace, root)
    }

    pub(crate) fn unavailable_shared_runtime(
        kind: CacheBackendKind,
    ) -> Arc<dyn DistributedCacheRuntime> {
        Arc::new(ExplicitDistributedCacheRuntimeRequired { kind })
    }

    #[doc(hidden)]
    pub fn emulated_shared_runtime(kind: CacheBackendKind) -> Arc<dyn DistributedCacheRuntime> {
        super::testing::test_only_sqlite_shared_runtime(
            kind,
            super::testing::test_scope_namespace(),
        )
    }

    #[cfg(test)]
    pub fn test_only_sqlite_shared_runtime(
        kind: CacheBackendKind,
        namespace: impl Into<String>,
    ) -> Arc<dyn DistributedCacheRuntime> {
        super::testing::test_only_sqlite_shared_runtime(kind, namespace.into())
    }

    #[cfg(test)]
    #[doc(hidden)]
    pub fn local_for_testing(kind: CacheBackendKind) -> Self {
        Self::with_shared_runtime(kind, Self::emulated_shared_runtime(kind))
    }

    #[cfg(test)]
    #[doc(hidden)]
    #[deprecated(
        note = "compatibility shim; behaves like local_for_testing(kind). use with_shared_runtime(kind, runtime) or local_for_testing(kind)"
    )]
    pub fn shared(kind: CacheBackendKind) -> Self {
        Self::local_for_testing(kind)
    }

    #[cfg(test)]
    #[doc(hidden)]
    #[deprecated(
        note = "compatibility shim; behaves like local_for_testing(kind). use with_shared_runtime(kind, runtime) or local_for_testing(kind)"
    )]
    pub fn scoped_shared(kind: CacheBackendKind, _scope: impl Into<String>) -> Self {
        Self::local_for_testing(kind)
    }

    pub fn kind(&self) -> CacheBackendKind {
        self.kind
    }

    pub fn is_shared(&self) -> bool {
        self.shared
    }

    pub fn insert(&self, entry: CacheEntry) {
        self.runtime.insert(entry);
    }

    pub fn lookup(&self, key: &CacheKey, now: CacheInstant) -> CacheLookup {
        self.runtime.lookup(key, now)
    }

    pub fn invalidate(&self, tags: &InvalidationSet) -> Vec<CacheKey> {
        self.runtime.invalidate(tags)
    }

    pub fn begin_fill(
        &self,
        key: &CacheKey,
        mode: RequestCoalescingMode,
        holder: impl Into<String>,
    ) -> FillDecision {
        self.runtime.begin_fill(key, mode, holder.into())
    }

    pub fn complete_fill(&self, lease: &FillLease) -> Result<(), CacheModelError> {
        self.runtime.complete_fill(lease)
    }

    pub fn metrics(&self) -> CacheMetrics {
        self.runtime.metrics()
    }
}

impl fmt::Debug for DistributedCacheClient {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("DistributedCacheClient")
            .field("kind", &self.kind)
            .finish()
    }
}

#[derive(Debug)]
struct ExplicitDistributedCacheRuntimeRequired {
    kind: CacheBackendKind,
}

impl ExplicitDistributedCacheRuntimeRequired {
    fn panic(&self) -> ! {
        panic!(
            "distributed cache backend `{kind:?}` requires an explicit shared runtime",
            kind = self.kind
        )
    }
}

impl DistributedCacheRuntime for ExplicitDistributedCacheRuntimeRequired {
    fn insert(&self, _entry: CacheEntry) {
        self.panic();
    }

    fn lookup(&self, _key: &CacheKey, _now: CacheInstant) -> CacheLookup {
        self.panic();
    }

    fn invalidate(&self, _tags: &InvalidationSet) -> Vec<CacheKey> {
        self.panic();
    }

    fn begin_fill(
        &self,
        _key: &CacheKey,
        _mode: RequestCoalescingMode,
        _holder: String,
    ) -> FillDecision {
        self.panic();
    }

    fn complete_fill(&self, _lease: &FillLease) -> Result<(), CacheModelError> {
        self.panic();
    }

    fn metrics(&self) -> CacheMetrics {
        CacheMetrics::default()
    }

    fn is_shared_backend(&self) -> bool {
        false
    }
}