use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use tokio::sync::RwLock;
use trait_kit::AsyncKit;
use trait_kit::prelude::*;
#[cfg(feature = "auth")]
use super::AuthModule;
#[cfg(feature = "auth")]
use super::CsrfConfigModule;
#[cfg(feature = "http")]
use super::PrometheusCollectorModule;
use super::RateLimitModule;
use super::{
AuditModule, CacheConfig, CacheModule, ConfigWatcherModule, DbConfig, DbModule,
EmbeddingModule, IpWhitelistModule, MetricsCollectorModule, PipelineQueueModule,
PriorityCalculatorModule, RerankModule, ResponseChannelModule, WorkerManagerModule,
};
use crate::audit::AuditLogger;
#[cfg(feature = "auth")]
use crate::auth::GarrisonHandle;
#[cfg(feature = "http")]
use crate::metrics::PrometheusCollector;
use crate::rate_limit::LimiteronAdapter;
use crate::service::embedding::EmbeddingService;
use crate::service::rerank::RerankService;
use crate::{
metrics::InferenceCollector,
pipeline::{PriorityCalculator, PriorityRequestQueue, ResponseChannel, WorkerManager},
};
impl ModuleMeta for EmbeddingModule {
const NAME: &'static str = "embedding";
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
&[]
}
}
impl AsyncAutoBuilder for EmbeddingModule {
type Capability = Arc<RwLock<EmbeddingService>>;
type Error = TraitKitError;
fn build<'a>(
kit: &'a AsyncKit,
) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>> {
Box::pin(async move { kit.config::<Self::Capability>() })
}
}
impl AsyncLifecycle for EmbeddingModule {
fn on_ready<'a>(
_kit: &'a AsyncKit<trait_kit::AsyncReady>,
) -> Pin<Box<dyn Future<Output = Result<(), Self::Error>> + Send + 'a>> {
Box::pin(async {
log::info!("EmbeddingModule: model engine ready");
Ok(())
})
}
}
impl AsyncHealthCheck for EmbeddingModule {
fn check(_cap: &Self::Capability) -> HealthStatus {
HealthStatus::Healthy
}
}
impl ModuleMeta for RerankModule {
const NAME: &'static str = "rerank";
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
&[]
}
}
impl AsyncAutoBuilder for RerankModule {
type Capability = Arc<RwLock<RerankService>>;
type Error = TraitKitError;
fn build<'a>(
kit: &'a AsyncKit,
) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>> {
Box::pin(async move { kit.config::<Self::Capability>() })
}
}
impl AsyncLifecycle for RerankModule {
fn on_ready<'a>(
_kit: &'a AsyncKit<trait_kit::AsyncReady>,
) -> Pin<Box<dyn Future<Output = Result<(), Self::Error>> + Send + 'a>> {
Box::pin(async {
log::info!("RerankModule: rerank service ready");
Ok(())
})
}
}
impl AsyncHealthCheck for RerankModule {
fn check(_cap: &Self::Capability) -> HealthStatus {
HealthStatus::Healthy
}
}
#[cfg(feature = "auth")]
impl ModuleMeta for AuthModule {
const NAME: &'static str = "auth";
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
&[]
}
}
#[cfg(feature = "auth")]
impl AsyncAutoBuilder for AuthModule {
type Capability = Option<Arc<GarrisonHandle>>;
type Error = TraitKitError;
fn build<'a>(
kit: &'a AsyncKit,
) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>> {
Box::pin(async move { kit.config::<Self::Capability>() })
}
}
impl ModuleMeta for RateLimitModule {
const NAME: &'static str = "rate_limit";
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
&[]
}
}
impl AsyncAutoBuilder for RateLimitModule {
type Capability = Arc<LimiteronAdapter>;
type Error = TraitKitError;
fn build<'a>(
kit: &'a AsyncKit,
) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>> {
Box::pin(async move { kit.config::<Self::Capability>() })
}
}
impl AsyncHealthCheck for RateLimitModule {
fn check(cap: &Self::Capability) -> HealthStatus {
if cap.is_healthy() {
HealthStatus::Healthy
} else {
HealthStatus::Unhealthy {
detail: "rate limiter health check failed".into(),
}
}
}
}
impl AsyncLifecycle for RateLimitModule {
fn on_ready<'a>(
_kit: &'a AsyncKit<trait_kit::AsyncReady>,
) -> Pin<Box<dyn Future<Output = Result<(), Self::Error>> + Send + 'a>> {
Box::pin(async {
log::info!("RateLimitModule: on_ready — limiteron rate limiter active");
Ok(())
})
}
fn on_shutdown<'a>(
_cap: &'a Self::Capability,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async {
log::info!("RateLimitModule: on_shutdown — rate limiter shut down");
})
}
}
impl ModuleMeta for CacheModule {
const NAME: &'static str = "cache";
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
&[]
}
}
impl AsyncAutoBuilder for CacheModule {
type Capability = bool;
type Error = TraitKitError;
fn build<'a>(
kit: &'a AsyncKit,
) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>> {
Box::pin(async move {
Ok(kit
.config::<CacheConfig>()
.map(|c| c.enabled)
.unwrap_or(false))
})
}
}
impl AsyncHealthCheck for CacheModule {
fn check(cap: &Self::Capability) -> HealthStatus {
if *cap {
HealthStatus::Healthy
} else {
HealthStatus::Degraded {
detail: "cache disabled".into(),
}
}
}
}
impl ModuleMeta for DbModule {
const NAME: &'static str = "db";
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
&[]
}
}
impl AsyncAutoBuilder for DbModule {
type Capability = bool;
type Error = TraitKitError;
fn build<'a>(
kit: &'a AsyncKit,
) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>> {
Box::pin(async move { Ok(kit.config::<DbConfig>().map(|c| c.enabled).unwrap_or(false)) })
}
}
impl ModuleMeta for AuditModule {
const NAME: &'static str = "audit";
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
&[]
}
}
impl AsyncAutoBuilder for AuditModule {
type Capability = Option<Arc<AuditLogger>>;
type Error = TraitKitError;
fn build<'a>(
kit: &'a AsyncKit,
) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>> {
Box::pin(async move { kit.config::<Self::Capability>() })
}
}
impl AsyncLifecycle for AuditModule {
fn on_shutdown<'a>(cap: &'a Self::Capability) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
if let Some(logger) = cap {
log::info!("AuditModule: flushing audit log before shutdown");
if let Err(e) = logger.flush().await {
log::error!("AuditModule: failed to flush audit log: {}", e);
}
}
})
}
}
#[cfg(feature = "auth")]
impl ModuleMeta for CsrfConfigModule {
const NAME: &'static str = "csrf_config";
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
&[]
}
}
#[cfg(feature = "auth")]
impl AsyncAutoBuilder for CsrfConfigModule {
type Capability = Option<Arc<crate::auth::GarrisonCsrfConfig>>;
type Error = TraitKitError;
fn build<'a>(
kit: &'a AsyncKit,
) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>> {
Box::pin(async move { kit.config::<Self::Capability>() })
}
}
impl ModuleMeta for MetricsCollectorModule {
const NAME: &'static str = "metrics_collector";
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
&[]
}
}
impl AsyncAutoBuilder for MetricsCollectorModule {
type Capability = Option<Arc<InferenceCollector>>;
type Error = TraitKitError;
fn build<'a>(
kit: &'a AsyncKit,
) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>> {
Box::pin(async move { kit.config::<Self::Capability>() })
}
}
#[cfg(feature = "http")]
impl ModuleMeta for PrometheusCollectorModule {
const NAME: &'static str = "prometheus_collector";
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
&[]
}
}
#[cfg(feature = "http")]
impl AsyncAutoBuilder for PrometheusCollectorModule {
type Capability = Option<Arc<PrometheusCollector>>;
type Error = TraitKitError;
fn build<'a>(
kit: &'a AsyncKit,
) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>> {
Box::pin(async move { kit.config::<Self::Capability>() })
}
}
impl ModuleMeta for IpWhitelistModule {
const NAME: &'static str = "ip_whitelist";
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
&[]
}
}
impl AsyncAutoBuilder for IpWhitelistModule {
type Capability = Vec<String>;
type Error = TraitKitError;
fn build<'a>(
kit: &'a AsyncKit,
) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>> {
Box::pin(async move { kit.config::<Self::Capability>() })
}
}
impl ModuleMeta for PipelineQueueModule {
const NAME: &'static str = "pipeline_queue";
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
&[]
}
}
impl AsyncAutoBuilder for PipelineQueueModule {
type Capability = Arc<PriorityRequestQueue>;
type Error = TraitKitError;
fn build<'a>(
kit: &'a AsyncKit,
) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>> {
Box::pin(async move { kit.config::<Self::Capability>() })
}
}
impl ModuleMeta for ResponseChannelModule {
const NAME: &'static str = "response_channel";
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
&[]
}
}
impl AsyncAutoBuilder for ResponseChannelModule {
type Capability = Arc<ResponseChannel>;
type Error = TraitKitError;
fn build<'a>(
kit: &'a AsyncKit,
) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>> {
Box::pin(async move { kit.config::<Self::Capability>() })
}
}
impl ModuleMeta for PriorityCalculatorModule {
const NAME: &'static str = "priority_calculator";
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
&[]
}
}
impl AsyncAutoBuilder for PriorityCalculatorModule {
type Capability = Arc<PriorityCalculator>;
type Error = TraitKitError;
fn build<'a>(
kit: &'a AsyncKit,
) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>> {
Box::pin(async move { kit.config::<Self::Capability>() })
}
}
impl ModuleMeta for WorkerManagerModule {
const NAME: &'static str = "worker_manager";
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
&[]
}
}
impl AsyncAutoBuilder for WorkerManagerModule {
type Capability = Arc<WorkerManager>;
type Error = TraitKitError;
fn build<'a>(
kit: &'a AsyncKit,
) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>> {
Box::pin(async move { kit.config::<Self::Capability>() })
}
}
impl ModuleMeta for ConfigWatcherModule {
const NAME: &'static str = "config_watcher";
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
&[]
}
}
impl AsyncAutoBuilder for ConfigWatcherModule {
type Capability = Arc<confers::watcher::WatcherGuard>;
type Error = TraitKitError;
fn build<'a>(
kit: &'a AsyncKit,
) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>> {
Box::pin(async move { kit.config::<Self::Capability>() })
}
}
impl AsyncLifecycle for ConfigWatcherModule {
fn on_ready<'a>(
_kit: &'a AsyncKit<trait_kit::AsyncReady>,
) -> Pin<Box<dyn Future<Output = Result<(), Self::Error>> + Send + 'a>> {
Box::pin(async {
log::info!("ConfigWatcherModule: config file watcher ready");
Ok(())
})
}
fn on_shutdown<'a>(cap: &'a Self::Capability) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
log::info!("ConfigWatcherModule: shutting down config watcher");
if let Err(e) = cap.shutdown(std::time::Duration::from_secs(5)).await {
log::error!("ConfigWatcherModule: error during watcher shutdown: {}", e);
}
})
}
}
#[cfg(test)]
mod impl_tests {
use super::*;
use crate::config::model::Precision;
use crate::engine::InferenceEngine;
use crate::error::VecboostError;
use async_trait::async_trait;
struct TestMockEngine;
#[async_trait]
impl InferenceEngine for TestMockEngine {
fn embed(&self, _text: &str) -> Result<Vec<f32>, VecboostError> {
Ok(vec![0.0; 128])
}
fn embed_batch(&self, texts: &[String]) -> Result<Vec<Vec<f32>>, VecboostError> {
Ok(texts.iter().map(|_| vec![0.0; 128]).collect())
}
fn precision(&self) -> &Precision {
&Precision::Fp32
}
fn supports_mixed_precision(&self) -> bool {
false
}
async fn try_fallback_to_cpu(
&mut self,
_config: &crate::config::model::ModelConfig,
) -> Result<(), VecboostError> {
Ok(())
}
}
#[test]
fn test_embedding_module_name() {
assert_eq!(EmbeddingModule::NAME, "embedding");
}
#[test]
fn test_rerank_module_name() {
assert_eq!(RerankModule::NAME, "rerank");
}
#[test]
fn test_rate_limit_module_name() {
assert_eq!(RateLimitModule::NAME, "rate_limit");
}
#[test]
fn test_cache_module_name() {
assert_eq!(CacheModule::NAME, "cache");
}
#[test]
fn test_db_module_name() {
assert_eq!(DbModule::NAME, "db");
}
#[test]
fn test_audit_module_name() {
assert_eq!(AuditModule::NAME, "audit");
}
#[test]
fn test_metrics_collector_module_name() {
assert_eq!(MetricsCollectorModule::NAME, "metrics_collector");
}
#[test]
fn test_ip_whitelist_module_name() {
assert_eq!(IpWhitelistModule::NAME, "ip_whitelist");
}
#[test]
fn test_pipeline_queue_module_name() {
assert_eq!(PipelineQueueModule::NAME, "pipeline_queue");
}
#[test]
fn test_response_channel_module_name() {
assert_eq!(ResponseChannelModule::NAME, "response_channel");
}
#[test]
fn test_priority_calculator_module_name() {
assert_eq!(PriorityCalculatorModule::NAME, "priority_calculator");
}
#[test]
fn test_worker_manager_module_name() {
assert_eq!(WorkerManagerModule::NAME, "worker_manager");
}
#[test]
fn test_config_watcher_module_name() {
assert_eq!(ConfigWatcherModule::NAME, "config_watcher");
}
#[cfg(feature = "auth")]
#[test]
fn test_auth_module_name() {
assert_eq!(AuthModule::NAME, "auth");
}
#[cfg(feature = "auth")]
#[test]
fn test_csrf_config_module_name() {
assert_eq!(CsrfConfigModule::NAME, "csrf_config");
}
#[cfg(feature = "http")]
#[test]
fn test_prometheus_collector_module_name() {
assert_eq!(PrometheusCollectorModule::NAME, "prometheus_collector");
}
#[test]
fn test_all_modules_have_no_dependencies() {
assert!(EmbeddingModule::dependencies().is_empty());
assert!(RerankModule::dependencies().is_empty());
assert!(RateLimitModule::dependencies().is_empty());
assert!(CacheModule::dependencies().is_empty());
assert!(DbModule::dependencies().is_empty());
assert!(AuditModule::dependencies().is_empty());
assert!(MetricsCollectorModule::dependencies().is_empty());
assert!(IpWhitelistModule::dependencies().is_empty());
assert!(PipelineQueueModule::dependencies().is_empty());
assert!(ResponseChannelModule::dependencies().is_empty());
assert!(PriorityCalculatorModule::dependencies().is_empty());
assert!(WorkerManagerModule::dependencies().is_empty());
assert!(ConfigWatcherModule::dependencies().is_empty());
}
#[test]
fn test_embedding_health_check_always_healthy() {
let engine: Arc<RwLock<dyn InferenceEngine + Send + Sync>> =
Arc::new(RwLock::new(TestMockEngine));
let service = crate::service::embedding::EmbeddingService::new(engine, None);
let cap: <EmbeddingModule as AsyncAutoBuilder>::Capability = Arc::new(RwLock::new(service));
let status = <EmbeddingModule as AsyncHealthCheck>::check(&cap);
assert!(matches!(status, HealthStatus::Healthy));
}
#[test]
fn test_rerank_health_check_always_healthy() {
let engine: Arc<RwLock<dyn InferenceEngine + Send + Sync>> =
Arc::new(RwLock::new(TestMockEngine));
let service = crate::service::rerank::RerankService::new(engine, None);
let cap: <RerankModule as AsyncAutoBuilder>::Capability = Arc::new(RwLock::new(service));
let status = <RerankModule as AsyncHealthCheck>::check(&cap);
assert!(matches!(status, HealthStatus::Healthy));
}
#[test]
fn test_cache_health_check_enabled() {
let status = <CacheModule as AsyncHealthCheck>::check(&true);
assert!(matches!(status, HealthStatus::Healthy));
}
#[test]
fn test_cache_health_check_disabled() {
let status = <CacheModule as AsyncHealthCheck>::check(&false);
assert!(matches!(status, HealthStatus::Degraded { .. }));
}
}