Skip to main content

longbridge_httpcli/
config.rs

1use longbridge_oauth::OAuth;
2
3use crate::HttpClientError;
4
5/// Reads an env var by trying `LONGBRIDGE_<suffix>` first, then falling back
6/// to `LONGPORT_<suffix>`.  Returns `None` if neither is set.
7fn env_var(suffix: &str) -> Option<String> {
8    std::env::var(format!("LONGBRIDGE_{suffix}"))
9        .ok()
10        .or_else(|| std::env::var(format!("LONGPORT_{suffix}")).ok())
11}
12
13/// Like [`env_var`] but returns an error (using the new `LONGBRIDGE_` name)
14/// when neither variable is set.
15fn env_var_required(suffix: &str) -> Result<String, HttpClientError> {
16    env_var(suffix).ok_or_else(|| HttpClientError::MissingEnvVar {
17        name: format!("LONGBRIDGE_{suffix}"),
18    })
19}
20
21/// Authentication configuration
22#[derive(Debug, Clone)]
23pub enum AuthConfig {
24    /// Legacy API Key mode: HMAC-SHA256 signed requests
25    ApiKey {
26        /// App key
27        app_key: String,
28        /// App secret (used for HMAC-SHA256 signing)
29        app_secret: String,
30        /// Static access token
31        access_token: String,
32    },
33    /// OAuth 2.0 mode: Bearer token, auto-refreshed via the [`OAuth`] client
34    OAuth(OAuth),
35}
36
37/// Configuration options for Http client
38#[derive(Debug, Clone)]
39pub struct HttpClientConfig {
40    /// HTTP API url
41    pub(crate) http_url: Option<String>,
42    /// Authentication configuration
43    pub(crate) auth: AuthConfig,
44}
45
46impl HttpClientConfig {
47    /// Create a new `HttpClientConfig` using API Key authentication.
48    ///
49    /// `LONGBRIDGE_HTTP_URL` is read from the environment (or `.env` file) and
50    /// applied automatically if set.
51    ///
52    /// # Arguments
53    ///
54    /// * `app_key` - Application key
55    /// * `app_secret` - Application secret (used for request signing)
56    /// * `access_token` - Access token
57    pub fn from_apikey(
58        app_key: impl Into<String>,
59        app_secret: impl Into<String>,
60        access_token: impl Into<String>,
61    ) -> Self {
62        let _ = dotenv::dotenv();
63        Self {
64            http_url: env_var("HTTP_URL"),
65            auth: AuthConfig::ApiKey {
66                app_key: app_key.into(),
67                app_secret: app_secret.into(),
68                access_token: access_token.into(),
69            },
70        }
71    }
72
73    /// Create a new `HttpClientConfig` for OAuth 2.0 authentication.
74    ///
75    /// `LONGBRIDGE_HTTP_URL` is read from the environment (or `.env` file) and
76    /// applied automatically if set.
77    ///
78    /// The [`OAuth`] client handles token lifecycle automatically, including
79    /// expiry checks and token refresh.
80    ///
81    /// # Arguments
82    ///
83    /// * `oauth` - An [`OAuth`] client obtained from
84    ///   [`longbridge_oauth::OAuthBuilder`]
85    pub fn from_oauth(oauth: OAuth) -> Self {
86        let _ = dotenv::dotenv();
87        Self {
88            http_url: env_var("HTTP_URL"),
89            auth: AuthConfig::OAuth(oauth),
90        }
91    }
92
93    /// Create a new `HttpClientConfig` from environment variables (API Key
94    /// mode).
95    ///
96    /// # Variables
97    ///
98    /// - `LONGBRIDGE_APP_KEY` - App key (required)
99    /// - `LONGBRIDGE_APP_SECRET` - App secret (required)
100    /// - `LONGBRIDGE_ACCESS_TOKEN` - Access token (required)
101    /// - `LONGBRIDGE_HTTP_URL` - HTTP endpoint URL (optional)
102    ///
103    /// # Note
104    ///
105    /// For OAuth 2.0 authentication, use
106    /// [`from_oauth`](HttpClientConfig::from_oauth) instead.
107    pub fn from_apikey_env() -> Result<Self, HttpClientError> {
108        let _ = dotenv::dotenv();
109
110        let app_key = env_var_required("APP_KEY")?;
111        let app_secret = env_var_required("APP_SECRET")?;
112        let access_token = env_var_required("ACCESS_TOKEN")?;
113
114        Ok(Self {
115            http_url: env_var("HTTP_URL"),
116            auth: AuthConfig::ApiKey {
117                app_key,
118                app_secret,
119                access_token,
120            },
121        })
122    }
123
124    /// Specifies the url of the OpenAPI server.
125    ///
126    /// Default: <https://openapi.longbridge.com>
127    /// NOTE: Usually you don't need to change it.
128    #[must_use]
129    pub fn http_url(self, url: impl Into<String>) -> Self {
130        Self {
131            http_url: Some(url.into()),
132            ..self
133        }
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn test_httpclient_config_new() {
143        let config = HttpClientConfig::from_apikey("app-key", "app-secret", "access-token");
144
145        match &config.auth {
146            AuthConfig::ApiKey {
147                app_key,
148                app_secret,
149                access_token,
150            } => {
151                assert_eq!(app_key, "app-key");
152                assert_eq!(app_secret, "app-secret");
153                assert_eq!(access_token, "access-token");
154            }
155            _ => panic!("Expected ApiKey auth config"),
156        }
157        assert_eq!(config.http_url, None);
158    }
159
160    #[test]
161    fn test_httpclient_config_http_url() {
162        let config = HttpClientConfig::from_apikey("app-key", "app-secret", "access-token")
163            .http_url("https://custom.example.com");
164
165        assert_eq!(
166            config.http_url,
167            Some("https://custom.example.com".to_string())
168        );
169    }
170}