taler-rust

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

commit 4e0704b3732a7a139f9ec7eef8a5adfdfa391aaf
parent 25696769c34802040aa0af5a1022adfe0fd93f54
Author: Antoine A <>
Date:   Thu, 11 Jun 2026 14:57:46 +0200

common: clean code and improve logic

Diffstat:
Mcommon/http-client/src/builder.rs | 96++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mcommon/taler-api/src/config.rs | 65+++++++++++++++++++++++++++++++++++------------------------------
Mcommon/taler-common/src/cli.rs | 6+++---
Mcommon/taler-common/src/db.rs | 14+++++++++++++-
Mcommon/taler-common/src/log.rs | 1-
Mtaler-apns-relay/src/config.rs | 4++--
Mtaler-cyclos/src/config.rs | 20++++++++++----------
Mtaler-cyclos/src/cyclos_api/api.rs | 1+
Mtaler-magnet-bank/src/config.rs | 34+++++++++++++++++-----------------
9 files changed, 139 insertions(+), 102 deletions(-)

diff --git a/common/http-client/src/builder.rs b/common/http-client/src/builder.rs @@ -32,15 +32,12 @@ use url::Url; use crate::{Client, ClientErr, Ctx, headers::HeaderParser, sse::SseClient}; -struct Builder { - headers: HeaderMap, - body: Full<Bytes>, -} - pub struct Req { client: Client, url: Url, - builder: Result<Builder, ClientErr>, + body: Bytes, + headers: HeaderMap, + err: Option<ClientErr>, ctx: Ctx, } @@ -56,10 +53,9 @@ impl Req { Self { client: client.clone(), url, - builder: Ok(Builder { - headers: HeaderMap::new(), - body: Full::default(), - }), + headers: HeaderMap::new(), + body: Bytes::default(), + err: None, ctx: Ctx { path, method, @@ -76,6 +72,14 @@ impl Req { &self.url } + pub fn headers(&self) -> &HeaderMap { + &self.headers + } + + pub fn body(&self) -> &Bytes { + &self.body + } + pub fn header<K, V>(mut self, key: K, value: V) -> Self where K: TryInto<HeaderName>, @@ -83,12 +87,17 @@ impl Req { V: TryInto<HeaderValue>, <V as TryInto<HeaderValue>>::Error: Into<http::Error>, { - self.builder = self.builder.and_then(move |mut builder| { + let res = (|| { let name = key.try_into().map_err(Into::into)?; let value = value.try_into().map_err(Into::into)?; - builder.headers.insert(name, value); - Ok(builder) - }); + self.headers.insert(name, value); + Ok(()) + })(); + if let Err(e) = res + && self.err.is_none() + { + self.err = Some(e); + } self } @@ -99,27 +108,30 @@ impl Req { V: TryInto<HeaderValue>, <V as TryInto<HeaderValue>>::Error: Into<http::Error>, { - self.builder = self.builder.and_then(move |mut builder| { + let res = (|| { let name = key.try_into().map_err(Into::into)?; let mut value = value.try_into().map_err(Into::into)?; value.set_sensitive(true); - builder.headers.insert(name, value); - Ok(builder) - }); + self.headers.insert(name, value); + Ok(()) + })(); + if let Err(e) = res + && self.err.is_none() + { + self.err = Some(e); + } self } pub fn query<T: Serialize>(mut self, name: &str, value: T) -> Self { - if self.builder.is_ok() { - let mut pairs = self.url.query_pairs_mut(); - let serializer = serde_urlencoded::Serializer::new(&mut pairs); - if let Err(e) = [(name, value)].serialize(serializer) { - drop(pairs); - self.builder = Err(e.into()); - return self; - } + let mut pairs = self.url.query_pairs_mut(); + let serializer = serde_urlencoded::Serializer::new(&mut pairs); + if let Err(e) = [(name, value)].serialize(serializer) + && self.err.is_none() + { + self.err = Some(e.into()); } - + drop(pairs); self } @@ -128,17 +140,17 @@ impl Req { let serializer: &mut serde_json::Serializer<&mut Vec<u8>> = &mut serde_json::Serializer::new(&mut buf); if let Err(e) = serde_path_to_error::serialize(json, serializer).map_err(ClientErr::ReqJson) + && self.err.is_none() { - self.builder = Err(e); + self.err = Some(e); return self; }; - if let Ok(builder) = &mut self.builder { - builder.headers.insert( - header::CONTENT_TYPE, - HeaderValue::from_static("application/json"), - ); - builder.body = Full::new(buf.into()) - } + self.content(buf.into(), HeaderValue::from_static("application/json")) + } + + pub fn content(mut self, body: Bytes, ty: HeaderValue) -> Self { + self.headers.insert(header::CONTENT_TYPE, ty); + self.body = body; self } @@ -179,18 +191,22 @@ impl Req { let Self { client, ctx, - builder, url, + err, + headers, + body, } = self; + if let Some(e) = err { + return Err((ctx, e)); + } let req = match async { - let Builder { headers, body } = builder?; let mut builder = http::request::Request::builder() .uri(url.as_str()) .method(ctx.method.clone()); if let Some(headers_mut) = builder.headers_mut() { *headers_mut = headers; } - let req = builder.body(body)?; + let req = builder.body(Full::new(body))?; Ok(req) } .await @@ -218,6 +234,10 @@ impl Res { self.head.status } + pub fn headers(&self) -> &HeaderMap { + &self.head.headers + } + pub fn str_header(&self, name: &'static str) -> Result<String, ClientErr> { self.head .headers diff --git a/common/taler-api/src/config.rs b/common/taler-api/src/config.rs @@ -26,16 +26,17 @@ use taler_common::{ use crate::{Serve, auth::AuthMethod}; /// Basic database config +#[derive(Debug)] pub struct DbCfg { pub cfg: PgConnectOptions, pub sql_dir: String, } impl DbCfg { - pub fn parse(sect: Section) -> Result<Self, ValueErr> { + pub fn parse(s: Section) -> Result<Self, ValueErr> { Ok(Self { - cfg: sect.postgres("CONFIG").require()?, - sql_dir: sect.path("SQL_DIR").require()?, + cfg: s.postgres("CONFIG").require()?, + sql_dir: s.path("SQL_DIR").require()?, }) } } @@ -47,6 +48,26 @@ pub enum AuthCfg { } impl AuthCfg { + pub fn parse(s: &Section) -> Result<Self, ValueErr> { + map_config!(s, "auth_method", "AUTH_METHOD", + "none" => { AuthCfg::None }, + "basic" => { + AuthCfg::Basic { + username: s.str("USERNAME").require()?, + password: s.str("PASSWORD").require()? + } + }, + "bearer" => { + if let Some(token) = s.str("AUTH_TOKEN").opt()? { + AuthCfg::Bearer(token) + } else { + AuthCfg::Bearer(s.str("TOKEN").require()?) + } + } + ) + .require() + } + pub fn method(&self) -> AuthMethod { match self { AuthCfg::None => AuthMethod::None, @@ -64,27 +85,11 @@ pub struct ApiCfg { } impl ApiCfg { - pub fn parse(sect: Section) -> Result<Option<Self>, ValueErr> { - Ok(if sect.boolean("ENABLED").require()? { - let auth = map_config!(sect, "auth_method", "AUTH_METHOD", - "none" => { AuthCfg::None }, - "basic" => { - AuthCfg::Basic { - username: sect.str("USERNAME").require()?, - password: sect.str("PASSWORD").require()? - } - }, - "bearer" => { - let token = sect.str("AUTH_TOKEN").opt()?; - if let Some(token) = token { - AuthCfg::Bearer(token) - } else { - AuthCfg::Bearer(sect.str("TOKEN").require()?) - } - } - ) - .require()?; - Some(Self { auth }) + pub fn parse(s: Section) -> Result<Option<Self>, ValueErr> { + Ok(if s.boolean("ENABLED").require()? { + Some(Self { + auth: AuthCfg::parse(&s)?, + }) } else { None }) @@ -92,16 +97,16 @@ impl ApiCfg { } impl Serve { - pub fn parse(sect: Section) -> Result<Self, ValueErr> { - map_config!(sect, "serve", "SERVE", + pub fn parse(s: &Section) -> Result<Self, ValueErr> { + map_config!(s, "serve", "SERVE", "tcp" => { - let port = sect.number("PORT").require()?; - let ip: IpAddr = sect.parse("IP addr", "BIND_TO").require()?; + let port = s.number("PORT").require()?; + let ip: IpAddr = s.parse("IP addr", "BIND_TO").require()?; Serve::Tcp(SocketAddr::new(ip, port)) }, "unix" => { - let path = sect.path("UNIXPATH").require()?; - let permission = sect.unix_mode("UNIXPATH_MODE").require()?; + let path = s.path("UNIXPATH").require()?; + let permission = s.unix_mode("UNIXPATH_MODE").require()?; Serve::Unix { path, permission } }, "systemd" => { Serve::Systemd } diff --git a/common/taler-common/src/cli.rs b/common/taler-common/src/cli.rs @@ -50,11 +50,11 @@ impl ConfigCmd { option, filename, } => { - let sect = cfg.section(&section); + let s = cfg.section(&section); let value = if filename { - sect.path(&option).require()? + s.path(&option).require()? } else { - sect.str(&option).require()? + s.str(&option).require()? }; writeln!(&mut out, "{value}")?; } diff --git a/common/taler-common/src/db.rs b/common/taler-common/src/db.rs @@ -128,7 +128,19 @@ pub async fn dbinit( } } - exec_sql_file(&mut *tx, &format!("{prefix}-procedures.sql"), "procedures").await?; + if let Err(e) = exec_sql_file(&mut *tx, &format!("{prefix}-procedures.sql"), "procedures").await + { + if let MigrationErr::Io(_, e) = &e + && e.kind() == ErrorKind::NotFound + { + debug!( + target: "dbinit", + "no procedures.sql for the SQL collection: '{prefix}'" + ); + } else { + return Err(e); + } + } tx.commit().await?; diff --git a/common/taler-common/src/log.rs b/common/taler-common/src/log.rs @@ -128,7 +128,6 @@ pub fn taler_logger(max_level: Option<Level>, verbose: bool) -> impl SubscriberI *metadata.level() <= max_level && (verbose || !(target.starts_with("sqlx") - || target.starts_with("log") || target.starts_with("axum") || target.contains("hyper_util") || target.starts_with("h2") diff --git a/taler-apns-relay/src/config.rs b/taler-apns-relay/src/config.rs @@ -31,9 +31,9 @@ pub struct ServeCfg { impl ServeCfg { pub fn parse(cfg: &Config) -> Result<Self, ValueErr> { - let sect = cfg.section("apns-relay-httpd"); + let s = cfg.section("apns-relay-httpd"); - let serve = Serve::parse(sect)?; + let serve = Serve::parse(&s)?; Ok(Self { serve }) } diff --git a/taler-cyclos/src/config.rs b/taler-cyclos/src/config.rs @@ -111,9 +111,9 @@ impl ServeCfg { let main = MainCfg::parse(cfg)?; let payto = parse_account_payto(cfg, &main)?; - let sect = cfg.section("cyclos-httpd"); + let s = cfg.section("cyclos-httpd"); - let serve = Serve::parse(sect)?; + let serve = Serve::parse(&s)?; let wire_gateway = ApiCfg::parse(cfg.section("cyclos-httpd-wire-gateway-api"))?; let revenue = ApiCfg::parse(cfg.section("cyclos-httpd-revenue-api"))?; @@ -175,18 +175,18 @@ pub struct WorkerCfg { impl WorkerCfg { pub fn parse(cfg: &Config) -> Result<Self, ValueErr> { let main = MainCfg::parse(cfg)?; - let sect = cfg.section("cyclos-worker"); + let s = cfg.section("cyclos-worker"); Ok(Self { - frequency: sect.duration("FREQUENCY").require()?, - account_type: map_config!(sect, "account type", "ACCOUNT_TYPE", + frequency: s.duration("FREQUENCY").require()?, + account_type: map_config!(s, "account type", "ACCOUNT_TYPE", "exchange" => { AccountType::Exchange }, "normal" => { AccountType::Normal } ) .require()?, - account_type_id: sect + account_type_id: s .parse("cyclos account type id", "ACCOUNT_TYPE_ID") .require()?, - payment_type_id: sect + payment_type_id: s .parse("cyclos payment type id", "PAYMENT_TYPE_ID") .require()?, host: HostCfg::parse(cfg, &main)?, @@ -207,12 +207,12 @@ impl HarnessCfg { pub fn parse(cfg: &Config) -> Result<Self, ValueErr> { let worker = WorkerCfg::parse(cfg)?; - let sect = cfg.section("cyclos-harness"); + let s = cfg.section("cyclos-harness"); Ok(Self { worker, - username: sect.str("USERNAME").require()?, - password: sect.str("PASSWORD").require()?, + username: s.str("USERNAME").require()?, + password: s.str("PASSWORD").require()?, }) } } diff --git a/taler-cyclos/src/cyclos_api/api.rs b/taler-cyclos/src/cyclos_api/api.rs @@ -58,6 +58,7 @@ pub enum CyclosErr { } pub type ApiResult<R> = std::result::Result<R, ApiErr<CyclosErr>>; + pub struct CyclosRequest<'a> { req: Req, auth: &'a CyclosAuth, diff --git a/taler-magnet-bank/src/config.rs b/taler-magnet-bank/src/config.rs @@ -34,9 +34,9 @@ pub fn parse_db_cfg(cfg: &Config) -> Result<DbCfg, ValueErr> { } pub fn parse_account_payto(cfg: &Config) -> Result<FullHuPayto, ValueErr> { - let sect = cfg.section("magnet-bank"); - let iban: HuIban = sect.parse("iban", "IBAN").require()?; - let name = sect.str("NAME").require()?; + let s = cfg.section("magnet-bank"); + let iban: HuIban = s.parse("iban", "IBAN").require()?; + let name = s.str("NAME").require()?; Ok(FullHuPayto::new(iban, &name)) } @@ -53,9 +53,9 @@ impl ServeCfg { pub fn parse(cfg: &Config) -> Result<Self, ValueErr> { let payto = parse_account_payto(cfg)?; - let sect = cfg.section("magnet-bank-httpd"); + let s = cfg.section("magnet-bank-httpd"); - let serve = Serve::parse(sect)?; + let serve = Serve::parse(&s)?; let wire_gateway = ApiCfg::parse(cfg.section("magnet-bank-httpd-wire-gateway-api"))?; let revenue = ApiCfg::parse(cfg.section("magnet-bank-httpd-revenue-api"))?; @@ -90,23 +90,23 @@ pub struct WorkerCfg { impl WorkerCfg { pub fn parse(cfg: &Config) -> Result<Self, ValueErr> { let payto = parse_account_payto(cfg)?; - let sect = cfg.section("magnet-bank-worker"); + let s = cfg.section("magnet-bank-worker"); Ok(Self { payto, - frequency: sect.duration("FREQUENCY").require()?, - account_type: map_config!(sect, "account type", "ACCOUNT_TYPE", + frequency: s.duration("FREQUENCY").require()?, + account_type: map_config!(s, "account type", "ACCOUNT_TYPE", "exchange" => { AccountType::Exchange }, "normal" => { AccountType::Normal } ) .require()?, - api_url: sect.base_url("API_URL").require()?, + api_url: s.base_url("API_URL").require()?, consumer: Token { - key: sect.str("CONSUMER_KEY").require()?, - secret: sect.str("CONSUMER_SECRET").require()?, + key: s.str("CONSUMER_KEY").require()?, + secret: s.str("CONSUMER_SECRET").require()?, }, - keys_path: sect.path("KEYS_FILE").require()?, - ignore_tx_before: sect.date("IGNORE_TRANSACTIONS_BEFORE").opt()?, - ignore_bounces_before: sect.date("IGNORE_BOUNCES_BEFORE").opt()?, + keys_path: s.path("KEYS_FILE").require()?, + ignore_tx_before: s.date("IGNORE_TRANSACTIONS_BEFORE").opt()?, + ignore_bounces_before: s.date("IGNORE_BOUNCES_BEFORE").opt()?, }) } } @@ -121,9 +121,9 @@ impl HarnessCfg { pub fn parse(cfg: &Config) -> Result<Self, ValueErr> { let worker = WorkerCfg::parse(cfg)?; - let sect = cfg.section("magnet-bank-harness"); - let iban: HuIban = sect.parse("iban", "IBAN").require()?; - let name = sect.str("NAME").require()?; + let s = cfg.section("magnet-bank-harness"); + let iban: HuIban = s.parse("iban", "IBAN").require()?; + let name = s.str("NAME").require()?; Ok(Self { worker,