Skip to main content

launchdarkly_sdk_transport/
transport.rs

1//! HTTP transport abstraction for LaunchDarkly SDKs.
2//!
3//! This module defines the [`HttpTransport`] trait which allows users to plug in
4//! their own HTTP client implementation (hyper, reqwest, or custom).
5
6use bytes::Bytes;
7use futures::Stream;
8use std::error::Error as StdError;
9use std::fmt;
10use std::future::Future;
11use std::pin::Pin;
12
13// Re-export http crate types for convenience
14pub use http::{HeaderMap, HeaderValue, Request, Response, StatusCode, Uri};
15
16/// A pinned, boxed stream of bytes returned by HTTP transports.
17///
18/// This represents the streaming response body from an HTTP request.
19pub type ByteStream = Pin<Box<dyn Stream<Item = Result<Bytes, TransportError>> + Send + Sync>>;
20
21/// A pinned, boxed future for an HTTP response.
22///
23/// This represents the future returned by [`HttpTransport::request`].
24pub type ResponseFuture =
25    Pin<Box<dyn Future<Output = Result<Response<ByteStream>, TransportError>> + Send + Sync>>;
26
27/// Error type for HTTP transport operations.
28///
29/// This wraps transport-specific errors (network failures, timeouts, etc.) in a
30/// common error type that the SDKs can handle uniformly.
31#[derive(Debug)]
32pub struct TransportError {
33    inner: Box<dyn StdError + Send + Sync + 'static>,
34}
35
36impl TransportError {
37    /// Create a new transport error from any error type.
38    ///
39    /// This is used by transport implementations to wrap their specific error types
40    /// into the common `TransportError` type.
41    ///
42    /// # Example
43    ///
44    /// ```
45    /// use launchdarkly_sdk_transport::TransportError;
46    /// use std::io;
47    ///
48    /// let io_err = io::Error::new(io::ErrorKind::ConnectionRefused, "connection refused");
49    /// let transport_err = TransportError::new(io_err);
50    /// ```
51    pub fn new(err: impl StdError + Send + Sync + 'static) -> Self {
52        Self {
53            inner: Box::new(err),
54        }
55    }
56
57    /// Get a reference to the inner error.
58    ///
59    /// Use this to access the underlying error for detailed inspection, logging,
60    /// or to check for specific error types.
61    ///
62    /// # Example
63    ///
64    /// ```
65    /// use launchdarkly_sdk_transport::TransportError;
66    /// use std::io;
67    ///
68    /// fn diagnose_error(err: TransportError) {
69    ///     let inner = err.inner();
70    ///
71    ///     // Check if it's an I/O error
72    ///     if let Some(io_err) = inner.downcast_ref::<io::Error>() {
73    ///         match io_err.kind() {
74    ///             io::ErrorKind::TimedOut => println!("Operation timed out"),
75    ///             io::ErrorKind::ConnectionRefused => println!("Connection refused"),
76    ///             _ => println!("I/O error: {}", io_err),
77    ///         }
78    ///     } else {
79    ///         println!("Other error: {}", inner);
80    ///     }
81    /// }
82    /// ```
83    pub fn inner(&self) -> &(dyn StdError + Send + Sync + 'static) {
84        &*self.inner
85    }
86}
87
88impl fmt::Display for TransportError {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        write!(f, "transport error: {}", self.inner)
91    }
92}
93
94impl StdError for TransportError {
95    fn source(&self) -> Option<&(dyn StdError + 'static)> {
96        Some(&*self.inner)
97    }
98}
99
100/// Trait for pluggable HTTP transport implementations.
101///
102/// Implement this trait to provide HTTP request/response functionality. The transport is
103/// responsible for:
104///
105/// - Establishing HTTP connections (with TLS if needed)
106/// - Sending HTTP requests
107/// - Returning streaming HTTP responses
108/// - Handling timeouts (if desired)
109///
110/// # Example
111///
112/// ```no_run
113/// use launchdarkly_sdk_transport::{HttpTransport, ByteStream, TransportError, Request,
114/// ResponseFuture};
115/// use bytes::Bytes;
116///
117/// #[derive(Clone)]
118/// struct MyTransport {
119///     // Your HTTP client here
120/// }
121///
122/// impl HttpTransport for MyTransport {
123///     fn request(&self, request: Request<Option<Bytes>>) -> ResponseFuture {
124///         // Extract body from request
125///         // Convert request to your HTTP client's format
126///         // Make the request
127///         // Return streaming response
128///         todo!()
129///     }
130/// }
131/// ```
132pub trait HttpTransport: Send + Sync + Clone + 'static {
133    /// Execute an HTTP request and return a streaming response.
134    ///
135    /// # Arguments
136    ///
137    /// * `request` - The HTTP request to execute. The body type is `Option<Bytes>`
138    ///   to support methods like REPORT that may include a request body. Use `None` for
139    ///   requests without a body (like GET). `Bytes` can contain either text or binary data.
140    ///
141    /// # Returns
142    ///
143    /// A future that resolves to an HTTP response with a streaming body, or a
144    /// transport error if the request fails.
145    ///
146    /// The response should include:
147    /// - Status code
148    /// - Response headers
149    /// - A stream of body bytes
150    ///
151    /// # Notes
152    ///
153    /// - The transport should NOT follow redirects. This should be left to the consumer.
154    /// - The transport should NOT retry requests. This should also be left to the consumer.
155    /// - The transport MAY implement timeouts as desired
156    fn request(&self, request: Request<Option<Bytes>>) -> ResponseFuture;
157}