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}