coil-cache 0.1.1

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

use crate::CacheModelError;
use serde::{Deserialize, Serialize};

pub(crate) fn require_non_empty(
    field: &'static str,
    value: String,
) -> Result<String, CacheModelError> {
    let trimmed = value.trim();
    if trimmed.is_empty() {
        Err(CacheModelError::EmptyField { field })
    } else {
        Ok(trimmed.to_string())
    }
}

pub(crate) fn validate_token(
    field: &'static str,
    value: String,
) -> Result<String, CacheModelError> {
    let trimmed = require_non_empty(field, value)?;
    if trimmed
        .chars()
        .all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_' | '.' | ':' | '/'))
    {
        Ok(trimmed)
    } else {
        Err(CacheModelError::InvalidToken {
            field,
            value: trimmed,
        })
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct VariationKey(String);

impl VariationKey {
    pub fn new(value: impl Into<String>) -> Self {
        Self(value.into())
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl fmt::Display for VariationKey {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(&self.0)
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct InvalidationTag(String);

impl InvalidationTag {
    pub fn new(value: impl Into<String>) -> Result<Self, CacheModelError> {
        Ok(Self(validate_token("invalidation_tag", value.into())?))
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl fmt::Display for InvalidationTag {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(&self.0)
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct CacheNamespace(String);

impl CacheNamespace {
    pub fn new(value: impl Into<String>) -> Result<Self, CacheModelError> {
        Ok(Self(validate_token("cache_namespace", value.into())?))
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl fmt::Display for CacheNamespace {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(&self.0)
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct CacheKey {
    namespace: CacheNamespace,
    resource: String,
    variation: Option<VariationKey>,
}

impl CacheKey {
    pub fn new(
        namespace: CacheNamespace,
        resource: impl Into<String>,
        variation: Option<VariationKey>,
    ) -> Result<Self, CacheModelError> {
        Ok(Self {
            namespace,
            resource: require_non_empty("cache_resource", resource.into())?,
            variation,
        })
    }

    pub fn namespace(&self) -> &CacheNamespace {
        &self.namespace
    }

    pub fn resource(&self) -> &str {
        &self.resource
    }

    pub fn variation(&self) -> Option<&VariationKey> {
        self.variation.as_ref()
    }
}

impl fmt::Display for CacheKey {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.variation {
            Some(variation) => write!(f, "{}:{}|{}", self.namespace, self.resource, variation),
            None => write!(f, "{}:{}", self.namespace, self.resource),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct EntityTag(String);

impl EntityTag {
    pub fn new(value: impl Into<String>) -> Result<Self, CacheModelError> {
        Ok(Self(validate_token("etag", value.into())?))
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl fmt::Display for EntityTag {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(&self.0)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct CacheInstant(u64);

impl CacheInstant {
    pub const fn from_unix_seconds(seconds: u64) -> Self {
        Self(seconds)
    }

    pub const fn as_unix_seconds(self) -> u64 {
        self.0
    }
}