use std::fmt::{Display, Formatter};
use std::sync::{Arc, OnceLock};
use dashmap::{DashMap, VacantEntry};
use serde_json::Value;
use crate::validator::{OpenApiPayloadValidator, ValidationError};
static GLOBAL_CACHE: OnceLock<ValidatorCache> = OnceLock::new();
pub fn global_validator_cache() -> &'static ValidatorCache {
GLOBAL_CACHE.get_or_init(ValidatorCache::new)
}
#[derive(Debug)]
pub enum CacheError {
ValidatorNotFound,
ValidatorAlreadyExists,
FailedToCreateValidator(ValidationError)
}
impl Display for CacheError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
CacheError::ValidatorNotFound => write!(f, "Validator not found in cache"),
CacheError::ValidatorAlreadyExists => write!(f, "Validator already exists in cache"),
CacheError::FailedToCreateValidator(err) => write!(f, "Failed to create new validator: {}", err)
}
}
}
impl std::error::Error for CacheError {}
#[derive(Default)]
pub struct ValidatorCache {
cache: DashMap<String, Arc<OpenApiPayloadValidator>>,
}
impl ValidatorCache {
pub fn new() -> Self {
ValidatorCache {
cache: DashMap::new(),
}
}
pub fn insert(&self, id: String, spec: Value) -> Result<Arc<OpenApiPayloadValidator>, CacheError> {
match self.cache.entry(id) {
dashmap::mapref::entry::Entry::Occupied(_) => {
Err(CacheError::ValidatorAlreadyExists)
},
dashmap::mapref::entry::Entry::Vacant(entry) => {
Self::create_validator(entry, spec)
}
}
}
fn create_validator(entry: VacantEntry<String, Arc<OpenApiPayloadValidator>>, spec: Value) -> Result<Arc<OpenApiPayloadValidator>, CacheError> {
match OpenApiPayloadValidator::new(spec) {
Ok(validator) => {
log::debug!("Added validator to cache with ID: {}", entry.key());
let validator = Arc::new(validator);
entry.insert(validator.clone());
Ok(validator)
},
Err(e) => {
log::error!("Failed to create validator for ID {}: {}", entry.key(), e);
Err(CacheError::FailedToCreateValidator(e))
}
}
}
pub fn insert_or_replace(&self, id: String, validator: OpenApiPayloadValidator) {
self.cache.insert(id, Arc::new(validator));
}
pub fn get(&self, id: &str) -> Result<Arc<OpenApiPayloadValidator>, CacheError> {
match self.cache.get(id) {
Some(validator) => Ok(Arc::clone(validator.value())),
None => Err(CacheError::ValidatorNotFound),
}
}
pub fn remove(&self, id: &str) -> Result<(), CacheError> {
if self.cache.remove(id).is_none() {
return Err(CacheError::ValidatorNotFound);
}
Ok(())
}
pub fn get_or_insert(&self, id: String, spec: Value) -> Result<Arc<OpenApiPayloadValidator>, CacheError> {
match self.cache.entry(id) {
dashmap::mapref::entry::Entry::Occupied(entry) => {
Ok(Arc::clone(entry.get()))
},
dashmap::mapref::entry::Entry::Vacant(entry) => {
Self::create_validator(entry, spec)
}
}
}
pub fn contains(&self, id: &str) -> bool {
self.cache.contains_key(id)
}
pub fn len(&self) -> usize {
self.cache.len()
}
pub fn is_empty(&self) -> bool {
self.cache.is_empty()
}
pub fn clear(&self) {
self.cache.clear();
log::debug!("Cleared validator cache");
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_cache_get_insert() {
let cache = ValidatorCache::new();
assert!(cache.get("test").is_err());
let spec = json!({
"openapi": "3.1.0"
});
let validator = cache.insert("test".to_string(), spec).unwrap();
assert!(!cache.is_empty());
assert_eq!(cache.len(), 1);
let cached = cache.get("test").unwrap();
assert!(Arc::ptr_eq(&validator, &cached));
}
#[test]
fn test_cache_get_or_insert() {
let cache = ValidatorCache::new();
assert!(cache.get("test").is_err());
let spec = json!({
"openapi": "3.1.0"
});
let validator1 = cache.get_or_insert("test".to_string(), spec.clone()).unwrap();
let validator2 = cache.get_or_insert("test".to_string(), json!({"openapi": "3.0.0"})).unwrap();
assert!(Arc::ptr_eq(&validator1, &validator2));
assert_eq!(cache.len(), 1);
}
#[test]
fn test_cache_clear() {
let cache = ValidatorCache::new();
let spec = json!({
"openapi": "3.1.0"
});
cache.insert("test1".to_string(), spec.clone()).unwrap();
cache.insert("test2".to_string(), spec.clone()).unwrap();
cache.insert("test3".to_string(), spec).unwrap();
assert_eq!(cache.len(), 3);
cache.clear();
assert!(cache.is_empty());
}
#[test]
fn test_global_cache() {
let cache = global_validator_cache();
cache.clear();
let spec = json!({
"openapi": "3.1.0"
});
cache.insert("global_test".to_string(), spec).unwrap();
let same_cache = global_validator_cache();
assert!(same_cache.get("global_test").is_ok());
cache.clear();
}
}