From 11495ca1bb905ccc7d9d68bd7b7329c4d9433817 Mon Sep 17 00:00:00 2001 From: Eugene Goryachev Date: Wed, 7 Jan 2026 02:22:19 +0300 Subject: [PATCH 1/4] feat: Add env var placeholders support --- pico_limbo/src/configuration/config.rs | 14 +- .../src/configuration/env_placeholders.rs | 134 ++++++++++++++++++ pico_limbo/src/configuration/mod.rs | 1 + pico_limbo/src/server/start_server.rs | 11 ++ server.toml | 68 +++++++++ 5 files changed, 225 insertions(+), 3 deletions(-) create mode 100644 pico_limbo/src/configuration/env_placeholders.rs create mode 100644 server.toml diff --git a/pico_limbo/src/configuration/config.rs b/pico_limbo/src/configuration/config.rs index c5c51126..a5156ee6 100644 --- a/pico_limbo/src/configuration/config.rs +++ b/pico_limbo/src/configuration/config.rs @@ -1,5 +1,6 @@ use crate::configuration::boss_bar::BossBarConfig; use crate::configuration::compression::CompressionConfig; +use crate::configuration::env_placeholders::expand_env_placeholders; use crate::configuration::forwarding::ForwardingConfig; use crate::configuration::game_mode_config::GameModeConfig; use crate::configuration::server_list::ServerListConfig; @@ -21,6 +22,12 @@ pub enum ConfigError { #[error("TOML deserialization error: {0}")] TomlDeserialize(#[from] toml::de::Error), + + #[error("Missing environment variable: {0}")] + MissingEnvVar(String), + + #[error("Invalid environment placeholder at {line}:{char}")] + InvalidEnvPlaceholder { line: usize, char: usize }, } /// Application configuration, serializable to/from TOML. @@ -100,12 +107,13 @@ pub fn load_or_create>(path: P) -> Result { let path = path.as_ref(); if path.exists() { - let toml_str = fs::read_to_string(path)?; + let raw_toml_str = fs::read_to_string(path)?; - if toml_str.trim().is_empty() { + if raw_toml_str.trim().is_empty() { create_default_config(path) } else { - let cfg: Config = toml::from_str(&toml_str)?; + let expanded_toml_str = expand_env_placeholders(&raw_toml_str)?; + let cfg: Config = toml::from_str(expanded_toml_str.as_ref())?; Ok(cfg) } } else { diff --git a/pico_limbo/src/configuration/env_placeholders.rs b/pico_limbo/src/configuration/env_placeholders.rs new file mode 100644 index 00000000..ccaf6dd8 --- /dev/null +++ b/pico_limbo/src/configuration/env_placeholders.rs @@ -0,0 +1,134 @@ +use crate::configuration::config::ConfigError; +use std::borrow::Cow; + +/// Expands environment placeholders in the given text. +/// +/// Replaces occurrences of `${ENV_VAR}` with the corresponding value from the +/// process environment (via `std::env`). If a referenced variable is not set, +/// returns `ConfigError::MissingEnvVar`. +/// +/// The sequence `\${` is treated as an escape and is converted to a literal `${` +/// without performing substitution. +/// +/// On malformed placeholders (e.g. missing closing }, empty/invalid variable +/// name, or a newline inside the placeholder), returns +/// `ConfigError::InvalidEnvPlaceholder { line, char }`, where line and char +/// are 1-based positions of the $ that started the placeholder. +/// +/// For efficiency, if the input contains no `${`, the function +/// returns a `borrowed Cow::Borrowed` without allocating +pub fn expand_env_placeholders<'a>(input: &'a str) -> Result, ConfigError> { + if !input.contains("${") { + return Ok(Cow::Borrowed(input)); + } + + fn bump(ch: char, line: &mut usize, col: &mut usize) { + if ch == '\n' { + *line += 1; + *col = 1; + } else { + *col += 1; + } + } + + fn is_valid_env_name(name: &str) -> bool { + let mut it = name.chars(); + let Some(first) = it.next() else { + return false; + }; + if !(first == '_' || first.is_ascii_alphabetic()) { + return false; + } + it.all(|c| c == '_' || c.is_ascii_alphanumeric()) + } + + let mut out = String::with_capacity(input.len()); + let mut it = input.char_indices().peekable(); + + let mut line: usize = 1; + let mut col: usize = 1; + + while let Some((_i, ch)) = it.next() { + let start_line = line; + let start_col = col; + + match ch { + '\\' => { + let mut look = it.clone(); + if matches!(look.next(), Some((_, '$'))) && matches!(look.next(), Some((_, '{'))) { + // съедаем '$' и '{' + let (_, d) = it.next().unwrap(); + let (_j, b) = it.next().unwrap(); + + // учёт позиции + bump('\\', &mut line, &mut col); + bump(d, &mut line, &mut col); + bump(b, &mut line, &mut col); + + out.push_str("${"); + continue; + } + + bump('\\', &mut line, &mut col); + out.push('\\'); + } + '$' => { + if !matches!(it.peek(), Some((_, '{'))) { + bump('$', &mut line, &mut col); + out.push('$'); + continue; + } + let (brace_idx, brace) = it.next().unwrap(); + + bump('$', &mut line, &mut col); + bump(brace, &mut line, &mut col); + let name_start = brace_idx + brace.len_utf8(); + let mut name_end: Option = None; + + while let Some((k, c)) = it.next() { + match c { + '}' => { + name_end = Some(k); + bump('}', &mut line, &mut col); + break; + } + '\n' => { + return Err(ConfigError::InvalidEnvPlaceholder { + line: start_line, + char: start_col, + }); + } + _ => bump(c, &mut line, &mut col), + } + } + + let name_end = name_end.ok_or(ConfigError::InvalidEnvPlaceholder { + line: start_line, + char: start_col, + })?; + + let name = &input[name_start..name_end]; + + if name.is_empty() || !is_valid_env_name(name) { + return Err(ConfigError::InvalidEnvPlaceholder { + line: start_line, + char: start_col, + }); + } + + let Some(val) = std::env::var_os(name) else { + return Err(ConfigError::MissingEnvVar(name.to_string())); + }; + + out.push_str(&val.to_string_lossy()); + } + + _ => { + bump(ch, &mut line, &mut col); + out.push(ch); + } + } + } + + Ok(Cow::Owned(out)) +} diff --git a/pico_limbo/src/configuration/mod.rs b/pico_limbo/src/configuration/mod.rs index 9091a028..f8868d36 100644 --- a/pico_limbo/src/configuration/mod.rs +++ b/pico_limbo/src/configuration/mod.rs @@ -1,6 +1,7 @@ pub mod boss_bar; mod compression; pub mod config; +mod env_placeholders; mod forwarding; mod game_mode_config; mod require_boolean; diff --git a/pico_limbo/src/server/start_server.rs b/pico_limbo/src/server/start_server.rs index aa2b1428..a8dc0c1f 100644 --- a/pico_limbo/src/server/start_server.rs +++ b/pico_limbo/src/server/start_server.rs @@ -38,6 +38,17 @@ fn load_configuration(config_path: &PathBuf) -> Option { Err(ConfigError::Io(message, ..)) => { error!("Failed to load configuration: {}", message); } + Err(ConfigError::InvalidEnvPlaceholder { line, char }) => { + error!( + "Failed to load configuration: invalid environment placeholder at {line}:{char}" + ); + } + Err(ConfigError::MissingEnvVar(var)) => { + error!( + "Failed to load configuration: missing environment variable {}", + var + ); + } Err(ConfigError::TomlSerialize(message, ..)) => { error!("Failed to save default configuration file: {}", message); } diff --git a/server.toml b/server.toml new file mode 100644 index 00000000..07815963 --- /dev/null +++ b/server.toml @@ -0,0 +1,68 @@ +bind = "0.0.0.0:25565" +welcome_message = "Welcome to PicoLimbo!" +action_bar = "Welcome to PicoLimbo!" +default_game_mode = "spectator" +hardcore = false +fetch_player_skins = false +reduced_debug_info = false +allow_unsupported_versions = false +allow_flight = false + +[forwarding] +method = "NONE" +secret = "${PATH}" + +[world] +spawn_position = [ + 0.0, + 320.0, + 0.0, +] +spawn_rotation = [ + 0.0, + 0.0, +] +dimension = "end" +time = "day" + +[world.experimental] +view_distance = 2 +schematic_file = "" +lock_time = false + +[world.boundaries] +enabled = true +min_y = -64 +teleport_message = "You have reached the bottom of the world." + +[server_list] +reply_to_status = true +max_players = 20 +message_of_the_day = "A Minecraft Server" +show_online_player_count = true +server_icon = "server-icon.png" + +[compression] +threshold = -1 +level = 6 + +[tab_list] +enabled = true +header = "Welcome to PicoLimbo" +footer = "Enjoy your stay!" +player_listed = true + +[boss_bar] +enabled = false +title = "Welcome to PicoLimbo!" +health = 1.0 +color = "pink" +division = 0 + +[title] +enabled = false +title = "Welcome!" +subtitle = "Enjoy your stay" +fade_in = 10 +stay = 70 +fade_out = 20 From 9e8b72c966fe8bbba9c1b13eb81b05bf8b1dd521 Mon Sep 17 00:00:00 2001 From: Eugene Goryachev Date: Wed, 7 Jan 2026 16:25:04 +0300 Subject: [PATCH 2/4] fix: remove server.toml --- server.toml | 68 ----------------------------------------------------- 1 file changed, 68 deletions(-) delete mode 100644 server.toml diff --git a/server.toml b/server.toml deleted file mode 100644 index 07815963..00000000 --- a/server.toml +++ /dev/null @@ -1,68 +0,0 @@ -bind = "0.0.0.0:25565" -welcome_message = "Welcome to PicoLimbo!" -action_bar = "Welcome to PicoLimbo!" -default_game_mode = "spectator" -hardcore = false -fetch_player_skins = false -reduced_debug_info = false -allow_unsupported_versions = false -allow_flight = false - -[forwarding] -method = "NONE" -secret = "${PATH}" - -[world] -spawn_position = [ - 0.0, - 320.0, - 0.0, -] -spawn_rotation = [ - 0.0, - 0.0, -] -dimension = "end" -time = "day" - -[world.experimental] -view_distance = 2 -schematic_file = "" -lock_time = false - -[world.boundaries] -enabled = true -min_y = -64 -teleport_message = "You have reached the bottom of the world." - -[server_list] -reply_to_status = true -max_players = 20 -message_of_the_day = "A Minecraft Server" -show_online_player_count = true -server_icon = "server-icon.png" - -[compression] -threshold = -1 -level = 6 - -[tab_list] -enabled = true -header = "Welcome to PicoLimbo" -footer = "Enjoy your stay!" -player_listed = true - -[boss_bar] -enabled = false -title = "Welcome to PicoLimbo!" -health = 1.0 -color = "pink" -division = 0 - -[title] -enabled = false -title = "Welcome!" -subtitle = "Enjoy your stay" -fade_in = 10 -stay = 70 -fade_out = 20 From 73aa8784afa73d6150b41c8d3fdcdb578bab3f1a Mon Sep 17 00:00:00 2001 From: Eugene Goryachev Date: Wed, 7 Jan 2026 18:58:25 +0300 Subject: [PATCH 3/4] refactor: rewrite env var placeholders using regex --- Cargo.lock | 13 ++ Cargo.toml | 1 + pico_limbo/Cargo.toml | 3 +- pico_limbo/src/configuration/config.rs | 3 - .../src/configuration/env_placeholders.rs | 139 +++--------------- pico_limbo/src/server/start_server.rs | 5 - 6 files changed, 38 insertions(+), 126 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3df4e26d..0d71b8b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -950,6 +950,7 @@ dependencies = [ "pico_structures", "pico_text_component", "rand", + "regex", "registries", "reqwest", "serde", @@ -1185,6 +1186,18 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + [[package]] name = "regex-automata" version = "0.4.13" diff --git a/Cargo.toml b/Cargo.toml index 464e5283..8cf16300 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ quick-xml = "0.38.4" quote = "1.0.42" rand = "0.9.2" rayon = "1.11.0" +regex = "1.12.2" reqwest = { version = "0.12.26", default-features = false, features = ["json", "rustls-tls-native-roots", "http2"] } serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.145" diff --git a/pico_limbo/Cargo.toml b/pico_limbo/Cargo.toml index 58eb7524..b823d0d9 100644 --- a/pico_limbo/Cargo.toml +++ b/pico_limbo/Cargo.toml @@ -17,6 +17,7 @@ clap = { workspace = true } futures = { workspace = true } hmac = { workspace = true } rand = { workspace = true } +regex = { workspace = true } reqwest = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } @@ -26,4 +27,4 @@ thiserror = { workspace = true } tokio = { workspace = true } toml = { workspace = true } tracing = { workspace = true } -tracing-subscriber = { workspace = true } +tracing-subscriber = { workspace = true } \ No newline at end of file diff --git a/pico_limbo/src/configuration/config.rs b/pico_limbo/src/configuration/config.rs index a5156ee6..e82d9a3d 100644 --- a/pico_limbo/src/configuration/config.rs +++ b/pico_limbo/src/configuration/config.rs @@ -25,9 +25,6 @@ pub enum ConfigError { #[error("Missing environment variable: {0}")] MissingEnvVar(String), - - #[error("Invalid environment placeholder at {line}:{char}")] - InvalidEnvPlaceholder { line: usize, char: usize }, } /// Application configuration, serializable to/from TOML. diff --git a/pico_limbo/src/configuration/env_placeholders.rs b/pico_limbo/src/configuration/env_placeholders.rs index ccaf6dd8..235ccc9d 100644 --- a/pico_limbo/src/configuration/env_placeholders.rs +++ b/pico_limbo/src/configuration/env_placeholders.rs @@ -1,5 +1,7 @@ +use regex::{Captures, Regex}; + use crate::configuration::config::ConfigError; -use std::borrow::Cow; +use std::{borrow::Cow, sync::LazyLock}; /// Expands environment placeholders in the given text. /// @@ -9,126 +11,29 @@ use std::borrow::Cow; /// /// The sequence `\${` is treated as an escape and is converted to a literal `${` /// without performing substitution. -/// -/// On malformed placeholders (e.g. missing closing }, empty/invalid variable -/// name, or a newline inside the placeholder), returns -/// `ConfigError::InvalidEnvPlaceholder { line, char }`, where line and char -/// are 1-based positions of the $ that started the placeholder. -/// -/// For efficiency, if the input contains no `${`, the function -/// returns a `borrowed Cow::Borrowed` without allocating pub fn expand_env_placeholders<'a>(input: &'a str) -> Result, ConfigError> { - if !input.contains("${") { - return Ok(Cow::Borrowed(input)); - } - - fn bump(ch: char, line: &mut usize, col: &mut usize) { - if ch == '\n' { - *line += 1; - *col = 1; - } else { - *col += 1; + static RE: LazyLock = + LazyLock::new(|| Regex::new(r#"(\\)?\$\{([A-Za-z_][A-Za-z0-9_]*)\}"#).unwrap()); + + for caps in RE.captures_iter(input) { + let is_escaped = caps.get(1).is_some(); + let name = &caps[2]; + if is_escaped { + continue; } - } - - fn is_valid_env_name(name: &str) -> bool { - let mut it = name.chars(); - let Some(first) = it.next() else { - return false; - }; - if !(first == '_' || first.is_ascii_alphabetic()) { - return false; + if std::env::var(name).is_err() { + return Err(ConfigError::MissingEnvVar(name.to_string())); } - it.all(|c| c == '_' || c.is_ascii_alphanumeric()) } - let mut out = String::with_capacity(input.len()); - let mut it = input.char_indices().peekable(); - - let mut line: usize = 1; - let mut col: usize = 1; - - while let Some((_i, ch)) = it.next() { - let start_line = line; - let start_col = col; - - match ch { - '\\' => { - let mut look = it.clone(); - if matches!(look.next(), Some((_, '$'))) && matches!(look.next(), Some((_, '{'))) { - // съедаем '$' и '{' - let (_, d) = it.next().unwrap(); - let (_j, b) = it.next().unwrap(); - - // учёт позиции - bump('\\', &mut line, &mut col); - bump(d, &mut line, &mut col); - bump(b, &mut line, &mut col); - - out.push_str("${"); - continue; - } - - bump('\\', &mut line, &mut col); - out.push('\\'); - } - '$' => { - if !matches!(it.peek(), Some((_, '{'))) { - bump('$', &mut line, &mut col); - out.push('$'); - continue; - } - let (brace_idx, brace) = it.next().unwrap(); - - bump('$', &mut line, &mut col); - bump(brace, &mut line, &mut col); - let name_start = brace_idx + brace.len_utf8(); - let mut name_end: Option = None; - - while let Some((k, c)) = it.next() { - match c { - '}' => { - name_end = Some(k); - bump('}', &mut line, &mut col); - break; - } - '\n' => { - return Err(ConfigError::InvalidEnvPlaceholder { - line: start_line, - char: start_col, - }); - } - _ => bump(c, &mut line, &mut col), - } - } - - let name_end = name_end.ok_or(ConfigError::InvalidEnvPlaceholder { - line: start_line, - char: start_col, - })?; - - let name = &input[name_start..name_end]; - - if name.is_empty() || !is_valid_env_name(name) { - return Err(ConfigError::InvalidEnvPlaceholder { - line: start_line, - char: start_col, - }); - } - - let Some(val) = std::env::var_os(name) else { - return Err(ConfigError::MissingEnvVar(name.to_string())); - }; - - out.push_str(&val.to_string_lossy()); - } - - _ => { - bump(ch, &mut line, &mut col); - out.push(ch); - } + let out = RE.replace_all(input, |caps: &Captures| { + let is_escaped = caps.get(1).is_some(); + let name = &caps[2]; + if is_escaped { + format!("${{{}}}", name) + } else { + std::env::var(name).unwrap() } - } - - Ok(Cow::Owned(out)) + }); + Ok(out) } diff --git a/pico_limbo/src/server/start_server.rs b/pico_limbo/src/server/start_server.rs index a8dc0c1f..00f6aa3f 100644 --- a/pico_limbo/src/server/start_server.rs +++ b/pico_limbo/src/server/start_server.rs @@ -38,11 +38,6 @@ fn load_configuration(config_path: &PathBuf) -> Option { Err(ConfigError::Io(message, ..)) => { error!("Failed to load configuration: {}", message); } - Err(ConfigError::InvalidEnvPlaceholder { line, char }) => { - error!( - "Failed to load configuration: invalid environment placeholder at {line}:{char}" - ); - } Err(ConfigError::MissingEnvVar(var)) => { error!( "Failed to load configuration: missing environment variable {}", From 221b5c383d3a7b46f18511d1854bc3b6954597c8 Mon Sep 17 00:00:00 2001 From: Quozul <30729291+Quozul@users.noreply.github.com> Date: Wed, 28 Jan 2026 09:58:45 +0100 Subject: [PATCH 4/4] refactor: safer env placeholder parsing and unit testing --- pico_limbo/src/configuration/config.rs | 6 +- .../src/configuration/env_placeholders.rs | 144 ++++++++++++++++-- pico_limbo/src/server/start_server.rs | 7 +- 3 files changed, 133 insertions(+), 24 deletions(-) diff --git a/pico_limbo/src/configuration/config.rs b/pico_limbo/src/configuration/config.rs index e82d9a3d..e5aca30a 100644 --- a/pico_limbo/src/configuration/config.rs +++ b/pico_limbo/src/configuration/config.rs @@ -1,6 +1,6 @@ use crate::configuration::boss_bar::BossBarConfig; use crate::configuration::compression::CompressionConfig; -use crate::configuration::env_placeholders::expand_env_placeholders; +use crate::configuration::env_placeholders::{EnvPlaceholderError, expand_env_placeholders}; use crate::configuration::forwarding::ForwardingConfig; use crate::configuration::game_mode_config::GameModeConfig; use crate::configuration::server_list::ServerListConfig; @@ -23,8 +23,8 @@ pub enum ConfigError { #[error("TOML deserialization error: {0}")] TomlDeserialize(#[from] toml::de::Error), - #[error("Missing environment variable: {0}")] - MissingEnvVar(String), + #[error("Failed to apply environment placeholders: {0}")] + EnvPlaceholder(#[from] EnvPlaceholderError), } /// Application configuration, serializable to/from TOML. diff --git a/pico_limbo/src/configuration/env_placeholders.rs b/pico_limbo/src/configuration/env_placeholders.rs index 235ccc9d..9dc0809c 100644 --- a/pico_limbo/src/configuration/env_placeholders.rs +++ b/pico_limbo/src/configuration/env_placeholders.rs @@ -1,7 +1,18 @@ -use regex::{Captures, Regex}; +use regex::Regex; -use crate::configuration::config::ConfigError; +use std::env::VarError; +use std::fmt::Write; use std::{borrow::Cow, sync::LazyLock}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum EnvPlaceholderError { + #[error("Missing environment variable: {0}")] + MissingEnvVar(#[from] VarError), + + #[error("Format error: {0}")] + Format(#[from] std::fmt::Error), +} /// Expands environment placeholders in the given text. /// @@ -11,29 +22,130 @@ use std::{borrow::Cow, sync::LazyLock}; /// /// The sequence `\${` is treated as an escape and is converted to a literal `${` /// without performing substitution. -pub fn expand_env_placeholders<'a>(input: &'a str) -> Result, ConfigError> { +pub fn expand_env_placeholders(input: &str) -> Result, EnvPlaceholderError> { static RE: LazyLock = - LazyLock::new(|| Regex::new(r#"(\\)?\$\{([A-Za-z_][A-Za-z0-9_]*)\}"#).unwrap()); + LazyLock::new(|| Regex::new(r"(\\)?\$\{([A-Za-z_][A-Za-z0-9_]*)}").unwrap()); + + if !RE.is_match(input) { + return Ok(Cow::Borrowed(input)); + } + + let mut new_string = String::with_capacity(input.len()); + let mut last_match_end = 0; for caps in RE.captures_iter(input) { + let match_whole = caps.get(0).unwrap(); + + new_string.push_str(&input[last_match_end..match_whole.start()]); + let is_escaped = caps.get(1).is_some(); let name = &caps[2]; + if is_escaped { - continue; + write!(new_string, "${{{name}}}")?; + } else { + let val = std::env::var(name)?; + new_string.push_str(&val); } - if std::env::var(name).is_err() { - return Err(ConfigError::MissingEnvVar(name.to_string())); + + last_match_end = match_whole.end(); + } + + new_string.push_str(&input[last_match_end..]); + + Ok(Cow::Owned(new_string)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn should_replace_env_variables() { + // Given + unsafe { + std::env::set_var("PATH", "sup3r-s3cr3t"); } + let config = r#"secret = "${PATH}""#.to_string(); + + // When + let result = expand_env_placeholders(&config).unwrap(); + + // Then + assert_eq!(result, r#"secret = "sup3r-s3cr3t""#); } - let out = RE.replace_all(input, |caps: &Captures| { - let is_escaped = caps.get(1).is_some(); - let name = &caps[2]; - if is_escaped { - format!("${{{}}}", name) - } else { - std::env::var(name).unwrap() + #[test] + fn should_replace_env_variables_and_keep_remaining() { + // Given + unsafe { + std::env::set_var("PATH", "sup3r-s3cr3t"); + } + let config = r#"[forwarding] +secret = "${PATH}" +method = "MODERN""# + .to_string(); + + // When + let result = expand_env_placeholders(&config).unwrap(); + + // Then + assert_eq!( + result, + r#"[forwarding] +secret = "sup3r-s3cr3t" +method = "MODERN""# + ); + } + + #[test] + fn should_not_replace_escaped_env_variables() { + // Given + unsafe { + std::env::set_var("PATH", "sup3r-s3cr3t"); } - }); - Ok(out) + let config = r#"secret = "$\{PATH}""#.to_string(); + + // When + let result = expand_env_placeholders(&config).unwrap(); + + // Then + assert_eq!(result, r#"secret = "$\{PATH}""#); + } + + #[test] + fn should_not_replace_partial_env_variables() { + // Given + let config = r#"secret = "${PATH""#.to_string(); + + // When + let result = expand_env_placeholders(&config).unwrap(); + + // Then + assert_eq!(result, r#"secret = "${PATH""#); + } + + #[test] + fn should_return_config_as_is_when_no_placeholder() { + // Given + let config = r#"secret = "another-secret""#.to_string(); + + // When + let result = expand_env_placeholders(&config).unwrap(); + + // Then + assert_eq!(result, r#"secret = "another-secret""#); + } + + #[test] + fn empty_config() { + // Given + let config = String::new(); + + // When + let result = expand_env_placeholders(&config).unwrap(); + + // Then + assert_eq!(result, ""); + } } diff --git a/pico_limbo/src/server/start_server.rs b/pico_limbo/src/server/start_server.rs index 00f6aa3f..745d0efb 100644 --- a/pico_limbo/src/server/start_server.rs +++ b/pico_limbo/src/server/start_server.rs @@ -38,11 +38,8 @@ fn load_configuration(config_path: &PathBuf) -> Option { Err(ConfigError::Io(message, ..)) => { error!("Failed to load configuration: {}", message); } - Err(ConfigError::MissingEnvVar(var)) => { - error!( - "Failed to load configuration: missing environment variable {}", - var - ); + Err(ConfigError::EnvPlaceholder(var)) => { + error!("Failed to load configuration: {}", var); } Err(ConfigError::TomlSerialize(message, ..)) => { error!("Failed to save default configuration file: {}", message);