1use std::{
6 env::{self, VarError},
7 fmt::{self, Display},
8 str::FromStr,
9 time::Duration,
10};
11
12use duration_string::DurationString;
13use strum::VariantNames;
14use tracing::{error, info};
15
16#[derive(Clone)]
18pub(crate) struct StringEnvVar {
19 value: String,
21 redacted: bool,
23}
24
25pub(super) enum StringEnvVarParams {
27 Plain(String, Option<String>),
29 Redacted(String, Option<String>),
31}
32
33impl From<&str> for StringEnvVarParams {
34 fn from(s: &str) -> Self {
35 StringEnvVarParams::Plain(String::from(s), None)
36 }
37}
38
39impl From<String> for StringEnvVarParams {
40 fn from(s: String) -> Self {
41 StringEnvVarParams::Plain(s, None)
42 }
43}
44
45impl From<(&str, bool)> for StringEnvVarParams {
46 fn from((s, r): (&str, bool)) -> Self {
47 if r {
48 StringEnvVarParams::Redacted(String::from(s), None)
49 } else {
50 StringEnvVarParams::Plain(String::from(s), None)
51 }
52 }
53}
54
55impl From<(&str, bool, &str)> for StringEnvVarParams {
56 fn from((s, r, c): (&str, bool, &str)) -> Self {
57 if r {
58 StringEnvVarParams::Redacted(String::from(s), Some(String::from(c)))
59 } else {
60 StringEnvVarParams::Plain(String::from(s), Some(String::from(c)))
61 }
62 }
63}
64
65impl StringEnvVar {
67 pub(super) fn new(var_name: &str, param: StringEnvVarParams) -> Self {
90 let (default_value, redacted, choices) = match param {
91 StringEnvVarParams::Plain(s, c) => (s, false, c),
92 StringEnvVarParams::Redacted(s, c) => (s, true, c),
93 };
94
95 match env::var(var_name) {
96 Ok(value) => {
97 let value = Self { value, redacted };
98 info!(env=var_name, value=%value, "Env Var Defined");
99 value
100 },
101 Err(err) => {
102 let value = Self {
103 value: default_value,
104 redacted,
105 };
106 if err == VarError::NotPresent {
107 if let Some(choices) = choices {
108 info!(env=var_name, default=%value, choices=choices, "Env Var Defaulted");
109 } else {
110 info!(env=var_name, default=%value, "Env Var Defaulted");
111 }
112 } else if let Some(choices) = choices {
113 info!(env=var_name, default=%value, choices=choices, error=?err,
114 "Env Var Error");
115 } else {
116 info!(env=var_name, default=%value, error=?err, "Env Var Error");
117 }
118
119 value
120 },
121 }
122 }
123
124 pub(super) fn new_optional(var_name: &str, redacted: bool) -> Option<Self> {
126 match env::var(var_name) {
127 Ok(value) => {
128 let value = Self { value, redacted };
129 info!(env = var_name, value = %value, "Env Var Defined");
130 Some(value)
131 },
132 Err(VarError::NotPresent) => {
133 info!(env = var_name, "Env Var Not Set");
134 None
135 },
136 Err(error) => {
137 error!(env = var_name, error = ?error, "Env Var Error");
138 None
139 },
140 }
141 }
142
143 pub(super) fn new_as_enum<T: FromStr + Display + VariantNames>(
145 var_name: &str, default: T, redacted: bool,
146 ) -> T
147 where <T as std::str::FromStr>::Err: std::fmt::Display {
148 let mut choices = String::new();
149 for name in T::VARIANTS {
150 if choices.is_empty() {
151 choices.push('[');
152 } else {
153 choices.push(',');
154 }
155 choices.push_str(name);
156 }
157 choices.push(']');
158
159 let choice = StringEnvVar::new(
160 var_name,
161 (default.to_string().as_str(), redacted, choices.as_str()).into(),
162 );
163
164 let value = match T::from_str(choice.as_str()) {
165 Ok(var) => var,
166 Err(error) => {
167 error!(error=%error, default=%default, choices=choices, choice=%choice,
168 "Invalid choice. Using Default.");
169 default
170 },
171 };
172
173 value
174 }
175
176 pub(crate) fn new_as_duration(var_name: &str, default: Duration) -> Duration {
178 let choices = "A value in the format of `[0-9]+(ns|us|ms|[smhdwy])`";
179 let default = DurationString::new(default);
180
181 let raw_value = StringEnvVar::new(
182 var_name,
183 (default.to_string().as_str(), false, choices).into(),
184 )
185 .as_string();
186
187 DurationString::try_from(raw_value.clone())
188 .inspect(|err| {
189 error!("Invalid Duration: {raw_value} : {err}. Defaulting to {default}.",);
190 })
191 .unwrap_or(default)
192 .into()
193 }
194
195 pub(super) fn new_as_int<T>(var_name: &str, default: T, min: T, max: T) -> T
197 where
198 T: FromStr + Display + PartialOrd + tracing::Value,
199 <T as std::str::FromStr>::Err: std::fmt::Display,
200 {
201 let choices = format!("A value in the range {min} to {max} inclusive");
202
203 let raw_value = StringEnvVar::new(
204 var_name,
205 (default.to_string().as_str(), false, choices.as_str()).into(),
206 )
207 .as_string();
208
209 match raw_value.parse::<T>() {
210 Ok(value) => {
211 if value < min {
212 error!("{var_name} out of range. Range = {min} to {max} inclusive. Clamped to {min}");
213 min
214 } else if value > max {
215 error!("{var_name} out of range. Range = {min} to {max} inclusive. Clamped to {max}");
216 max
217 } else {
218 value
219 }
220 },
221 Err(error) => {
222 error!(error=%error, default=default, "{var_name} not an integer. Range = {min} to {max} inclusive. Defaulted");
223 default
224 },
225 }
226 }
227
228 pub(crate) fn as_str(&self) -> &str {
234 &self.value
235 }
236
237 pub(crate) fn as_string(&self) -> String {
243 self.value.clone()
244 }
245}
246
247impl fmt::Display for StringEnvVar {
248 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249 if self.redacted {
250 return write!(f, "REDACTED");
251 }
252 write!(f, "{}", self.value)
253 }
254}
255
256impl fmt::Debug for StringEnvVar {
257 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258 if self.redacted {
259 return write!(f, "REDACTED");
260 }
261 write!(f, "env: {}", self.value)
262 }
263}