use crate::config::AppConfig;
use llm_shield_core::Scanner;
use llm_shield_models::cache::{CacheConfig, ResultCache};
use std::collections::HashMap;
use std::sync::Arc;
#[cfg(feature = "cloud")]
use llm_shield_cloud::{CloudLogger, CloudMetrics, CloudSecretManager, CloudStorage};
#[derive(Clone)]
pub struct AppState {
pub config: Arc<AppConfig>,
pub scanners: Arc<HashMap<String, Arc<dyn Scanner>>>,
pub cache: Arc<ResultCache>,
#[cfg(feature = "cloud")]
pub secret_manager: Option<Arc<dyn CloudSecretManager>>,
#[cfg(feature = "cloud")]
pub cloud_storage: Option<Arc<dyn CloudStorage>>,
#[cfg(feature = "cloud")]
pub cloud_metrics: Option<Arc<dyn CloudMetrics>>,
#[cfg(feature = "cloud")]
pub cloud_logger: Option<Arc<dyn CloudLogger>>,
}
impl AppState {
pub fn new(config: AppConfig) -> Self {
let cache_config = CacheConfig {
max_size: config.cache.max_size,
ttl: config.cache.ttl(),
};
let cache = ResultCache::new(cache_config);
Self {
config: Arc::new(config),
scanners: Arc::new(HashMap::new()),
cache: Arc::new(cache),
#[cfg(feature = "cloud")]
secret_manager: None,
#[cfg(feature = "cloud")]
cloud_storage: None,
#[cfg(feature = "cloud")]
cloud_metrics: None,
#[cfg(feature = "cloud")]
cloud_logger: None,
}
}
pub fn with_scanner(mut self, scanner: Arc<dyn Scanner>) -> Self {
let scanners = Arc::make_mut(&mut self.scanners);
scanners.insert(scanner.name().to_string(), scanner);
self
}
pub fn get_scanner(&self, name: &str) -> Option<Arc<dyn Scanner>> {
self.scanners.get(name).cloned()
}
pub fn list_scanners(&self) -> Vec<String> {
self.scanners.keys().cloned().collect()
}
pub fn scanner_count(&self) -> usize {
self.scanners.len()
}
#[cfg(feature = "cloud")]
pub fn with_secret_manager(mut self, manager: Arc<dyn CloudSecretManager>) -> Self {
self.secret_manager = Some(manager);
self
}
#[cfg(feature = "cloud")]
pub fn with_cloud_storage(mut self, storage: Arc<dyn CloudStorage>) -> Self {
self.cloud_storage = Some(storage);
self
}
#[cfg(feature = "cloud")]
pub fn with_cloud_metrics(mut self, metrics: Arc<dyn CloudMetrics>) -> Self {
self.cloud_metrics = Some(metrics);
self
}
#[cfg(feature = "cloud")]
pub fn with_cloud_logger(mut self, logger: Arc<dyn CloudLogger>) -> Self {
self.cloud_logger = Some(logger);
self
}
}
pub struct AppStateBuilder {
config: AppConfig,
scanners: HashMap<String, Arc<dyn Scanner>>,
#[cfg(feature = "cloud")]
secret_manager: Option<Arc<dyn CloudSecretManager>>,
#[cfg(feature = "cloud")]
cloud_storage: Option<Arc<dyn CloudStorage>>,
#[cfg(feature = "cloud")]
cloud_metrics: Option<Arc<dyn CloudMetrics>>,
#[cfg(feature = "cloud")]
cloud_logger: Option<Arc<dyn CloudLogger>>,
}
impl AppStateBuilder {
pub fn new(config: AppConfig) -> Self {
Self {
config,
scanners: HashMap::new(),
#[cfg(feature = "cloud")]
secret_manager: None,
#[cfg(feature = "cloud")]
cloud_storage: None,
#[cfg(feature = "cloud")]
cloud_metrics: None,
#[cfg(feature = "cloud")]
cloud_logger: None,
}
}
pub fn register_scanner(mut self, scanner: Arc<dyn Scanner>) -> Self {
self.scanners.insert(scanner.name().to_string(), scanner);
self
}
pub fn register_scanners(mut self, scanners: Vec<Arc<dyn Scanner>>) -> Self {
for scanner in scanners {
self.scanners.insert(scanner.name().to_string(), scanner);
}
self
}
#[cfg(feature = "cloud")]
pub fn with_secret_manager(mut self, manager: Arc<dyn CloudSecretManager>) -> Self {
self.secret_manager = Some(manager);
self
}
#[cfg(feature = "cloud")]
pub fn with_cloud_storage(mut self, storage: Arc<dyn CloudStorage>) -> Self {
self.cloud_storage = Some(storage);
self
}
#[cfg(feature = "cloud")]
pub fn with_cloud_metrics(mut self, metrics: Arc<dyn CloudMetrics>) -> Self {
self.cloud_metrics = Some(metrics);
self
}
#[cfg(feature = "cloud")]
pub fn with_cloud_logger(mut self, logger: Arc<dyn CloudLogger>) -> Self {
self.cloud_logger = Some(logger);
self
}
pub fn build(self) -> AppState {
let cache_config = CacheConfig {
max_size: self.config.cache.max_size,
ttl: self.config.cache.ttl(),
};
let cache = ResultCache::new(cache_config);
AppState {
config: Arc::new(self.config),
scanners: Arc::new(self.scanners),
cache: Arc::new(cache),
#[cfg(feature = "cloud")]
secret_manager: self.secret_manager,
#[cfg(feature = "cloud")]
cloud_storage: self.cloud_storage,
#[cfg(feature = "cloud")]
cloud_metrics: self.cloud_metrics,
#[cfg(feature = "cloud")]
cloud_logger: self.cloud_logger,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use llm_shield_core::{async_trait, Result, ScanResult, ScannerType, Vault};
struct MockScanner {
name: String,
}
#[async_trait]
impl Scanner for MockScanner {
fn name(&self) -> &str {
&self.name
}
async fn scan(&self, input: &str, _vault: &Vault) -> Result<ScanResult> {
Ok(ScanResult::new(input.to_string(), true, 0.0))
}
fn scanner_type(&self) -> ScannerType {
ScannerType::Input
}
}
#[test]
fn test_app_state_creation() {
let config = AppConfig::default();
let state = AppState::new(config);
assert_eq!(state.scanner_count(), 0);
assert!(state.list_scanners().is_empty());
}
#[test]
fn test_app_state_with_scanner() {
let config = AppConfig::default();
let scanner = Arc::new(MockScanner {
name: "test_scanner".to_string(),
});
let state = AppState::new(config).with_scanner(scanner);
assert_eq!(state.scanner_count(), 1);
assert!(state.get_scanner("test_scanner").is_some());
assert!(state.get_scanner("nonexistent").is_none());
}
#[test]
fn test_list_scanners() {
let config = AppConfig::default();
let scanner1 = Arc::new(MockScanner {
name: "scanner1".to_string(),
});
let scanner2 = Arc::new(MockScanner {
name: "scanner2".to_string(),
});
let state = AppState::new(config)
.with_scanner(scanner1)
.with_scanner(scanner2);
let scanners = state.list_scanners();
assert_eq!(scanners.len(), 2);
assert!(scanners.contains(&"scanner1".to_string()));
assert!(scanners.contains(&"scanner2".to_string()));
}
#[test]
fn test_app_state_builder() {
let config = AppConfig::default();
let scanner1 = Arc::new(MockScanner {
name: "scanner1".to_string(),
});
let scanner2 = Arc::new(MockScanner {
name: "scanner2".to_string(),
});
let state = AppStateBuilder::new(config)
.register_scanner(scanner1)
.register_scanner(scanner2)
.build();
assert_eq!(state.scanner_count(), 2);
}
#[test]
fn test_app_state_builder_multiple() {
let config = AppConfig::default();
let scanners = vec![
Arc::new(MockScanner {
name: "scanner1".to_string(),
}) as Arc<dyn Scanner>,
Arc::new(MockScanner {
name: "scanner2".to_string(),
}),
Arc::new(MockScanner {
name: "scanner3".to_string(),
}),
];
let state = AppStateBuilder::new(config)
.register_scanners(scanners)
.build();
assert_eq!(state.scanner_count(), 3);
}
#[test]
fn test_app_state_clone() {
let config = AppConfig::default();
let scanner = Arc::new(MockScanner {
name: "test".to_string(),
});
let state1 = AppState::new(config).with_scanner(scanner);
let state2 = state1.clone();
assert_eq!(state1.scanner_count(), state2.scanner_count());
assert!(state2.get_scanner("test").is_some());
}
#[test]
fn test_cache_configuration() {
let mut config = AppConfig::default();
config.cache.max_size = 5000;
config.cache.ttl_secs = 600;
let state = AppState::new(config);
assert!(state.cache.get("nonexistent").is_none());
}
}