nautilus_tardis/common/
credential.rs1use std::fmt::Debug;
19
20use nautilus_core::{env::get_or_env_var_opt, string::secret::REDACTED};
21use zeroize::ZeroizeOnDrop;
22
23use super::consts::TARDIS_API_KEY;
24
25#[derive(Clone, ZeroizeOnDrop)]
27pub struct Credential {
28 api_key: Box<[u8]>,
29}
30
31impl Debug for Credential {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 f.debug_struct(stringify!(Credential))
34 .field("api_key", &REDACTED)
35 .finish()
36 }
37}
38
39impl Credential {
40 #[must_use]
42 pub fn new(api_key: impl Into<String>) -> Self {
43 let api_key_bytes = api_key.into().into_bytes();
44
45 Self {
46 api_key: api_key_bytes.into_boxed_slice(),
47 }
48 }
49
50 #[must_use]
57 pub fn api_key(&self) -> &str {
58 std::str::from_utf8(&self.api_key).expect("API key is valid UTF-8")
59 }
60
61 #[must_use]
66 pub fn api_key_masked(&self) -> String {
67 nautilus_core::string::secret::mask_api_key(self.api_key())
68 }
69
70 #[must_use]
73 pub fn resolve(api_key: Option<String>) -> Option<Self> {
74 get_or_env_var_opt(api_key, TARDIS_API_KEY).map(Self::new)
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use rstest::rstest;
81
82 use super::*;
83
84 #[rstest]
85 fn test_api_key_masked_short() {
86 let credential = Credential::new("short");
87 assert_eq!(credential.api_key_masked(), "*****");
88 }
89
90 #[rstest]
91 fn test_api_key_masked_long() {
92 let credential = Credential::new("abcdefghijklmnop");
93 assert_eq!(credential.api_key_masked(), "abcd...mnop");
94 }
95
96 #[rstest]
97 fn test_debug_redaction() {
98 let credential = Credential::new("test_api_key");
99 let debug_str = format!("{credential:?}");
100 assert!(debug_str.contains(REDACTED));
101 assert!(!debug_str.contains("test_api_key"));
102 }
103
104 #[rstest]
105 fn test_resolve_with_explicit_key() {
106 let credential = Credential::resolve(Some("my_key".to_string()));
107 assert!(credential.is_some());
108 assert_eq!(credential.unwrap().api_key(), "my_key");
109 }
110
111 #[rstest]
112 fn test_resolve_prefers_explicit_over_env() {
113 let credential = Credential::resolve(Some("explicit_key".to_string()));
114 assert_eq!(credential.unwrap().api_key(), "explicit_key");
115 }
116}