plugx_config/parser/
env.rs1use crate::parser::Parser;
43use anyhow::{anyhow, bail};
44use cfg_if::cfg_if;
45use plugx_input::{position, position::InputPosition, Input};
46use std::fmt::{Debug, Display, Formatter};
47
48#[derive(Debug, Clone)]
49pub struct Env {
50 separator: String,
51}
52
53impl Default for Env {
54 fn default() -> Self {
55 Self {
56 separator: crate::loader::env::default::separator(),
57 }
58 }
59}
60
61impl Display for Env {
62 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
63 f.write_str("Environment-Variables")
64 }
65}
66
67impl Parser for Env {
68 fn supported_format_list(&self) -> Vec<String> {
69 ["env".into()].into()
70 }
71
72 fn try_parse(&self, bytes: &[u8]) -> anyhow::Result<Input> {
73 let text = String::from_utf8(bytes.to_vec())
74 .map_err(|error| anyhow!("Could not decode contents to UTF-8 ({error})"))?;
75 let mut list = dotenv_parser::parse_dotenv(text.as_str())
76 .map_err(|error| anyhow!(error))?
77 .into_iter()
78 .collect::<Vec<(String, String)>>();
79 list.sort_by_key(|(key, _)| key.to_string());
80
81 let mut map = Input::new_map();
82 update_input_from_env(
83 &mut map,
84 list.into_iter()
85 .map(|(key, value)| {
86 (
87 key.split(self.separator.as_str())
88 .map(|key| key.to_lowercase())
89 .collect::<Vec<String>>(),
90 value,
91 )
92 })
93 .collect::<Vec<_>>()
94 .as_slice(),
95 )
96 .map_err(|error| anyhow!(error))?;
97 cfg_if! {
98 if #[cfg(feature = "tracing")] {
99 tracing::trace!(
100 input=text,
101 output=%map,
102 "Parsed environment-variable contents"
103 );
104 } else if #[cfg(feature = "logging")] {
105 log::trace!("msg=\"Parsed environment-variable contents\" input={text:?} output={:?}", map.to_string());
106 }
107 }
108 Ok(map)
109 }
110
111 fn is_format_supported(&self, bytes: &[u8]) -> Option<bool> {
112 if let Ok(text) = String::from_utf8(bytes.to_vec()) {
113 Some(dotenv_parser::parse_dotenv(text.as_str()).is_ok())
114 } else {
115 Some(false)
116 }
117 }
118}
119
120impl Env {
121 pub fn new() -> Self {
122 Default::default()
123 }
124
125 pub fn set_key_separator<K: AsRef<str>>(&mut self, key_separator: K) {
126 self.separator = key_separator.as_ref().to_string();
127 }
128
129 pub fn with_key_separator<K: AsRef<str>>(mut self, key_separator: K) -> Self {
130 self.set_key_separator(key_separator);
131 self
132 }
133}
134
135fn update_input_from_env(
136 input: &mut Input,
137 env_list: &[(Vec<String>, String)],
138) -> anyhow::Result<()> {
139 for (key_list, value) in env_list {
140 if key_list.is_empty() || key_list.iter().any(|key| key.is_empty()) {
141 continue;
142 }
143 update_input_from_key_list(input, key_list, value.clone(), position::new())?;
144 }
145 Ok(())
146}
147
148fn update_input_from_key_list(
149 input: &mut Input,
150 key_list: &[String],
151 value: String,
152 position: InputPosition,
153) -> anyhow::Result<()> {
154 if key_list.len() == 1 {
155 let value = if let Ok(value) = serde_json::from_str::<Input>(value.as_str()) {
156 value
157 } else {
158 Input::from(value.clone())
159 };
160 let key = key_list[0].clone();
161 input.map_mut().insert(key, value);
162 Ok(())
163 } else {
164 let (key, key_list) = key_list.split_first().unwrap();
165 let position = position.new_with_key(key);
166 if !input.as_map().contains_key(key) {
167 input.map_mut().insert(key.clone(), Input::new_map());
168 }
169 let inner_input = input.map_mut().get_mut(key).unwrap();
170 if inner_input.is_map() {
171 update_input_from_key_list(inner_input, key_list, value, position)
172 } else {
173 bail!(
174 "{position} already exists with type {}, but we needed {}",
175 inner_input.type_name(),
176 Input::map_type_name()
177 )
178 }
179 }
180}