Skip to main content

pmcp/shared/
middleware_presets.rs

1//! Pre-configured middleware stacks for common use cases.
2//!
3//! This module provides ready-to-use middleware configurations for typical scenarios,
4//! allowing users to quickly set up production-quality middleware without needing to
5//! understand all the implementation details.
6//!
7//! # Available Presets
8//!
9//! - **stdio**: For stdio transports (logging, validation, NO compression)
10//! - **http**: For HTTP transports (OAuth, logging, retry, compression)
11//! - **websocket**: For WebSocket transports (reconnection, heartbeat)
12//!
13//! # Examples
14//!
15//! ```rust
16//! use pmcp::shared::middleware_presets::PresetConfig;
17//! use pmcp::{ClientBuilder, StdioTransport};
18//!
19//! # async fn example() -> Result<(), pmcp::Error> {
20//! // Use stdio preset for stdio transport
21//! let transport = StdioTransport::new();
22//! let client = ClientBuilder::new(transport)
23//!     .middleware_chain(PresetConfig::stdio().build_protocol_chain())
24//!     .build();
25//! # Ok(())
26//! # }
27//! ```
28
29use crate::shared::{EnhancedMiddlewareChain, MetricsMiddleware};
30use std::sync::Arc;
31
32/// Configuration for middleware presets.
33///
34/// Provides factory methods for creating common middleware configurations.
35#[derive(Debug, Clone)]
36pub struct PresetConfig {
37    service_name: String,
38}
39
40impl PresetConfig {
41    /// Create a new preset configuration with the given service name.
42    pub fn new(service_name: impl Into<String>) -> Self {
43        Self {
44            service_name: service_name.into(),
45        }
46    }
47
48    /// Create a stdio transport preset (metrics, NO compression).
49    ///
50    /// **Critical**: This preset explicitly excludes compression middleware because
51    /// compression would break stdio's line-delimited JSON framing.
52    ///
53    /// Includes:
54    /// - `MetricsMiddleware`: Performance and error metrics
55    ///
56    /// # Examples
57    ///
58    /// ```rust
59    /// use pmcp::shared::middleware_presets::PresetConfig;
60    /// use pmcp::{ClientBuilder, StdioTransport};
61    ///
62    /// # async fn example() -> Result<(), pmcp::Error> {
63    /// let transport = StdioTransport::new();
64    /// let client = ClientBuilder::new(transport)
65    ///     .middleware_chain(PresetConfig::stdio().build_protocol_chain())
66    ///     .build();
67    /// # Ok(())
68    /// # }
69    /// ```
70    pub fn stdio() -> Self {
71        Self::new("pmcp-stdio-client")
72    }
73
74    /// Create an HTTP transport preset (metrics).
75    ///
76    /// Includes:
77    /// - `MetricsMiddleware`: Performance and error metrics
78    ///
79    /// Note: OAuth and compression middleware should be added via
80    /// `StreamableHttpTransportConfigBuilder::with_http_middleware()`.
81    ///
82    /// # Examples
83    ///
84    /// ```rust,ignore
85    /// use pmcp::shared::middleware_presets::PresetConfig;
86    /// use pmcp::shared::streamable_http::StreamableHttpTransportConfigBuilder;
87    /// use pmcp::shared::StreamableHttpTransport;
88    /// use pmcp::ClientBuilder;
89    /// use url::Url;
90    ///
91    /// # fn example() -> Result<(), pmcp::Error> {
92    /// // Protocol middleware preset
93    /// let protocol_chain = PresetConfig::http().build_protocol_chain();
94    ///
95    /// // HTTP middleware (OAuth, etc.) configured separately
96    /// let config = StreamableHttpTransportConfigBuilder::new(
97    ///         Url::parse("http://localhost:8080").unwrap()
98    ///     )
99    ///     // .with_http_middleware(...) // Add HTTP middleware here
100    ///     .build();
101    ///
102    /// let transport = StreamableHttpTransport::new(config);
103    /// let client = ClientBuilder::new(transport)
104    ///     .middleware_chain(protocol_chain)
105    ///     .build();
106    /// # Ok(())
107    /// # }
108    /// ```
109    pub fn http() -> Self {
110        Self::new("pmcp-http-client")
111    }
112
113    /// Create a WebSocket transport preset (metrics).
114    ///
115    /// Includes:
116    /// - `MetricsMiddleware`: Performance and error metrics
117    ///
118    /// Note: WebSocket-specific features like reconnection and heartbeat are
119    /// handled by the WebSocket transport itself, not middleware.
120    ///
121    /// # Examples
122    ///
123    /// ```rust
124    /// use pmcp::shared::middleware_presets::PresetConfig;
125    /// # use pmcp::ClientBuilder;
126    ///
127    /// # async fn example() -> Result<(), pmcp::Error> {
128    /// let protocol_chain = PresetConfig::websocket().build_protocol_chain();
129    /// # Ok(())
130    /// # }
131    /// ```
132    pub fn websocket() -> Self {
133        Self::new("pmcp-websocket-client")
134    }
135
136    /// Build a protocol-level middleware chain from this preset.
137    ///
138    /// Creates an `EnhancedMiddlewareChain` with the middleware configured
139    /// for this preset's transport type.
140    ///
141    /// Currently includes:
142    /// - `MetricsMiddleware`: Performance and error metrics tracking
143    ///
144    /// Users can add additional middleware (logging, retry, etc.) via
145    /// `.with_protocol_middleware()` after using the preset.
146    pub fn build_protocol_chain(&self) -> EnhancedMiddlewareChain {
147        let mut chain = EnhancedMiddlewareChain::new();
148
149        // Add metrics middleware for performance tracking
150        chain.add(Arc::new(MetricsMiddleware::new(self.service_name.clone())));
151
152        chain
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn test_stdio_preset() {
162        let preset = PresetConfig::stdio();
163        assert_eq!(preset.service_name, "pmcp-stdio-client");
164
165        let _chain = preset.build_protocol_chain();
166        // Chain should have metrics middleware
167        // (Exact count checking would require exposing middleware list, which we don't do)
168    }
169
170    #[test]
171    fn test_http_preset() {
172        let preset = PresetConfig::http();
173        assert_eq!(preset.service_name, "pmcp-http-client");
174
175        let _chain = preset.build_protocol_chain();
176        // Chain should have metrics middleware
177    }
178
179    #[test]
180    fn test_websocket_preset() {
181        let preset = PresetConfig::websocket();
182        assert_eq!(preset.service_name, "pmcp-websocket-client");
183
184        let _chain = preset.build_protocol_chain();
185        // Chain should have metrics middleware
186    }
187
188    #[test]
189    fn test_custom_service_name() {
190        let preset = PresetConfig::new("my-custom-service");
191        assert_eq!(preset.service_name, "my-custom-service");
192
193        let _chain = preset.build_protocol_chain();
194        // Chain should have metrics middleware with custom service name
195    }
196}