1use std::fmt;
2
3use crate::error::{EmbedError, Result};
4
5#[derive(Clone)]
6pub struct ApiKey(String);
7
8impl ApiKey {
9 pub fn from_env(key: &str) -> Result<Self> {
10 std::env::var(key)
11 .map(ApiKey)
12 .map_err(|_| EmbedError::Config(format!("environment variable `{key}` not set")))
13 }
14}
15
16impl fmt::Debug for ApiKey {
17 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18 f.debug_tuple("ApiKey").field(&"***").finish()
19 }
20}
21
22impl fmt::Display for ApiKey {
23 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24 f.write_str("***")
25 }
26}
27
28impl std::ops::Deref for ApiKey {
29 type Target = str;
30
31 fn deref(&self) -> &str {
32 &self.0
33 }
34}
35
36#[cfg(test)]
37impl From<String> for ApiKey {
38 fn from(s: String) -> Self {
39 ApiKey(s)
40 }
41}
42
43#[cfg(test)]
44impl From<&str> for ApiKey {
45 fn from(s: &str) -> Self {
46 ApiKey(s.to_string())
47 }
48}
49
50#[cfg(test)]
51#[allow(clippy::unwrap_used)]
52mod tests {
53 use super::*;
54
55 #[test]
56 fn api_key_display_is_redacted() {
57 let key = ApiKey("sk-secret-123".to_string());
58 assert_eq!(format!("{key}"), "***");
59 }
60
61 #[test]
62 fn api_key_debug_is_redacted() {
63 let key = ApiKey("sk-secret-123".to_string());
64 let debug = format!("{key:?}");
65 assert!(debug.contains("***"));
66 assert!(!debug.contains("sk-secret"));
67 }
68
69 #[test]
70 fn api_key_deref_gives_raw_value() {
71 let key = ApiKey("sk-secret-123".to_string());
72 assert_eq!(&*key, "sk-secret-123");
73 }
74
75 #[test]
76 fn api_key_from_env_missing() {
77 let result = ApiKey::from_env("ARGYPH_TEST_NONEXISTENT_KEY");
79 assert!(result.is_err());
80 let err = result.unwrap_err();
81 assert!(matches!(err, EmbedError::Config(_)));
82 }
83}