capnweb-transport 0.1.0

Transport layer implementations for Cap'n Web protocol (HTTP, WebSocket, WebTransport)
Documentation
//! Transport negotiation for Cap'n Web protocol.
//!
//! This module provides functionality to automatically negotiate and select
//! the best available transport mechanism when connecting to a Cap'n Web server.

use crate::{RpcTransport, TransportError};

/// Configuration for transport negotiation.
///
/// Allows customizing the transport selection process, including
/// preferred transports, timeouts, and retry policies.
#[derive(Debug, Clone, Default)]
pub struct NegotiationConfig {
    /// Maximum time to spend attempting each transport type
    pub timeout_ms: Option<u64>,
    /// Whether to try WebTransport (HTTP/3) first
    pub prefer_webtransport: bool,
    /// Whether to try WebSocket
    pub enable_websocket: bool,
    /// Whether to fall back to HTTP batch
    pub enable_http_batch: bool,
}

/// Transport preference order for negotiation.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransportPreference {
    /// Prefer WebTransport for lowest latency
    WebTransport,
    /// Prefer WebSocket for compatibility
    WebSocket,
    /// Prefer HTTP batch for simplicity
    HttpBatch,
}

/// Result of transport negotiation.
///
/// Contains the successfully negotiated transport and metadata
/// about the negotiation process.
pub struct NegotiationResult {
    /// The transport that was successfully established
    pub transport: Box<dyn RpcTransport>,
    /// Which transport type was selected
    pub transport_type: TransportPreference,
    /// Time taken to negotiate in milliseconds
    pub negotiation_time_ms: u64,
}

impl std::fmt::Debug for NegotiationResult {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("NegotiationResult")
            .field("transport_type", &self.transport_type)
            .field("negotiation_time_ms", &self.negotiation_time_ms)
            .field("transport", &"Box<dyn RpcTransport>")
            .finish()
    }
}

/// Negotiates the best available transport for connecting to a Cap'n Web server.
///
/// This function attempts to establish a connection using available transports
/// in order of preference:
/// 1. WebTransport (HTTP/3) - Provides the lowest latency and best performance
/// 2. WebSocket - Offers broad compatibility with existing infrastructure
/// 3. HTTP Batch - Universal fallback that works everywhere
///
/// # Arguments
///
/// * `url` - The server URL to connect to (e.g., <https://example.com>)
/// * `config` - Optional configuration for the negotiation process
///
/// # Returns
///
/// Returns a `NegotiationResult` containing the best available transport that
/// successfully connected, along with metadata about the negotiation.
///
/// # Errors
///
/// Returns `TransportError::NoAvailableTransport` if all transport attempts fail.
/// Individual transport errors are logged but not returned directly.
///
/// # Example
///
/// ```no_run
/// use capnweb_transport::negotiate::{negotiate, NegotiationConfig};
///
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// // Use default negotiation (tries all transports)
/// let result = negotiate("https://example.com", None).await?;
/// println!("Connected via {:?}", result.transport_type);
///
/// // Custom configuration preferring WebSocket
/// let config = NegotiationConfig {
///     prefer_webtransport: false,
///     enable_websocket: true,
///     enable_http_batch: true,
///     timeout_ms: Some(5000),
/// };
/// let result = negotiate("https://example.com", Some(config)).await?;
/// # Ok(())
/// # }
/// ```
///
/// # Performance
///
/// The negotiation process runs transport attempts sequentially to avoid
/// unnecessary connection overhead. Future versions may support parallel
/// negotiation with cancellation.
pub async fn negotiate(
    url: &str,
    config: Option<NegotiationConfig>,
) -> Result<NegotiationResult, TransportError> {
    let _config = config.unwrap_or_default();
    let _url = url;

    // TODO: Implement actual negotiation logic
    // This is a placeholder that will be implemented when transport
    // selection logic is added. For now, return an error indicating
    // the feature is not yet available.

    Err(TransportError::Protocol(
        "Transport negotiation not yet implemented".to_string(),
    ))
}

/// Attempts to establish a WebTransport connection.
///
/// # Arguments
///
/// * `url` - The server URL
/// * `timeout_ms` - Connection timeout in milliseconds
///
/// # Returns
///
/// Returns the established transport or an error if connection fails.
#[cfg(feature = "webtransport")]
#[allow(dead_code)]
async fn try_webtransport(
    url: &str,
    timeout_ms: Option<u64>,
) -> Result<Box<dyn RpcTransport>, TransportError> {
    let _url = url;
    let _timeout = timeout_ms;
    // TODO: Implement WebTransport connection attempt
    Err(TransportError::Protocol(
        "WebTransport not available".to_string(),
    ))
}

/// Attempts to establish a WebSocket connection.
///
/// # Arguments
///
/// * `url` - The server URL
/// * `timeout_ms` - Connection timeout in milliseconds
///
/// # Returns
///
/// Returns the established transport or an error if connection fails.
#[cfg(feature = "websocket")]
#[allow(dead_code)]
async fn try_websocket(
    url: &str,
    timeout_ms: Option<u64>,
) -> Result<Box<dyn RpcTransport>, TransportError> {
    let _url = url;
    let _timeout = timeout_ms;
    // TODO: Implement WebSocket connection attempt
    Err(TransportError::Protocol(
        "WebSocket not available".to_string(),
    ))
}

/// Attempts to establish an HTTP batch transport connection.
///
/// # Arguments
///
/// * `url` - The server URL
/// * `timeout_ms` - Connection timeout in milliseconds
///
/// # Returns
///
/// Returns the established transport or an error if connection fails.
#[cfg(feature = "http-batch")]
#[allow(dead_code)]
async fn try_http_batch(
    url: &str,
    timeout_ms: Option<u64>,
) -> Result<Box<dyn RpcTransport>, TransportError> {
    let _url = url;
    let _timeout = timeout_ms;
    // TODO: Implement HTTP batch connection attempt
    Err(TransportError::Protocol(
        "HTTP batch not available".to_string(),
    ))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_negotiate_returns_error_when_not_implemented() {
        let result = negotiate("https://example.com", None).await;
        assert!(result.is_err());
    }

    #[test]
    fn test_negotiation_config_default() {
        let config = NegotiationConfig::default();
        assert_eq!(config.timeout_ms, None);
        assert!(!config.prefer_webtransport);
        assert!(!config.enable_websocket);
        assert!(!config.enable_http_batch);
    }
}