1use std::borrow::Cow;
14use std::sync::LazyLock;
15use std::sync::atomic::{AtomicBool, Ordering};
16
17pub const PREFIX: &str = "DRE_SECRET_";
18pub const MASK: &str = "*****";
19const MIN_LEN: usize = 4;
20
21static ENABLED: AtomicBool = AtomicBool::new(true);
22
23static SECRETS: LazyLock<Vec<String>> = LazyLock::new(|| {
25 let mut v: Vec<String> = std::env::vars()
26 .filter(|(k, v)| k.starts_with(PREFIX) && v.chars().count() >= MIN_LEN)
27 .map(|(_, v)| v)
28 .collect();
29 v.sort_by_key(|s| std::cmp::Reverse(s.len()));
30 v.dedup();
31 v
32});
33
34pub fn set_enabled(on: bool) {
35 ENABLED.store(on, Ordering::Relaxed);
36}
37
38pub fn mask(s: &str) -> Cow<'_, str> {
40 if !ENABLED.load(Ordering::Relaxed) || !SECRETS.iter().any(|x| s.contains(x.as_str())) {
41 return Cow::Borrowed(s);
42 }
43 let mut out = s.to_string();
44 for x in SECRETS.iter() {
45 out = out.replace(x.as_str(), MASK);
46 }
47 Cow::Owned(out)
48}
49
50pub fn redact_json(v: &mut serde_json::Value) {
52 use serde_json::Value;
53 match v {
54 Value::String(s) => {
55 if let Cow::Owned(m) = mask(s) {
56 *s = m;
57 }
58 }
59 Value::Array(a) => a.iter_mut().for_each(redact_json),
60 Value::Object(o) => {
61 let entries = std::mem::take(o);
62 for (k, mut val) in entries {
63 redact_json(&mut val);
64 o.insert(mask(&k).into_owned(), val);
65 }
66 }
67 _ => {}
68 }
69}
70
71fn redacted<T: serde::Serialize>(v: &T) -> serde_json::Result<serde_json::Value> {
72 let mut v = serde_json::to_value(v)?;
73 redact_json(&mut v);
74 Ok(v)
75}
76
77pub fn to_json_pretty<T: serde::Serialize>(v: &T) -> serde_json::Result<String> {
79 serde_json::to_string_pretty(&redacted(v)?)
80}
81
82pub fn to_json_line<T: serde::Serialize>(v: &T) -> serde_json::Result<String> {
84 serde_json::to_string(&redacted(v)?)
85}