plugx_config/loader/
env.rs1use crate::{
49 entity::ConfigurationEntity,
50 loader::{self, Error, Loader},
51};
52use cfg_if::cfg_if;
53use serde::Deserialize;
54use std::fmt::{Display, Formatter};
55use std::{env, fmt::Debug};
56use url::Url;
57
58pub const NAME: &str = "Environment-Variables";
59pub const SCHEME_LIST: &[&str] = &["env"];
60
61#[derive(Debug, Default, Clone)]
63pub struct Env {
64 options: EnvOptions,
65}
66
67#[derive(Debug, Clone, Deserialize)]
68#[serde(default)]
69struct EnvOptions {
70 prefix: String,
71 separator: String,
72 strip_prefix: bool,
73}
74
75impl Default for EnvOptions {
76 fn default() -> Self {
77 Self {
78 prefix: default::prefix(),
79 separator: default::separator(),
80 strip_prefix: default::strip_prefix(),
81 }
82 }
83}
84
85pub mod default {
86
87 #[inline]
88 pub fn prefix() -> String {
89 let mut prefix = option_env!("CARGO_BIN_NAME").unwrap_or("").to_string();
90 if prefix.is_empty() {
91 prefix = option_env!("CARGO_CRATE_NAME").unwrap_or("").to_string();
92 }
93 if !prefix.is_empty() {
94 prefix += separator().as_str();
95 }
96 prefix
97 }
98
99 #[inline(always)]
100 pub fn separator() -> String {
101 "__".to_string()
102 }
103
104 #[inline(always)]
105 pub fn strip_prefix() -> bool {
106 true
107 }
108}
109
110impl Env {
111 pub fn new() -> Self {
113 Default::default()
114 }
115
116 pub fn set_prefix<P: AsRef<str>>(&mut self, prefix: P) {
118 self.options.prefix = prefix.as_ref().to_string();
119 }
120
121 pub fn with_prefix<P: AsRef<str>>(mut self, prefix: P) -> Self {
123 self.set_prefix(prefix);
124 self
125 }
126
127 pub fn set_separator<S: AsRef<str>>(&mut self, separator: S) {
129 self.options.separator = separator.as_ref().to_string();
130 }
131
132 pub fn with_separator<S: AsRef<str>>(mut self, separator: S) -> Self {
134 self.set_separator(separator);
135 self
136 }
137
138 pub fn set_strip_prefix(&mut self, strip_prefix: bool) {
140 self.options.strip_prefix = strip_prefix;
141 }
142
143 pub fn with_strip_prefix(mut self, strip_prefix: bool) -> Self {
145 self.set_strip_prefix(strip_prefix);
146 self
147 }
148}
149
150impl Display for Env {
151 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
152 f.write_str(NAME)
153 }
154}
155
156impl Loader for Env {
157 fn scheme_list(&self) -> Vec<String> {
159 SCHEME_LIST.iter().cloned().map(String::from).collect()
160 }
161
162 fn load(
164 &self,
165 url: &Url,
166 maybe_whitelist: Option<&[String]>,
167 _skip_soft_errors: bool,
168 ) -> Result<Vec<(String, ConfigurationEntity)>, Error> {
169 let EnvOptions {
170 mut prefix,
171 mut separator,
172 mut strip_prefix,
173 } = loader::deserialize_query_string(NAME, url)?;
174 if self.options.prefix != default::prefix() {
175 prefix = self.options.prefix.clone()
176 }
177 if self.options.separator != default::separator() {
178 separator = self.options.separator.clone()
179 }
180 if self.options.strip_prefix != default::strip_prefix() {
181 strip_prefix = self.options.strip_prefix
182 }
183 if !separator.is_empty() && !prefix.is_empty() && !prefix.ends_with(separator.as_str()) {
184 prefix += separator.as_str()
185 }
186 let mut result = Vec::new();
187 env::vars()
188 .filter(|(key, _)| prefix.is_empty() || key.starts_with(prefix.as_str()))
189 .map(|(mut key, value)| {
190 if !prefix.is_empty() && strip_prefix {
191 key = key.chars().skip(prefix.chars().count()).collect::<String>()
192 }
193 (key, value)
194 })
195 .filter(|(key, _)| !key.is_empty())
196 .map(|(key, value)| {
197 let key_list = if separator.is_empty() {
198 [key].to_vec()
199 } else {
200 key.splitn(2, separator.as_str())
201 .map(|key| key.to_string())
202 .collect()
203 };
204 (key_list, value)
205 })
206 .filter(|(key_list, _)| !key_list[0].is_empty())
207 .map(|(mut key_list, value)| {
208 let plugin_name = key_list.remove(0).to_lowercase();
209 let key = if key_list.len() == 1 {
210 key_list.remove(0)
211 } else {
212 String::new()
213 };
214 (plugin_name, key, value)
215 })
216 .filter(|(_, key, _)| !key.is_empty())
217 .map(|(_plugin_name, _key, _value)| {
218 cfg_if! {
219 if #[cfg(feature = "tracing")] {
220 tracing::trace!(
221 plugin=_plugin_name,
222 key=_key,
223 value=_value,
224 "Detected environment-variable"
225 );
226 } else if #[cfg(feature = "logging")] {
227 log::trace!(
228 "msg=\"Detected environment-variable\" plugin={_plugin_name:?} key={_key:?} value={_value:?}"
229 );
230 }
231 }
232 (_plugin_name, _key, _value)
233 })
234 .filter(|(plugin_name, _, _)| {
235 maybe_whitelist
236 .as_ref()
237 .map(|whitelist| whitelist.contains(plugin_name))
238 .unwrap_or(true)
239 })
240 .for_each(|(plugin_name, key, value)| {
241 let key_value = format!("{key}={value:?}");
242 if let Some((_, _, configuration)) =
243 result.iter_mut().find(|(name, _, _)| *name == plugin_name)
244 {
245 *configuration += "\n";
246 *configuration += key_value.as_str();
247 } else {
248 result.push((plugin_name, format!("{prefix}*"), key_value));
249 }
250 });
251 Ok(result
252 .into_iter()
253 .map(|(plugin_name, key, contents)| {
254 (
255 plugin_name.clone(),
256 ConfigurationEntity::new(key, url.clone(), plugin_name, NAME)
257 .with_format("env")
258 .with_contents(contents),
259 )
260 })
261 .map(|(_plugin_name, _configuration)| {
262 cfg_if! {
263 if #[cfg(feature = "tracing")] {
264 tracing::trace!(
265 plugin=_plugin_name,
266 format=_configuration.maybe_format().unwrap_or(&"<unknown>".to_string()),
267 contents=_configuration.maybe_contents().unwrap(),
268 "Detected configuration from environment-variable"
269 );
270 } else if #[cfg(feature = "logging")] {
271 log::trace!(
272 "msg=\"Detected configuration from environment-variable\" plugin={_plugin_name:?} format={:?} contents={:?}",
273 _configuration.maybe_format().unwrap_or(&"<unknown>".to_string()),
274 _configuration.maybe_contents().unwrap(),
275 );
276 }
277 }
278 (_plugin_name, _configuration)
279 })
280 .collect())
281 }
282}