tensor_cache 0.4.0

Multi-layer LLM response cache with exact, semantic, and embedding lookup
Documentation
// SPDX-License-Identifier: MIT OR Apache-2.0
use thiserror::Error;

#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum CacheError {
    #[error("cache entry not found: {0}")]
    NotFound(String),

    #[error("embedding dimension mismatch: expected {expected}, got {got}")]
    DimensionMismatch { expected: usize, got: usize },

    #[error("storage error: {0}")]
    StorageError(String),

    #[error("serialization error: {0}")]
    SerializationError(String),

    #[error("tokenizer error: {0}")]
    TokenizerError(String),

    #[error("cache full: {current} entries >= {capacity} capacity")]
    CacheFull { current: usize, capacity: usize },

    #[error("invalid configuration: {0}")]
    InvalidConfig(String),

    #[error("operation cancelled: {0}")]
    Cancelled(String),

    #[error("lock poisoned: {0}")]
    LockPoisoned(String),
}

impl From<tensor_store::TensorStoreError> for CacheError {
    fn from(e: tensor_store::TensorStoreError) -> Self {
        Self::StorageError(e.to_string())
    }
}

impl From<bitcode::Error> for CacheError {
    fn from(e: bitcode::Error) -> Self {
        Self::SerializationError(e.to_string())
    }
}

pub type Result<T> = std::result::Result<T, CacheError>;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_error_display() {
        let e = CacheError::NotFound("key123".into());
        assert_eq!(e.to_string(), "cache entry not found: key123");

        let e = CacheError::DimensionMismatch {
            expected: 1536,
            got: 768,
        };
        assert_eq!(
            e.to_string(),
            "embedding dimension mismatch: expected 1536, got 768"
        );

        let e = CacheError::StorageError("disk full".into());
        assert_eq!(e.to_string(), "storage error: disk full");

        let e = CacheError::CacheFull {
            current: 10000,
            capacity: 10000,
        };
        assert_eq!(e.to_string(), "cache full: 10000 entries >= 10000 capacity");
    }

    #[test]
    fn test_error_clone_eq() {
        let e1 = CacheError::NotFound("test".into());
        let e2 = e1.clone();
        assert_eq!(e1, e2);
    }

    #[test]
    fn test_from_tensor_store_error() {
        let store_err = tensor_store::TensorStoreError::NotFound("key".into());
        let cache_err: CacheError = store_err.into();
        assert!(matches!(cache_err, CacheError::StorageError(_)));
    }

    #[test]
    fn test_serialization_error_display() {
        let e = CacheError::SerializationError("invalid format".into());
        assert_eq!(e.to_string(), "serialization error: invalid format");
    }

    #[test]
    fn test_tokenizer_error_display() {
        let e = CacheError::TokenizerError("failed to load encoder".into());
        assert_eq!(e.to_string(), "tokenizer error: failed to load encoder");
    }

    #[test]
    fn test_invalid_config_display() {
        let e = CacheError::InvalidConfig("negative capacity".into());
        assert_eq!(e.to_string(), "invalid configuration: negative capacity");
    }

    #[test]
    fn test_cancelled_display() {
        let e = CacheError::Cancelled("timeout".into());
        assert_eq!(e.to_string(), "operation cancelled: timeout");
    }

    #[test]
    fn test_from_bincode_error() {
        let invalid_data = b"not valid bincode";
        let result: std::result::Result<String, bitcode::Error> =
            bitcode::deserialize(invalid_data);
        if let Err(bincode_err) = result {
            let cache_err: CacheError = bincode_err.into();
            assert!(matches!(cache_err, CacheError::SerializationError(_)));
        }
    }
}