commit 14d99474e131e33f86cbe1ead7ce68bd9f33c07a
parent 8cb5cdb8d45cb095cbb277df52dd6d1daca1120e
Author: Antoine A <>
Date: Thu, 17 Sep 2026 18:09:10 +0200
common: clean DD102 implementation
Diffstat:
10 files changed, 176 insertions(+), 191 deletions(-)
diff --git a/adapters/taler-cyclos/src/config.rs b/adapters/taler-cyclos/src/config.rs
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2025, 2026 Taler Systems SA
+ Copyright (C) 2025-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
@@ -22,7 +22,7 @@ use taler_api::{
config::{ApiCfg, DbCfg},
};
use taler_common::{
- config::{Config, ValueErr},
+ config::{CfgErr, Config},
map_config,
types::amount::Currency,
};
@@ -36,11 +36,11 @@ pub enum AccountType {
Normal,
}
-pub fn parse_db_cfg(cfg: &Config) -> Result<DbCfg, ValueErr> {
+pub fn parse_db_cfg(cfg: &Config) -> Result<DbCfg, CfgErr> {
DbCfg::parse(cfg.section("cyclosdb-postgres"))
}
-pub fn parse_account_payto(cfg: &Config, main: &MainCfg) -> Result<FullCyclosPayto, ValueErr> {
+pub fn parse_account_payto(cfg: &Config, main: &MainCfg) -> Result<FullCyclosPayto, CfgErr> {
let sect = cfg.section("cyclos");
let id = sect.parse("cyclos account id", "ACCOUNT_ID").require()?;
let name = sect.str("NAME").require()?;
@@ -62,7 +62,7 @@ pub struct MainCfg {
}
impl MainCfg {
- pub fn parse(cfg: &Config) -> Result<Self, ValueErr> {
+ pub fn parse(cfg: &Config) -> Result<Self, CfgErr> {
let sect = cfg.section("cyclos");
let url = sect.base_url("CYCLOS_URL").require()?;
let root = format_compact!(
@@ -86,7 +86,7 @@ pub struct HostCfg {
}
impl HostCfg {
- pub fn parse(cfg: &Config, main: &MainCfg) -> Result<Self, ValueErr> {
+ pub fn parse(cfg: &Config, main: &MainCfg) -> Result<Self, CfgErr> {
let sect = cfg.section("cyclos-worker");
Ok(Self {
username: sect.str("USERNAME").require()?,
@@ -108,7 +108,7 @@ pub struct ServeCfg {
}
impl ServeCfg {
- pub fn parse(cfg: &Config) -> Result<Self, ValueErr> {
+ pub fn parse(cfg: &Config) -> Result<Self, CfgErr> {
let main = MainCfg::parse(cfg)?;
let payto = parse_account_payto(cfg, &main)?;
@@ -144,7 +144,7 @@ pub struct SetupCfg {
}
impl SetupCfg {
- pub fn parse(cfg: &Config) -> Result<Self, ValueErr> {
+ pub fn parse(cfg: &Config) -> Result<Self, CfgErr> {
let main = MainCfg::parse(cfg)?;
let main_s = cfg.section("cyclos");
let worker_s = cfg.section("cyclos-worker");
@@ -176,7 +176,7 @@ pub struct WorkerCfg {
}
impl WorkerCfg {
- pub fn parse(cfg: &Config) -> Result<Self, ValueErr> {
+ pub fn parse(cfg: &Config) -> Result<Self, CfgErr> {
let main = MainCfg::parse(cfg)?;
let s = cfg.section("cyclos-worker");
Ok(Self {
@@ -207,7 +207,7 @@ pub struct HarnessCfg {
}
impl HarnessCfg {
- pub fn parse(cfg: &Config) -> Result<Self, ValueErr> {
+ pub fn parse(cfg: &Config) -> Result<Self, CfgErr> {
let worker = WorkerCfg::parse(cfg)?;
let s = cfg.section("cyclos-harness");
diff --git a/adapters/taler-magnet-bank/src/config.rs b/adapters/taler-magnet-bank/src/config.rs
@@ -22,18 +22,18 @@ use taler_api::{
config::{ApiCfg, DbCfg},
};
use taler_common::{
- config::{Config, ValueErr},
+ config::{CfgErr, Config},
map_config,
};
use url::Url;
use crate::{FullHuPayto, HuIban, magnet_api::oauth::Token};
-pub fn parse_db_cfg(cfg: &Config) -> Result<DbCfg, ValueErr> {
+pub fn parse_db_cfg(cfg: &Config) -> Result<DbCfg, CfgErr> {
DbCfg::parse(cfg.section("magnet-bankdb-postgres"))
}
-pub fn parse_account_payto(cfg: &Config) -> Result<FullHuPayto, ValueErr> {
+pub fn parse_account_payto(cfg: &Config) -> Result<FullHuPayto, CfgErr> {
let s = cfg.section("magnet-bank");
let iban: HuIban = s.parse("iban", "IBAN").require()?;
let name = s.str("NAME").require()?;
@@ -51,7 +51,7 @@ pub struct ServeCfg {
}
impl ServeCfg {
- pub fn parse(cfg: &Config) -> Result<Self, ValueErr> {
+ pub fn parse(cfg: &Config) -> Result<Self, CfgErr> {
let payto = parse_account_payto(cfg)?;
let s = cfg.section("magnet-bank-httpd");
@@ -91,7 +91,7 @@ pub struct WorkerCfg {
}
impl WorkerCfg {
- pub fn parse(cfg: &Config) -> Result<Self, ValueErr> {
+ pub fn parse(cfg: &Config) -> Result<Self, CfgErr> {
let payto = parse_account_payto(cfg)?;
let s = cfg.section("magnet-bank-worker");
Ok(Self {
@@ -121,7 +121,7 @@ pub struct HarnessCfg {
}
impl HarnessCfg {
- pub fn parse(cfg: &Config) -> Result<Self, ValueErr> {
+ pub fn parse(cfg: &Config) -> Result<Self, CfgErr> {
let worker = WorkerCfg::parse(cfg)?;
let s = cfg.section("magnet-bank-harness");
diff --git a/adapters/taler-magnet-bank/src/setup.rs b/adapters/taler-magnet-bank/src/setup.rs
@@ -17,7 +17,7 @@
use std::io::ErrorKind;
use aws_lc_rs::{encoding::AsBigEndian, signature::EcdsaKeyPair};
-use taler_common::{json_file, types::base32::Base32};
+use taler_common::{config::CfgErr, config_bail, json_file, types::base32::Base32};
use tracing::{info, warn};
use crate::{
@@ -42,21 +42,19 @@ pub struct Keys {
pub signing_key: EcdsaKeyPair,
}
-pub fn load(cfg: &WorkerCfg) -> anyhow::Result<Keys> {
+pub fn load(cfg: &WorkerCfg) -> Result<Keys, CfgErr> {
// Load JSON file
let file: KeysFile = match json_file::load(&cfg.keys_path) {
Ok(file) => file,
- Err(e) => {
- return Err(anyhow::anyhow!(
- "Could not read magnet keys at '{}': {}",
- cfg.keys_path,
- e.kind()
- ));
- }
+ Err(e) => config_bail!(
+ "could not read magnet keys at '{}': {}",
+ cfg.keys_path,
+ e.kind()
+ ),
};
- fn incomplete_err() -> anyhow::Error {
- anyhow::anyhow!("Missing magnet keys, run 'taler-magnet-bank setup' first")
+ fn incomplete_err() -> CfgErr {
+ CfgErr::custom("missing magnet keys, run 'taler-magnet-bank setup' first")
}
// Check full
@@ -64,7 +62,7 @@ pub fn load(cfg: &WorkerCfg) -> anyhow::Result<Keys> {
let signing_key = file.signing_key.ok_or_else(incomplete_err)?;
// Load signing key
- let signing_key = parse_private_key(&signing_key)?;
+ let signing_key = parse_private_key(&signing_key).map_err(CfgErr::custom)?;
Ok(Keys {
access_token,
diff --git a/adapters/taler-magnet-bank/src/worker.rs b/adapters/taler-magnet-bank/src/worker.rs
@@ -77,12 +77,7 @@ pub async fn run_worker(
transient: bool,
) -> anyhow::Result<()> {
let cfg = WorkerCfg::parse(cfg)?;
- let keys = setup::load(&cfg).map_err(|err| taler_common::config::ValueErr::Invalid {
- ty: "keys file".into(),
- section: "magnet-bank-worker".into(),
- option: "KEYS_FILE".into(),
- err: err.to_string(),
- })?;
+ let keys = setup::load(&cfg)?;
let client = AuthClient::new(client, &cfg.api_url, &cfg.consumer).upgrade(&keys.access_token);
if transient {
diff --git a/adapters/taler-wise/src/config.rs b/adapters/taler-wise/src/config.rs
@@ -16,15 +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, ConfigErr, ValueErr},
- map_config,
+ config::{CfgErr, Config},
+ config_bail, map_config,
types::{
amount::Currency,
payto::{ACH, BankID},
@@ -39,7 +38,7 @@ pub enum AccountType {
Normal,
}
-pub fn parse_db_cfg(cfg: &Config) -> Result<DbCfg, ValueErr> {
+pub fn parse_db_cfg(cfg: &Config) -> Result<DbCfg, CfgErr> {
DbCfg::parse(cfg.section("wisedb-postgres"))
}
@@ -49,7 +48,7 @@ pub struct WiseBalance {
pub payto: WiseAccount,
}
-fn balances(cfg: &Config) -> Result<Vec<WiseBalance>, ValueErr> {
+fn balances(cfg: &Config) -> Result<Vec<WiseBalance>, CfgErr> {
cfg.sections()
.filter_map(|s| {
(|| {
@@ -91,7 +90,7 @@ pub struct SetupCfg {
}
impl SetupCfg {
- pub fn parse(cfg: &Config) -> Result<Self, ValueErr> {
+ pub fn parse(cfg: &Config) -> Result<Self, CfgErr> {
let s = cfg.section("wise-worker");
Ok(Self {
profile_id: s.number("PROFILE_ID").opt()?,
@@ -113,7 +112,7 @@ pub struct ServeCfg {
}
impl ServeCfg {
- pub fn parse(cfg: &Config) -> Result<Self, ValueErr> {
+ pub fn parse(cfg: &Config) -> Result<Self, CfgErr> {
let s = cfg.section("wise");
Ok(Self {
name: s.cstr("NAME").require()?,
@@ -135,7 +134,7 @@ pub struct WorkerCfg {
}
impl WorkerCfg {
- pub fn parse(cfg: &Config) -> Result<Self, ConfigErr> {
+ pub fn parse(cfg: &Config) -> Result<Self, CfgErr> {
let s = cfg.section("wise-worker");
let cfg = Self {
profile_id: s.number("PROFILE_ID").require()?,
@@ -144,9 +143,7 @@ impl WorkerCfg {
balances: balances(cfg)?,
};
if cfg.balances.is_empty() {
- Err(anyhow!(
- "Missing configured balance in a section [wise-balance-*]"
- ))?;
+ config_bail!("Missing configured balance in a section [wise-balance-*]");
}
Ok(cfg)
}
diff --git a/common/taler-api/src/config.rs b/common/taler-api/src/config.rs
@@ -18,7 +18,7 @@ use std::net::{IpAddr, SocketAddr};
use sqlx::postgres::PgConnectOptions;
use taler_common::{
- config::{Section, ValueErr},
+ config::{CfgErr, Section},
encoding::base64,
map_config,
};
@@ -33,7 +33,7 @@ pub struct DbCfg {
}
impl DbCfg {
- pub fn parse(s: Section) -> Result<Self, ValueErr> {
+ pub fn parse(s: Section) -> Result<Self, CfgErr> {
Ok(Self {
cfg: s.postgres("CONFIG").require()?,
sql_dir: s.path("SQL_DIR").require()?,
@@ -48,7 +48,7 @@ pub enum AuthCfg {
}
impl AuthCfg {
- pub fn parse(s: &Section) -> Result<Self, ValueErr> {
+ pub fn parse(s: &Section) -> Result<Self, CfgErr> {
map_config!(s, "auth_method", "AUTH_METHOD",
"none" => { AuthCfg::None },
"basic" => {
@@ -85,7 +85,7 @@ pub struct ApiCfg {
}
impl ApiCfg {
- pub fn parse(s: Section) -> Result<Option<Self>, ValueErr> {
+ pub fn parse(s: Section) -> Result<Option<Self>, CfgErr> {
Ok(if s.boolean("ENABLED").require()? {
Some(Self {
auth: AuthCfg::parse(&s)?,
@@ -97,7 +97,7 @@ impl ApiCfg {
}
impl Serve {
- pub fn parse(s: &Section) -> Result<Self, ValueErr> {
+ pub fn parse(s: &Section) -> Result<Self, CfgErr> {
map_config!(s, "serve", "SERVE",
"tcp" => {
let port = s.number("PORT").require()?;
diff --git a/common/taler-common/src/config.rs b/common/taler-common/src/config.rs
@@ -15,14 +15,8 @@
*/
use std::{
- borrow::Cow,
- fmt::{Debug, Display},
- fs::Permissions,
- os::unix::fs::PermissionsExt,
- path::PathBuf,
- str::FromStr,
- sync::Arc,
- time::Duration,
+ borrow::Cow, fs::Permissions, os::unix::fs::PermissionsExt, path::PathBuf, str::FromStr,
+ sync::Arc, time::Duration,
};
use compact_str::CompactString;
@@ -30,13 +24,10 @@ use indexmap::IndexMap;
use jiff::{SignedDuration, Span};
use url::Url;
-use crate::{
- config::parser::ParserErr,
- types::{
- amount::{Amount, Currency},
- payto::PaytoURI,
- validate_base_url,
- },
+use crate::types::{
+ amount::{Amount, Currency},
+ payto::PaytoURI,
+ validate_base_url,
};
pub mod parser {
@@ -53,70 +44,29 @@ pub mod parser {
use tracing::{trace, warn};
use super::Config;
- use crate::config::{Inner, Line, Location, make_lowercase};
-
- #[derive(Debug)]
-
- pub enum ParserErr {
- IO(Cow<'static, str>, PathBuf, std::io::Error),
- Line(Cow<'static, str>, PathBuf, usize, Option<String>),
- }
-
- impl Display for ParserErr {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- ParserErr::IO(action, path, err) => write!(
- f,
- "Could not {action} at '{}': {}",
- path.to_string_lossy(),
- err.kind()
- ),
- ParserErr::Line(msg, path, line, cause) => {
- if let Some(cause) = cause {
- write!(f, "{msg} at '{}:{line}': {cause}", path.to_string_lossy())
- } else {
- write!(f, "{msg} at '{}:{line}'", path.to_string_lossy())
- }
- }
- }
- }
- }
-
- impl std::error::Error for ParserErr {
- fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
- None
- }
-
- fn description(&self) -> &str {
- "description() is deprecated; use Display"
- }
-
- fn cause(&self) -> Option<&dyn std::error::Error> {
- self.source()
- }
- }
+ use crate::config::{CfgErr, Inner, Line, Location, make_lowercase};
fn io_err(
action: impl Into<Cow<'static, str>>,
path: impl Into<PathBuf>,
err: std::io::Error,
- ) -> ParserErr {
- ParserErr::IO(action.into(), path.into(), err)
+ ) -> CfgErr {
+ CfgErr::IO(action.into(), path.into(), err)
}
fn line_err(
msg: impl Into<Cow<'static, str>>,
path: impl Into<PathBuf>,
line: usize,
- ) -> ParserErr {
- ParserErr::Line(msg.into(), path.into(), line, None)
+ ) -> CfgErr {
+ CfgErr::Line(msg.into(), path.into(), line, None)
}
fn line_cause_err(
msg: impl Into<Cow<'static, str>>,
cause: impl Display,
path: impl Into<PathBuf>,
line: usize,
- ) -> ParserErr {
- ParserErr::Line(msg.into(), path.into(), line, Some(cause.to_string()))
+ ) -> CfgErr {
+ CfgErr::Line(msg.into(), path.into(), line, Some(cause.to_string()))
}
pub struct Parser {
sections: IndexMap<String, IndexMap<String, Line>>,
@@ -135,7 +85,7 @@ pub mod parser {
}
}
- pub fn load_env(&mut self, src: ConfigSource) -> Result<(), ParserErr> {
+ pub fn load_env(&mut self, src: ConfigSource) -> Result<(), CfgErr> {
let ConfigSource { project_name, .. } = src;
// Load default path
@@ -188,7 +138,7 @@ pub mod parser {
Ok(())
}
- pub fn parse_str(&mut self, str: &str) -> Result<(), ParserErr> {
+ pub fn parse_str(&mut self, str: &str) -> Result<(), CfgErr> {
self.parse(
std::io::Cursor::new(str),
PathBuf::from_str("mem").unwrap(),
@@ -196,7 +146,7 @@ pub mod parser {
)
}
- pub fn parse_file(&mut self, src: PathBuf, depth: u8) -> Result<(), ParserErr> {
+ pub fn parse_file(&mut self, src: PathBuf, depth: u8) -> Result<(), CfgErr> {
trace!(target: "config", "load file at '{}'", src.to_string_lossy());
match std::fs::File::open(&src) {
Ok(file) => self.parse(BufReader::new(file), src, depth + 1),
@@ -209,7 +159,7 @@ pub mod parser {
mut reader: B,
src: PathBuf,
depth: u8,
- ) -> Result<(), ParserErr> {
+ ) -> Result<(), CfgErr> {
let file = self.files.len();
self.files.push(src.clone());
let src = &src;
@@ -235,7 +185,7 @@ pub mod parser {
} else if let Some(directive) = l.strip_prefix("@") {
// Parse directive
let Some((name, arg)) = directive.split_once('@') else {
- return Err(line_err(format!("Invalid directive line '{l}'"), src, line));
+ return Err(line_err(format!("invalid directive line '{l}'"), src, line));
};
let arg = arg.trim_ascii_start();
// Exit current section
@@ -246,7 +196,7 @@ pub mod parser {
};
// Check recursion depth
if depth > 128 {
- return Err(line_err("Recursion limit in config inlining", src, line));
+ return Err(line_err("recursion limit in config inlining", src, line));
}
match make_lowercase(name).as_ref() {
@@ -254,7 +204,7 @@ pub mod parser {
"inline-matching" => {
let paths =
glob::glob(&parent.join(arg).to_string_lossy()).map_err(|e| {
- line_cause_err("Malformed glob regex", e, src, line)
+ line_cause_err("malformed glob regex", e, src, line)
})?;
for path in paths {
let path =
@@ -265,7 +215,7 @@ pub mod parser {
"inline-secret" => {
let (section, secret_file) = arg.split_once(" ").ok_or_else(||
line_err(
- "Invalid configuration, @inline-secret@ directive requires exactly two arguments",
+ "invalid configuration, @inline-secret@ directive requires exactly two arguments",
src,
line
)
@@ -275,7 +225,7 @@ pub mod parser {
let mut secret_cfg = Parser::empty();
if let Err(e) = secret_cfg.parse_file(parent.join(secret_file), depth) {
- if let ParserErr::IO(_, path, err) = e {
+ if let CfgErr::IO(_, path, err) = e {
warn!(target: "config", "{}", io_err(format!("read secret section [{section}]"), &path, err))
} else {
return Err(e);
@@ -288,12 +238,12 @@ pub mod parser {
.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}]"), 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 => {
return Err(line_err(
- format!("Invalid directive '{unknown}'"),
+ format!("invalid directive '{unknown}'"),
src,
line,
));
@@ -323,11 +273,11 @@ pub mod parser {
},
);
} else {
- return Err(line_err("Expected section header or directive", src, line));
+ return Err(line_err("expected section header or directive", src, line));
}
} else {
return Err(line_err(
- "Expected section header, option assignment or directive",
+ "expected section header, option assignment or directive",
src,
line,
));
@@ -449,10 +399,7 @@ pub mod parser {
impl Config {
/// Load a config for a Taler component, optionally also load from a file.
/// This is the standard way to load a Taler component config
- pub fn load(
- src: ConfigSource,
- path: Option<impl Into<PathBuf>>,
- ) -> Result<Config, ParserErr> {
+ pub fn load(src: ConfigSource, path: Option<impl Into<PathBuf>>) -> Result<Config, CfgErr> {
let mut parser = Parser::empty();
parser.load_env(src)?;
match path {
@@ -470,14 +417,14 @@ pub mod parser {
}
/// Load config from an in memory string for testing
- pub fn from_mem(str: &str) -> Result<Config, ParserErr> {
+ pub fn from_mem(str: &str) -> Result<Config, CfgErr> {
let mut parser = Parser::empty();
parser.parse_str(str)?;
Ok(parser.finish())
}
/// Load config from an in memory string with env from a Taler component for testing
- pub fn from_mem_with_env(src: ConfigSource, str: &str) -> Result<Config, ParserErr> {
+ pub fn from_mem_with_env(src: ConfigSource, str: &str) -> Result<Config, CfgErr> {
let mut parser = Parser::empty();
parser.load_env(src)?;
parser.parse_str(str)?;
@@ -489,7 +436,7 @@ pub mod parser {
src: ConfigSource,
path: Option<impl Into<PathBuf>>,
str: &str,
- ) -> Result<Config, ParserErr> {
+ ) -> Result<Config, CfgErr> {
let mut parser = Parser::empty();
parser.load_env(src)?;
match path {
@@ -511,31 +458,79 @@ 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),
+#[macro_export]
+macro_rules! config_bail {
+ ($msg:literal $(,)?) => {
+ return Err($crate::config::CfgErr::Custom(format!($msg)))
+ };
+ ($err:expr $(,)?) => {
+ return Err($crate::config::CfgErr::Custom(format!($err)))
+ };
+ ($fmt:expr, $($arg:tt)*) => {
+ return Err($crate::config::CfgErr::Custom(format!($fmt, $($arg)*)))
+ };
}
-#[derive(Debug, thiserror::Error)]
-pub enum ValueErr {
- #[error("Missing {ty} option {option} in section [{section}]")]
+/// DD102: will fail with exit code 6 and will not be restarted by systemd
+#[derive(Debug)]
+pub enum CfgErr {
+ IO(Cow<'static, str>, PathBuf, std::io::Error),
+ Line(Cow<'static, str>, PathBuf, usize, Option<String>),
Missing {
ty: String,
section: String,
option: String,
},
- #[error("Invalid {ty} option {option} in section [{section}]: {err}")]
Invalid {
ty: String,
section: String,
option: String,
err: String,
},
+ Custom(String),
+}
+
+impl std::error::Error for CfgErr {}
+
+impl std::fmt::Display for CfgErr {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ CfgErr::IO(action, path, err) => write!(
+ f,
+ "could not {action} at '{}': {}",
+ path.to_string_lossy(),
+ err.kind()
+ ),
+ CfgErr::Line(msg, path, line, cause) => {
+ if let Some(cause) = cause {
+ write!(f, "{msg} at '{}:{line}': {cause}", path.to_string_lossy())
+ } else {
+ write!(f, "{msg} at '{}:{line}'", path.to_string_lossy())
+ }
+ }
+ CfgErr::Missing {
+ ty,
+ section,
+ option,
+ } => write!(f, "missing {ty} option {option} in section [{section}]"),
+ CfgErr::Invalid {
+ ty,
+ section,
+ option,
+ err,
+ } => write!(
+ f,
+ "invalid {ty} option {option} in section [{section}]: {err}"
+ ),
+ CfgErr::Custom(e) => e.fmt(f),
+ }
+ }
+}
+
+impl CfgErr {
+ pub fn custom(cause: impl std::fmt::Display) -> Self {
+ Self::Custom(cause.to_string())
+ }
}
#[derive(Debug, thiserror::Error)]
@@ -572,7 +567,7 @@ struct Inner {
#[derive(Clone)]
pub struct Config(Arc<Inner>);
-impl Debug for Config {
+impl std::fmt::Debug for Config {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
@@ -788,7 +783,7 @@ pub use map_config;
#[doc(hidden)]
pub enum MapErr {
Invalid(&'static [&'static str]),
- Err(ValueErr),
+ Err(CfgErr),
}
impl<'cfg, 'arg> Section<'cfg, 'arg> {
@@ -797,7 +792,7 @@ impl<'cfg, 'arg> Section<'cfg, 'arg> {
&self,
ty: &'arg str,
option: &'arg str,
- transform: impl FnOnce(&'cfg str) -> Result<T, ValueErr>,
+ transform: impl FnOnce(&'cfg str) -> Result<T, CfgErr>,
) -> Value<'arg, T> {
let value = self
.values
@@ -840,7 +835,7 @@ impl<'cfg, 'arg> Section<'cfg, 'arg> {
buf.push_str("' got '");
buf.push_str(v);
buf.push('\'');
- ValueErr::Invalid {
+ CfgErr::Invalid {
ty: ty.to_owned(),
section: self.name.to_lowercase(),
option: option.to_uppercase(),
@@ -853,14 +848,14 @@ impl<'cfg, 'arg> Section<'cfg, 'arg> {
}
/** Setup an accessor/converted for a [type] at [option] using [transform] */
- pub fn value<T, E: Display>(
+ pub fn value<T, E: std::fmt::Display>(
&self,
ty: &'arg str,
option: &'arg str,
transform: impl FnOnce(&'cfg str) -> Result<T, E>,
) -> Value<'arg, T> {
self.inner(ty, option, |v| {
- transform(v).map_err(|e| ValueErr::Invalid {
+ transform(v).map_err(|e| CfgErr::Invalid {
ty: ty.to_owned(),
section: self.name.to_lowercase(),
option: option.to_uppercase(),
@@ -1023,25 +1018,25 @@ impl<'cfg, 'arg> Section<'cfg, 'arg> {
}
pub struct Value<'arg, T> {
- value: Result<Option<T>, ValueErr>,
+ value: Result<Option<T>, CfgErr>,
option: &'arg str,
ty: &'arg str,
section: &'arg str,
}
impl<T> Value<'_, T> {
- pub fn opt(self) -> Result<Option<T>, ValueErr> {
+ pub fn opt(self) -> Result<Option<T>, CfgErr> {
self.value
}
/** Converted value of default if missing */
- pub fn default(self, default: T) -> Result<T, ValueErr> {
+ pub fn default(self, default: T) -> Result<T, CfgErr> {
Ok(self.value?.unwrap_or(default))
}
/** Converted value or throw if missing */
- pub fn require(self) -> Result<T, ValueErr> {
- self.value?.ok_or_else(|| ValueErr::Missing {
+ pub fn require(self) -> Result<T, CfgErr> {
+ self.value?.ok_or_else(|| CfgErr::Missing {
ty: self.ty.to_owned(),
section: self.section.to_lowercase(),
option: self.option.to_uppercase(),
@@ -1104,7 +1099,7 @@ mod test {
let check_ok = || Config::load(SOURCE, Some(&config_path)).unwrap();
check_err(format!(
- "Could not read config at '{config_path_fmt}': entity not found"
+ "could not read config at '{config_path_fmt}': entity not found"
));
let config_file = std::fs::File::create_new(&config_path).unwrap();
@@ -1116,7 +1111,7 @@ mod test {
return;
}
check_err(format!(
- "Could not read config at '{config_path_fmt}': permission denied"
+ "could not read config at '{config_path_fmt}': permission denied"
));
config_file
@@ -1125,7 +1120,7 @@ mod test {
check_ok();
std::fs::write(&config_path, "@inline@ test-second-conf.conf").unwrap();
check_err(format!(
- "Could not read config at '{second_path_fmt}': entity not found"
+ "could not read config at '{second_path_fmt}': entity not found"
));
let second_file = std::fs::File::create_new(&second_path).unwrap();
@@ -1133,7 +1128,7 @@ mod test {
.set_permissions(Permissions::from_mode(0o222))
.unwrap();
check_err(format!(
- "Could not read config at '{second_path_fmt}': permission denied"
+ "could not read config at '{second_path_fmt}': permission denied"
));
std::fs::write(&config_path, "@inline-matching@[*").unwrap();
@@ -1141,7 +1136,7 @@ mod test {
// (<tempdir>/[*), so the position moves with the length of $TMPDIR.
check_err_loose(
format!(
- "Malformed glob regex at '{config_path_fmt}:1': Pattern syntax error near position "
+ "malformed glob regex at '{config_path_fmt}:1': Pattern syntax error near position "
),
": invalid range pattern",
Config::load(SOURCE, Some(&config_path)),
@@ -1149,16 +1144,16 @@ mod test {
std::fs::write(&config_path, "@inline-matching@*second-conf.conf").unwrap();
check_err(format!(
- "Could not read config at '{second_path_fmt}': permission denied"
+ "could not read config at '{second_path_fmt}': permission denied"
));
std::fs::write(&config_path, "\n@inline-matching@*.conf").unwrap();
check_err(format!(
- "Recursion limit in config inlining at '{config_path_fmt}:2'"
+ "recursion limit in config inlining at '{config_path_fmt}:2'"
));
std::fs::write(&config_path, "\n\n@inline-matching@ *.conf").unwrap();
check_err(format!(
- "Recursion limit in config inlining at '{config_path_fmt}:3'"
+ "recursion limit in config inlining at '{config_path_fmt}:3'"
));
std::fs::write(&config_path, "@inline-secret@ secret test-second-conf.conf").unwrap();
@@ -1170,15 +1165,15 @@ mod test {
let check = |err: &str, content: &str| check_err(err, Config::from_mem(content));
check(
- "Expected section header, option assignment or directive at 'mem:1'",
+ "expected section header, option assignment or directive at 'mem:1'",
"syntax error",
);
check(
- "Expected section header or directive at 'mem:1'",
+ "expected section header or directive at 'mem:1'",
"key=value",
);
check(
- "Expected section header, option assignment or directive at 'mem:2'",
+ "expected section header, option assignment or directive at 'mem:2'",
"[section]\nbad-line",
);
@@ -1200,13 +1195,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(),
);
}
@@ -1228,7 +1223,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(),
);
@@ -1250,7 +1245,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(),
@@ -1406,12 +1401,12 @@ mod test {
let cfg = Config::from_mem("[section]\nvalue=tcp").unwrap();
check_err(
- "Missing number option PORT in section [section]",
+ "missing number option PORT in section [section]",
parse(&cfg.section("section"), "value").require(),
);
check_err(
- "Invalid mode option VALUE in section [section]: expected 'unix' got 'tcp'",
+ "invalid mode option VALUE in section [section]: expected 'unix' got 'tcp'",
map_config!(&cfg.section("section"), "mode", "value",
"unix" => { Mode::Unix },
)
diff --git a/common/taler-common/src/lib.rs b/common/taler-common/src/lib.rs
@@ -54,6 +54,11 @@ pub struct CommonArgs {
verbose: bool,
}
+/// DD102: will fail with exit code 9 and will not be restarted by systemd
+#[derive(Debug, thiserror::Error)]
+#[error("{0}")]
+pub struct PermanentErr(String);
+
pub fn taler_main(
src: ConfigSource,
args: CommonArgs,
@@ -79,18 +84,13 @@ pub fn taler_main(
if let Err(err) = result {
error!(target: "cli", "{}", err);
// 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
- },
- )
+ std::process::exit(if err.chain().any(|cause| cause.is::<config::CfgErr>()) {
+ 6
+ } else if err.chain().any(|cause| cause.is::<PermanentErr>()) {
+ 9
+ } else {
+ 1
+ })
}
}
diff --git a/taler-apns-relay/src/apns.rs b/taler-apns-relay/src/apns.rs
@@ -192,7 +192,7 @@ impl Client {
.install_default()
.expect("failed to install the default TLS provider");
- let invalid_key = |message: String| taler_common::config::ValueErr::Invalid {
+ let invalid_key = |message: String| taler_common::config::CfgErr::Invalid {
ty: "PKCS#8 private key file".into(),
section: "apns-relay-worker".into(),
option: "KEY_FILE".into(),
diff --git a/taler-apns-relay/src/config.rs b/taler-apns-relay/src/config.rs
@@ -18,9 +18,9 @@ use std::time::Duration;
use compact_str::CompactString;
use taler_api::{Serve, config::DbCfg};
-use taler_common::config::{Config, ValueErr};
+use taler_common::config::{CfgErr, Config};
-pub fn parse_db_cfg(cfg: &Config) -> Result<DbCfg, ValueErr> {
+pub fn parse_db_cfg(cfg: &Config) -> Result<DbCfg, CfgErr> {
DbCfg::parse(cfg.section("apns-relaydb-postgres"))
}
@@ -30,7 +30,7 @@ pub struct ServeCfg {
}
impl ServeCfg {
- pub fn parse(cfg: &Config) -> Result<Self, ValueErr> {
+ pub fn parse(cfg: &Config) -> Result<Self, CfgErr> {
let s = cfg.section("apns-relay-httpd");
let serve = Serve::parse(&s)?;
@@ -47,7 +47,7 @@ pub struct ApnsConfig {
}
impl ApnsConfig {
- pub fn parse(cfg: &Config) -> Result<Self, ValueErr> {
+ pub fn parse(cfg: &Config) -> Result<Self, CfgErr> {
let sect = cfg.section("apns-relay-worker");
Ok(Self {
key_path: sect.path("key_file").require()?,
@@ -65,7 +65,7 @@ pub struct WorkerCfg {
}
impl WorkerCfg {
- pub fn parse(cfg: &Config) -> Result<Self, ValueErr> {
+ pub fn parse(cfg: &Config) -> Result<Self, CfgErr> {
let sect = cfg.section("apns-relay-worker");
Ok(Self {
apns: ApnsConfig::parse(cfg)?,