1use std::ffi::OsString;
4
5#[derive(Debug, Clone)]
11pub struct EnvScrub {
12 names: Vec<String>,
13 prefixes: Vec<String>,
14}
15
16impl Default for EnvScrub {
17 fn default() -> Self {
18 Self {
19 names: vec![
20 "GOOGLE_APPLICATION_CREDENTIALS".into(),
21 "DISCORD_TOKEN".into(),
22 ],
23 prefixes: vec![
24 "OKBOT_".into(),
25 "ZALO_".into(),
26 "SLACK_".into(),
27 "DISCORD_".into(),
28 "GCHAT_".into(),
29 "TELEGRAM_".into(),
30 ],
31 }
32 }
33}
34
35impl EnvScrub {
36 pub fn with_name(mut self, name: impl Into<String>) -> Self {
38 let name = name.into();
39 if !self.names.contains(&name) {
40 self.names.push(name);
41 }
42 self
43 }
44
45 pub fn with_prefix(mut self, prefix: impl Into<String>) -> Self {
47 self.prefixes.push(prefix.into());
48 self
49 }
50
51 pub fn names_to_remove(&self, current: impl IntoIterator<Item = OsString>) -> Vec<OsString> {
53 let mut out: Vec<OsString> = self.names.iter().map(OsString::from).collect();
54 for name in current {
55 let Some(text) = name.to_str() else { continue };
56 if self.prefixes.iter().any(|p| text.starts_with(p.as_str())) && !out.contains(&name) {
57 out.push(name);
58 }
59 }
60 out
61 }
62
63 pub fn current(&self) -> Vec<OsString> {
65 self.names_to_remove(std::env::vars_os().map(|(k, _)| k))
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 #[test]
74 fn removes_names_and_prefixes() {
75 let scrub = EnvScrub::default().with_name("MY_TOKEN");
76 let removed = scrub.names_to_remove(
77 ["PATH", "ZALO_BOT_TOKEN", "OKBOT_X", "HOME", "MY_TOKEN"]
78 .into_iter()
79 .map(OsString::from),
80 );
81 assert!(removed.contains(&OsString::from("ZALO_BOT_TOKEN")));
82 assert!(removed.contains(&OsString::from("OKBOT_X")));
83 assert!(removed.contains(&OsString::from("MY_TOKEN")));
84 assert!(!removed.contains(&OsString::from("PATH")));
85 assert_eq!(
86 removed.iter().filter(|n| *n == "MY_TOKEN").count(),
87 1,
88 "no duplicates"
89 );
90 }
91}