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}