nautilus_architect_ax/common/
credential.rs1use core::fmt::Debug;
19
20use zeroize::ZeroizeOnDrop;
21
22#[derive(Clone, ZeroizeOnDrop)]
27pub struct Credential {
28 api_key: Box<str>,
29 api_secret: Box<str>,
30}
31
32impl Debug for Credential {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 f.debug_struct(stringify!(Credential))
35 .field("api_key", &self.masked_api_key())
36 .field("api_secret", &"<redacted>")
37 .finish()
38 }
39}
40
41impl Credential {
42 #[must_use]
44 pub fn new(api_key: impl Into<String>, api_secret: impl Into<String>) -> Self {
45 Self {
46 api_key: api_key.into().into_boxed_str(),
47 api_secret: api_secret.into().into_boxed_str(),
48 }
49 }
50
51 #[must_use]
53 pub fn api_key(&self) -> &str {
54 &self.api_key
55 }
56
57 #[must_use]
63 pub fn api_secret(&self) -> &str {
64 &self.api_secret
65 }
66
67 #[must_use]
72 pub fn masked_api_key(&self) -> String {
73 let key = self.api_key.as_ref();
74 let len = key.len();
75
76 if len <= 8 {
77 "*".repeat(len)
78 } else {
79 format!("{}...{}", &key[..4], &key[len - 4..])
80 }
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use rstest::rstest;
87
88 use super::*;
89
90 const API_KEY: &str = "test_api_key_123";
91 const API_SECRET: &str = "test_secret_456";
92
93 #[rstest]
94 fn test_credential_creation() {
95 let credential = Credential::new(API_KEY, API_SECRET);
96
97 assert_eq!(credential.api_key(), API_KEY);
98 assert_eq!(credential.api_secret(), API_SECRET);
99 }
100
101 #[rstest]
102 fn test_masked_api_key() {
103 let credential = Credential::new(API_KEY, API_SECRET);
104 let masked = credential.masked_api_key();
105
106 assert_eq!(masked, "test..._123");
107 assert!(!masked.contains("api_key"));
108 }
109
110 #[rstest]
111 fn test_masked_api_key_short() {
112 let credential = Credential::new("short", API_SECRET);
113 let masked = credential.masked_api_key();
114
115 assert_eq!(masked, "*****");
116 }
117
118 #[rstest]
119 fn test_debug_does_not_leak_secret() {
120 let credential = Credential::new(API_KEY, API_SECRET);
121 let debug_string = format!("{credential:?}");
122
123 assert!(!debug_string.contains(API_SECRET));
124 assert!(debug_string.contains("<redacted>"));
125 assert!(debug_string.contains("test..."));
126 }
127}