various: add Display impls for domain enums; improve contextual errors

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Ia16e7e34cda6ae3e12590ea1ea9268486a6a6964
This commit is contained in:
raf 2026-03-07 16:55:43 +03:00
commit cd63eeccff
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
6 changed files with 143 additions and 77 deletions

View file

@ -2,10 +2,25 @@ use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
/// Expand environment variables in a string.
/// Supports both ${VAR_NAME} and $VAR_NAME syntax.
/// Expand environment variables in a string using `std::env::var` for lookup.
/// Supports both `${VAR_NAME}` and `$VAR_NAME` syntax.
/// Returns an error if a referenced variable is not set.
fn expand_env_var_string(input: &str) -> crate::error::Result<String> {
expand_env_vars(input, |name| {
std::env::var(name).map_err(|_| {
crate::error::PinakesError::Config(format!(
"environment variable not set: {name}"
))
})
})
}
/// Expand environment variables in a string using the provided lookup function.
/// Supports both `${VAR_NAME}` and `$VAR_NAME` syntax.
fn expand_env_vars(
input: &str,
lookup: impl Fn(&str) -> crate::error::Result<String>,
) -> crate::error::Result<String> {
let mut result = String::new();
let mut chars = input.chars().peekable();
@ -44,16 +59,7 @@ fn expand_env_var_string(input: &str) -> crate::error::Result<String> {
));
}
// Look up the environment variable
match std::env::var(&var_name) {
Ok(value) => result.push_str(&value),
Err(_) => {
return Err(crate::error::PinakesError::Config(format!(
"environment variable not set: {}",
var_name
)));
},
}
result.push_str(&lookup(&var_name)?);
} else if ch == '\\' {
// Handle escaped characters
if let Some(&next_ch) = chars.peek() {
@ -769,6 +775,21 @@ pub enum StorageBackendType {
Postgres,
}
impl StorageBackendType {
pub fn as_str(&self) -> &'static str {
match self {
Self::Sqlite => "sqlite",
Self::Postgres => "postgres",
}
}
}
impl std::fmt::Display for StorageBackendType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SqliteConfig {
pub path: PathBuf,
@ -1200,64 +1221,58 @@ mod tests {
assert!(config.validate().is_ok());
}
// Environment variable expansion tests
// Environment variable expansion tests using expand_env_vars with a
// HashMap lookup. This avoids unsafe std::env::set_var and is
// thread-safe for parallel test execution.
fn test_lookup<'a>(
vars: &'a std::collections::HashMap<&str, &str>,
) -> impl Fn(&str) -> crate::error::Result<String> + 'a {
move |name| {
vars.get(name).map(|v| v.to_string()).ok_or_else(|| {
crate::error::PinakesError::Config(format!(
"environment variable not set: {name}"
))
})
}
}
#[test]
fn test_expand_env_var_simple() {
unsafe {
std::env::set_var("TEST_VAR_SIMPLE", "test_value");
}
let result = expand_env_var_string("$TEST_VAR_SIMPLE");
assert!(result.is_ok());
let vars =
std::collections::HashMap::from([("TEST_VAR_SIMPLE", "test_value")]);
let result = expand_env_vars("$TEST_VAR_SIMPLE", test_lookup(&vars));
assert_eq!(result.unwrap(), "test_value");
unsafe {
std::env::remove_var("TEST_VAR_SIMPLE");
}
}
#[test]
fn test_expand_env_var_braces() {
unsafe {
std::env::set_var("TEST_VAR_BRACES", "test_value");
}
let result = expand_env_var_string("${TEST_VAR_BRACES}");
assert!(result.is_ok());
let vars =
std::collections::HashMap::from([("TEST_VAR_BRACES", "test_value")]);
let result = expand_env_vars("${TEST_VAR_BRACES}", test_lookup(&vars));
assert_eq!(result.unwrap(), "test_value");
unsafe {
std::env::remove_var("TEST_VAR_BRACES");
}
}
#[test]
fn test_expand_env_var_embedded() {
unsafe {
std::env::set_var("TEST_VAR_EMBEDDED", "value");
}
let result = expand_env_var_string("prefix_${TEST_VAR_EMBEDDED}_suffix");
assert!(result.is_ok());
let vars =
std::collections::HashMap::from([("TEST_VAR_EMBEDDED", "value")]);
let result =
expand_env_vars("prefix_${TEST_VAR_EMBEDDED}_suffix", test_lookup(&vars));
assert_eq!(result.unwrap(), "prefix_value_suffix");
unsafe {
std::env::remove_var("TEST_VAR_EMBEDDED");
}
}
#[test]
fn test_expand_env_var_multiple() {
unsafe {
std::env::set_var("VAR1", "value1");
std::env::set_var("VAR2", "value2");
}
let result = expand_env_var_string("${VAR1}_${VAR2}");
assert!(result.is_ok());
let vars =
std::collections::HashMap::from([("VAR1", "value1"), ("VAR2", "value2")]);
let result = expand_env_vars("${VAR1}_${VAR2}", test_lookup(&vars));
assert_eq!(result.unwrap(), "value1_value2");
unsafe {
std::env::remove_var("VAR1");
std::env::remove_var("VAR2");
}
}
#[test]
fn test_expand_env_var_missing() {
let result = expand_env_var_string("${NONEXISTENT_VAR}");
let vars = std::collections::HashMap::new();
let result = expand_env_vars("${NONEXISTENT_VAR}", test_lookup(&vars));
assert!(result.is_err());
assert!(
result
@ -1269,7 +1284,8 @@ mod tests {
#[test]
fn test_expand_env_var_empty_name() {
let result = expand_env_var_string("${}");
let vars = std::collections::HashMap::new();
let result = expand_env_vars("${}", test_lookup(&vars));
assert!(result.is_err());
assert!(
result
@ -1281,43 +1297,33 @@ mod tests {
#[test]
fn test_expand_env_var_escaped() {
let result = expand_env_var_string("\\$NOT_A_VAR");
assert!(result.is_ok());
let vars = std::collections::HashMap::new();
let result = expand_env_vars("\\$NOT_A_VAR", test_lookup(&vars));
assert_eq!(result.unwrap(), "$NOT_A_VAR");
}
#[test]
fn test_expand_env_var_no_vars() {
let result = expand_env_var_string("plain_text");
assert!(result.is_ok());
let vars = std::collections::HashMap::new();
let result = expand_env_vars("plain_text", test_lookup(&vars));
assert_eq!(result.unwrap(), "plain_text");
}
#[test]
fn test_expand_env_var_underscore() {
unsafe {
std::env::set_var("TEST_VAR_NAME", "value");
}
let result = expand_env_var_string("$TEST_VAR_NAME");
assert!(result.is_ok());
let vars = std::collections::HashMap::from([("TEST_VAR_NAME", "value")]);
let result = expand_env_vars("$TEST_VAR_NAME", test_lookup(&vars));
assert_eq!(result.unwrap(), "value");
unsafe {
std::env::remove_var("TEST_VAR_NAME");
}
}
#[test]
fn test_expand_env_var_mixed_syntax() {
unsafe {
std::env::set_var("VAR1_MIXED", "v1");
std::env::set_var("VAR2_MIXED", "v2");
}
let result = expand_env_var_string("$VAR1_MIXED and ${VAR2_MIXED}");
assert!(result.is_ok());
let vars = std::collections::HashMap::from([
("VAR1_MIXED", "v1"),
("VAR2_MIXED", "v2"),
]);
let result =
expand_env_vars("$VAR1_MIXED and ${VAR2_MIXED}", test_lookup(&vars));
assert_eq!(result.unwrap(), "v1 and v2");
unsafe {
std::env::remove_var("VAR1_MIXED");
std::env::remove_var("VAR2_MIXED");
}
}
}