wb-cache 0.1.0

Your L1 in-app write-behind cache for various kinds of backends.
Documentation
use crate::entry::Entry;
use crate::traits::DataController;
use std::fmt::Debug;
use std::sync::Arc;

/// The result of a compute operation on an entry. Results that affect the cache content are reflected in the
/// controller's update pool.
///
/// Variants that contain an [`Entry`] are returned with the final entry value.
pub enum CompResult<DC>
where
    DC: DataController,
{
    /// The entry has been inserted into the cache.
    Inserted(Entry<DC>),
    /// The entry has been replaced with a new one.
    ReplacedWith(Entry<DC>),
    /// The entry has been removed from the cache.
    Removed(Entry<DC>),
    /// The entry is in the cache and remains unchanged.
    Unchanged(Entry<DC>),
    /// There is still no entry for the key.
    StillNone(Arc<DC::Key>),
}

impl<DC> Debug for CompResult<DC>
where
    DC: DataController,
{
    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Inserted(e) => fmt.debug_tuple("CompResult::Inserted").field(e).finish(),
            Self::Removed(e) => fmt.debug_tuple("CompResult::Removed").field(e).finish(),
            Self::ReplacedWith(e) => fmt.debug_tuple("CompResult::ReplacedWith").field(e).finish(),
            Self::Unchanged(e) => fmt.debug_tuple("CompResult::Unchanged").field(e).finish(),
            Self::StillNone(k) => fmt.debug_tuple("CompResult::StillNone").field(k).finish(),
        }
    }
}

/// What the cache controller should do with the initial value that was submitted for data controller processing.
#[derive(Debug, Clone, Copy)]
pub enum DataControllerOp {
    /// Do nothing, only put the returned update record into the update pool.
    Nop,
    /// Insert the value into the cache. This is possible for "immutable" records, i.e., those for which it can be
    /// guaranteed that they won't be changed by the backend. For example, if the primary key field is pre-generated by
    /// the client application, then inserting the record into the database won't change it. If the same guarantee can
    /// be made for all other fields as well, it means that injecting the value into the cache immediately would have
    /// the same effect as inserting it into the database and then reading it back.
    ///
    /// Contrary, if there is an auto-increment field in the record, then sending it to the backend and reading it back
    /// is mandatory to achieve a consistent final state.
    Insert,
    /// Revoke the value from the cache. Useful for cases when the data controller knows that the value must undergo a
    /// flushing operation either to be deleted from the backend or to be updated.
    ///
    /// _Note:_ There is a nuance in how the cache controller decides to flush an individual record when necessary: if
    /// there is an update record for its key but the data record itself is not cached, then it is time to flush that
    /// key under certain circumstances. Such circumstances may include, for example, a request to update the data
    /// record.  Since the cache controller does not know how to apply the update (and the data controller may also be
    /// uncertain about this), the only way to provide the user with a valid data record is to flush the update first.
    ///
    /// ![](https://raw.githubusercontent.com/vrurg/wb-cache/d203ef0eec0060d4fd5eca34bb6646bd24f642d3/docs/flush_individual.svg)
    Revoke,
    /// An extreme case of the `Revoke` operation. The value is not only revoked from the cache, but the corresponding entry
    /// is also removed from the update pool. This would be an ideal case of an "immutable" record which is inserted and deleted
    /// sufficiently fast to not require being written back to the backend.
    Drop,
}

/// The response that the cache controller receives from the data controller after processing a new/change/delete/access
/// request.
pub struct DataControllerResponse<DC>
where
    DC: DataController,
{
    /// The operation that the data controller suggests to perform with the value associated with the processed request.
    pub op:     DataControllerOp,
    /// The update record produced by the data controller. If it is not `None` then the cache controller must put it
    /// into the update pool and will later pass it into the
    /// [`DataController::write_back()`](crate::traits::DataController::write_back) method.
    pub update: Option<DC::CacheUpdate>,
}

impl<DC> Debug for DataControllerResponse<DC>
where
    DC: DataController,
{
    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        fmt.debug_struct("DataControllerResponse")
            .field("op", &self.op)
            .field("update", &self.update)
            .finish()
    }
}