1use std::{borrow::Cow, collections::HashMap};
2
3use crate::source::{utils, Expansion};
4
5use super::{Any, Source};
6use serde::de::{self, Unexpected};
7
8pub trait StringLookup {
10 fn lookup(&mut self, v: &str) -> Option<String>;
14}
15
16#[derive(Debug, Default, Clone, Copy)]
20pub struct EnvLookup;
21
22impl StringLookup for EnvLookup {
23 fn lookup(&mut self, v: &str) -> Option<String> {
24 std::env::var(v).ok()
25 }
26}
27
28impl StringLookup for HashMap<String, String> {
29 fn lookup(&mut self, v: &str) -> Option<String> {
30 self.get(v).cloned()
31 }
32}
33
34pub type EnvSource = StringSource<EnvLookup>;
51pub type MapSource = StringSource<HashMap<String, String>>;
55
56#[derive(Debug)]
93pub struct StringSource<T> {
94 variable: utils::Variable,
95 lookup: T,
96}
97
98impl<T> StringSource<T> {
99 pub fn new(lookup: T) -> Self {
118 Self {
119 variable: Default::default(),
120 lookup,
121 }
122 }
123
124 pub fn with_variable_prefix(mut self, prefix: impl Into<String>) -> Self {
140 self.variable.prefix = prefix.into();
141 self
142 }
143
144 pub fn with_variable_suffix(mut self, suffix: impl Into<String>) -> Self {
146 self.variable.suffix = suffix.into();
147 self
148 }
149
150 pub fn into_inner(self) -> T {
152 self.lookup
153 }
154}
155
156impl<T> Default for StringSource<T>
157where
158 T: Default,
159{
160 fn default() -> Self {
161 Self::new(Default::default())
162 }
163}
164
165impl<T> StringSource<T>
166where
167 T: StringLookup,
168{
169 fn missing_variable<E>(&self, var: &str) -> E
170 where
171 E: de::Error,
172 {
173 let var = self.variable.fmt(var);
174 E::custom(format!("got variable `{var}`, but it does not exist"))
175 }
176
177 fn mismatched_type<E>(&self, var: &str, unexpected: Unexpected<'_>, expected: &str) -> E
178 where
179 E: de::Error,
180 {
181 let var = self.variable.fmt(var);
182 E::invalid_value(
183 unexpected,
184 &format!("variable `{var}` to be {expected}").as_str(),
185 )
186 }
187
188 fn parsed<V, E>(&mut self, v: &str, expected: &str) -> Result<Option<V>, E>
189 where
190 V: std::str::FromStr,
191 V::Err: std::fmt::Display,
192 E: de::Error,
193 {
194 let Some(var) = self.variable.parse_str(v) else {
195 return Ok(None);
196 };
197
198 match self.lookup.lookup(var) {
199 Some(value) => value
200 .parse()
201 .map(Some)
202 .map_err(|_| self.mismatched_type(var, de::Unexpected::Str(&value), expected)),
203 None => Err(self.missing_variable(var)),
204 }
205 }
206}
207
208impl<T> Source for StringSource<T>
209where
210 T: StringLookup,
211{
212 fn expand_str<'a, E>(&mut self, v: Cow<'a, str>) -> Result<Expansion<Cow<'a, str>>, E>
213 where
214 E: de::Error,
215 {
216 let Some(var) = self.variable.parse_str(&v) else {
217 return Ok(Expansion::Original(v));
218 };
219
220 match self.lookup.lookup(var) {
221 Some(value) => match parse(Cow::Owned(value)) {
222 Any::Str(value) => Ok(Expansion::Expanded(value)),
223 other => Err(self.mismatched_type(var, other.unexpected(), "a string")),
224 },
225 None => Err(self.missing_variable(var)),
226 }
227 }
228
229 fn expand_bytes<'a, E>(&mut self, v: Cow<'a, [u8]>) -> Result<Expansion<Cow<'a, [u8]>>, E>
230 where
231 E: de::Error,
232 {
233 if self.variable.parse_bytes(&v).is_none() {
234 return Ok(Expansion::Original(v));
235 }
236
237 match bytes_to_str(v) {
238 Ok(s) => self.expand_str(s).map(|s| {
239 s.map(|s| match s {
240 Cow::Owned(s) => Cow::Owned(s.into_bytes()),
241 Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
242 })
243 }),
244 Err(v) => Ok(Expansion::Original(v)),
245 }
246 }
247
248 fn expand_bool<E>(&mut self, v: &str) -> Result<Option<bool>, E>
249 where
250 E: de::Error,
251 {
252 self.parsed(v, "a boolean")
253 }
254
255 fn expand_i8<E>(&mut self, v: &str) -> Result<Option<i8>, E>
256 where
257 E: de::Error,
258 {
259 self.parsed(v, "a signed integer (i8)")
260 }
261
262 fn expand_i16<E>(&mut self, v: &str) -> Result<Option<i16>, E>
263 where
264 E: de::Error,
265 {
266 self.parsed(v, "a signed integer (i16)")
267 }
268
269 fn expand_i32<E>(&mut self, v: &str) -> Result<Option<i32>, E>
270 where
271 E: de::Error,
272 {
273 self.parsed(v, "a signed integer (i32)")
274 }
275
276 fn expand_i64<E>(&mut self, v: &str) -> Result<Option<i64>, E>
277 where
278 E: de::Error,
279 {
280 self.parsed(v, "a signed integer (i64)")
281 }
282
283 fn expand_u8<E>(&mut self, v: &str) -> Result<Option<u8>, E>
284 where
285 E: de::Error,
286 {
287 self.parsed(v, "an unsigned integer (u8)")
288 }
289
290 fn expand_u16<E>(&mut self, v: &str) -> Result<Option<u16>, E>
291 where
292 E: de::Error,
293 {
294 self.parsed(v, "an unsigned integer (u16)")
295 }
296
297 fn expand_u32<E>(&mut self, v: &str) -> Result<Option<u32>, E>
298 where
299 E: de::Error,
300 {
301 self.parsed(v, "an unsigned integer (u32)")
302 }
303
304 fn expand_u64<E>(&mut self, v: &str) -> Result<Option<u64>, E>
305 where
306 E: de::Error,
307 {
308 self.parsed(v, "an unsigned integer (u64)")
309 }
310
311 fn expand_f32<E>(&mut self, v: &str) -> Result<Option<f32>, E>
312 where
313 E: de::Error,
314 {
315 self.parsed(v, "a floating point")
316 }
317
318 fn expand_f64<E>(&mut self, v: &str) -> Result<Option<f64>, E>
319 where
320 E: de::Error,
321 {
322 self.parsed(v, "a floating point")
323 }
324
325 fn expand_any<'a, E>(&mut self, v: Cow<'a, str>) -> Result<Expansion<Any<'a>, Cow<'a, str>>, E>
326 where
327 E: de::Error,
328 {
329 let Some(var) = self.variable.parse_str(&v) else {
330 return Ok(Expansion::Original(v));
331 };
332
333 self.lookup
334 .lookup(var)
335 .map(|value| parse(Cow::Owned(value)))
336 .map(Expansion::Expanded)
337 .ok_or_else(|| self.missing_variable(var))
338 }
339}
340
341fn bytes_to_str(v: Cow<'_, [u8]>) -> Result<Cow<'_, str>, Cow<'_, [u8]>> {
342 match v {
343 Cow::Owned(v) => String::from_utf8(v)
344 .map(Cow::Owned)
345 .map_err(|e| Cow::Owned(e.into_bytes())),
346 Cow::Borrowed(v) => std::str::from_utf8(v)
347 .map(Cow::Borrowed)
348 .map_err(|_| Cow::Borrowed(v)),
349 }
350}
351
352fn parse(s: Cow<'_, str>) -> Any<'_> {
354 fn strip_str(s: Cow<'_, str>) -> Cow<'_, str> {
355 match s.strip_prefix('"').and_then(|s| s.strip_suffix('"')) {
356 Some(s) => Cow::Owned(s.to_owned()),
357 None => s,
358 }
359 }
360
361 match utils::parse(s) {
362 Any::Str(s) => Any::Str(strip_str(s)),
363 other => other,
364 }
365}