use std::collections::HashMap;
#[derive(Clone)]
pub struct AlpacaAuth {
pub api_key_id: Option<String>,
pub api_secret_key: Option<String>,
}
impl AlpacaAuth {
pub fn from_env() -> Self {
Self {
api_key_id: std::env::var("ALPACA_API_KEY_ID")
.or_else(|_| std::env::var("APCA_API_KEY_ID"))
.ok(),
api_secret_key: std::env::var("ALPACA_API_SECRET_KEY")
.or_else(|_| std::env::var("APCA_API_SECRET_KEY"))
.ok(),
}
}
pub fn new(api_key_id: impl Into<String>, api_secret_key: impl Into<String>) -> Self {
Self {
api_key_id: Some(api_key_id.into()),
api_secret_key: Some(api_secret_key.into()),
}
}
pub fn none() -> Self {
Self {
api_key_id: None,
api_secret_key: None,
}
}
pub fn sign_headers(&self, headers: &mut HashMap<String, String>) {
if let Some(key_id) = &self.api_key_id {
headers.insert("APCA-API-KEY-ID".to_string(), key_id.clone());
}
if let Some(secret_key) = &self.api_secret_key {
headers.insert("APCA-API-SECRET-KEY".to_string(), secret_key.clone());
}
}
pub fn has_credentials(&self) -> bool {
self.api_key_id.is_some() && self.api_secret_key.is_some()
}
pub fn key_id(&self) -> Option<&str> {
self.api_key_id.as_deref()
}
}
impl Default for AlpacaAuth {
fn default() -> Self {
Self::from_env()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_auth() {
let auth = AlpacaAuth::new("test_key_id", "test_secret_key");
assert!(auth.has_credentials());
assert_eq!(auth.key_id(), Some("test_key_id"));
}
#[test]
fn test_none_auth() {
let auth = AlpacaAuth::none();
assert!(!auth.has_credentials());
assert_eq!(auth.key_id(), None);
}
#[test]
fn test_sign_headers() {
let auth = AlpacaAuth::new("my_key_id", "my_secret");
let mut headers = HashMap::new();
auth.sign_headers(&mut headers);
assert_eq!(headers.get("APCA-API-KEY-ID"), Some(&"my_key_id".to_string()));
assert_eq!(headers.get("APCA-API-SECRET-KEY"), Some(&"my_secret".to_string()));
}
#[test]
fn test_sign_headers_no_creds() {
let auth = AlpacaAuth::none();
let mut headers = HashMap::new();
auth.sign_headers(&mut headers);
assert!(headers.is_empty());
}
}