turbomcp_client/
handlers.rs

1//! Handler traits for bidirectional communication in MCP client
2//!
3//! This module provides handler traits and registration mechanisms for processing
4//! server-initiated requests. The MCP protocol is bidirectional, meaning servers
5//! can also send requests to clients for various purposes like elicitation,
6//! progress reporting, logging, and resource updates.
7//!
8//! ## Handler Types
9//!
10//! - **ElicitationHandler**: Handle user input requests from servers
11//! - **ProgressHandler**: Process progress notifications from long-running operations
12//! - **LogHandler**: Route server log messages to client logging systems
13//! - **ResourceUpdateHandler**: Handle notifications when resources change
14//!
15//! ## Usage
16//!
17//! ```rust,no_run
18//! use turbomcp_client::handlers::{ElicitationHandler, ElicitationRequest, ElicitationResponse, ElicitationAction, HandlerError};
19//! use async_trait::async_trait;
20//!
21//! // Implement elicitation handler
22//! #[derive(Debug)]
23//! struct MyElicitationHandler;
24//!
25//! #[async_trait]
26//! impl ElicitationHandler for MyElicitationHandler {
27//!     async fn handle_elicitation(
28//!         &self,
29//!         request: ElicitationRequest,
30//!     ) -> Result<ElicitationResponse, HandlerError> {
31//!         // Display the prompt to the user
32//!         eprintln!("\n{}", request.message());
33//!         eprintln!("---");
34//!
35//!         // Access the typed schema (not serde_json::Value!)
36//!         let mut content = std::collections::HashMap::new();
37//!         for (field_name, field_def) in &request.schema().properties {
38//!             eprint!("{}: ", field_name);
39//!
40//!             let mut input = String::new();
41//!             std::io::stdin().read_line(&mut input)
42//!                 .map_err(|e| HandlerError::Generic {
43//!                     message: e.to_string()
44//!                 })?;
45//!
46//!             let input = input.trim();
47//!
48//!             // Parse input based on field type (from typed schema!)
49//!             use turbomcp_protocol::types::PrimitiveSchemaDefinition;
50//!             let value: serde_json::Value = match field_def {
51//!                 PrimitiveSchemaDefinition::Boolean { .. } => {
52//!                     serde_json::json!(input == "true" || input == "yes" || input == "1")
53//!                 }
54//!                 PrimitiveSchemaDefinition::Number { .. } | PrimitiveSchemaDefinition::Integer { .. } => {
55//!                     input.parse::<f64>()
56//!                         .map(|n| serde_json::json!(n))
57//!                         .unwrap_or_else(|_| serde_json::json!(input))
58//!                 }
59//!                 _ => serde_json::json!(input),
60//!             };
61//!
62//!             content.insert(field_name.clone(), value);
63//!         }
64//!
65//!         Ok(ElicitationResponse::accept(content))
66//!     }
67//! }
68//! ```
69
70use async_trait::async_trait;
71use std::collections::HashMap;
72use std::sync::Arc;
73use std::time::Duration;
74use thiserror::Error;
75use tracing::{debug, error, info, warn};
76use turbomcp_protocol::MessageId;
77use turbomcp_protocol::jsonrpc::JsonRpcError;
78use turbomcp_protocol::types::LogLevel;
79
80// Re-export MCP protocol notification types directly (MCP spec compliance)
81pub use turbomcp_protocol::types::{
82    CancelledNotification,       // MCP 2025-06-18 spec
83    LoggingNotification,         // MCP 2025-06-18 spec
84    ProgressNotification,        // MCP 2025-06-18 spec
85    ResourceUpdatedNotification, // MCP 2025-06-18 spec
86};
87
88// ============================================================================
89// ERROR TYPES FOR HANDLER OPERATIONS
90// ============================================================================
91
92/// Errors that can occur during handler operations
93#[derive(Error, Debug)]
94#[non_exhaustive]
95pub enum HandlerError {
96    /// Handler operation failed due to user cancellation
97    #[error("User cancelled the operation")]
98    UserCancelled,
99
100    /// Handler operation timed out
101    #[error("Handler operation timed out after {timeout_seconds} seconds")]
102    Timeout { timeout_seconds: u64 },
103
104    /// Input validation failed
105    #[error("Invalid input: {details}")]
106    InvalidInput { details: String },
107
108    /// Handler configuration error
109    #[error("Handler configuration error: {message}")]
110    Configuration { message: String },
111
112    /// Generic handler error
113    #[error("Handler error: {message}")]
114    Generic { message: String },
115
116    /// External system error (e.g., UI framework, database)
117    #[error("External system error: {source}")]
118    External {
119        #[from]
120        source: Box<dyn std::error::Error + Send + Sync>,
121    },
122}
123
124impl HandlerError {
125    /// Convert handler error to JSON-RPC error
126    ///
127    /// This method centralizes the mapping between handler errors and
128    /// JSON-RPC error codes, ensuring consistency across all handlers.
129    ///
130    /// # Error Code Mapping
131    ///
132    /// - **-1**: User rejected sampling request (MCP 2025-06-18 spec)
133    /// - **-32801**: Handler operation timed out
134    /// - **-32602**: Invalid input (bad request)
135    /// - **-32601**: Handler configuration error (method not found)
136    /// - **-32603**: Generic/external handler error (internal error)
137    ///
138    /// # Examples
139    ///
140    /// ```rust
141    /// use turbomcp_client::handlers::HandlerError;
142    ///
143    /// let error = HandlerError::UserCancelled;
144    /// let jsonrpc_error = error.into_jsonrpc_error();
145    /// assert_eq!(jsonrpc_error.code, -1);
146    /// assert!(jsonrpc_error.message.contains("User rejected"));
147    /// ```
148    pub fn into_jsonrpc_error(&self) -> JsonRpcError {
149        let (code, message) = match self {
150            HandlerError::UserCancelled => (-1, "User rejected sampling request".to_string()),
151            HandlerError::Timeout { timeout_seconds } => (
152                -32801,
153                format!(
154                    "Handler operation timed out after {} seconds",
155                    timeout_seconds
156                ),
157            ),
158            HandlerError::InvalidInput { details } => {
159                (-32602, format!("Invalid input: {}", details))
160            }
161            HandlerError::Configuration { message } => {
162                (-32601, format!("Handler configuration error: {}", message))
163            }
164            HandlerError::Generic { message } => (-32603, format!("Handler error: {}", message)),
165            HandlerError::External { source } => {
166                (-32603, format!("External system error: {}", source))
167            }
168        };
169
170        JsonRpcError {
171            code,
172            message,
173            data: None,
174        }
175    }
176}
177
178pub type HandlerResult<T> = Result<T, HandlerError>;
179
180// ============================================================================
181// ELICITATION HANDLER TRAIT
182// ============================================================================
183
184/// Ergonomic wrapper around protocol ElicitRequest with request ID
185///
186/// This type wraps the protocol-level `ElicitRequest` and adds the request ID
187/// from the JSON-RPC envelope. It provides ergonomic accessors while preserving
188/// full type safety from the protocol layer.
189///
190/// # Design Philosophy
191///
192/// Rather than duplicating protocol types, we wrap them. This ensures:
193/// - Type safety is preserved (ElicitationSchema stays typed!)
194/// - No data loss (Duration instead of lossy integer seconds)
195/// - Single source of truth (protocol crate defines MCP types)
196/// - Automatic sync (protocol changes propagate automatically)
197///
198/// # Examples
199///
200/// ```rust,no_run
201/// use turbomcp_client::handlers::ElicitationRequest;
202///
203/// async fn handle(request: ElicitationRequest) {
204///     // Access request ID
205///     println!("ID: {:?}", request.id());
206///
207///     // Access message
208///     println!("Message: {}", request.message());
209///
210///     // Access typed schema (not Value!)
211///     for (name, property) in &request.schema().properties {
212///         println!("Field: {}", name);
213///     }
214///
215///     // Access timeout as Duration
216///     if let Some(timeout) = request.timeout() {
217///         println!("Timeout: {:?}", timeout);
218///     }
219/// }
220/// ```
221#[derive(Debug, Clone)]
222pub struct ElicitationRequest {
223    id: MessageId,
224    inner: turbomcp_protocol::types::ElicitRequest,
225}
226
227impl ElicitationRequest {
228    /// Create a new elicitation request wrapper
229    ///
230    /// # Arguments
231    ///
232    /// * `id` - Request ID from JSON-RPC envelope
233    /// * `request` - Protocol-level elicit request
234    pub fn new(id: MessageId, request: turbomcp_protocol::types::ElicitRequest) -> Self {
235        Self { id, inner: request }
236    }
237
238    /// Get request ID from JSON-RPC envelope
239    pub fn id(&self) -> &MessageId {
240        &self.id
241    }
242
243    /// Get human-readable message for the user
244    ///
245    /// This is the primary prompt/question being asked of the user.
246    pub fn message(&self) -> &str {
247        &self.inner.params.message
248    }
249
250    /// Get schema defining expected response structure
251    ///
252    /// Returns the typed `ElicitationSchema` which provides:
253    /// - Type-safe access to properties
254    /// - Required field information
255    /// - Validation constraints
256    ///
257    /// # Note
258    ///
259    /// This returns a TYPED schema, not `serde_json::Value`.
260    /// You can inspect the schema structure type-safely:
261    ///
262    /// ```rust,no_run
263    /// # use turbomcp_client::handlers::ElicitationRequest;
264    /// # use turbomcp_protocol::types::PrimitiveSchemaDefinition;
265    /// # async fn example(request: ElicitationRequest) {
266    /// for (name, definition) in &request.schema().properties {
267    ///     match definition {
268    ///         PrimitiveSchemaDefinition::String { description, .. } => {
269    ///             println!("String field: {}", name);
270    ///         }
271    ///         PrimitiveSchemaDefinition::Number { minimum, maximum, .. } => {
272    ///             println!("Number field: {} ({:?}-{:?})", name, minimum, maximum);
273    ///         }
274    ///         _ => {}
275    ///     }
276    /// }
277    /// # }
278    /// ```
279    pub fn schema(&self) -> &turbomcp_protocol::types::ElicitationSchema {
280        &self.inner.params.schema
281    }
282
283    /// Get optional timeout as Duration
284    ///
285    /// Converts milliseconds from the protocol to ergonomic `Duration` type.
286    /// No data loss occurs (unlike converting to integer seconds).
287    pub fn timeout(&self) -> Option<Duration> {
288        self.inner
289            .params
290            .timeout_ms
291            .map(|ms| Duration::from_millis(ms as u64))
292    }
293
294    /// Check if request can be cancelled by the user
295    pub fn is_cancellable(&self) -> bool {
296        self.inner.params.cancellable.unwrap_or(false)
297    }
298
299    /// Get access to underlying protocol request if needed
300    ///
301    /// For advanced use cases where you need the raw protocol type.
302    pub fn as_protocol(&self) -> &turbomcp_protocol::types::ElicitRequest {
303        &self.inner
304    }
305
306    /// Consume wrapper and return protocol request
307    pub fn into_protocol(self) -> turbomcp_protocol::types::ElicitRequest {
308        self.inner
309    }
310}
311
312// Re-export protocol action enum (no need to duplicate)
313pub use turbomcp_protocol::types::ElicitationAction;
314
315/// Elicitation response builder
316///
317/// Wrapper around protocol `ElicitResult` with ergonomic factory methods.
318///
319/// # Examples
320///
321/// ```rust
322/// use turbomcp_client::handlers::ElicitationResponse;
323/// use std::collections::HashMap;
324///
325/// // Accept with content
326/// let mut content = HashMap::new();
327/// content.insert("name".to_string(), serde_json::json!("Alice"));
328/// let response = ElicitationResponse::accept(content);
329///
330/// // Decline
331/// let response = ElicitationResponse::decline();
332///
333/// // Cancel
334/// let response = ElicitationResponse::cancel();
335/// ```
336#[derive(Debug, Clone)]
337pub struct ElicitationResponse {
338    inner: turbomcp_protocol::types::ElicitResult,
339}
340
341impl ElicitationResponse {
342    /// Create response with accept action and user content
343    ///
344    /// # Arguments
345    ///
346    /// * `content` - User-submitted data conforming to the request schema
347    pub fn accept(content: HashMap<String, serde_json::Value>) -> Self {
348        Self {
349            inner: turbomcp_protocol::types::ElicitResult {
350                action: ElicitationAction::Accept,
351                content: Some(content),
352                _meta: None,
353            },
354        }
355    }
356
357    /// Create response with decline action (user explicitly declined)
358    pub fn decline() -> Self {
359        Self {
360            inner: turbomcp_protocol::types::ElicitResult {
361                action: ElicitationAction::Decline,
362                content: None,
363                _meta: None,
364            },
365        }
366    }
367
368    /// Create response with cancel action (user dismissed without choice)
369    pub fn cancel() -> Self {
370        Self {
371            inner: turbomcp_protocol::types::ElicitResult {
372                action: ElicitationAction::Cancel,
373                content: None,
374                _meta: None,
375            },
376        }
377    }
378
379    /// Get the action from this response
380    pub fn action(&self) -> ElicitationAction {
381        self.inner.action
382    }
383
384    /// Get the content from this response
385    pub fn content(&self) -> Option<&HashMap<String, serde_json::Value>> {
386        self.inner.content.as_ref()
387    }
388
389    /// Convert to protocol type for sending over the wire
390    pub(crate) fn into_protocol(self) -> turbomcp_protocol::types::ElicitResult {
391        self.inner
392    }
393}
394
395/// Handler for server-initiated elicitation requests
396///
397/// Elicitation is a mechanism where servers can request user input during
398/// operations. For example, a server might need user preferences, authentication
399/// credentials, or configuration choices to complete a task.
400///
401/// Implementations should:
402/// - Present the schema/prompt to the user in an appropriate UI
403/// - Validate user input against the provided schema
404/// - Handle user cancellation gracefully
405/// - Respect timeout constraints
406///
407/// # Examples
408///
409/// ```rust,no_run
410/// use turbomcp_client::handlers::{ElicitationAction, ElicitationHandler, ElicitationRequest, ElicitationResponse, HandlerResult};
411/// use async_trait::async_trait;
412/// use serde_json::json;
413///
414/// #[derive(Debug)]
415/// struct CLIElicitationHandler;
416///
417/// #[async_trait]
418/// impl ElicitationHandler for CLIElicitationHandler {
419///     async fn handle_elicitation(
420///         &self,
421///         request: ElicitationRequest,
422///     ) -> HandlerResult<ElicitationResponse> {
423///         println!("Server request: {}", request.message());
424///
425///         // In a real implementation, you would:
426///         // 1. Inspect the typed schema to understand what input is needed
427///         // 2. Present an appropriate UI (CLI prompts, GUI forms, etc.)
428///         // 3. Validate the user's input against the schema
429///         // 4. Return the structured response
430///
431///         let mut content = std::collections::HashMap::new();
432///         content.insert("user_choice".to_string(), json!("example_value"));
433///         Ok(ElicitationResponse::accept(content))
434///     }
435/// }
436/// ```
437#[async_trait]
438pub trait ElicitationHandler: Send + Sync + std::fmt::Debug {
439    /// Handle an elicitation request from the server
440    ///
441    /// This method is called when a server needs user input. The implementation
442    /// should present the request to the user and collect their response.
443    ///
444    /// # Arguments
445    ///
446    /// * `request` - The elicitation request containing prompt, schema, and metadata
447    ///
448    /// # Returns
449    ///
450    /// Returns the user's response or an error if the operation failed.
451    async fn handle_elicitation(
452        &self,
453        request: ElicitationRequest,
454    ) -> HandlerResult<ElicitationResponse>;
455}
456
457// ============================================================================
458// PROGRESS HANDLER TRAIT
459// ============================================================================
460
461// ProgressNotification is re-exported from protocol (see imports above)
462// This ensures MCP 2025-06-18 spec compliance
463
464/// Handler for server progress notifications
465///
466/// Progress handlers receive notifications about long-running server operations.
467/// This allows clients to display progress bars, status updates, or other
468/// feedback to users during operations that take significant time.
469///
470/// # Examples
471///
472/// ```rust,no_run
473/// use turbomcp_client::handlers::{ProgressHandler, ProgressNotification, HandlerResult};
474/// use async_trait::async_trait;
475///
476/// #[derive(Debug)]
477/// struct ProgressBarHandler;
478///
479/// #[async_trait]
480/// impl ProgressHandler for ProgressBarHandler {
481///     async fn handle_progress(&self, notification: ProgressNotification) -> HandlerResult<()> {
482///         // Use MCP spec fields directly
483///         let progress_val = notification.progress;
484///         if let Some(total) = notification.total {
485///             let percentage = (progress_val / total) * 100.0;
486///             println!("Progress: {:.1}%", percentage);
487///         } else {
488///             println!("Progress: {}", progress_val);
489///         }
490///
491///         if let Some(msg) = &notification.message {
492///             println!("Status: {}", msg);
493///         }
494///
495///         Ok(())
496///     }
497/// }
498/// ```
499#[async_trait]
500pub trait ProgressHandler: Send + Sync + std::fmt::Debug {
501    /// Handle a progress notification from the server
502    ///
503    /// This method is called when the server sends progress updates for
504    /// long-running operations.
505    ///
506    /// # Arguments
507    ///
508    /// * `notification` - Progress information including current status and completion state
509    ///
510    /// # Returns
511    ///
512    /// Returns `Ok(())` if the notification was processed successfully.
513    async fn handle_progress(&self, notification: ProgressNotification) -> HandlerResult<()>;
514}
515
516// ============================================================================
517// LOG HANDLER TRAIT
518// ============================================================================
519
520// LoggingNotification is re-exported from protocol (see imports above)
521// This ensures MCP 2025-06-18 spec compliance
522
523/// Handler for server log messages
524///
525/// Log handlers receive log messages from the server and can route them to
526/// the client's logging system. This is useful for debugging, monitoring,
527/// and maintaining a unified log across client and server.
528///
529/// # Examples
530///
531/// ```rust,no_run
532/// use turbomcp_client::handlers::{LogHandler, LoggingNotification, HandlerResult};
533/// use turbomcp_protocol::types::LogLevel;
534/// use async_trait::async_trait;
535///
536/// #[derive(Debug)]
537/// struct TraceLogHandler;
538///
539/// #[async_trait]
540/// impl LogHandler for TraceLogHandler {
541///     async fn handle_log(&self, log: LoggingNotification) -> HandlerResult<()> {
542///         // MCP spec: data can be any JSON type (string, object, etc.)
543///         let message = log.data.to_string();
544///         match log.level {
545///             LogLevel::Error => tracing::error!("Server: {}", message),
546///             LogLevel::Warning => tracing::warn!("Server: {}", message),
547///             LogLevel::Info => tracing::info!("Server: {}", message),
548///             LogLevel::Debug => tracing::debug!("Server: {}", message),
549///             LogLevel::Notice => tracing::info!("Server: {}", message),
550///             LogLevel::Critical => tracing::error!("Server CRITICAL: {}", message),
551///             LogLevel::Alert => tracing::error!("Server ALERT: {}", message),
552///             LogLevel::Emergency => tracing::error!("Server EMERGENCY: {}", message),
553///         }
554///         Ok(())
555///     }
556/// }
557/// ```
558#[async_trait]
559pub trait LogHandler: Send + Sync + std::fmt::Debug {
560    /// Handle a log message from the server
561    ///
562    /// This method is called when the server sends log messages to the client.
563    /// Implementations can route these to the client's logging system.
564    ///
565    /// # Arguments
566    ///
567    /// * `log` - The log notification with level and data (per MCP 2025-06-18 spec)
568    ///
569    /// # Returns
570    ///
571    /// Returns `Ok(())` if the log message was processed successfully.
572    async fn handle_log(&self, log: LoggingNotification) -> HandlerResult<()>;
573}
574
575// ============================================================================
576// RESOURCE UPDATE HANDLER TRAIT
577// ============================================================================
578
579// ResourceUpdatedNotification is re-exported from protocol (see imports above)
580// This ensures MCP 2025-06-18 spec compliance
581//
582// Per MCP spec: This notification ONLY contains the URI of the changed resource.
583// Clients must call resources/read to get the updated content.
584
585/// Handler for resource update notifications
586///
587/// Resource update handlers receive notifications when resources that the
588/// client has subscribed to are modified. This enables reactive updates
589/// to cached data or UI refreshes when server-side resources change.
590///
591/// # Examples
592///
593/// ```rust,no_run
594/// use turbomcp_client::handlers::{ResourceUpdateHandler, ResourceUpdatedNotification, HandlerResult};
595/// use async_trait::async_trait;
596///
597/// #[derive(Debug)]
598/// struct CacheInvalidationHandler;
599///
600/// #[async_trait]
601/// impl ResourceUpdateHandler for CacheInvalidationHandler {
602///     async fn handle_resource_update(
603///         &self,
604///         notification: ResourceUpdatedNotification,
605///     ) -> HandlerResult<()> {
606///         // Per MCP spec: notification only contains URI
607///         // Client must call resources/read to get updated content
608///         println!("Resource {} was updated", notification.uri);
609///
610///         // In a real implementation, you might:
611///         // - Invalidate cached data for this resource
612///         // - Refresh UI components that display this resource
613///         // - Log the change for audit purposes
614///         // - Trigger dependent computations
615///         
616///         Ok(())
617///     }
618/// }
619/// ```
620#[async_trait]
621pub trait ResourceUpdateHandler: Send + Sync + std::fmt::Debug {
622    /// Handle a resource update notification
623    ///
624    /// This method is called when a subscribed resource changes on the server.
625    ///
626    /// # Arguments
627    ///
628    /// * `notification` - Information about the resource change
629    ///
630    /// # Returns
631    ///
632    /// Returns `Ok(())` if the notification was processed successfully.
633    async fn handle_resource_update(
634        &self,
635        notification: ResourceUpdatedNotification,
636    ) -> HandlerResult<()>;
637}
638
639// ============================================================================
640// ROOTS HANDLER TRAIT
641// ============================================================================
642
643/// Roots handler for responding to server requests for filesystem roots
644///
645/// Per MCP 2025-06-18 specification, `roots/list` is a SERVER->CLIENT request.
646/// Servers ask clients what filesystem roots (directories/files) they have access to.
647/// This is commonly used when servers need to understand their operating boundaries,
648/// such as which repositories or project directories they can access.
649///
650/// # Examples
651///
652/// ```rust,no_run
653/// use turbomcp_client::handlers::{RootsHandler, HandlerResult};
654/// use turbomcp_protocol::types::Root;
655/// use async_trait::async_trait;
656///
657/// #[derive(Debug)]
658/// struct MyRootsHandler {
659///     project_dirs: Vec<String>,
660/// }
661///
662/// #[async_trait]
663/// impl RootsHandler for MyRootsHandler {
664///     async fn handle_roots_request(&self) -> HandlerResult<Vec<Root>> {
665///         Ok(self.project_dirs
666///             .iter()
667///             .map(|dir| Root {
668///                 uri: format!("file://{}", dir).into(),
669///                 name: Some(dir.split('/').last().unwrap_or("").to_string()),
670///             })
671///             .collect())
672///     }
673/// }
674/// ```
675#[async_trait]
676pub trait RootsHandler: Send + Sync + std::fmt::Debug {
677    /// Handle a roots/list request from the server
678    ///
679    /// This method is called when the server wants to know which filesystem roots
680    /// the client has available. The implementation should return a list of Root
681    /// objects representing directories or files the server can operate on.
682    ///
683    /// # Returns
684    ///
685    /// Returns a vector of Root objects, each with a URI (must start with file://)
686    /// and optional human-readable name.
687    ///
688    /// # Note
689    ///
690    /// Per MCP specification, URIs must start with `file://` for now. This restriction
691    /// may be relaxed in future protocol versions.
692    async fn handle_roots_request(&self) -> HandlerResult<Vec<turbomcp_protocol::types::Root>>;
693}
694
695// ============================================================================
696// CANCELLATION HANDLER TRAIT
697// ============================================================================
698
699/// Cancellation handler for processing cancellation notifications
700///
701/// Per MCP 2025-06-18 specification, `notifications/cancelled` can be sent by
702/// either side to indicate cancellation of a previously-issued request.
703///
704/// When the server sends a cancellation notification, it indicates that a request
705/// the client sent is being cancelled and the result will be unused. The client
706/// SHOULD cease any associated processing.
707///
708/// # MCP Specification
709///
710/// From the MCP spec:
711/// - "The request SHOULD still be in-flight, but due to communication latency,
712///    it is always possible that this notification MAY arrive after the request
713///    has already finished."
714/// - "A client MUST NOT attempt to cancel its `initialize` request."
715///
716/// # Examples
717///
718/// ```rust,no_run
719/// use turbomcp_client::handlers::{CancellationHandler, CancelledNotification, HandlerResult};
720/// use async_trait::async_trait;
721///
722/// #[derive(Debug)]
723/// struct MyCancellationHandler;
724///
725/// #[async_trait]
726/// impl CancellationHandler for MyCancellationHandler {
727///     async fn handle_cancellation(&self, notification: CancelledNotification) -> HandlerResult<()> {
728///         println!("Request {} was cancelled", notification.request_id);
729///         if let Some(reason) = &notification.reason {
730///             println!("Reason: {}", reason);
731///         }
732///
733///         // In a real implementation:
734///         // - Look up the in-flight request by notification.request_id
735///         // - Signal cancellation (e.g., via CancellationToken)
736///         // - Clean up any resources
737///
738///         Ok(())
739///     }
740/// }
741/// ```
742#[async_trait]
743pub trait CancellationHandler: Send + Sync + std::fmt::Debug {
744    /// Handle a cancellation notification
745    ///
746    /// This method is called when the server cancels a request that the client
747    /// previously issued.
748    ///
749    /// # Arguments
750    ///
751    /// * `notification` - The cancellation notification containing request ID and optional reason
752    ///
753    /// # Returns
754    ///
755    /// Returns `Ok(())` if the cancellation was processed successfully.
756    async fn handle_cancellation(&self, notification: CancelledNotification) -> HandlerResult<()>;
757}
758
759// ============================================================================
760// LIST CHANGED HANDLER TRAITS
761// ============================================================================
762
763/// Handler for resource list changes
764///
765/// Per MCP 2025-06-18 specification, `notifications/resources/list_changed` is
766/// an optional notification from the server to the client, informing it that the
767/// list of resources it can read from has changed.
768///
769/// This notification has no parameters - it simply signals that the client should
770/// re-query the server's resource list if needed.
771///
772/// # Examples
773///
774/// ```rust,no_run
775/// use turbomcp_client::handlers::{ResourceListChangedHandler, HandlerResult};
776/// use async_trait::async_trait;
777///
778/// #[derive(Debug)]
779/// struct MyResourceListHandler;
780///
781/// #[async_trait]
782/// impl ResourceListChangedHandler for MyResourceListHandler {
783///     async fn handle_resource_list_changed(&self) -> HandlerResult<()> {
784///         println!("Server's resource list changed - refreshing...");
785///         // In a real implementation, re-query: client.list_resources().await
786///         Ok(())
787///     }
788/// }
789/// ```
790#[async_trait]
791pub trait ResourceListChangedHandler: Send + Sync + std::fmt::Debug {
792    /// Handle a resource list changed notification
793    ///
794    /// This method is called when the server's available resource list changes.
795    ///
796    /// # Returns
797    ///
798    /// Returns `Ok(())` if the notification was processed successfully.
799    async fn handle_resource_list_changed(&self) -> HandlerResult<()>;
800}
801
802/// Handler for prompt list changes
803///
804/// Per MCP 2025-06-18 specification, `notifications/prompts/list_changed` is
805/// an optional notification from the server to the client, informing it that the
806/// list of prompts it offers has changed.
807///
808/// # Examples
809///
810/// ```rust,no_run
811/// use turbomcp_client::handlers::{PromptListChangedHandler, HandlerResult};
812/// use async_trait::async_trait;
813///
814/// #[derive(Debug)]
815/// struct MyPromptListHandler;
816///
817/// #[async_trait]
818/// impl PromptListChangedHandler for MyPromptListHandler {
819///     async fn handle_prompt_list_changed(&self) -> HandlerResult<()> {
820///         println!("Server's prompt list changed - refreshing...");
821///         Ok(())
822///     }
823/// }
824/// ```
825#[async_trait]
826pub trait PromptListChangedHandler: Send + Sync + std::fmt::Debug {
827    /// Handle a prompt list changed notification
828    ///
829    /// This method is called when the server's available prompt list changes.
830    ///
831    /// # Returns
832    ///
833    /// Returns `Ok(())` if the notification was processed successfully.
834    async fn handle_prompt_list_changed(&self) -> HandlerResult<()>;
835}
836
837/// Handler for tool list changes
838///
839/// Per MCP 2025-06-18 specification, `notifications/tools/list_changed` is
840/// an optional notification from the server to the client, informing it that the
841/// list of tools it offers has changed.
842///
843/// # Examples
844///
845/// ```rust,no_run
846/// use turbomcp_client::handlers::{ToolListChangedHandler, HandlerResult};
847/// use async_trait::async_trait;
848///
849/// #[derive(Debug)]
850/// struct MyToolListHandler;
851///
852/// #[async_trait]
853/// impl ToolListChangedHandler for MyToolListHandler {
854///     async fn handle_tool_list_changed(&self) -> HandlerResult<()> {
855///         println!("Server's tool list changed - refreshing...");
856///         Ok(())
857///     }
858/// }
859/// ```
860#[async_trait]
861pub trait ToolListChangedHandler: Send + Sync + std::fmt::Debug {
862    /// Handle a tool list changed notification
863    ///
864    /// This method is called when the server's available tool list changes.
865    ///
866    /// # Returns
867    ///
868    /// Returns `Ok(())` if the notification was processed successfully.
869    async fn handle_tool_list_changed(&self) -> HandlerResult<()>;
870}
871
872// ============================================================================
873// HANDLER REGISTRY FOR CLIENT
874// ============================================================================
875
876/// Registry for managing client-side handlers
877///
878/// This registry holds all the handler implementations and provides methods
879/// for registering and invoking them. It's used internally by the Client
880/// to dispatch server-initiated requests to the appropriate handlers.
881#[derive(Debug, Default)]
882pub struct HandlerRegistry {
883    /// Roots handler for filesystem root requests
884    pub roots: Option<Arc<dyn RootsHandler>>,
885
886    /// Elicitation handler for user input requests
887    pub elicitation: Option<Arc<dyn ElicitationHandler>>,
888
889    /// Progress handler for operation updates
890    pub progress: Option<Arc<dyn ProgressHandler>>,
891
892    /// Log handler for server log messages
893    pub log: Option<Arc<dyn LogHandler>>,
894
895    /// Resource update handler for resource change notifications
896    pub resource_update: Option<Arc<dyn ResourceUpdateHandler>>,
897
898    /// Cancellation handler for cancellation notifications
899    pub cancellation: Option<Arc<dyn CancellationHandler>>,
900
901    /// Resource list changed handler
902    pub resource_list_changed: Option<Arc<dyn ResourceListChangedHandler>>,
903
904    /// Prompt list changed handler
905    pub prompt_list_changed: Option<Arc<dyn PromptListChangedHandler>>,
906
907    /// Tool list changed handler
908    pub tool_list_changed: Option<Arc<dyn ToolListChangedHandler>>,
909}
910
911impl HandlerRegistry {
912    /// Create a new empty handler registry
913    pub fn new() -> Self {
914        Self::default()
915    }
916
917    /// Register a roots handler
918    pub fn set_roots_handler(&mut self, handler: Arc<dyn RootsHandler>) {
919        debug!("Registering roots handler");
920        self.roots = Some(handler);
921    }
922
923    /// Register an elicitation handler
924    pub fn set_elicitation_handler(&mut self, handler: Arc<dyn ElicitationHandler>) {
925        debug!("Registering elicitation handler");
926        self.elicitation = Some(handler);
927    }
928
929    /// Register a progress handler
930    pub fn set_progress_handler(&mut self, handler: Arc<dyn ProgressHandler>) {
931        debug!("Registering progress handler");
932        self.progress = Some(handler);
933    }
934
935    /// Register a log handler
936    pub fn set_log_handler(&mut self, handler: Arc<dyn LogHandler>) {
937        debug!("Registering log handler");
938        self.log = Some(handler);
939    }
940
941    /// Register a resource update handler
942    pub fn set_resource_update_handler(&mut self, handler: Arc<dyn ResourceUpdateHandler>) {
943        debug!("Registering resource update handler");
944        self.resource_update = Some(handler);
945    }
946
947    /// Register a cancellation handler
948    pub fn set_cancellation_handler(&mut self, handler: Arc<dyn CancellationHandler>) {
949        debug!("Registering cancellation handler");
950        self.cancellation = Some(handler);
951    }
952
953    /// Register a resource list changed handler
954    pub fn set_resource_list_changed_handler(
955        &mut self,
956        handler: Arc<dyn ResourceListChangedHandler>,
957    ) {
958        debug!("Registering resource list changed handler");
959        self.resource_list_changed = Some(handler);
960    }
961
962    /// Register a prompt list changed handler
963    pub fn set_prompt_list_changed_handler(&mut self, handler: Arc<dyn PromptListChangedHandler>) {
964        debug!("Registering prompt list changed handler");
965        self.prompt_list_changed = Some(handler);
966    }
967
968    /// Register a tool list changed handler
969    pub fn set_tool_list_changed_handler(&mut self, handler: Arc<dyn ToolListChangedHandler>) {
970        debug!("Registering tool list changed handler");
971        self.tool_list_changed = Some(handler);
972    }
973
974    /// Check if a roots handler is registered
975    pub fn has_roots_handler(&self) -> bool {
976        self.roots.is_some()
977    }
978
979    /// Check if an elicitation handler is registered
980    pub fn has_elicitation_handler(&self) -> bool {
981        self.elicitation.is_some()
982    }
983
984    /// Check if a progress handler is registered
985    pub fn has_progress_handler(&self) -> bool {
986        self.progress.is_some()
987    }
988
989    /// Check if a log handler is registered
990    pub fn has_log_handler(&self) -> bool {
991        self.log.is_some()
992    }
993
994    /// Check if a resource update handler is registered
995    pub fn has_resource_update_handler(&self) -> bool {
996        self.resource_update.is_some()
997    }
998
999    /// Get the progress handler if registered
1000    pub fn get_progress_handler(&self) -> Option<Arc<dyn ProgressHandler>> {
1001        self.progress.clone()
1002    }
1003
1004    /// Get the log handler if registered
1005    pub fn get_log_handler(&self) -> Option<Arc<dyn LogHandler>> {
1006        self.log.clone()
1007    }
1008
1009    /// Get the resource update handler if registered
1010    pub fn get_resource_update_handler(&self) -> Option<Arc<dyn ResourceUpdateHandler>> {
1011        self.resource_update.clone()
1012    }
1013
1014    /// Get the cancellation handler if registered
1015    pub fn get_cancellation_handler(&self) -> Option<Arc<dyn CancellationHandler>> {
1016        self.cancellation.clone()
1017    }
1018
1019    /// Get the resource list changed handler if registered
1020    pub fn get_resource_list_changed_handler(&self) -> Option<Arc<dyn ResourceListChangedHandler>> {
1021        self.resource_list_changed.clone()
1022    }
1023
1024    /// Get the prompt list changed handler if registered
1025    pub fn get_prompt_list_changed_handler(&self) -> Option<Arc<dyn PromptListChangedHandler>> {
1026        self.prompt_list_changed.clone()
1027    }
1028
1029    /// Get the tool list changed handler if registered
1030    pub fn get_tool_list_changed_handler(&self) -> Option<Arc<dyn ToolListChangedHandler>> {
1031        self.tool_list_changed.clone()
1032    }
1033
1034    /// Handle a roots/list request from the server
1035    pub async fn handle_roots_request(&self) -> HandlerResult<Vec<turbomcp_protocol::types::Root>> {
1036        match &self.roots {
1037            Some(handler) => {
1038                info!("Processing roots/list request from server");
1039                handler.handle_roots_request().await
1040            }
1041            None => {
1042                warn!("No roots handler registered, returning empty roots list");
1043                // Return empty list per MCP spec - client has no roots available
1044                Ok(Vec::new())
1045            }
1046        }
1047    }
1048
1049    /// Handle an elicitation request
1050    pub async fn handle_elicitation(
1051        &self,
1052        request: ElicitationRequest,
1053    ) -> HandlerResult<ElicitationResponse> {
1054        match &self.elicitation {
1055            Some(handler) => {
1056                info!("Processing elicitation request: {}", request.id);
1057                handler.handle_elicitation(request).await
1058            }
1059            None => {
1060                warn!("No elicitation handler registered, declining request");
1061                Err(HandlerError::Configuration {
1062                    message: "No elicitation handler registered".to_string(),
1063                })
1064            }
1065        }
1066    }
1067
1068    /// Handle a progress notification
1069    pub async fn handle_progress(&self, notification: ProgressNotification) -> HandlerResult<()> {
1070        match &self.progress {
1071            Some(handler) => {
1072                debug!("Processing progress notification");
1073                handler.handle_progress(notification).await
1074            }
1075            None => {
1076                debug!("No progress handler registered, ignoring notification");
1077                Ok(())
1078            }
1079        }
1080    }
1081
1082    /// Handle a log message
1083    pub async fn handle_log(&self, log: LoggingNotification) -> HandlerResult<()> {
1084        match &self.log {
1085            Some(handler) => handler.handle_log(log).await,
1086            None => {
1087                debug!("No log handler registered, ignoring log message");
1088                Ok(())
1089            }
1090        }
1091    }
1092
1093    /// Handle a resource update notification
1094    pub async fn handle_resource_update(
1095        &self,
1096        notification: ResourceUpdatedNotification,
1097    ) -> HandlerResult<()> {
1098        match &self.resource_update {
1099            Some(handler) => {
1100                debug!("Processing resource update: {}", notification.uri);
1101                handler.handle_resource_update(notification).await
1102            }
1103            None => {
1104                debug!("No resource update handler registered, ignoring notification");
1105                Ok(())
1106            }
1107        }
1108    }
1109}
1110
1111// ============================================================================
1112// DEFAULT HANDLER IMPLEMENTATIONS
1113// ============================================================================
1114
1115/// Default elicitation handler that declines all requests
1116#[derive(Debug)]
1117pub struct DeclineElicitationHandler;
1118
1119#[async_trait]
1120impl ElicitationHandler for DeclineElicitationHandler {
1121    async fn handle_elicitation(
1122        &self,
1123        request: ElicitationRequest,
1124    ) -> HandlerResult<ElicitationResponse> {
1125        warn!("Declining elicitation request: {}", request.message());
1126        Ok(ElicitationResponse::decline())
1127    }
1128}
1129
1130/// Default progress handler that logs progress to tracing
1131#[derive(Debug)]
1132pub struct LoggingProgressHandler;
1133
1134#[async_trait]
1135impl ProgressHandler for LoggingProgressHandler {
1136    async fn handle_progress(&self, notification: ProgressNotification) -> HandlerResult<()> {
1137        // Per MCP spec: progress notifications have progress, total (optional), message (optional)
1138        let progress = notification.progress;
1139        let progress_str = if let Some(total) = notification.total {
1140            format!("{}/{}", progress, total)
1141        } else {
1142            format!("{}", progress)
1143        };
1144
1145        if let Some(message) = &notification.message {
1146            info!("Progress {}: {}", progress_str, message);
1147        } else {
1148            info!("Progress: {}", progress_str);
1149        }
1150
1151        Ok(())
1152    }
1153}
1154
1155/// Default log handler that routes server logs to tracing
1156#[derive(Debug)]
1157pub struct TracingLogHandler;
1158
1159#[async_trait]
1160impl LogHandler for TracingLogHandler {
1161    async fn handle_log(&self, log: LoggingNotification) -> HandlerResult<()> {
1162        let logger_prefix = log.logger.as_deref().unwrap_or("server");
1163
1164        // Per MCP spec: data can be any JSON type (string, object, etc.)
1165        let message = log.data.to_string();
1166        match log.level {
1167            LogLevel::Error => error!("[{}] {}", logger_prefix, message),
1168            LogLevel::Warning => warn!("[{}] {}", logger_prefix, message),
1169            LogLevel::Info => info!("[{}] {}", logger_prefix, message),
1170            LogLevel::Debug => debug!("[{}] {}", logger_prefix, message),
1171            LogLevel::Notice => info!("[{}] [NOTICE] {}", logger_prefix, message),
1172            LogLevel::Critical => error!("[{}] [CRITICAL] {}", logger_prefix, message),
1173            LogLevel::Alert => error!("[{}] [ALERT] {}", logger_prefix, message),
1174            LogLevel::Emergency => error!("[{}] [EMERGENCY] {}", logger_prefix, message),
1175        }
1176
1177        Ok(())
1178    }
1179}
1180
1181/// Default resource update handler that logs changes
1182#[derive(Debug)]
1183pub struct LoggingResourceUpdateHandler;
1184
1185#[async_trait]
1186impl ResourceUpdateHandler for LoggingResourceUpdateHandler {
1187    async fn handle_resource_update(
1188        &self,
1189        notification: ResourceUpdatedNotification,
1190    ) -> HandlerResult<()> {
1191        // Per MCP spec: notification only contains URI
1192        info!("Resource {} was updated", notification.uri);
1193        Ok(())
1194    }
1195}
1196
1197/// Default cancellation handler that logs cancellation notifications
1198#[derive(Debug)]
1199pub struct LoggingCancellationHandler;
1200
1201#[async_trait]
1202impl CancellationHandler for LoggingCancellationHandler {
1203    async fn handle_cancellation(&self, notification: CancelledNotification) -> HandlerResult<()> {
1204        if let Some(reason) = &notification.reason {
1205            info!(
1206                "Request {} was cancelled: {}",
1207                notification.request_id, reason
1208            );
1209        } else {
1210            info!("Request {} was cancelled", notification.request_id);
1211        }
1212        Ok(())
1213    }
1214}
1215
1216/// Default resource list changed handler that logs changes
1217#[derive(Debug)]
1218pub struct LoggingResourceListChangedHandler;
1219
1220#[async_trait]
1221impl ResourceListChangedHandler for LoggingResourceListChangedHandler {
1222    async fn handle_resource_list_changed(&self) -> HandlerResult<()> {
1223        info!("Server's resource list changed");
1224        Ok(())
1225    }
1226}
1227
1228/// Default prompt list changed handler that logs changes
1229#[derive(Debug)]
1230pub struct LoggingPromptListChangedHandler;
1231
1232#[async_trait]
1233impl PromptListChangedHandler for LoggingPromptListChangedHandler {
1234    async fn handle_prompt_list_changed(&self) -> HandlerResult<()> {
1235        info!("Server's prompt list changed");
1236        Ok(())
1237    }
1238}
1239
1240/// Default tool list changed handler that logs changes
1241#[derive(Debug)]
1242pub struct LoggingToolListChangedHandler;
1243
1244#[async_trait]
1245impl ToolListChangedHandler for LoggingToolListChangedHandler {
1246    async fn handle_tool_list_changed(&self) -> HandlerResult<()> {
1247        info!("Server's tool list changed");
1248        Ok(())
1249    }
1250}
1251
1252#[cfg(test)]
1253mod tests {
1254    use super::*;
1255    use serde_json::json;
1256    use tokio;
1257
1258    // Test handler implementations
1259    #[derive(Debug)]
1260    struct TestElicitationHandler;
1261
1262    #[async_trait]
1263    impl ElicitationHandler for TestElicitationHandler {
1264        async fn handle_elicitation(
1265            &self,
1266            _request: ElicitationRequest,
1267        ) -> HandlerResult<ElicitationResponse> {
1268            let mut content = HashMap::new();
1269            content.insert("test".to_string(), json!("response"));
1270            Ok(ElicitationResponse::accept(content))
1271        }
1272    }
1273
1274    #[derive(Debug)]
1275    struct TestProgressHandler;
1276
1277    #[async_trait]
1278    impl ProgressHandler for TestProgressHandler {
1279        async fn handle_progress(&self, _notification: ProgressNotification) -> HandlerResult<()> {
1280            Ok(())
1281        }
1282    }
1283
1284    #[tokio::test]
1285    async fn test_handler_registry_creation() {
1286        let registry = HandlerRegistry::new();
1287        assert!(!registry.has_elicitation_handler());
1288        assert!(!registry.has_progress_handler());
1289        assert!(!registry.has_log_handler());
1290        assert!(!registry.has_resource_update_handler());
1291    }
1292
1293    #[tokio::test]
1294    async fn test_elicitation_handler_registration() {
1295        let mut registry = HandlerRegistry::new();
1296        let handler = Arc::new(TestElicitationHandler);
1297
1298        registry.set_elicitation_handler(handler);
1299        assert!(registry.has_elicitation_handler());
1300    }
1301
1302    #[tokio::test]
1303    async fn test_elicitation_request_handling() {
1304        let mut registry = HandlerRegistry::new();
1305        let handler = Arc::new(TestElicitationHandler);
1306        registry.set_elicitation_handler(handler);
1307
1308        // Create protocol request
1309        let proto_request = turbomcp_protocol::types::ElicitRequest {
1310            params: turbomcp_protocol::types::ElicitRequestParams {
1311                message: "Test prompt".to_string(),
1312                schema: turbomcp_protocol::types::ElicitationSchema::new(),
1313                timeout_ms: None,
1314                cancellable: None,
1315            },
1316            _meta: None,
1317        };
1318
1319        // Wrap for handler
1320        let request = ElicitationRequest::new(
1321            turbomcp_protocol::MessageId::String("test-123".to_string()),
1322            proto_request,
1323        );
1324
1325        let response = registry.handle_elicitation(request).await.unwrap();
1326        assert_eq!(response.action(), ElicitationAction::Accept);
1327        assert!(response.content().is_some());
1328    }
1329
1330    #[tokio::test]
1331    async fn test_progress_handler_registration() {
1332        let mut registry = HandlerRegistry::new();
1333        let handler = Arc::new(TestProgressHandler);
1334
1335        registry.set_progress_handler(handler);
1336        assert!(registry.has_progress_handler());
1337    }
1338
1339    #[tokio::test]
1340    async fn test_default_handlers() {
1341        let decline_handler = DeclineElicitationHandler;
1342
1343        // Create protocol request
1344        let proto_request = turbomcp_protocol::types::ElicitRequest {
1345            params: turbomcp_protocol::types::ElicitRequestParams {
1346                message: "Test".to_string(),
1347                schema: turbomcp_protocol::types::ElicitationSchema::new(),
1348                timeout_ms: None,
1349                cancellable: None,
1350            },
1351            _meta: None,
1352        };
1353
1354        // Wrap for handler
1355        let request = ElicitationRequest::new(
1356            turbomcp_protocol::MessageId::String("test".to_string()),
1357            proto_request,
1358        );
1359
1360        let response = decline_handler.handle_elicitation(request).await.unwrap();
1361        assert_eq!(response.action(), ElicitationAction::Decline);
1362    }
1363
1364    #[tokio::test]
1365    async fn test_handler_error_types() {
1366        let error = HandlerError::UserCancelled;
1367        assert!(error.to_string().contains("User cancelled"));
1368
1369        let timeout_error = HandlerError::Timeout {
1370            timeout_seconds: 30,
1371        };
1372        assert!(timeout_error.to_string().contains("30 seconds"));
1373    }
1374
1375    // ========================================================================
1376    // JSON-RPC Error Mapping Tests
1377    // ========================================================================
1378
1379    #[test]
1380    fn test_user_cancelled_error_mapping() {
1381        let error = HandlerError::UserCancelled;
1382        let jsonrpc_error = error.into_jsonrpc_error();
1383
1384        assert_eq!(
1385            jsonrpc_error.code, -1,
1386            "User cancelled should map to -1 per MCP 2025-06-18 spec"
1387        );
1388        assert!(jsonrpc_error.message.contains("User rejected"));
1389        assert!(jsonrpc_error.data.is_none());
1390    }
1391
1392    #[test]
1393    fn test_timeout_error_mapping() {
1394        let error = HandlerError::Timeout {
1395            timeout_seconds: 30,
1396        };
1397        let jsonrpc_error = error.into_jsonrpc_error();
1398
1399        assert_eq!(jsonrpc_error.code, -32801, "Timeout should map to -32801");
1400        assert!(jsonrpc_error.message.contains("30 seconds"));
1401        assert!(jsonrpc_error.data.is_none());
1402    }
1403
1404    #[test]
1405    fn test_invalid_input_error_mapping() {
1406        let error = HandlerError::InvalidInput {
1407            details: "Missing required field".to_string(),
1408        };
1409        let jsonrpc_error = error.into_jsonrpc_error();
1410
1411        assert_eq!(
1412            jsonrpc_error.code, -32602,
1413            "Invalid input should map to -32602"
1414        );
1415        assert!(jsonrpc_error.message.contains("Invalid input"));
1416        assert!(jsonrpc_error.message.contains("Missing required field"));
1417        assert!(jsonrpc_error.data.is_none());
1418    }
1419
1420    #[test]
1421    fn test_configuration_error_mapping() {
1422        let error = HandlerError::Configuration {
1423            message: "Handler not registered".to_string(),
1424        };
1425        let jsonrpc_error = error.into_jsonrpc_error();
1426
1427        assert_eq!(
1428            jsonrpc_error.code, -32601,
1429            "Configuration error should map to -32601"
1430        );
1431        assert!(
1432            jsonrpc_error
1433                .message
1434                .contains("Handler configuration error")
1435        );
1436        assert!(jsonrpc_error.message.contains("Handler not registered"));
1437        assert!(jsonrpc_error.data.is_none());
1438    }
1439
1440    #[test]
1441    fn test_generic_error_mapping() {
1442        let error = HandlerError::Generic {
1443            message: "Something went wrong".to_string(),
1444        };
1445        let jsonrpc_error = error.into_jsonrpc_error();
1446
1447        assert_eq!(
1448            jsonrpc_error.code, -32603,
1449            "Generic error should map to -32603"
1450        );
1451        assert!(jsonrpc_error.message.contains("Handler error"));
1452        assert!(jsonrpc_error.message.contains("Something went wrong"));
1453        assert!(jsonrpc_error.data.is_none());
1454    }
1455
1456    #[test]
1457    fn test_external_error_mapping() {
1458        let external_err = Box::new(std::io::Error::other("Database connection failed"));
1459        let error = HandlerError::External {
1460            source: external_err,
1461        };
1462        let jsonrpc_error = error.into_jsonrpc_error();
1463
1464        assert_eq!(
1465            jsonrpc_error.code, -32603,
1466            "External error should map to -32603"
1467        );
1468        assert!(jsonrpc_error.message.contains("External system error"));
1469        assert!(jsonrpc_error.message.contains("Database connection failed"));
1470        assert!(jsonrpc_error.data.is_none());
1471    }
1472
1473    #[test]
1474    fn test_error_code_uniqueness() {
1475        // Verify that user-facing errors have unique codes
1476        let user_cancelled = HandlerError::UserCancelled.into_jsonrpc_error().code;
1477        let timeout = HandlerError::Timeout { timeout_seconds: 1 }
1478            .into_jsonrpc_error()
1479            .code;
1480        let invalid_input = HandlerError::InvalidInput {
1481            details: "test".to_string(),
1482        }
1483        .into_jsonrpc_error()
1484        .code;
1485        let configuration = HandlerError::Configuration {
1486            message: "test".to_string(),
1487        }
1488        .into_jsonrpc_error()
1489        .code;
1490
1491        // These should all be different
1492        assert_ne!(user_cancelled, timeout);
1493        assert_ne!(user_cancelled, invalid_input);
1494        assert_ne!(user_cancelled, configuration);
1495        assert_ne!(timeout, invalid_input);
1496        assert_ne!(timeout, configuration);
1497        assert_ne!(invalid_input, configuration);
1498    }
1499
1500    #[test]
1501    fn test_error_messages_are_informative() {
1502        // Verify all error messages contain useful information
1503        let errors = vec![
1504            HandlerError::UserCancelled,
1505            HandlerError::Timeout {
1506                timeout_seconds: 42,
1507            },
1508            HandlerError::InvalidInput {
1509                details: "test detail".to_string(),
1510            },
1511            HandlerError::Configuration {
1512                message: "test config".to_string(),
1513            },
1514            HandlerError::Generic {
1515                message: "test generic".to_string(),
1516            },
1517        ];
1518
1519        for error in errors {
1520            let jsonrpc_error = error.into_jsonrpc_error();
1521            assert!(
1522                !jsonrpc_error.message.is_empty(),
1523                "Error message should not be empty"
1524            );
1525            assert!(
1526                jsonrpc_error.message.len() > 10,
1527                "Error message should be descriptive"
1528            );
1529        }
1530    }
1531}