Skip to main content

rust_okx/transport/
mod.rs

1//! The HTTP transport abstraction.
2//!
3//! [`Transport`] is intentionally minimal: it knows nothing about OKX
4//! authentication, endpoints, or response envelopes. It only sends a fully
5//! built [`http::Request`] and returns the raw [`http::Response`]. This keeps
6//! custom and mock implementations trivial, and lets cross-cutting concerns
7//! (retries, logging, rate limiting) be added as wrapper types instead of new
8//! trait methods.
9//!
10//! The trait uses return-position `impl Future` (no `async_trait`), so calling
11//! it incurs no boxing or dynamic dispatch — the [`OkxClient`](crate::OkxClient)
12//! is generic over `T: Transport`.
13
14use std::error::Error as StdError;
15use std::fmt;
16use std::future::Future;
17
18use bytes::Bytes;
19
20#[cfg(feature = "reqwest")]
21mod reqwest_impl;
22#[cfg(feature = "reqwest")]
23pub use reqwest_impl::ReqwestTransport;
24
25/// The default transport type used as the `OkxClient<T>` type parameter.
26///
27/// When the `reqwest` feature is enabled this is [`ReqwestTransport`]. Otherwise
28/// it is a placeholder that does not implement [`Transport`]; you must then
29/// supply your own transport via [`OkxClient::with_transport`](crate::OkxClient::with_transport).
30#[cfg(feature = "reqwest")]
31pub type DefaultTransport = ReqwestTransport;
32
33/// See [`DefaultTransport`].
34#[cfg(not(feature = "reqwest"))]
35pub type DefaultTransport = UnconfiguredTransport;
36
37/// Placeholder used as the default `OkxClient` transport when no built-in
38/// transport feature is enabled. It does not implement [`Transport`].
39#[cfg(not(feature = "reqwest"))]
40#[derive(Debug, Clone, Copy, Default)]
41pub struct UnconfiguredTransport;
42
43type BoxError = Box<dyn StdError + Send + Sync>;
44
45/// An opaque transport-layer error.
46///
47/// The concrete source error (e.g. `reqwest::Error`) is boxed so it does not
48/// leak into this crate's public API.
49#[derive(Debug)]
50pub struct TransportError(BoxError);
51
52impl TransportError {
53    /// Wrap any boxable error as a [`TransportError`].
54    pub fn new(error: impl Into<BoxError>) -> Self {
55        Self(error.into())
56    }
57
58    /// Create a [`TransportError`] from a message string.
59    pub fn message(message: impl Into<String>) -> Self {
60        Self(message.into().into())
61    }
62}
63
64impl fmt::Display for TransportError {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        write!(f, "{}", self.0)?;
67
68        let mut source = self.0.source();
69        while let Some(error) = source {
70            write!(f, ": {error}")?;
71            source = error.source();
72        }
73
74        Ok(())
75    }
76}
77
78impl StdError for TransportError {
79    fn source(&self) -> Option<&(dyn StdError + 'static)> {
80        Some(self.0.as_ref())
81    }
82}
83
84/// Sends HTTP requests on behalf of the client.
85///
86/// Implementors receive a complete request (URL, method, headers, and body
87/// already set, including authentication headers) and return the raw response.
88///
89/// # Example
90///
91/// ```
92/// use bytes::Bytes;
93/// use rust_okx::{Transport, TransportError};
94///
95/// #[derive(Clone)]
96/// struct AlwaysOk;
97///
98/// impl Transport for AlwaysOk {
99///     fn send(
100///         &self,
101///         _request: http::Request<Bytes>,
102///     ) -> impl std::future::Future<Output = Result<http::Response<Bytes>, TransportError>> + Send
103///     {
104///         async move {
105///             Ok(http::Response::builder()
106///                 .status(200)
107///                 .body(Bytes::from_static(b"{\"code\":\"0\",\"msg\":\"\",\"data\":[]}"))
108///                 .unwrap())
109///         }
110///     }
111/// }
112/// ```
113pub trait Transport: Send + Sync {
114    /// Send a request and return the response.
115    fn send(
116        &self,
117        request: http::Request<Bytes>,
118    ) -> impl Future<Output = Result<http::Response<Bytes>, TransportError>> + Send;
119}