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(
90 var_name: &str,
91 param: StringEnvVarParams,
92 ) -> Self {
93 let (default_value, redacted, choices) = match param {
94 StringEnvVarParams::Plain(s, c) => (s, false, c),
95 StringEnvVarParams::Redacted(s, c) => (s, true, c),
96 };
97
98 match env::var(var_name) {
99 Ok(value) => {
100 let value = Self { value, redacted };
101 info!(env=var_name, value=%value, "Env Var Defined");
102 value
103 },
104 Err(err) => {
105 let value = Self {
106 value: default_value,
107 redacted,
108 };
109 if err == VarError::NotPresent {
110 if let Some(choices) = choices {
111 info!(env=var_name, default=%value, choices=choices, "Env Var Defaulted");
112 } else {
113 info!(env=var_name, default=%value, "Env Var Defaulted");
114 }
115 } else if let Some(choices) = choices {
116 info!(env=var_name, default=%value, choices=choices, error=?err,
117 "Env Var Error");
118 } else {
119 info!(env=var_name, default=%value, error=?err, "Env Var Error");
120 }
121
122 value
123 },
124 }
125 }
126
127 pub(super) fn new_optional(
129 var_name: &str,
130 redacted: bool,
131 ) -> Option<Self> {
132 match env::var(var_name) {
133 Ok(value) => {
134 let value = Self { value, redacted };
135 info!(env = var_name, value = %value, "Env Var Defined");
136 Some(value)
137 },
138 Err(VarError::NotPresent) => {
139 info!(env = var_name, "Env Var Not Set");
140 None
141 },
142 Err(error) => {
143 error!(env = var_name, error = ?error, "Env Var Error");
144 None
145 },
146 }
147 }
148
149 pub(super) fn new_as_enum<T: FromStr + Display + VariantNames>(
151 var_name: &str,
152 default: T,
153 redacted: bool,
154 ) -> T
155 where
156 <T as std::str::FromStr>::Err: std::fmt::Display,
157 {
158 let mut choices = String::new();
159 for name in T::VARIANTS {
160 if choices.is_empty() {
161 choices.push('[');
162 } else {
163 choices.push(',');
164 }
165 choices.push_str(name);
166 }
167 choices.push(']');
168
169 let choice = StringEnvVar::new(
170 var_name,
171 (default.to_string().as_str(), redacted, choices.as_str()).into(),
172 );
173
174 let value = match T::from_str(choice.as_str()) {
175 Ok(var) => var,
176 Err(error) => {
177 error!(error=%error, default=%default, choices=choices, choice=%choice,
178 "Invalid choice. Using Default.");
179 default
180 },
181 };
182
183 value
184 }
185
186 pub(crate) fn new_as_duration(
188 var_name: &str,
189 default: Duration,
190 ) -> Duration {
191 let choices = "A value in the format of `[0-9]+(ns|us|ms|[smhdwy])`";
192 let default = DurationString::new(default);
193
194 let raw_value = StringEnvVar::new(
195 var_name,
196 (default.to_string().as_str(), false, choices).into(),
197 )
198 .as_string();
199
200 DurationString::try_from(raw_value.clone())
201 .inspect_err(|err| {
202 error!(
203 error = ?err,
204 default = ?default,
205 duration_str = raw_value,
206 "Invalid Duration string. Defaulting to default value.",
207 );
208 })
209 .unwrap_or(default)
210 .into()
211 }
212
213 pub(crate) fn new_as_duration_optional(var_name: &str) -> Option<Duration> {
215 let choices = "A value in the format of `[0-9]+(ns|us|ms|[smhdwy])`";
216
217 let raw_value = Self::new_optional(var_name, false)?.as_string();
218
219 DurationString::try_from(raw_value.clone())
220 .inspect_err(|err| {
221 error!(
222 error = ?err,
223 choices=choices,
224 duration_str = raw_value,
225 "Invalid Duration string. Defaulting to None",
226 );
227 })
228 .map(Into::into)
229 .ok()
230 }
231
232 pub(super) fn new_as_int<T>(
234 var_name: &str,
235 default: T,
236 min: T,
237 max: T,
238 ) -> T
239 where
240 T: FromStr + Display + PartialOrd + tracing::Value,
241 <T as std::str::FromStr>::Err: std::fmt::Display,
242 {
243 let choices = format!("A value in the range {min} to {max} inclusive");
244
245 let raw_value = StringEnvVar::new(
246 var_name,
247 (default.to_string().as_str(), false, choices.as_str()).into(),
248 )
249 .as_string();
250
251 match raw_value.parse::<T>() {
252 Ok(value) => {
253 if value < min {
254 error!("{var_name} out of range. Range = {min} to {max} inclusive. Clamped to {min}");
255 min
256 } else if value > max {
257 error!("{var_name} out of range. Range = {min} to {max} inclusive. Clamped to {max}");
258 max
259 } else {
260 value
261 }
262 },
263 Err(error) => {
264 error!(error=%error, default=default, "{var_name} not an integer. Range = {min} to {max} inclusive. Defaulted");
265 default
266 },
267 }
268 }
269
270 pub(crate) fn as_str(&self) -> &str {
276 &self.value
277 }
278
279 pub(crate) fn as_string(&self) -> String {
285 self.value.clone()
286 }
287}
288
289impl fmt::Display for StringEnvVar {
290 fn fmt(
291 &self,
292 f: &mut fmt::Formatter<'_>,
293 ) -> fmt::Result {
294 if self.redacted {
295 return write!(f, "REDACTED");
296 }
297 write!(f, "{}", self.value)
298 }
299}
300
301impl fmt::Debug for StringEnvVar {
302 fn fmt(
303 &self,
304 f: &mut fmt::Formatter<'_>,
305 ) -> fmt::Result {
306 if self.redacted {
307 return write!(f, "REDACTED");
308 }
309 write!(f, "env: {}", self.value)
310 }
311}