Skip to main content

bybit_rust_api/rest/
api_key_pair.rs

1use anyhow::Context;
2use serde::Deserialize;
3use std::collections::HashMap;
4use std::fs::File;
5use std::io::BufReader;
6
7#[derive(Debug, Clone, Deserialize)]
8pub struct ApiKeyPair {
9    profile_name: String,
10    key: String,
11    secret: String,
12}
13
14#[derive(Debug, Deserialize)]
15pub struct ApiKeyPairs {
16    #[serde(rename = "profiles")]
17    pairs: HashMap<String, ApiKeyPair>,
18}
19
20impl ApiKeyPair {
21    pub fn new(profile_name: String, key: String, secret: String) -> ApiKeyPair {
22        ApiKeyPair {
23            profile_name: profile_name.to_string(),
24            key: key.to_string(),
25            secret: secret.to_string(),
26        }
27    }
28
29    /// Load API credentials from environment variables.
30    ///
31    /// Reads `BYBIT_API_KEY` and `BYBIT_API_SECRET`.
32    /// Optionally loads `.env` file via `dotenvy`.
33    ///
34    /// # Example
35    /// ```ignore
36    /// // .env file or environment:
37    /// // BYBIT_API_KEY=your_key
38    /// // BYBIT_API_SECRET=your_secret
39    ///
40    /// dotenvy::dotenv().ok(); // load .env if present
41    /// let keys = ApiKeyPair::from_env()?;
42    /// ```
43    pub fn from_env() -> anyhow::Result<Self> {
44        let key = std::env::var("BYBIT_API_KEY").context("BYBIT_API_KEY not set")?;
45        let secret = std::env::var("BYBIT_API_SECRET").context("BYBIT_API_SECRET not set")?;
46        Ok(ApiKeyPair::new("env".to_string(), key, secret))
47    }
48
49    /// Load API credentials from Testnet environment variables.
50    ///
51    /// Reads `BYBIT_TESTNET_API_KEY` and `BYBIT_TESTNET_API_SECRET`.
52    pub fn from_env_testnet() -> anyhow::Result<Self> {
53        let key = std::env::var("BYBIT_TESTNET_API_KEY")
54            .or_else(|_| std::env::var("BYBIT_API_KEY"))
55            .context("BYBIT_API_KEY or BYBIT_TESTNET_API_KEY not set")?;
56        let secret = std::env::var("BYBIT_TESTNET_API_SECRET")
57            .or_else(|_| std::env::var("BYBIT_API_SECRET"))
58            .context("BYBIT_API_SECRET or BYBIT_TESTNET_API_SECRET not set")?;
59        Ok(ApiKeyPair::new("testnet".to_string(), key, secret))
60    }
61
62    pub fn profile_name(&self) -> &str {
63        self.profile_name.as_str()
64    }
65
66    pub fn key(&self) -> &str {
67        self.key.as_str()
68    }
69
70    pub fn secret(&self) -> &str {
71        self.secret.as_str()
72    }
73}
74
75impl ApiKeyPairs {
76    pub fn new() -> ApiKeyPairs {
77        ApiKeyPairs {
78            pairs: HashMap::new(),
79        }
80    }
81}
82
83impl Default for ApiKeyPairs {
84    fn default() -> Self {
85        Self::new()
86    }
87}
88
89impl ApiKeyPairs {
90    pub fn add(&mut self, profile_name: String, key: String, secret: String) {
91        self.pairs.insert(
92            profile_name.to_string(),
93            ApiKeyPair::new(profile_name, key, secret),
94        );
95    }
96
97    pub fn get(&self, profile_name: &str) -> Option<&ApiKeyPair> {
98        self.pairs.get(profile_name)
99    }
100
101    /// Load API keys from a JSON file.
102    ///
103    /// Expected format: array of `{ "profile_name", "key", "secret" }` objects.
104    pub fn load_from_json_file(file_path: &str) -> Result<ApiKeyPairs, Box<dyn std::error::Error>> {
105        let mut api_key_pairs = ApiKeyPairs::new();
106        let pairs: Vec<ApiKeyPair> =
107            serde_json::from_reader(BufReader::new(File::open(file_path)?))?;
108        for pair in pairs {
109            api_key_pairs.add(pair.profile_name, pair.key, pair.secret);
110        }
111        Ok(api_key_pairs)
112    }
113}