serde_vars/source/
string.rs

1use std::{borrow::Cow, collections::HashMap};
2
3use crate::source::{utils, Expansion};
4
5use super::{Any, Source};
6use serde::de::{self, Unexpected};
7
8/// A simple lookup function, used by the [`StringSource`].
9pub trait StringLookup {
10    /// Looks up the variable `v` and returns its value.
11    ///
12    /// Returns `None` if the variable cannot be found.
13    fn lookup(&mut self, v: &str) -> Option<String>;
14}
15
16/// A [`StringLookup`] which uses the process environment.
17///
18/// Generally used through [`EnvSource`].
19#[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
34/// A source which uses values from the environment.
35///
36/// See the [`crate`] and [`StringSource`] documentation for more details.
37///
38/// # Examples:
39///
40/// ```
41/// use serde_vars::EnvSource;
42///
43/// let mut source = EnvSource::default();
44/// # unsafe { std::env::set_var("MY_VAR", "some secret value"); }
45///
46/// let mut de = serde_json::Deserializer::from_str(r#""${MY_VAR}""#);
47/// let r: String = serde_vars::deserialize(&mut de, &mut source).unwrap();
48/// assert_eq!(r, "some secret value");
49/// ```
50pub type EnvSource = StringSource<EnvLookup>;
51/// A source which uses values provided from a [`HashMap`].
52///
53/// See the [`crate`] and [`StringSource`] documentation for more details.
54pub type MapSource = StringSource<HashMap<String, String>>;
55
56/// A [`Source`] which provides values using a string based [`StringLookup`].
57///
58/// This source only works with strings, but since data can be serialized into any type,
59/// it implements some basic conversion of data types through [`std::str::FromStr`].
60///
61/// During de-serialization most of the time, the target type is known. For example, when
62/// de-serializing a field `foo: u32`, the target type is known to be `u32`. In these cases the [`StringSource`],
63/// will attempt to parse the requested type from the provided string.
64///
65/// When de-serializing self-describing formats, like JSON or YAML into dynamic containers,
66/// like for example:
67///
68/// ```
69/// #[derive(serde::Deserialize)]
70/// #[serde(untagged)]
71/// enum StringOrInt {
72///     String(String),
73///     Int(u64),
74/// }
75/// ```
76///
77/// The target type is inferred from the parsed type. In these cases the [`StringSource`],
78/// needs to make the type decision.
79///
80/// Dynamic parsing ([`Source::expand_any`]), parses in order `bool`, `u64`, `f64`, `String` and yields the
81/// first one which succeeds. To explicitly force a string, the source allows to explicitly wrap
82/// the value in an additional pair of `"`, which will be stripped:
83///
84/// - `true`, `false` -> `bool`
85/// - `123`, `42` -> `u64`
86/// - `-123`, `-42` -> `i64`
87/// - `-123.0`, `42.12` -> `f64`
88/// - `foobar`, `"foobar"`, `"true"`, `123 some more` -> `String`
89///
90/// For consistency reasons, known string expansions use the same parsing logic and require
91/// ambiguous values to be explicitly marked as a string.
92#[derive(Debug)]
93pub struct StringSource<T> {
94    variable: utils::Variable,
95    lookup: T,
96}
97
98impl<T> StringSource<T> {
99    /// Creates a [`Self`] using the specified [`StringLookup`].
100    ///
101    /// By default the created source uses `${` and `}` as variable specifiers.
102    /// These can be changed using [`Self::with_variable_prefix`] and [`Self::with_variable_suffix`].
103    ///
104    /// # Examples:
105    ///
106    /// ```
107    /// use serde_vars::StringSource;
108    /// use std::collections::HashMap;
109    ///
110    /// let source = HashMap::from([("MY_VAR".to_owned(), "some secret value".to_owned())]);
111    /// let mut source = StringSource::new(source);
112    ///
113    /// let mut de = serde_json::Deserializer::from_str(r#""${MY_VAR}""#);
114    /// let r: String = serde_vars::deserialize(&mut de, &mut source).unwrap();
115    /// assert_eq!(r, "some secret value");
116    /// ```
117    pub fn new(lookup: T) -> Self {
118        Self {
119            variable: Default::default(),
120            lookup,
121        }
122    }
123
124    /// Changes the variable prefix.
125    ///
126    /// # Examples:
127    ///
128    /// ```
129    /// # use serde_vars::StringSource;
130    /// # use std::collections::HashMap;
131    /// #
132    /// let source = HashMap::from([("MY_VAR".to_owned(), "some secret value".to_owned())]);
133    /// let mut source = StringSource::new(source).with_variable_prefix("${env:");
134    ///
135    /// let mut de = serde_json::Deserializer::from_str(r#""${env:MY_VAR}""#);
136    /// let r: String = serde_vars::deserialize(&mut de, &mut source).unwrap();
137    /// assert_eq!(r, "some secret value");
138    /// ```
139    pub fn with_variable_prefix(mut self, prefix: impl Into<String>) -> Self {
140        self.variable.prefix = prefix.into();
141        self
142    }
143
144    /// Changes the variable suffix.
145    pub fn with_variable_suffix(mut self, suffix: impl Into<String>) -> Self {
146        self.variable.suffix = suffix.into();
147        self
148    }
149
150    /// Returns the contained [`StringLookup`].
151    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
352/// Like [`utils::parse`], but additionally also strips optional `"` from the string.
353fn 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}