Skip to main content

longport_httpcli/
config.rs

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