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}