taler-rust

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

commit 8cb5cdb8d45cb095cbb277df52dd6d1daca1120e
parent b2cc6de2b7551860d1c864df316c408df3708a14
Author: Antoine A <>
Date:   Thu, 17 Sep 2026 17:21:17 +0200

common: add config err wrapper

Diffstat:
Madapters/taler-wise/src/config.rs | 31+++++++------------------------
Mcommon/taler-common/src/config.rs | 32++++++++++++++++++--------------
Mcommon/taler-common/src/lib.rs | 61++++++++++++++-----------------------------------------------
3 files changed, 39 insertions(+), 85 deletions(-)

diff --git a/adapters/taler-wise/src/config.rs b/adapters/taler-wise/src/config.rs @@ -16,13 +16,14 @@ use std::time::Duration; +use anyhow::anyhow; use compact_str::CompactString; use taler_api::{ Serve, config::{ApiCfg, DbCfg}, }; use taler_common::{ - config::{Config, ValueErr}, + config::{Config, ConfigErr, ValueErr}, map_config, types::{ amount::Currency, @@ -52,7 +53,7 @@ fn balances(cfg: &Config) -> Result<Vec<WiseBalance>, ValueErr> { cfg.sections() .filter_map(|s| { (|| { - Ok(if s.name.starts_with("wise-balance") { + Ok(if s.name.starts_with("wise-balance-") { Some(WiseBalance { id: s.number("id").require()?, currency: s.currency("currency").require()?, @@ -134,7 +135,7 @@ pub struct WorkerCfg { } impl WorkerCfg { - pub fn parse(cfg: &Config) -> Result<Self, ValueErr> { + pub fn parse(cfg: &Config) -> Result<Self, ConfigErr> { let s = cfg.section("wise-worker"); let cfg = Self { profile_id: s.number("PROFILE_ID").require()?, @@ -143,28 +144,10 @@ impl WorkerCfg { balances: balances(cfg)?, }; if cfg.balances.is_empty() { - return Err(ValueErr::Missing { - ty: "balance ID".into(), - section: "wise-balance-*".into(), - option: "ID".into(), - }); + Err(anyhow!( + "Missing configured balance in a section [wise-balance-*]" + ))?; } Ok(cfg) } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn worker_requires_a_balance() { - let cfg = - Config::from_mem("[wise-worker]\nPROFILE_ID=1\nTOKEN=test\nFREQUENCY=1 min\n").unwrap(); - assert!(matches!( - WorkerCfg::parse(&cfg), - Err(ValueErr::Missing { section, option, .. }) - if section == "wise-balance-*" && option == "ID" - )); - } -} diff --git a/common/taler-common/src/config.rs b/common/taler-common/src/config.rs @@ -30,10 +30,13 @@ use indexmap::IndexMap; use jiff::{SignedDuration, Span}; use url::Url; -use crate::types::{ - amount::{Amount, Currency}, - payto::PaytoURI, - validate_base_url, +use crate::{ + config::parser::ParserErr, + types::{ + amount::{Amount, Currency}, + payto::PaytoURI, + validate_base_url, + }, }; pub mod parser { @@ -49,17 +52,9 @@ pub mod parser { use indexmap::IndexMap; use tracing::{trace, warn}; - use super::{Config, ValueErr}; + use super::Config; use crate::config::{Inner, Line, Location, make_lowercase}; - #[derive(Debug, thiserror::Error)] - pub enum ConfigErr { - #[error("config error, {0}")] - Parser(#[from] ParserErr), - #[error("invalid config, {0}")] - Value(#[from] ValueErr), - } - #[derive(Debug)] pub enum ParserErr { @@ -517,6 +512,16 @@ pub mod parser { } #[derive(Debug, thiserror::Error)] +pub enum ConfigErr { + #[error(transparent)] + Parser(#[from] ParserErr), + #[error(transparent)] + Value(#[from] ValueErr), + #[error(transparent)] + Custom(#[from] anyhow::Error), +} + +#[derive(Debug, thiserror::Error)] pub enum ValueErr { #[error("Missing {ty} option {option} in section [{section}]")] Missing { @@ -534,7 +539,6 @@ pub enum ValueErr { } #[derive(Debug, thiserror::Error)] - pub enum PathsubErr { #[error("recursion limit in path substitution exceeded for '{0}'")] Recursion(String), diff --git a/common/taler-common/src/lib.rs b/common/taler-common/src/lib.rs @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2024, 2025, 2026 Taler Systems SA + Copyright (C) 2024-2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -78,52 +78,19 @@ pub fn taler_main( let result = runtime.block_on(app(&cfg)); if let Err(err) = result { error!(target: "cli", "{}", err); - std::process::exit(error_exit_status(&err)); - } -} - -/// DD102: only diagnosed configuration errors suppress service recovery. -fn error_exit_status(err: &anyhow::Error) -> i32 { - if err.chain().any(|cause| { - cause.is::<config::parser::ParserErr>() - || cause.is::<config::ValueErr>() - || cause.is::<config::PathsubErr>() - }) { - 6 - } else { - 1 - } -} - -#[cfg(test)] -mod exit_status_tests { - use super::*; - - #[test] - fn configuration_errors_keep_their_status_through_context() { - let cfg = Config::from_mem("[test]\nport = invalid\n").unwrap(); - let err = cfg - .section("test") - .number::<u16>("port") - .require() - .unwrap_err(); - assert_eq!( - 6, - error_exit_status(&anyhow::Error::new(err).context("starting server")) - ); - let err = cfg.section("test").str("missing").require().unwrap_err(); - assert_eq!(6, error_exit_status(&anyhow::Error::new(err))); - let err = Config::from_mem("not a configuration entry").unwrap_err(); - assert_eq!(6, error_exit_status(&anyhow::Error::new(err))); - } - - #[test] - fn unavailable_dependencies_remain_restartable() { - let err = std::io::Error::from(std::io::ErrorKind::ConnectionRefused); - assert_eq!( - 1, - error_exit_status(&anyhow::Error::new(err).context("database")) - ); + // DD102: only diagnosed configuration errors suppress service recovery + std::process::exit( + if err.chain().any(|cause| { + cause.is::<config::parser::ParserErr>() + || cause.is::<config::ValueErr>() + || cause.is::<config::PathsubErr>() + || cause.is::<config::ConfigErr>() + }) { + 6 + } else { + 1 + }, + ) } }