taler-rust

GNU Taler code in Rust. Largely core banking integrations.
Log | Files | Refs | Submodules | README | LICENSE

commit 349fb8bcde2da24d32249824f5a6c7a09758d069
parent e8a8977ada44fe6015d5a50eb8db63e3dfe693f2
Author: Antoine A <>
Date:   Thu, 25 Jun 2026 13:19:11 +0200

common: improve config parsing error

Diffstat:
Mcommon/taler-common/src/config.rs | 73+++++++++++++++++++++++++++++++++++++++++++++----------------------------
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(&section_up) + secret_cfg.sections.swap_remove(&section) { 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(&section.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(),