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 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
184 pub(crate) fn new_as_duration(
186 var_name: &str,
187 default: Duration,
188 ) -> Duration {
189 let choices = "A value in the format of `[0-9]+(ns|us|ms|[smhdwy])`";
190 let default = DurationString::new(default);
191
192 let raw_value = StringEnvVar::new(
193 var_name,
194 (default.to_string().as_str(), false, choices).into(),
195 )
196 .as_string();
197
198 DurationString::try_from(raw_value.clone())
199 .inspect_err(|err| {
200 error!(
201 error = ?err,
202 default = ?default,
203 duration_str = raw_value,
204 "Invalid Duration string. Defaulting to default value.",
205 );
206 })
207 .unwrap_or(default)
208 .into()
209 }
210
211 pub(super) fn new_as_int<T>(
213 var_name: &str,
214 default: T,
215 min: T,
216 max: T,
217 ) -> T
218 where
219 T: FromStr + Display + PartialOrd + tracing::Value,
220 <T as std::str::FromStr>::Err: std::fmt::Display,
221 {
222 let choices = format!("A value in the range {min} to {max} inclusive");
223
224 let raw_value = StringEnvVar::new(
225 var_name,
226 (default.to_string().as_str(), false, choices.as_str()).into(),
227 )
228 .as_string();
229
230 match raw_value.parse::<T>() {
231 Ok(value) => {
232 if value < min {
233 error!(
234 "{var_name} out of range. Range = {min} to {max} inclusive. Clamped to {min}"
235 );
236 min
237 } else if value > max {
238 error!(
239 "{var_name} out of range. Range = {min} to {max} inclusive. Clamped to {max}"
240 );
241 max
242 } else {
243 value
244 }
245 },
246 Err(error) => {
247 error!(error=%error, default=default, "{var_name} not an integer. Range = {min} to {max} inclusive. Defaulted");
248 default
249 },
250 }
251 }
252
253 pub(crate) fn as_str(&self) -> &str {
259 &self.value
260 }
261
262 pub(crate) fn as_string(&self) -> String {
268 self.value.clone()
269 }
270}
271
272impl fmt::Display for StringEnvVar {
273 fn fmt(
274 &self,
275 f: &mut fmt::Formatter<'_>,
276 ) -> fmt::Result {
277 if self.redacted {
278 return write!(f, "REDACTED");
279 }
280 write!(f, "{}", self.value)
281 }
282}
283
284impl fmt::Debug for StringEnvVar {
285 fn fmt(
286 &self,
287 f: &mut fmt::Formatter<'_>,
288 ) -> fmt::Result {
289 if self.redacted {
290 return write!(f, "REDACTED");
291 }
292 write!(f, "env: {}", self.value)
293 }
294}