airsprotocols_mcp/protocol/
transport.rs

1//! MCP Transport Abstractions and Event-Driven Architecture
2//!
3//! This module provides the event-driven transport abstraction aligned with the
4//! official Model Context Protocol (MCP) specification, providing sophisticated
5//! message handling that matches the patterns used in official TypeScript and Python SDKs.
6//!
7//! # Architecture
8//!
9//! The MCP-compliant transport layer is built around:
10//! - **MessageHandler**: Event-driven protocol logic (separation of concerns)  
11//! - **Transport**: Event-driven transport interface
12//! - **MessageContext**: Session and metadata management
13//! - **TransportError**: Comprehensive error handling
14//!
15//! # Design Philosophy
16//!
17//! - **Event-Driven**: Uses callbacks instead of blocking receive() operations
18//! - **Specification-Aligned**: Matches official MCP SDK patterns exactly
19//! - **Clean Separation**: Transport handles delivery, MessageHandler handles protocol
20//! - **Natural Correlation**: Uses JSON-RPC message IDs, no artificial mechanisms
21//! - **Session-Aware**: Supports multi-session transports like HTTP
22//!
23//! # Examples
24//!
25//! ## Generic MessageHandler Pattern
26//!
27//! ```rust
28//! use airsprotocols_mcp::protocol::{MessageHandler, JsonRpcMessage, MessageContext, TransportError};
29//! use std::sync::Arc;
30//! use async_trait::async_trait;
31//!
32//! // Example with unit context (for STDIO transport)
33//! struct EchoHandler;
34//!
35//! #[async_trait]
36//! impl MessageHandler<()> for EchoHandler {
37//!     async fn handle_message(&self, message: JsonRpcMessage, _context: MessageContext<()>) {
38//!         println!("Received message: {:?}", message);
39//!     }
40//!     
41//!     async fn handle_error(&self, error: TransportError) {
42//!         eprintln!("Transport error: {:?}", error);
43//!     }
44//!     
45//!     async fn handle_close(&self) {
46//!         println!("Transport closed");
47//!     }
48//! }
49//! ```
50
51// Layer 1: Standard library imports
52use std::collections::HashMap;
53use std::time::Duration;
54
55// Layer 2: Third-party crate imports
56use async_trait::async_trait;
57use chrono::{DateTime, Utc};
58use thiserror::Error;
59
60// Layer 3: Internal module imports
61use super::message::{JsonRpcMessage, JsonRpcRequest, JsonRpcResponse};
62use super::types::{ProtocolVersion, ServerCapabilities, ServerConfig, ServerInfo};
63
64/// Transport error types for comprehensive error handling
65///
66/// This enum covers all possible transport-level errors that can occur
67/// during MCP message handling, providing specific error types for
68/// different failure scenarios.
69#[derive(Error, Debug)]
70pub enum TransportError {
71    /// Connection-related errors
72    #[error("Connection error: {message}")]
73    Connection { message: String },
74
75    /// I/O operation errors
76    #[error("I/O error: {source}")]
77    Io { source: std::io::Error },
78
79    /// Message serialization/deserialization errors
80    #[error("Serialization error: {source}")]
81    Serialization { source: serde_json::Error },
82
83    /// Protocol-level errors
84    #[error("Protocol error: {message}")]
85    Protocol { message: String },
86
87    /// Timeout errors
88    #[error("Timeout error: {message}")]
89    Timeout { message: String },
90
91    /// Authentication/authorization errors
92    #[error("Authentication error: {message}")]
93    Auth { message: String },
94
95    /// Request-specific timeout errors (client operation)
96    #[error("Request timeout after {duration:?}")]
97    RequestTimeout { duration: Duration },
98
99    /// Response parsing errors (client receiving invalid response)
100    #[error("Invalid response format: {message}")]
101    InvalidResponse { message: String },
102
103    /// Client not ready for requests (not connected, initializing, etc.)
104    #[error("Client not ready: {reason}")]
105    NotReady { reason: String },
106
107    /// Generic transport errors
108    #[error("Transport error: {message}")]
109    Other { message: String },
110}
111
112impl From<std::io::Error> for TransportError {
113    fn from(error: std::io::Error) -> Self {
114        TransportError::Io { source: error }
115    }
116}
117
118impl From<serde_json::Error> for TransportError {
119    fn from(error: serde_json::Error) -> Self {
120        TransportError::Serialization { source: error }
121    }
122}
123
124impl TransportError {
125    /// Create a request timeout error with the specified duration
126    ///
127    /// This convenience constructor is useful for client implementations
128    /// when a request times out after waiting for the specified duration.
129    ///
130    /// # Arguments
131    ///
132    /// * `duration` - The duration after which the request timed out
133    ///
134    /// # Examples
135    ///
136    /// ```rust
137    /// use airsprotocols_mcp::protocol::TransportError;
138    /// use std::time::Duration;
139    ///
140    /// let timeout_error = TransportError::request_timeout(Duration::from_secs(30));
141    /// ```
142    pub fn request_timeout(duration: Duration) -> Self {
143        Self::RequestTimeout { duration }
144    }
145
146    /// Create an invalid response error with the specified message
147    ///
148    /// This convenience constructor is useful for client implementations
149    /// when they receive a response that cannot be parsed or is malformed.
150    ///
151    /// # Arguments
152    ///
153    /// * `message` - Description of what made the response invalid
154    ///
155    /// # Examples
156    ///
157    /// ```rust
158    /// use airsprotocols_mcp::protocol::TransportError;
159    ///
160    /// let invalid_response = TransportError::invalid_response("Missing required 'result' field");
161    /// ```
162    pub fn invalid_response(message: impl Into<String>) -> Self {
163        Self::InvalidResponse {
164            message: message.into(),
165        }
166    }
167
168    /// Create a not ready error with the specified reason
169    ///
170    /// This convenience constructor is useful for client implementations
171    /// when they cannot process requests due to not being ready.
172    ///
173    /// # Arguments
174    ///
175    /// * `reason` - Description of why the client is not ready
176    ///
177    /// # Examples
178    ///
179    /// ```rust
180    /// use airsprotocols_mcp::protocol::TransportError;
181    ///
182    /// let not_ready = TransportError::not_ready("Transport not connected");
183    /// ```
184    pub fn not_ready(reason: impl Into<String>) -> Self {
185        Self::NotReady {
186            reason: reason.into(),
187        }
188    }
189}
190
191/// Message context for session and metadata management
192///
193/// This structure carries session information and metadata for each message,
194/// enabling proper handling of multi-session transports like HTTP.
195///
196/// # Examples
197///
198/// ```rust
199/// use airsprotocols_mcp::protocol::MessageContext;
200/// use chrono::Utc;
201///
202/// // Default generic context (for STDIO)
203/// let context = MessageContext::<()>::new("session-123".to_string())
204///     .with_remote_addr("192.168.1.100:8080".to_string())
205///     .with_user_agent("airsprotocols-mcp-client/1.0".to_string());
206///
207/// assert_eq!(context.session_id(), Some("session-123"));
208/// assert_eq!(context.remote_addr(), Some("192.168.1.100:8080"));
209/// ```
210#[derive(Debug, Clone)]
211pub struct MessageContext<T = ()> {
212    /// Session identifier (if applicable)
213    session_id: Option<String>,
214
215    /// Timestamp when message was received
216    timestamp: DateTime<Utc>,
217
218    /// Remote address/endpoint information
219    remote_addr: Option<String>,
220
221    /// Additional metadata
222    metadata: HashMap<String, String>,
223
224    /// Transport-specific data (generic for different transport types)
225    transport_data: Option<T>,
226}
227
228impl<T> MessageContext<T> {
229    /// Create a new message context with transport-specific data
230    pub fn new_with_transport_data(session_id: impl Into<String>, transport_data: T) -> Self {
231        Self {
232            session_id: Some(session_id.into()),
233            timestamp: Utc::now(),
234            remote_addr: None,
235            metadata: HashMap::new(),
236            transport_data: Some(transport_data),
237        }
238    }
239
240    /// Create a new message context without transport data (for simple transports)
241    pub fn new(session_id: impl Into<String>) -> Self
242    where
243        T: Default,
244    {
245        Self {
246            session_id: Some(session_id.into()),
247            timestamp: Utc::now(),
248            remote_addr: None,
249            metadata: HashMap::new(),
250            transport_data: None,
251        }
252    }
253
254    /// Create a new message context without session ID or transport data
255    pub fn without_session() -> Self
256    where
257        T: Default,
258    {
259        Self {
260            session_id: None,
261            timestamp: Utc::now(),
262            remote_addr: None,
263            metadata: HashMap::new(),
264            transport_data: None,
265        }
266    }
267
268    /// Get session ID
269    pub fn session_id(&self) -> Option<&str> {
270        self.session_id.as_deref()
271    }
272
273    /// Get message timestamp
274    pub fn timestamp(&self) -> DateTime<Utc> {
275        self.timestamp
276    }
277
278    /// Get remote address
279    pub fn remote_addr(&self) -> Option<&str> {
280        self.remote_addr.as_deref()
281    }
282
283    /// Get metadata value
284    pub fn get_metadata(&self, key: &str) -> Option<&str> {
285        self.metadata.get(key).map(|s| s.as_str())
286    }
287
288    /// Set remote address
289    pub fn with_remote_addr(mut self, addr: String) -> Self {
290        self.remote_addr = Some(addr);
291        self
292    }
293
294    /// Add metadata
295    pub fn with_metadata(mut self, key: String, value: String) -> Self {
296        self.metadata.insert(key, value);
297        self
298    }
299
300    /// Convenience method to add user agent
301    pub fn with_user_agent(self, user_agent: String) -> Self {
302        self.with_metadata("user-agent".to_string(), user_agent)
303    }
304
305    /// Convenience method to add content type
306    pub fn with_content_type(self, content_type: String) -> Self {
307        self.with_metadata("content-type".to_string(), content_type)
308    }
309
310    /// Get transport-specific data
311    ///
312    /// Returns a reference to the transport-specific data if it exists.
313    /// This allows handlers to access transport-specific context information.
314    pub fn transport_data(&self) -> Option<&T> {
315        self.transport_data.as_ref()
316    }
317
318    /// Set transport-specific data
319    ///
320    /// Adds or updates transport-specific data for this context.
321    pub fn with_transport_data(mut self, data: T) -> Self {
322        self.transport_data = Some(data);
323        self
324    }
325
326    /// Check if transport data is available
327    ///
328    /// Returns true if this context contains transport-specific data.
329    pub fn has_transport_data(&self) -> bool {
330        self.transport_data.is_some()
331    }
332}
333
334/// Event-driven message handler trait
335///
336/// This trait defines the interface for handling MCP protocol logic,
337/// providing clean separation between transport (message delivery) and
338/// protocol (MCP semantics) concerns.
339///
340/// The event-driven design matches the official MCP specification patterns
341/// and eliminates the complexity of blocking receive() operations.
342///
343/// The generic type parameter `T` represents transport-specific context data
344/// that can be included with each message (e.g., HTTP request details).
345///
346/// # Examples
347///
348/// ```rust
349/// use airsprotocols_mcp::protocol::{MessageHandler, JsonRpcMessage, MessageContext, TransportError};
350/// use async_trait::async_trait;
351/// use std::sync::Arc;
352///
353/// struct EchoHandler;
354///
355/// #[async_trait]
356/// impl MessageHandler<()> for EchoHandler {
357///     async fn handle_message(&self, message: JsonRpcMessage, context: MessageContext<()>) {
358///         println!("Received message: {:?}", message);
359///         // Echo logic would go here
360///     }
361///
362///     async fn handle_error(&self, error: TransportError) {
363///         eprintln!("Transport error: {}", error);
364///     }
365///
366///     async fn handle_close(&self) {
367///         println!("Transport closed");
368///     }
369/// }
370/// ```
371#[async_trait]
372pub trait MessageHandler<T = ()>: Send + Sync {
373    /// Handle an incoming JSON-RPC message
374    ///
375    /// This method is called for every message received by the transport,
376    /// including requests, responses, and notifications.
377    ///
378    /// # Arguments
379    ///
380    /// * `message` - The JSON-RPC message received
381    /// * `context` - Session and metadata information with transport-specific data
382    async fn handle_message(&self, message: JsonRpcMessage, context: MessageContext<T>);
383
384    /// Handle a transport-level error
385    ///
386    /// This method is called when the transport encounters an error that
387    /// doesn't result in a valid JSON-RPC message (e.g., connection failures).
388    ///
389    /// # Arguments
390    ///
391    /// * `error` - The transport error that occurred
392    async fn handle_error(&self, error: TransportError);
393
394    /// Handle transport closure
395    ///
396    /// This method is called when the transport is closed, either gracefully
397    /// or due to an error. It provides an opportunity for cleanup.
398    async fn handle_close(&self);
399}
400
401/// MCP-compliant transport trait
402///
403/// This trait defines the event-driven transport interface aligned with the
404/// official MCP specification. It replaces the blocking receive() pattern
405/// with event-driven message handling via MessageHandler callbacks.
406///
407/// # Design Principles
408///
409/// - **Event-Driven**: Uses MessageHandler callbacks instead of blocking receive()
410/// - **Session-Aware**: Supports multi-session transports (e.g., HTTP)
411/// - **Lifecycle Management**: Explicit start/close for resource management
412/// - **Natural Correlation**: Uses JSON-RPC message IDs, no artificial mechanisms
413/// - **Transport Agnostic**: Works with STDIO, HTTP, WebSocket, etc.
414///
415/// # Examples
416///
417/// See specific transport implementations:
418/// - `transport::adapters::stdio::StdioTransport` for STDIO communication
419/// - `transport::adapters::http::HttpTransport` for HTTP-based communication
420///
421/// ```rust
422/// use airsprotocols_mcp::protocol::{Transport, JsonRpcMessage};
423/// use async_trait::async_trait;
424///
425/// // Transport implementations provide event-driven message handling
426/// // via the MessageHandler pattern - see transport adapters for examples
427/// ```
428#[async_trait]
429pub trait Transport: Send + Sync {
430    /// Transport-specific error type
431    type Error: std::error::Error + Send + Sync + 'static;
432
433    /// Start the transport and begin listening for messages
434    ///
435    /// This method initializes the transport and begins accepting incoming
436    /// messages. For connection-based transports, this establishes the connection.
437    /// For server transports, this starts listening for connections.
438    ///
439    /// # Returns
440    ///
441    /// * `Ok(())` - Transport started successfully
442    /// * `Err(Self::Error)` - Failed to start transport
443    async fn start(&mut self) -> Result<(), Self::Error>;
444
445    /// Close the transport and clean up resources
446    ///
447    /// This method gracefully shuts down the transport, closes connections,
448    /// and releases resources. It should be idempotent.
449    ///
450    /// # Returns
451    ///
452    /// * `Ok(())` - Transport closed successfully
453    /// * `Err(Self::Error)` - Error during closure (resources may still be cleaned up)
454    async fn close(&mut self) -> Result<(), Self::Error>;
455
456    /// Send a JSON-RPC message through the transport
457    ///
458    /// This method sends a message through the transport. For connection-based
459    /// transports, this sends over the active connection. For HTTP transports,
460    /// this may initiate a new request/response cycle.
461    ///
462    /// # Arguments
463    ///
464    /// * `message` - JSON-RPC message to send
465    ///
466    /// # Returns
467    ///
468    /// * `Ok(())` - Message sent successfully
469    /// * `Err(Self::Error)` - Failed to send message
470    async fn send(&mut self, message: &JsonRpcMessage) -> Result<(), Self::Error>;
471
472    /// Get the current session ID (if applicable)
473    ///
474    /// For session-based transports, this returns the current session identifier.
475    /// For single-connection transports like STDIO, this may return None.
476    ///
477    /// # Returns
478    ///
479    /// * `Some(String)` - Current session ID
480    /// * `None` - No session or single-connection transport
481    fn session_id(&self) -> Option<String>;
482
483    /// Set session context for the transport
484    ///
485    /// For session-based transports, this sets the current session context.
486    /// This method allows the transport to track which session is being used.
487    ///
488    /// # Arguments
489    ///
490    /// * `session_id` - Session identifier to set (None to clear)
491    fn set_session_context(&mut self, session_id: Option<String>);
492
493    /// Check if the transport is currently connected
494    ///
495    /// # Returns
496    ///
497    /// * `true` - Transport is connected and ready to send/receive
498    /// * `false` - Transport is disconnected or not ready
499    fn is_connected(&self) -> bool;
500
501    /// Get the transport type identifier
502    ///
503    /// This returns a string identifying the transport type (e.g., "stdio", "http", "websocket").
504    /// Useful for logging and debugging.
505    ///
506    /// # Returns
507    ///
508    /// Static string identifying the transport type
509    fn transport_type(&self) -> &'static str;
510}
511
512/// Client-oriented transport interface for request-response communication
513///
514/// This trait provides a clean, synchronous interface for client-side MCP communication,
515/// focusing on the natural request-response pattern that clients expect. Unlike the
516/// server-oriented `Transport` trait which uses event-driven `MessageHandler` patterns,
517/// `TransportClient` provides direct request-response semantics.
518///
519/// # Design Philosophy
520///
521/// - **Request-Response Pattern**: Direct mapping to client mental model
522/// - **Synchronous Flow**: No complex correlation mechanisms needed
523/// - **Simple Interface**: Single method for core operation
524/// - **Transport Agnostic**: Each implementation handles its own details
525/// - **Error Clarity**: Clear error types for client scenarios
526///
527/// # Examples
528///
529/// ```rust
530/// use airsprotocols_mcp::protocol::{TransportClient, JsonRpcRequest, RequestId};
531/// use serde_json::json;
532///
533/// async fn example_usage<T: TransportClient>(mut client: T) -> Result<(), Box<dyn std::error::Error>> {
534///     // Create a request
535///     let request = JsonRpcRequest::new(
536///         "initialize",
537///         Some(json!({"capabilities": {}})),
538///         RequestId::new_string("init-1")
539///     );
540///     
541///     // Send request and receive response directly
542///     let response = client.call(request).await?;
543///     
544///     println!("Received response: {:?}", response);
545///     Ok(())
546/// }
547/// ```
548#[async_trait]
549pub trait TransportClient: Send + Sync {
550    /// Transport-specific error type
551    type Error: std::error::Error + Send + Sync + 'static;
552
553    /// Send a JSON-RPC request and receive the response
554    ///
555    /// This is the core method of the client interface, providing direct
556    /// request-response semantics. The implementation handles all transport-specific
557    /// details including connection management, serialization, and correlation.
558    ///
559    /// # Arguments
560    ///
561    /// * `request` - The JSON-RPC request to send
562    ///
563    /// # Returns
564    ///
565    /// * `Ok(JsonRpcResponse)` - The response from the server
566    /// * `Err(Self::Error)` - Transport or protocol error
567    ///
568    /// # Error Handling
569    ///
570    /// Implementations should map transport-specific errors to appropriate
571    /// error types, providing clear context for debugging and error handling.
572    async fn call(&mut self, request: JsonRpcRequest) -> Result<JsonRpcResponse, Self::Error>;
573
574    /// Check if the transport client is ready to send requests
575    ///
576    /// This method allows callers to verify that the transport is in a state
577    /// where requests can be sent successfully.
578    ///
579    /// # Returns
580    ///
581    /// * `true` - Client is ready to send requests
582    /// * `false` - Client is not ready (not connected, initializing, etc.)
583    fn is_ready(&self) -> bool;
584
585    /// Get the transport type identifier
586    ///
587    /// Returns a string identifying the transport type for logging and debugging.
588    ///
589    /// # Returns
590    ///
591    /// Static string identifying the transport type (e.g., "stdio", "http")
592    fn transport_type(&self) -> &'static str;
593
594    /// Close the client transport and clean up resources
595    ///
596    /// This method gracefully shuts down the client transport and releases
597    /// any resources. It should be idempotent and safe to call multiple times.
598    ///
599    /// # Returns
600    ///
601    /// * `Ok(())` - Transport closed successfully
602    /// * `Err(Self::Error)` - Error during closure (resources may still be cleaned up)
603    async fn close(&mut self) -> Result<(), Self::Error>;
604}
605
606/// Type alias for boxed transport clients with standardized error handling
607///
608/// This type alias provides a convenient way to work with transport clients
609/// when you need trait objects, using the standard `TransportError` type.
610///
611/// # Examples
612///
613/// ```rust
614/// use airsprotocols_mcp::protocol::{BoxedTransportClient, TransportError};
615///
616/// async fn use_any_client(mut client: BoxedTransportClient) -> Result<(), TransportError> {
617///     // Work with any transport client implementation
618///     // ...
619///     Ok(())
620/// }
621/// ```
622pub type BoxedTransportClient = Box<dyn TransportClient<Error = TransportError>>;
623
624/// Result type for transport client operations
625///
626/// This type alias provides a convenient shorthand for transport client results
627/// using the standard `TransportError` type.
628///
629/// # Examples
630///
631/// ```rust
632/// use airsprotocols_mcp::protocol::{TransportClientResult, JsonRpcResponse};
633///
634/// fn process_response(response: TransportClientResult<JsonRpcResponse>) {
635///     match response {
636///         Ok(resp) => println!("Success: {:?}", resp),
637///         Err(err) => eprintln!("Error: {}", err),
638///     }
639/// }
640/// ```
641pub type TransportClientResult<T> = Result<T, TransportError>;
642
643/// Transport configuration trait for type-safe transport settings
644///
645/// This trait provides a standardized interface for transport-specific configuration
646/// management while maintaining access to universal MCP core requirements.
647///
648/// Reference: ADR-011 Transport Configuration Separation
649pub trait TransportConfig: Send + Sync {
650    /// Set or update the MCP server configuration
651    ///
652    /// This method allows updating the core MCP requirements (server info,
653    /// capabilities, protocol version) that are common across all transports.
654    fn set_server_config(&mut self, server_config: ServerConfig);
655
656    /// Get reference to the MCP server configuration
657    ///
658    /// Returns None if no server configuration has been set.
659    fn server_config(&self) -> Option<&ServerConfig>;
660
661    /// Get effective MCP capabilities for this transport
662    ///
663    /// This method combines the base capabilities from the server config
664    /// with any transport-specific capability modifications.
665    fn effective_capabilities(&self) -> ServerCapabilities;
666
667    /// Get server info from server config
668    ///
669    /// Convenience method that extracts server info from the server config.
670    fn server_info(&self) -> Option<&ServerInfo> {
671        self.server_config().map(|c| &c.server_info)
672    }
673
674    /// Get protocol version from server config
675    ///
676    /// Convenience method that extracts protocol version from the server config.
677    fn protocol_version(&self) -> Option<&ProtocolVersion> {
678        self.server_config().map(|c| &c.protocol_version)
679    }
680}
681
682#[cfg(test)]
683mod tests {
684    use super::*;
685    use crate::protocol::message::{JsonRpcRequest, JsonRpcResponse, RequestId};
686    use serde_json::json;
687
688    /// Mock TransportClient implementation for testing
689    ///
690    /// This simple mock demonstrates that the TransportClient trait
691    /// provides a clean, implementable interface for client operations.
692    struct MockTransportClient {
693        ready: bool,
694        should_fail: bool,
695    }
696
697    impl MockTransportClient {
698        fn new() -> Self {
699            Self {
700                ready: true,
701                should_fail: false,
702            }
703        }
704
705        fn with_failure() -> Self {
706            Self {
707                ready: true,
708                should_fail: true,
709            }
710        }
711
712        fn not_ready() -> Self {
713            Self {
714                ready: false,
715                should_fail: false,
716            }
717        }
718    }
719
720    #[async_trait]
721    impl TransportClient for MockTransportClient {
722        type Error = TransportError;
723
724        async fn call(
725            &mut self,
726            request: JsonRpcRequest,
727        ) -> Result<JsonRpcResponse, TransportError> {
728            if !self.ready {
729                return Err(TransportError::not_ready("Mock client not ready"));
730            }
731
732            if self.should_fail {
733                return Err(TransportError::request_timeout(Duration::from_secs(30)));
734            }
735
736            // Mock successful response
737            Ok(JsonRpcResponse {
738                jsonrpc: "2.0".to_string(),
739                id: Some(request.id),
740                result: Some(json!({"status": "success", "method": request.method})),
741                error: None,
742            })
743        }
744
745        fn is_ready(&self) -> bool {
746            self.ready
747        }
748
749        fn transport_type(&self) -> &'static str {
750            "mock"
751        }
752
753        async fn close(&mut self) -> Result<(), TransportError> {
754            self.ready = false;
755            Ok(())
756        }
757    }
758
759    #[tokio::test]
760    async fn test_transport_client_basic_call() {
761        let mut client = MockTransportClient::new();
762
763        assert!(client.is_ready());
764        assert_eq!(client.transport_type(), "mock");
765
766        let request = JsonRpcRequest::new(
767            "test_method",
768            Some(json!({"param": "value"})),
769            RequestId::new_string("test-1"),
770        );
771
772        let response = client.call(request.clone()).await.unwrap();
773
774        assert_eq!(response.jsonrpc, "2.0");
775        assert_eq!(response.id, Some(request.id));
776        assert!(response.result.is_some());
777        assert!(response.error.is_none());
778    }
779
780    #[tokio::test]
781    async fn test_transport_client_not_ready() {
782        let mut client = MockTransportClient::not_ready();
783
784        assert!(!client.is_ready());
785
786        let request = JsonRpcRequest::new("test_method", None, RequestId::new_string("test-2"));
787
788        let result = client.call(request).await;
789        assert!(result.is_err());
790
791        if let Err(TransportError::NotReady { reason }) = result {
792            assert_eq!(reason, "Mock client not ready");
793        } else {
794            panic!("Expected NotReady error");
795        }
796    }
797
798    #[tokio::test]
799    async fn test_transport_client_timeout() {
800        let mut client = MockTransportClient::with_failure();
801
802        let request = JsonRpcRequest::new("test_method", None, RequestId::new_string("test-3"));
803
804        let result = client.call(request).await;
805        assert!(result.is_err());
806
807        if let Err(TransportError::RequestTimeout { duration }) = result {
808            assert_eq!(duration, Duration::from_secs(30));
809        } else {
810            panic!("Expected RequestTimeout error");
811        }
812    }
813
814    #[tokio::test]
815    async fn test_transport_client_close() {
816        let mut client = MockTransportClient::new();
817
818        assert!(client.is_ready());
819
820        client.close().await.unwrap();
821
822        assert!(!client.is_ready());
823    }
824
825    #[test]
826    fn test_convenience_error_constructors() {
827        let timeout_error = TransportError::request_timeout(Duration::from_secs(10));
828        if let TransportError::RequestTimeout { duration } = timeout_error {
829            assert_eq!(duration, Duration::from_secs(10));
830        } else {
831            panic!("Expected RequestTimeout error");
832        }
833
834        let invalid_response = TransportError::invalid_response("Bad JSON");
835        if let TransportError::InvalidResponse { message } = invalid_response {
836            assert_eq!(message, "Bad JSON");
837        } else {
838            panic!("Expected InvalidResponse error");
839        }
840
841        let not_ready = TransportError::not_ready("Connecting");
842        if let TransportError::NotReady { reason } = not_ready {
843            assert_eq!(reason, "Connecting");
844        } else {
845            panic!("Expected NotReady error");
846        }
847    }
848}