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 #[allow(
54 clippy::missing_panics_doc,
55 reason = "the API key is created from a UTF-8 String"
56 )]
57 #[must_use]
58 pub fn api_key(&self) -> &str {
59 std::str::from_utf8(&self.api_key).expect("API key is valid UTF-8")
60 }
61
62 #[must_use]
67 pub fn api_key_masked(&self) -> String {
68 nautilus_core::string::secret::mask_api_key(self.api_key())
69 }
70
71 #[must_use]
74 pub fn resolve(api_key: Option<String>) -> Option<Self> {
75 get_or_env_var_opt(api_key, TARDIS_API_KEY).map(Self::new)
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use rstest::rstest;
82
83 use super::*;
84
85 #[rstest]
86 fn test_api_key_masked_short() {
87 let credential = Credential::new("short");
88 assert_eq!(credential.api_key_masked(), "*****");
89 }
90
91 #[rstest]
92 fn test_api_key_masked_long() {
93 let credential = Credential::new("abcdefghijklmnop");
94 assert_eq!(credential.api_key_masked(), "abcd...mnop");
95 }
96
97 #[rstest]
98 fn test_debug_redaction() {
99 let credential = Credential::new("test_api_key");
100 let debug_str = format!("{credential:?}");
101 assert!(debug_str.contains(REDACTED));
102 assert!(!debug_str.contains("test_api_key"));
103 }
104
105 #[rstest]
106 fn test_resolve_with_explicit_key() {
107 let credential = Credential::resolve(Some("my_key".to_string()));
108 assert!(credential.is_some());
109 assert_eq!(credential.unwrap().api_key(), "my_key");
110 }
111
112 #[rstest]
113 fn test_resolve_prefers_explicit_over_env() {
114 let credential = Credential::resolve(Some("explicit_key".to_string()));
115 assert_eq!(credential.unwrap().api_key(), "explicit_key");
116 }
117}