sassi 0.1.0-beta.1

Typed in-memory pool with composable predicate algebra and cross-runtime trait queries.
Documentation
//! [`TenantKey`] — adopter-owned tenant labels for applications that keep
//! tenant context beside Sassi handles.
//!
//! Sassi does not infer tenant isolation from cached values and does not
//! turn [`crate::punnu::PunnuConfig::namespace`] into an L1 tenancy boundary.
//! `PunnuConfig::namespace` is for L2 backend keyspace separation.
//!
//! Applications that need tenant or substrate separation should make that
//! boundary explicit: own separate pool handles per substrate, use distinct
//! wrapper types, choose a tenant-qualified id when identity itself is
//! tenant-qualified, or carry `TenantKey` alongside the pool/reference that
//! is allowed to serve a request.
//!
//! Do not share one `Punnu<T>` across tenants while fetching by an unqualified
//! primitive id and relying on `TenantKey` beside the handle. L1 storage and
//! single-flight coalescing are keyed by `T::Id`, not by fetcher context. If two
//! tenants can both ask for `id = 7`, encode the tenant into `T::Id`, use a
//! tenant-specific wrapper type, or keep separate pools.

/// Opaque tenant identifier.
///
/// A `TenantKey` is just a wrapper around `String`: no parsing, no schema,
/// and no built-in authorization semantics. Sassi never inspects the contents.
/// Consumers choose the encoding; typical patterns are tenant slugs (`"acme"`),
/// stable external ids, or environment-prefixed labels (`"prod_acme"`).
///
/// `TenantKey` is useful application state, not an automatic cache-key
/// dimension. Passing it around beside a shared pool does not change
/// `get_or_fetch` or L1 identity semantics.
///
/// # Construction
///
/// Use the standard `From<String>` / `From<&str>` impls, or
/// [`TenantKey::none`] for the "single-tenant pool" sentinel.
///
/// ```
/// use sassi::TenantKey;
///
/// let acme: TenantKey = "acme".into();
/// assert_eq!(acme.as_str(), "acme");
/// assert!(TenantKey::none().is_none());
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TenantKey(String);

impl TenantKey {
    /// Construct from anything that converts into a `String`.
    pub fn new(key: impl Into<String>) -> Self {
        Self(key.into())
    }

    /// Sentinel for "no tenant" — equivalent to a single-tenant pool.
    /// Returns `None` so the caller can branch on tenancy without
    /// constructing a placeholder string.
    pub fn none() -> Option<Self> {
        None
    }

    /// Borrow the underlying string. Useful for cache-key composition
    /// and for diagnostics.
    pub fn as_str(&self) -> &str {
        &self.0
    }

    /// Move the underlying string out of the wrapper.
    pub fn into_string(self) -> String {
        self.0
    }
}

impl From<String> for TenantKey {
    fn from(s: String) -> Self {
        Self(s)
    }
}

impl From<&str> for TenantKey {
    fn from(s: &str) -> Self {
        Self(s.to_owned())
    }
}

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