commit 349fb8bcde2da24d32249824f5a6c7a09758d069
parent e8a8977ada44fe6015d5a50eb8db63e3dfe693f2
Author: Antoine A <>
Date: Thu, 25 Jun 2026 13:19:11 +0200
common: improve config parsing error
Diffstat:
1 file changed, 45 insertions(+), 28 deletions(-)
diff --git a/common/taler-common/src/config.rs b/common/taler-common/src/config.rs
@@ -15,6 +15,7 @@
*/
use std::{
+ borrow::Cow,
fmt::{Debug, Display},
fs::Permissions,
os::unix::fs::PermissionsExt,
@@ -49,7 +50,7 @@ pub mod parser {
use tracing::{trace, warn};
use super::{Config, ValueErr};
- use crate::config::{Inner, Line, Location};
+ use crate::config::{Inner, Line, Location, make_lowercase};
#[derive(Debug, thiserror::Error)]
pub enum ConfigErr {
@@ -169,7 +170,7 @@ pub mod parser {
)
}),
);
- self.sections.insert("PATHS".to_owned(), paths);
+ self.sections.insert("paths".to_owned(), paths);
// Load default configs
let cfg_dir = dir.join("share").join(project_name).join("config.d");
@@ -253,7 +254,7 @@ pub mod parser {
return Err(line_err("Recursion limit in config inlining", src, line));
}
- match name.to_lowercase().as_str() {
+ match make_lowercase(name).as_ref() {
"inline" => self.parse_file(parent.join(arg), depth)?,
"inline-matching" => {
let paths =
@@ -275,7 +276,7 @@ pub mod parser {
)
)?;
- let section_up = section.to_uppercase();
+ let section = section.to_lowercase();
let mut secret_cfg = Parser::empty();
if let Err(e) = secret_cfg.parse_file(parent.join(secret_file), depth) {
@@ -285,14 +286,14 @@ pub mod parser {
return Err(e);
}
} else if let Some(secret_section) =
- secret_cfg.sections.swap_remove(§ion_up)
+ secret_cfg.sections.swap_remove(§ion)
{
self.sections
- .entry(section_up)
+ .entry(section)
.or_default()
.extend(secret_section);
} else {
- warn!(target: "config", "{}", line_err(format!("Configuration file at '{secret_file}' loaded with @inline-secret@ does not contain section '{section_up}'"), src, line));
+ warn!(target: "config", "{}", line_err(format!("Configuration file at '{secret_file}' loaded with @inline-secret@ does not contain section [{section}]"), src, line));
}
}
unknown => {
@@ -306,7 +307,7 @@ pub mod parser {
} else if let Some(section) = l.strip_prefix('[').and_then(|l| l.strip_suffix(']'))
{
current_section =
- Some(self.sections.entry(section.to_uppercase()).or_default());
+ Some(self.sections.entry(section.to_lowercase()).or_default());
} else if let Some((name, value)) = l.split_once('=') {
if let Some(current_section) = &mut current_section {
// Trim whitespace
@@ -517,13 +518,13 @@ pub mod parser {
#[derive(Debug, thiserror::Error)]
pub enum ValueErr {
- #[error("Missing {ty} option '{option}' in section '{section}'")]
+ #[error("Missing {ty} option {option} in section [{section}]")]
Missing {
ty: String,
section: String,
option: String,
},
- #[error("Invalid {ty} option '{option}' in section '{section}': {err}")]
+ #[error("Invalid {ty} option {option} in section [{section}]: {err}")]
Invalid {
ty: String,
section: String,
@@ -573,20 +574,36 @@ impl Debug for Config {
}
}
+fn make_lowercase<'a>(s: &'a str) -> Cow<'a, str> {
+ if s.chars().all(|c| c.is_ascii_lowercase()) {
+ Cow::Borrowed(s)
+ } else {
+ Cow::Owned(s.to_ascii_lowercase())
+ }
+}
+
+fn make_uppercase<'a>(s: &'a str) -> Cow<'a, str> {
+ if s.chars().all(|c| c.is_ascii_uppercase()) {
+ Cow::Borrowed(s)
+ } else {
+ Cow::Owned(s.to_ascii_uppercase())
+ }
+}
+
impl Config {
/// Get a config section from its name
- pub fn section<'cfg, 'arg>(&'cfg self, section: &'arg str) -> Section<'cfg, 'arg> {
+ pub fn section<'cfg, 'arg>(&'cfg self, name: &'arg str) -> Section<'cfg, 'arg> {
Section {
- name: section,
+ name,
config: self,
- values: self.0.sections.get(§ion.to_uppercase()),
+ values: self.0.sections.get(make_lowercase(name).as_ref()),
}
}
/// List all config sections
pub fn sections<'cfg>(&'cfg self) -> impl Iterator<Item = Section<'cfg, 'cfg>> {
- self.0.sections.iter().map(|(section, values)| Section {
- name: section,
+ self.0.sections.iter().map(|(name, values)| Section {
+ name,
config: self,
values: Some(values),
})
@@ -611,8 +628,8 @@ impl Config {
if let Some(path_res) = cfg
.0
.sections
- .get("PATHS")
- .and_then(|section| section.get(name))
+ .get("paths")
+ .and_then(|section| section.get(make_uppercase(name).as_ref()))
{
return Some(cfg.pathsub(&path_res.content, depth + 1));
}
@@ -780,7 +797,7 @@ impl<'cfg, 'arg> Section<'cfg, 'arg> {
) -> Value<'arg, T> {
let value = self
.values
- .and_then(|m| m.get(&option.to_uppercase()))
+ .and_then(|m| m.get(make_uppercase(option).as_ref()))
.filter(|it| !it.content.is_empty())
.map(|raw| transform(&raw.content))
.transpose();
@@ -821,8 +838,8 @@ impl<'cfg, 'arg> Section<'cfg, 'arg> {
buf.push('\'');
ValueErr::Invalid {
ty: ty.to_owned(),
- section: self.name.to_owned(),
- option: option.to_owned(),
+ section: self.name.to_lowercase(),
+ option: option.to_uppercase(),
err: buf,
}
}
@@ -841,8 +858,8 @@ impl<'cfg, 'arg> Section<'cfg, 'arg> {
self.inner(ty, option, |v| {
transform(v).map_err(|e| ValueErr::Invalid {
ty: ty.to_owned(),
- section: self.name.to_owned(),
- option: option.to_owned(),
+ section: self.name.to_lowercase(),
+ option: option.to_uppercase(),
err: e.to_string(),
})
})
@@ -1022,8 +1039,8 @@ impl<T> Value<'_, T> {
pub fn require(self) -> Result<T, ValueErr> {
self.value?.ok_or_else(|| ValueErr::Missing {
ty: self.ty.to_owned(),
- section: self.section.to_owned(),
- option: self.option.to_owned(),
+ section: self.section.to_lowercase(),
+ option: self.option.to_uppercase(),
})
}
}
@@ -1156,13 +1173,13 @@ mod test {
// Missing section
check_err(
- "Missing string option 'value' in section 'unknown'",
+ "Missing string option VALUE in section [unknown]",
cfg.section("unknown").str("value").require(),
);
// Missing value
check_err(
- "Missing string option 'value' in section 'section-a'",
+ "Missing string option VALUE in section [section-a]",
cfg.section("section-a").str("value").require(),
);
}
@@ -1181,7 +1198,7 @@ mod test {
// Check missing msg
let cfg = conf("");
check_err(
- format!("Missing {ty} option 'value' in section 'section'"),
+ format!("Missing {ty} option VALUE in section [section]"),
lambda(&cfg.section("section"), "value").require(),
);
@@ -1203,7 +1220,7 @@ mod test {
let cfg = conf(&format!("[section]\nvalue={raw}"));
check_err(
format!(
- "Invalid {ty} option 'value' in section 'section': {}",
+ "Invalid {ty} option VALUE in section [section]: {}",
error_fmt(raw)
),
lambda(&cfg.section("section"), "value").require(),