Skip to main content

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}