-
Notifications
You must be signed in to change notification settings - Fork 88
Feature add env #425
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop-2.x
Are you sure you want to change the base?
Feature add env #425
Changes from all commits
4cefae6
2b41f6d
adb102f
fabaeef
351b987
48375a3
79c048f
a208997
5bda26c
9b88283
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,214 @@ | ||
| use std::env; | ||
| use anyhow::{bail, Result}; | ||
|
|
||
| fn resolve_placeholder(kind: &str, value: &str) -> Result<Option<String>> { | ||
| match kind { | ||
| "env" => match env::var(value) { | ||
| Ok(val) => Ok(Some(val)), | ||
| Err(e) => bail!("couldn't find ENV-Key >>{}<< {}", value, e), | ||
| }, | ||
| _ => Ok(None), | ||
| } | ||
| } | ||
|
|
||
| pub fn replace_placeholders(input: &str) -> Result<String> { | ||
| let mut output = String::new(); | ||
| let mut cursor = 0; | ||
|
|
||
| while cursor < input.len() { | ||
| let next = input[cursor..].find('{'); | ||
|
|
||
| let start_rel = match next { | ||
| Some(pos) => pos, | ||
| None => { | ||
| output.push_str(&input[cursor..]); | ||
| break; | ||
| } | ||
| }; | ||
|
|
||
| let start = cursor + start_rel; | ||
|
|
||
| // Check if escaped: "\{" | ||
| if start > 0 && input.as_bytes()[start - 1] == b'\\' { | ||
| // push everything before the backslash | ||
| output.push_str(&input[cursor..start - 1]); | ||
| // push literal '{' | ||
| output.push('{'); | ||
|
|
||
| cursor = start + 1; | ||
| continue; | ||
| } | ||
|
|
||
| let end_rel = match input[start + 1..].find('}') { | ||
| Some(pos) => pos, | ||
| None => bail!("No closing '}}' found for '{{' at position {}", start), | ||
| }; | ||
|
|
||
| let end = start + 1 + end_rel; | ||
|
|
||
| // Push preceding text | ||
| output.push_str(&input[cursor..start]); | ||
|
|
||
| let placeholder = &input[start + 1..end]; | ||
|
|
||
| if let Some((kind, value)) = placeholder.split_once(':') { | ||
| match resolve_placeholder(kind, value)? { | ||
| Some(resolved) => output.push_str(&resolved), | ||
| None => { | ||
| output.push('{'); | ||
| output.push_str(placeholder); | ||
| output.push('}'); | ||
| } | ||
| } | ||
| } else { | ||
| output.push('{'); | ||
| output.push_str(placeholder); | ||
| output.push('}'); | ||
| } | ||
|
|
||
| cursor = end + 1; | ||
| } | ||
|
|
||
| Ok(output) | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| use std::env; | ||
|
|
||
| #[test] | ||
| fn env_var_missing() { | ||
| let result = resolve_placeholder("env", "LALA_SHOULD_NOT_EXIST"); | ||
| assert!(result.is_err()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn env_var_exists() { | ||
| env::set_var("TEST_ENV_EXISTS", "value"); | ||
|
|
||
| let result = resolve_placeholder("env", "TEST_ENV_EXISTS"); | ||
| assert!(result.is_ok()); | ||
| assert_eq!(result.unwrap(), Some("value".to_string())); | ||
| } | ||
|
|
||
| #[test] | ||
| fn passthrough_no_placeholders() { | ||
| let input = "LALA"; | ||
| let result = replace_placeholders(input); | ||
| assert!(result.is_ok()); | ||
| assert_eq!(result.unwrap(), input); | ||
| } | ||
|
|
||
| #[test] | ||
| fn single_env_placeholder() { | ||
| env::set_var("TEST_HOME", "/home/test"); | ||
|
|
||
| let result = replace_placeholders("{env:TEST_HOME}"); | ||
| assert!(result.is_ok()); | ||
| assert_eq!(result.unwrap(), "/home/test"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn unknown_kind_passthrough() { | ||
| let result = replace_placeholders("{envA:HOME}"); | ||
| assert!(result.is_ok()); | ||
| assert_eq!(result.unwrap(), "{envA:HOME}"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn interpolate_env_with_suffix() { | ||
| env::set_var("TEST_HOME", "/home/test"); | ||
|
|
||
| let result = replace_placeholders("{env:TEST_HOME}/src/modules"); | ||
| assert!(result.is_ok()); | ||
| assert_eq!(result.unwrap(), "/home/test/src/modules"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn interpolate_multiple_env_values() { | ||
| env::set_var("TEST_HOME", "/home/test"); | ||
| env::set_var("TEST_USER", "user"); | ||
|
|
||
| let input = "prefix_{env:TEST_HOME}_middle_{env:TEST_USER}_suffix"; | ||
| let result = replace_placeholders(input).unwrap(); | ||
|
|
||
| let expected = "prefix_/home/test_middle_user_suffix"; | ||
| assert_eq!(result, expected); | ||
| } | ||
|
|
||
| #[test] | ||
| fn plain_string_passthrough() { | ||
| let input = "plain_string_without_env"; | ||
| let result = replace_placeholders(input); | ||
| assert!(result.is_ok()); | ||
| assert_eq!(result.unwrap(), input); | ||
| } | ||
|
|
||
| #[test] | ||
| fn missing_closing_brace() { | ||
| let result = replace_placeholders("{env:TEST_HOME"); | ||
| assert!(result.is_err()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn nonexistent_env_var() { | ||
| let result = | ||
| replace_placeholders("{env:THIS_SHOULD_NOT_EXIST_123}"); | ||
| assert!(result.is_err()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn nonexistent_env_var_in_interpolation() { | ||
| let result = | ||
| replace_placeholders("prefix_{env:THIS_SHOULD_NOT_EXIST_456}_suffix"); | ||
| assert!(result.is_err()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn placeholder_without_colon_passthrough() { | ||
| let result = replace_placeholders("{justtext}"); | ||
| assert!(result.is_ok()); | ||
| assert_eq!(result.unwrap(), "{justtext}"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn escaped_open_brace() { | ||
| // No env needed — should NOT resolve | ||
| let result = replace_placeholders(r"\{env:TEST_HOME}").unwrap(); | ||
| assert_eq!(result, "{env:TEST_HOME}"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn escaped_brace_with_text() { | ||
| let result = replace_placeholders(r"prefix_\{env:TEST_HOME}_suffix").unwrap(); | ||
| assert_eq!(result, "prefix_{env:TEST_HOME}_suffix"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn escaped_and_real_placeholder() { | ||
| std::env::set_var("TEST_HOME_ESC", "/home/test"); | ||
|
|
||
| let input = r"\{env:TEST_HOME_ESC}_{env:TEST_HOME_ESC}"; | ||
| let result = replace_placeholders(input).unwrap(); | ||
|
|
||
| assert_eq!(result, "{env:TEST_HOME_ESC}_/home/test"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn double_escape_sequence() { | ||
| std::env::set_var("TEST_HOME_ESC2", "/home/test"); | ||
|
|
||
| // "\\{" → literal "\" + "{" | ||
| let result = replace_placeholders(r"\\{env:TEST_HOME_ESC2}").unwrap(); | ||
|
|
||
| // First "\" is literal, then placeholder is evaluated | ||
| assert_eq!(result, r"\/home/test"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn escaped_non_placeholder() { | ||
| let result = replace_placeholders(r"\{justtext}").unwrap(); | ||
| assert_eq!(result, "{justtext}"); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,7 +7,7 @@ use std::{ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| str::FromStr, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use ferron_common::observability::ObservabilityBackendChannels; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use ferron_common::{observability::ObservabilityBackendChannels, util::replace_placeholders}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use glob::glob; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use kdl::{KdlDocument, KdlNode, KdlValue}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -23,7 +23,10 @@ fn kdl_node_to_configuration_entry(kdl_node: &KdlNode) -> ServerConfigurationEnt | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut props = HashMap::new(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for kdl_entry in kdl_node.iter() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let value = match kdl_entry.value().to_owned() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| KdlValue::String(value) => ServerConfigurationValue::String(value), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| KdlValue::String(value) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let resolved_value = replace_placeholders(&value).expect("Failed to resolve environment variable in configuration"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If environment variable wouldn't be found (see https://github.com/ferronweb/ferron/pull/425/changes#diff-63c5becf3cec064da1306878c6a2ffe08a0e70be83a1d6c801b59e2b268339abR8), the web server would crash... Could this be handled more gracefully, like it's in: ferron/ferron-common/src/util/header_placeholders.rs Lines 3 to 118 in 9148eb9
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ServerConfigurationValue::String(resolved_value) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| KdlValue::Integer(value) => ServerConfigurationValue::Integer(value), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| KdlValue::Float(value) => ServerConfigurationValue::Float(value), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| KdlValue::Bool(value) => ServerConfigurationValue::Bool(value), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will probably be best to render all placeholders first and then process the config files. Will this config work?
{env.HTTP_ADDRESS} { proxy "http://localhost:3000/" // Replace "http://localhost:3000" with the backend server URL }