1use std::{
2 fmt,
3 fs,
4};
5
6use anyhow::Context as _;
7use hashbrown::HashMap;
8use serde::de::{
9 self,
10 MapAccess,
11 Visitor,
12};
13use serde::{
14 Deserialize,
15 Deserializer,
16};
17use serde_json::Value as JsonValue;
18
19#[allow(dead_code)]
20#[derive(Debug)]
21enum AnyValue {
22 String(String),
23 Number(serde_json::Number),
24 Bool(bool),
25}
26
27impl fmt::Display for AnyValue {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 match self {
30 AnyValue::String(s) => write!(f, "{}", s),
31 AnyValue::Number(n) => write!(f, "{}", n),
32 AnyValue::Bool(b) => write!(f, "{}", b),
33 }
34 }
35}
36
37impl<'de> Deserialize<'de> for AnyValue {
38 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
39 where
40 D: Deserializer<'de>,
41 {
42 let value: JsonValue = Deserialize::deserialize(deserializer)?;
43 match value {
44 JsonValue::String(s) => Ok(AnyValue::String(s)),
45 JsonValue::Number(n) => Ok(AnyValue::Number(n)),
46 JsonValue::Bool(b) => Ok(AnyValue::Bool(b)),
47 _ => Err(de::Error::custom("expected a string, number, or boolean")),
48 }
49 }
50}
51
52pub(crate) fn deserialize_environment<'de, D>(deserializer: D) -> Result<HashMap<String, String>, D::Error>
53where
54 D: Deserializer<'de>,
55{
56 struct EnvironmentVisitor;
57
58 impl<'de> Visitor<'de> for EnvironmentVisitor {
59 type Value = HashMap<String, String>;
60
61 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
62 formatter.write_str("a map of strings to any value (string, int, or bool)")
63 }
64
65 fn visit_map<M>(self, mut access: M) -> Result<HashMap<String, String>, M::Error>
66 where
67 M: MapAccess<'de>,
68 {
69 let mut map = HashMap::new();
70 while let Some((key, value)) = access.next_entry::<String, AnyValue>()? {
71 map.insert(key, value.to_string());
72 }
73 Ok(map)
74 }
75 }
76
77 deserializer.deserialize_map(EnvironmentVisitor)
78}
79
80pub(crate) fn load_env_files(env_files: &[String]) -> anyhow::Result<HashMap<String, String>> {
81 let mut local_env: HashMap<String, String> = HashMap::new();
82 for env_file in env_files {
83 let contents =
84 fs::read_to_string(env_file).with_context(|| format!("Failed to read env file - {}", env_file))?;
85
86 for line in contents.lines() {
87 if let Some((key, value)) = line.split_once('=') {
88 local_env.insert(key.trim().to_string(), value.trim().to_string());
89 }
90 }
91 }
92
93 Ok(local_env)
94}