use std::collections::HashMap;
use sha2::{Sha256, Digest};
#[derive(Clone)]
pub struct ZerodhaAuth {
pub api_key: String,
pub api_secret: String,
pub access_token: Option<String>,
}
impl ZerodhaAuth {
pub fn from_env() -> Self {
Self {
api_key: std::env::var("ZERODHA_API_KEY")
.unwrap_or_default(),
api_secret: std::env::var("ZERODHA_API_SECRET")
.unwrap_or_default(),
access_token: std::env::var("ZERODHA_ACCESS_TOKEN").ok(),
}
}
pub fn new(api_key: impl Into<String>, api_secret: impl Into<String>) -> Self {
Self {
api_key: api_key.into(),
api_secret: api_secret.into(),
access_token: None,
}
}
pub fn with_token(
api_key: impl Into<String>,
api_secret: impl Into<String>,
access_token: impl Into<String>,
) -> Self {
Self {
api_key: api_key.into(),
api_secret: api_secret.into(),
access_token: Some(access_token.into()),
}
}
pub fn generate_checksum(&self, request_token: &str) -> String {
let message = format!("{}{}{}", self.api_key, request_token, self.api_secret);
let mut hasher = Sha256::new();
hasher.update(message.as_bytes());
format!("{:x}", hasher.finalize())
}
pub fn sign_headers(&self, headers: &mut HashMap<String, String>) {
if let Some(token) = &self.access_token {
headers.insert(
"Authorization".to_string(),
format!("token {}:{}", self.api_key, token),
);
}
}
pub fn has_token(&self) -> bool {
self.access_token.is_some()
}
pub fn get_login_url(&self, redirect_params: Option<&str>) -> String {
let base = "https://kite.zerodha.com/connect/login?v=3";
if let Some(params) = redirect_params {
format!("{}&api_key={}&redirect_params={}", base, self.api_key, params)
} else {
format!("{}&api_key={}", base, self.api_key)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_checksum() {
let auth = ZerodhaAuth::new("my_api_key", "my_api_secret");
let checksum = auth.generate_checksum("request_token");
assert_eq!(checksum.len(), 64);
assert!(checksum.chars().all(|c| c.is_ascii_hexdigit()));
let checksum2 = auth.generate_checksum("request_token");
assert_eq!(checksum, checksum2);
let checksum3 = auth.generate_checksum("different_token");
assert_ne!(checksum, checksum3);
}
#[test]
fn test_sign_headers() {
let auth = ZerodhaAuth::with_token("my_api_key", "my_secret", "my_access_token");
let mut headers = HashMap::new();
auth.sign_headers(&mut headers);
assert_eq!(
headers.get("Authorization"),
Some(&"token my_api_key:my_access_token".to_string())
);
}
#[test]
fn test_get_login_url() {
let auth = ZerodhaAuth::new("test_key", "test_secret");
let url = auth.get_login_url(None);
assert!(url.contains("api_key=test_key"));
assert!(url.contains("v=3"));
let url_with_params = auth.get_login_url(Some("custom_data"));
assert!(url_with_params.contains("redirect_params=custom_data"));
}
#[test]
fn test_has_token() {
let auth_without_token = ZerodhaAuth::new("key", "secret");
assert!(!auth_without_token.has_token());
let auth_with_token = ZerodhaAuth::with_token("key", "secret", "token");
assert!(auth_with_token.has_token());
}
}