ultrafast_gateway/plugins/mod.rs
1//! # Plugin System Module
2//!
3//! This module provides a comprehensive plugin system for the Ultrafast Gateway,
4//! allowing dynamic extension of gateway functionality through modular plugins.
5//!
6//! ## Overview
7//!
8//! The plugin system enables:
9//! - **Dynamic Functionality**: Runtime plugin loading and management
10//! - **Request/Response Modification**: Intercept and modify requests/responses
11//! - **Content Filtering**: Automatic content filtering and validation
12//! - **Cost Tracking**: Real-time cost monitoring and analysis
13//! - **Enhanced Logging**: Structured logging with custom formats
14//! - **Error Handling**: Custom error processing and recovery
15//!
16//! ## Plugin Architecture
17//!
18//! The plugin system uses a lifecycle-based architecture:
19//!
20//! 1. **Initialization**: Plugin setup and configuration
21//! 2. **Activation**: Plugin startup and resource allocation
22//! 3. **Execution**: Request/response processing hooks
23//! 4. **Deactivation**: Clean shutdown and resource cleanup
24//!
25//! ## Plugin Types
26//!
27//! ### Content Filtering Plugin
28//!
29//! Automatically filters and validates request/response content:
30//! - **Content Validation**: Checks for inappropriate content
31//! - **Security Filtering**: Removes malicious content
32//! - **Compliance Checking**: Ensures regulatory compliance
33//! - **Custom Rules**: Configurable filtering rules
34//!
35//! ### Cost Tracking Plugin
36//!
37//! Monitors and tracks costs across all providers:
38//! - **Real-time Cost Tracking**: Live cost monitoring
39//! - **Provider Cost Analysis**: Per-provider cost breakdown
40//! - **User Cost Allocation**: Per-user cost tracking
41//! - **Budget Management**: Cost limit enforcement
42//!
43//! ### Logging Plugin
44//!
45//! Enhanced logging with custom formats and destinations:
46//! - **Structured Logging**: JSON and custom log formats
47//! - **Multi-destination**: File, database, and external logging
48//! - **Log Filtering**: Configurable log filtering
49//! - **Performance Logging**: Request/response performance logs
50//!
51//! ## Plugin Lifecycle
52//!
53//! Each plugin follows a defined lifecycle:
54//!
55//! ```rust
56//! use ultrafast_gateway::plugins::{Plugin, PluginManager};
57//!
58//! // Create plugin manager
59//! let mut manager = PluginManager::new();
60//!
61//! // Register and initialize plugin
62//! let plugin = Plugin::ContentFiltering(/* config */);
63//! manager.register_plugin(plugin).await?;
64//!
65//! // Plugin is now active and processing requests
66//! ```
67//!
68//! ## Hook System
69//!
70//! Plugins can hook into different stages of request processing:
71//!
72//! - **Before Request**: Modify incoming requests
73//! - **After Response**: Modify outgoing responses
74//! - **On Error**: Handle and process errors
75//!
76//! ## Configuration
77//!
78//! Plugins are configured via TOML configuration:
79//!
80//! ```toml
81//! [[plugins]]
82//! name = "content_filtering"
83//! enabled = true
84//! priority = 5
85//!
86//! [plugins.config]
87//! filter_level = "moderate"
88//! custom_rules = ["rule1", "rule2"]
89//!
90//! [[plugins]]
91//! name = "cost_tracking"
92//! enabled = true
93//! priority = 10
94//!
95//! [plugins.config]
96//! budget_limit = 100.0
97//! alert_threshold = 0.8
98//! ```
99//!
100//! ## Performance Impact
101//!
102//! The plugin system is designed for minimal performance impact:
103//! - **Async Processing**: Non-blocking plugin execution
104//! - **Priority-based Execution**: Configurable execution order
105//! - **Error Isolation**: Plugin errors don't affect core functionality
106//! - **Resource Management**: Automatic resource cleanup
107//!
108//! ## Security Considerations
109//!
110//! The plugin system includes security features:
111//! - **Sandboxed Execution**: Isolated plugin execution
112//! - **Input Validation**: Plugin input validation
113//! - **Error Handling**: Secure error handling
114//! - **Resource Limits**: Plugin resource limitations
115
116use crate::config::PluginConfig;
117use crate::gateway_error::GatewayError;
118use axum::body::Body;
119use axum::http::Request;
120use axum::response::Response;
121use dashmap::DashMap;
122use serde::{Deserialize, Serialize};
123use std::sync::Arc;
124use tokio::sync::RwLock;
125use uuid::Uuid;
126
127// pub mod rate_limiting; // DEPRECATED: Use auth middleware rate limiting instead
128pub mod content_filtering;
129pub mod cost_tracking;
130pub mod input_validation;
131pub mod logging;
132
133/// Plugin lifecycle states.
134///
135/// Represents the current state of a plugin in its lifecycle.
136/// Plugins transition through these states during initialization,
137/// activation, and shutdown.
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub enum PluginState {
140 /// Plugin is not yet initialized
141 Inactive,
142 /// Plugin is currently starting up
143 Starting,
144 /// Plugin is active and processing requests
145 Active,
146 /// Plugin is shutting down
147 Stopping,
148 /// Plugin has failed with an error message
149 Failed(String),
150}
151
152/// Metadata for a plugin instance.
153///
154/// Contains information about a plugin's identity, state,
155/// configuration, and lifecycle management.
156#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct PluginMetadata {
158 /// Unique identifier for this plugin instance
159 pub id: String,
160 /// Human-readable plugin name
161 pub name: String,
162 /// Plugin version string
163 pub version: String,
164 /// Whether the plugin is enabled
165 pub enabled: bool,
166 /// Current lifecycle state of the plugin
167 pub state: PluginState,
168 /// List of plugin dependencies (other plugin names)
169 pub dependencies: Vec<String>,
170 /// Execution priority (lower numbers = higher priority)
171 pub priority: i32,
172 /// Last error message if the plugin failed
173 pub last_error: Option<String>,
174}
175
176/// Trait for plugin lifecycle management.
177///
178/// Defines the interface for plugin initialization, activation,
179/// deactivation, and health monitoring. All plugins must implement
180/// this trait to participate in the plugin system.
181#[async_trait::async_trait]
182pub trait PluginLifecycle: Send + Sync {
183 /// Initialize the plugin with its configuration.
184 ///
185 /// This method is called when the plugin is first registered.
186 /// It should perform any necessary setup and validation.
187 async fn initialize(&mut self) -> Result<(), GatewayError>;
188
189 /// Start the plugin and begin processing requests.
190 ///
191 /// This method is called to activate the plugin. It should
192 /// allocate any necessary resources and begin processing.
193 async fn start(&mut self) -> Result<(), GatewayError>;
194
195 /// Stop the plugin and stop processing requests.
196 ///
197 /// This method is called to deactivate the plugin. It should
198 /// gracefully shut down and release resources.
199 async fn stop(&mut self) -> Result<(), GatewayError>;
200
201 /// Clean up plugin resources.
202 ///
203 /// This method is called during plugin shutdown to perform
204 /// final cleanup operations.
205 async fn cleanup(&mut self) -> Result<(), GatewayError>;
206
207 /// Perform a health check on the plugin.
208 ///
209 /// This method should verify that the plugin is functioning
210 /// correctly and return an error if there are issues.
211 async fn health_check(&self) -> Result<(), GatewayError>;
212
213 /// Get a reference to the plugin's metadata.
214 fn metadata(&self) -> &PluginMetadata;
215
216 /// Get a mutable reference to the plugin's metadata.
217 fn metadata_mut(&mut self) -> &mut PluginMetadata;
218}
219
220/// Trait for plugin request/response hooks.
221///
222/// Defines the interface for plugins to intercept and modify
223/// requests and responses during processing. Plugins can implement
224/// these hooks to add custom functionality.
225#[async_trait::async_trait]
226pub trait PluginHooks: Send + Sync {
227 /// Hook called before a request is processed.
228 ///
229 /// This method is called before the request is sent to the
230 /// provider. Plugins can modify the request or perform
231 /// validation here.
232 async fn before_request(&self, request: &mut Request<Body>) -> Result<(), GatewayError>;
233
234 /// Hook called after a response is received.
235 ///
236 /// This method is called after the response is received from
237 /// the provider. Plugins can modify the response or perform
238 /// post-processing here.
239 async fn after_response(&self, response: &mut Response<Body>) -> Result<(), GatewayError>;
240
241 /// Hook called when an error occurs.
242 ///
243 /// This method is called when an error occurs during request
244 /// processing. Plugins can handle or modify the error here.
245 async fn on_error(&self, error: &GatewayError) -> Result<(), GatewayError>;
246}
247
248/// Enum representing different plugin types.
249///
250/// Each variant contains the specific plugin implementation.
251/// This enum provides a unified interface for all plugin types
252/// while maintaining type safety.
253#[derive(Debug)]
254pub enum Plugin {
255 /// Cost tracking plugin for monitoring provider costs
256 CostTracking(cost_tracking::CostTrackingPlugin),
257 /// Content filtering plugin for request/response filtering
258 ContentFiltering(content_filtering::ContentFilteringPlugin),
259 /// Enhanced logging plugin for custom logging functionality
260 Logging(logging::LoggingPlugin),
261 /// Lightweight input validation plugin
262 InputValidation(input_validation::InputValidationPlugin),
263}
264
265impl Plugin {
266 pub fn metadata(&self) -> PluginMetadata {
267 match self {
268 Plugin::CostTracking(_) => PluginMetadata {
269 id: Uuid::new_v4().to_string(),
270 name: "cost_tracking".to_string(),
271 version: "1.0.0".to_string(),
272 enabled: true,
273 state: PluginState::Inactive,
274 dependencies: vec![],
275 priority: 10,
276 last_error: None,
277 },
278 Plugin::ContentFiltering(_) => PluginMetadata {
279 id: Uuid::new_v4().to_string(),
280 name: "content_filtering".to_string(),
281 version: "1.0.0".to_string(),
282 enabled: true,
283 state: PluginState::Inactive,
284 dependencies: vec![],
285 priority: 5, // Higher priority than cost tracking
286 last_error: None,
287 },
288 Plugin::Logging(_) => PluginMetadata {
289 id: Uuid::new_v4().to_string(),
290 name: "logging".to_string(),
291 version: "1.0.0".to_string(),
292 enabled: true,
293 state: PluginState::Inactive,
294 dependencies: vec![],
295 priority: 20, // Lower priority
296 last_error: None,
297 },
298 Plugin::InputValidation(_) => PluginMetadata {
299 id: Uuid::new_v4().to_string(),
300 name: "input_validation".to_string(),
301 version: "1.0.0".to_string(),
302 enabled: true,
303 state: PluginState::Inactive,
304 dependencies: vec![],
305 priority: 4, // before content filtering
306 last_error: None,
307 },
308 }
309 }
310
311 pub fn name(&self) -> &str {
312 match self {
313 Plugin::CostTracking(_) => "cost_tracking",
314 Plugin::ContentFiltering(_) => "content_filtering",
315 Plugin::Logging(_) => "logging",
316 Plugin::InputValidation(_) => "input_validation",
317 }
318 }
319
320 pub fn enabled(&self) -> bool {
321 match self {
322 Plugin::CostTracking(p) => p.enabled(),
323 Plugin::ContentFiltering(p) => p.enabled(),
324 Plugin::Logging(p) => p.enabled(),
325 Plugin::InputValidation(p) => p.enabled(),
326 }
327 }
328
329 #[allow(dead_code)]
330 fn set_error(&mut self, error: String) {
331 // In a full implementation, plugins would store their own metadata
332 tracing::error!("Plugin {} error: {}", self.name(), error);
333 }
334
335 pub async fn before_request(&self, request: &mut Request<Body>) -> Result<(), GatewayError> {
336 match self {
337 Plugin::CostTracking(p) => p.before_request(request).await,
338 Plugin::ContentFiltering(p) => p.before_request(request).await,
339 Plugin::Logging(p) => p.before_request(request).await,
340 Plugin::InputValidation(p) => p.before_request(request).await,
341 }
342 }
343
344 pub async fn after_response(&self, response: &mut Response<Body>) -> Result<(), GatewayError> {
345 match self {
346 Plugin::CostTracking(p) => p.after_response(response).await,
347 Plugin::ContentFiltering(p) => p.after_response(response).await,
348 Plugin::Logging(p) => p.after_response(response).await,
349 Plugin::InputValidation(p) => p.after_response(response).await,
350 }
351 }
352
353 pub async fn on_error(&self, error: &GatewayError) -> Result<(), GatewayError> {
354 match self {
355 Plugin::CostTracking(p) => p.on_error(error).await,
356 Plugin::ContentFiltering(p) => p.on_error(error).await,
357 Plugin::Logging(p) => p.on_error(error).await,
358 Plugin::InputValidation(p) => p.on_error(error).await,
359 }
360 }
361}
362
363#[derive(Debug)]
364/// A plugin with managed lifecycle and metadata.
365///
366/// This struct wraps a plugin with its metadata and provides
367/// lifecycle management functionality. It ensures that plugins
368/// are properly initialized, started, and cleaned up.
369pub struct ManagedPlugin {
370 /// The underlying plugin implementation
371 plugin: Plugin,
372 /// Plugin metadata and state information
373 metadata: PluginMetadata,
374}
375
376impl ManagedPlugin {
377 pub fn new(plugin: Plugin) -> Self {
378 let metadata = plugin.metadata();
379 Self { plugin, metadata }
380 }
381
382 pub async fn initialize(&mut self) -> Result<(), GatewayError> {
383 self.metadata.state = PluginState::Starting;
384
385 match self
386 .plugin
387 .before_request(
388 &mut axum::http::Request::builder()
389 .body(axum::body::Body::empty())
390 .unwrap(),
391 )
392 .await
393 {
394 Ok(_) => {
395 self.metadata.state = PluginState::Active;
396 self.metadata.last_error = None;
397 tracing::info!("Plugin {} initialized successfully", self.metadata.name);
398 Ok(())
399 }
400 Err(e) => {
401 self.metadata.state = PluginState::Failed(e.to_string());
402 self.metadata.last_error = Some(e.to_string());
403 tracing::error!("Plugin {} initialization failed: {}", self.metadata.name, e);
404 Err(e)
405 }
406 }
407 }
408
409 pub async fn stop(&mut self) -> Result<(), GatewayError> {
410 self.metadata.state = PluginState::Stopping;
411 self.metadata.state = PluginState::Inactive;
412 tracing::info!("Plugin {} stopped", self.metadata.name);
413 Ok(())
414 }
415
416 pub fn is_active(&self) -> bool {
417 matches!(self.metadata.state, PluginState::Active) && self.metadata.enabled
418 }
419}
420
421/// Manages the lifecycle and execution of all plugins.
422///
423/// This struct provides centralized plugin management including
424/// registration, lifecycle management, and execution coordination.
425/// It ensures plugins are executed in the correct order and
426/// handles plugin failures gracefully.
427pub struct PluginManager {
428 /// Concurrent map of registered plugins by name
429 plugins: DashMap<String, ManagedPlugin>,
430 /// Plugin execution order (lower priority numbers execute first)
431 execution_order: Arc<RwLock<Vec<String>>>,
432}
433
434impl PluginManager {
435 pub fn new() -> Self {
436 Self {
437 plugins: DashMap::new(),
438 execution_order: Arc::new(RwLock::new(Vec::new())),
439 }
440 }
441
442 pub async fn register_plugin(&mut self, plugin: Plugin) -> Result<(), GatewayError> {
443 let mut managed_plugin = ManagedPlugin::new(plugin);
444 let plugin_name = managed_plugin.metadata.name.clone();
445 let _priority = managed_plugin.metadata.priority;
446
447 // Initialize the plugin
448 managed_plugin.initialize().await?;
449
450 // Insert into plugins map
451 self.plugins.insert(plugin_name.clone(), managed_plugin);
452
453 // Update execution order based on priority
454 {
455 let mut order = self.execution_order.write().await;
456 order.push(plugin_name.clone());
457 order.sort_by_key(|name| {
458 // Get priority from plugins map (this is a simplified approach)
459 match name.as_str() {
460 "input_validation" => 4,
461 "content_filtering" => 5,
462 "cost_tracking" => 10,
463 "logging" => 20,
464 _ => 100,
465 }
466 });
467 }
468
469 tracing::info!("Plugin registered and initialized: {}", plugin_name);
470 Ok(())
471 }
472
473 pub async fn get_plugin_metadata(&self, name: &str) -> Option<PluginMetadata> {
474 self.plugins.get(name).map(|p| p.metadata.clone())
475 }
476
477 pub async fn list_plugins(&self) -> Vec<PluginMetadata> {
478 self.plugins
479 .iter()
480 .map(|entry| entry.value().metadata.clone())
481 .collect()
482 }
483
484 pub async fn stop_plugin(&self, name: &str) -> Result<(), GatewayError> {
485 if let Some(mut plugin_entry) = self.plugins.get_mut(name) {
486 plugin_entry.stop().await?;
487 tracing::info!("Plugin stopped: {}", name);
488 }
489 Ok(())
490 }
491
492 pub async fn stop_all_plugins(&self) -> Result<(), GatewayError> {
493 for mut plugin_entry in self.plugins.iter_mut() {
494 let name = plugin_entry.key().clone();
495 if let Err(e) = plugin_entry.stop().await {
496 tracing::error!("Failed to stop plugin {}: {}", name, e);
497 }
498 }
499 tracing::info!("All plugins stopped");
500 Ok(())
501 }
502
503 pub async fn before_request(&self, request: &mut Request<Body>) -> Result<(), GatewayError> {
504 let execution_order = self.execution_order.read().await;
505
506 // Execute plugins in priority order
507 for plugin_name in execution_order.iter() {
508 if let Some(managed_plugin) = self.plugins.get(plugin_name) {
509 if managed_plugin.is_active() {
510 if let Err(e) = managed_plugin.plugin.before_request(request).await {
511 tracing::error!("Plugin {} failed in before_request: {}", plugin_name, e);
512 // Don't stop the chain for non-critical errors
513 if matches!(e, GatewayError::ContentFiltered { .. }) {
514 return Err(e);
515 }
516 }
517 }
518 }
519 }
520 Ok(())
521 }
522
523 pub async fn after_response(&self, response: &mut Response<Body>) -> Result<(), GatewayError> {
524 let execution_order = self.execution_order.read().await;
525
526 // Execute plugins in reverse priority order for cleanup
527 for plugin_name in execution_order.iter().rev() {
528 if let Some(managed_plugin) = self.plugins.get(plugin_name) {
529 if managed_plugin.is_active() {
530 if let Err(e) = managed_plugin.plugin.after_response(response).await {
531 tracing::error!("Plugin {} failed in after_response: {}", plugin_name, e);
532 // Continue with other plugins even if one fails
533 }
534 }
535 }
536 }
537 Ok(())
538 }
539
540 pub async fn on_error(&self, error: &GatewayError) -> Result<(), GatewayError> {
541 let execution_order = self.execution_order.read().await;
542
543 for plugin_name in execution_order.iter() {
544 if let Some(managed_plugin) = self.plugins.get(plugin_name) {
545 if managed_plugin.is_active() {
546 if let Err(e) = managed_plugin.plugin.on_error(error).await {
547 tracing::error!("Plugin {} failed in on_error: {}", plugin_name, e);
548 // Continue with other plugins even if one fails
549 }
550 }
551 }
552 }
553 Ok(())
554 }
555}
556
557impl Default for PluginManager {
558 fn default() -> Self {
559 Self::new()
560 }
561}
562
563impl Clone for Plugin {
564 fn clone(&self) -> Self {
565 match self {
566 Plugin::CostTracking(p) => Plugin::CostTracking(p.clone()),
567 Plugin::ContentFiltering(p) => Plugin::ContentFiltering(p.clone()),
568 Plugin::Logging(p) => Plugin::Logging(p.clone()),
569 Plugin::InputValidation(p) => Plugin::InputValidation(p.clone()),
570 }
571 }
572}
573
574pub fn create_plugin(config: &PluginConfig) -> Result<Plugin, GatewayError> {
575 match config.name.as_str() {
576 "rate_limiting" => {
577 // DEPRECATED: Rate limiting is now handled by auth middleware
578 Err(GatewayError::Config {
579 message: "rate_limiting plugin is deprecated. Use auth middleware rate limiting instead. Configure rate limits in [auth.rate_limiting] or per API key.".to_string(),
580 })
581 }
582 "cost_tracking" => Ok(Plugin::CostTracking(
583 cost_tracking::CostTrackingPlugin::new(config)?,
584 )),
585 "content_filtering" => Ok(Plugin::ContentFiltering(
586 content_filtering::ContentFilteringPlugin::new(config)?,
587 )),
588 "logging" => Ok(Plugin::Logging(logging::LoggingPlugin::new(config)?)),
589 "input_validation" => Ok(Plugin::InputValidation(
590 crate::plugins::input_validation::build_input_validation_plugin(
591 config
592 .config
593 .get("enabled")
594 .and_then(|v| v.as_bool())
595 .unwrap_or(true),
596 ),
597 )),
598 _ => Err(GatewayError::Config {
599 message: format!("Unknown plugin: {}", config.name),
600 }),
601 }
602}