Skip to main content

sonic/util/
serde.rs

1// Sonic
2//
3// Fast, lightweight and schema-less search backend
4// Copyright: 2019, Valerian Saliou <valerian@valeriansaliou.name>
5// License: Mozilla Public License v2.0 (MPL v2.0)
6
7pub mod env_var {
8    use regex::Regex;
9    use serde::{Deserialize, Deserializer};
10    use std::net::SocketAddr;
11    use std::path::PathBuf;
12
13    #[derive(Deserialize, PartialEq)]
14    struct WrappedString(String);
15
16    pub fn str<'de, D>(deserializer: D) -> Result<String, D::Error>
17    where
18        D: Deserializer<'de>,
19    {
20        let value = String::deserialize(deserializer)?;
21
22        match is_env_var(&value) {
23            true => Ok(get_env_var(&value)),
24            false => Ok(value),
25        }
26    }
27
28    pub fn opt_str<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
29    where
30        D: Deserializer<'de>,
31    {
32        Option::<WrappedString>::deserialize(deserializer).map(|option: Option<WrappedString>| {
33            option.map(|wrapped: WrappedString| {
34                let value = wrapped.0;
35
36                match is_env_var(&value) {
37                    true => get_env_var(&value),
38                    false => value,
39                }
40            })
41        })
42    }
43
44    pub fn socket_addr<'de, D>(deserializer: D) -> Result<SocketAddr, D::Error>
45    where
46        D: Deserializer<'de>,
47    {
48        let value = String::deserialize(deserializer)?;
49
50        match is_env_var(&value) {
51            true => Ok(get_env_var(&value).parse().unwrap()),
52            false => Ok(value.parse().unwrap()),
53        }
54    }
55
56    pub fn path_buf<'de, D>(deserializer: D) -> Result<PathBuf, D::Error>
57    where
58        D: Deserializer<'de>,
59    {
60        let value = String::deserialize(deserializer)?;
61
62        match is_env_var(&value) {
63            true => Ok(PathBuf::from(get_env_var(&value))),
64            false => Ok(PathBuf::from(value)),
65        }
66    }
67
68    fn is_env_var(value: &str) -> bool {
69        Regex::new(r"^\$\{env\.\w+\}$")
70            .expect("env_var: regex is invalid")
71            .is_match(value)
72    }
73
74    fn get_env_var(wrapped_key: &str) -> String {
75        let key: String = String::from(wrapped_key)
76            .drain(6..(wrapped_key.len() - 1))
77            .collect();
78
79        // NOTE: While we could deprecate the `${env.*}` syntax now that Sonic has
80        //   first-class support for environment variables, it would force people
81        //   to use the Sonic naming convention and potentially duplicate some
82        //   variables. For better UX, let’s keep it that way. It doesn’t require
83        //   dependencies nor bloat the code so it’s acceptable.
84
85        std::env::var(&key).unwrap_or_else(|_| panic!("env_var: variable '{key}' is not set"))
86    }
87
88    #[cfg(test)]
89    mod tests {
90        use super::*;
91
92        #[test]
93        fn it_checks_environment_variable_patterns() {
94            assert!(is_env_var("${env.XXX}"));
95            assert!(!is_env_var("${env.XXX"));
96            assert!(!is_env_var("${env.XXX}a"));
97            assert!(!is_env_var("a${env.XXX}"));
98            assert!(!is_env_var("{env.XXX}"));
99            assert!(!is_env_var("$env.XXX}"));
100            assert!(!is_env_var("${envXXX}"));
101            assert!(!is_env_var("${.XXX}"));
102            assert!(!is_env_var("${XXX}"));
103        }
104
105        #[test]
106        fn it_gets_environment_variable() {
107            unsafe { std::env::set_var("TEST", "test") };
108
109            assert_eq!(get_env_var("${env.TEST}"), "test");
110
111            unsafe { std::env::remove_var("TEST") };
112        }
113    }
114}