commit 1afd5eec11621049a1abeb930e77b5cddb749222
parent f254d533d053b7f85c3caf1bc67346f2ccd105f2
Author: Antoine A <>
Date: Sat, 23 May 2026 10:51:18 +0200
common: us mimalloc and clean code
Diffstat:
22 files changed, 133 insertions(+), 56 deletions(-)
diff --git a/common/taler-api/src/api.rs b/common/taler-api/src/api.rs
@@ -92,7 +92,7 @@ pub trait TalerRouter {
fn finalize(self) -> Self;
fn serve(
self,
- serve: Serve,
+ serve: &Serve,
lifetime: Option<u32>,
) -> impl std::future::Future<Output = std::io::Result<()>> + Send;
}
@@ -116,7 +116,7 @@ impl TalerRouter for Router {
.layer(middleware::from_fn(logger_middleware))
}
- async fn serve(mut self, serve: Serve, lifetime: Option<u32>) -> std::io::Result<()> {
+ async fn serve(mut self, serve: &Serve, lifetime: Option<u32>) -> std::io::Result<()> {
let listener = serve.resolve()?;
let notify = Arc::new(tokio::sync::Notify::new());
diff --git a/common/taler-api/src/db.rs b/common/taler-api/src/db.rs
@@ -91,8 +91,8 @@ macro_rules! serialized {
const MAX_RETRIES: u32 = 5;
loop {
- let res = $logic.await;
- if let sqlx::Result::Err(e) = &res
+ let res: sqlx::Result<_, sqlx::Error> = $logic.await;
+ if let Err(e) = &res
&& e.is_retryable_err()
&& attempts < MAX_RETRIES
{
diff --git a/common/taler-api/src/error.rs b/common/taler-api/src/error.rs
@@ -30,7 +30,7 @@ use taler_common::{
pub type ApiResult<T> = Result<T, ApiError>;
-#[derive(Debug)]
+#[derive(Debug, thiserror::Error)]
pub struct ApiError {
code: ErrorCode,
hint: Option<Box<str>>,
@@ -40,6 +40,16 @@ pub struct ApiError {
headers: Option<Box<HeaderMap>>,
}
+impl Display for ApiError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ if let Some(h) = &self.hint {
+ f.write_str(h)
+ } else {
+ f.write_str(self.code.description())
+ }
+ }
+}
+
impl ApiError {
pub fn new(code: ErrorCode) -> Self {
Self {
@@ -93,10 +103,12 @@ impl From<sqlx::Error> for ApiError {
sqlx::Error::Configuration(_) => {
(ErrorCode::GENERIC_DB_SETUP_FAILED, StatusCode::BAD_GATEWAY)
}
- sqlx::Error::Database(_) | sqlx::Error::Io(_) | sqlx::Error::Tls(_) => {
+ sqlx::Error::Database(_)
+ | sqlx::Error::Io(_)
+ | sqlx::Error::Tls(_)
+ | sqlx::Error::PoolTimedOut => {
(ErrorCode::GENERIC_DB_FETCH_FAILED, StatusCode::BAD_GATEWAY)
}
- sqlx::Error::PoolTimedOut => todo!(),
_ => (
ErrorCode::BANK_UNMANAGED_EXCEPTION,
StatusCode::INTERNAL_SERVER_ERROR,
diff --git a/common/taler-api/src/lib.rs b/common/taler-api/src/lib.rs
@@ -33,6 +33,7 @@ pub mod subject;
#[cfg(test)]
pub mod test;
+#[derive(Debug, Clone)]
pub enum Serve {
Tcp(SocketAddr),
Unix {
diff --git a/common/taler-api/src/subject.rs b/common/taler-api/src/subject.rs
@@ -472,12 +472,15 @@ mod test {
assert_eq!(parse_incoming_unstructured(&case), result);
}
- // Check prefer prefixed over simple
- for case in [format!("{mixed_l}-{mixed_r} {standard_l}-{standard_r}")] {
+ // Check prefer prefixed over simple ones
+ for case in [
+ format!("{standard_l}-{standard_r} {mixed_l}-{mixed_r}"),
+ format!("{mixed_l}-{mixed_r} {standard_l}-{standard_r}"),
+ ] {
let res = parse_incoming_unstructured(&case);
- if ty == IncomingType::reserve {
- assert_eq!(res, Err(IncomingSubjectErr::Ambiguous));
- } else {
+ if !(ty == IncomingType::reserve
+ && matches!(res, Err(IncomingSubjectErr::Ambiguous)))
+ {
assert_eq!(res, result);
}
}
@@ -486,7 +489,7 @@ mod test {
for case in [
"does not contain any reserve", // Check fail if none
&standard[1..], // Check fail if missing char
- //"2MZT6RS3RVB3B0E2RDMYW0YRA3Y0VPHYV0CYDE6XBB0YMPFXCEG0", // Check fail if not a valid key
+ // "2MZT6RS3RVB3B0E2RDMYW0YRA3Y0VPHYV0CYDE6XBB0YMPFXCEG0", // Check fail if not a valid key TODO aws-lc does not check
] {
assert_eq!(
parse_incoming_unstructured(case),
diff --git a/common/taler-common/Cargo.toml b/common/taler-common/Cargo.toml
@@ -11,6 +11,7 @@ license-file.workspace = true
doctest = false
[dependencies]
+mimalloc = "0.1"
glob = "0.3"
indexmap = "2.7"
tempfile.workspace = true
diff --git a/common/taler-common/src/cli.rs b/common/taler-common/src/cli.rs
@@ -16,6 +16,8 @@
use std::io::Write as _;
+use compact_str::CompactString;
+
use crate::config::Config;
/// Inspect the configuration
@@ -23,18 +25,18 @@ use crate::config::Config;
pub enum ConfigCmd {
/// Lookup config value
Get {
- section: String,
- option: String,
+ section: CompactString,
+ option: CompactString,
/// Interpret value as path with dollar-expansion
- #[clap(short, long, default_value_t = false)]
+ #[arg(short, long)]
filename: bool,
},
/// Substitute variables in a path
- Pathsub { path_expr: String },
+ Pathsub { path_expr: CompactString },
/// Dump the configuration
Dump {
/// output extra diagnostics
- #[clap(short, long, default_value_t = false)]
+ #[arg(short, long)]
diagnostics: bool,
},
}
diff --git a/common/taler-common/src/config.rs b/common/taler-common/src/config.rs
@@ -1121,6 +1121,7 @@ mod test {
const DEFAULT_CONF: &str = "[PATHS]\nDATADIR=mydir\nRECURSIVE=$RECURSIVE";
+ #[allow(clippy::type_complexity)]
fn routine<T: Debug + Eq>(
ty: &str,
mut lambda: impl for<'cfg, 'arg> FnMut(&Section<'cfg, 'arg>, &'arg str) -> Value<'arg, T>,
@@ -1272,7 +1273,7 @@ mod test {
format!("amount '{it}' invalid fraction (invalid digit found in string)")
}),
(&["KUDOS:999999999999999999"], |it| {
- format!("amount '{it}' value overflow (must be <= 9007199254740992)")
+ format!("amount '{it}' value overflow (must be <= 4503599627370496)")
}),
(&["EUR:12"], |_| {
"expected currency KUDOS got EUR".to_owned()
diff --git a/common/taler-common/src/lib.rs b/common/taler-common/src/lib.rs
@@ -17,6 +17,7 @@
use std::{path::PathBuf, time::Duration};
use config::{Config, parser::ConfigSource};
+use mimalloc::MiMalloc;
use tracing::error;
use tracing_subscriber::util::SubscriberInitExt;
@@ -34,16 +35,17 @@ pub mod json_file;
pub mod log;
pub mod types;
+#[global_allocator]
+static GLOBAL: MiMalloc = MiMalloc;
+
#[derive(clap::Parser, Debug, Clone)]
pub struct CommonArgs {
/// Specifies the configuration file
- #[clap(long, short)]
- #[arg(global = true)]
+ #[arg(short, long, global = true)]
config: Option<PathBuf>,
/// Configure logging to use LOGLEVEL
- #[clap(long, short('L'))]
- #[arg(global = true)]
+ #[arg(short('L'), long, global = true)]
log: Option<tracing::Level>,
}
diff --git a/common/taler-common/src/types/amount.rs b/common/taler-common/src/types/amount.rs
@@ -22,6 +22,8 @@ use std::{
str::FromStr,
};
+use compact_str::format_compact;
+
use super::utils::InlineStr;
/** Number of characters we use to represent currency names */
@@ -29,7 +31,7 @@ use super::utils::InlineStr;
pub const CURRENCY_LEN: usize = 11;
/** Maximum legal value for an amount, based on IEEE double */
-pub const MAX_VALUE: u64 = 2 << 52;
+pub const MAX_VALUE: u64 = 2 << 51;
/** The number of digits in a fraction part of an amount */
pub const FRAC_BASE_NB_DIGITS: u8 = 8;
@@ -257,7 +259,7 @@ impl Display for Decimal {
if self.frac == 0 {
f.write_fmt(format_args!("{}", self.val))
} else {
- let num = format!("{:08}", self.frac);
+ let num = format_compact!("{:08}", self.frac);
f.write_fmt(format_args!("{}.{}", self.val, num.trim_end_matches('0')))
}
}
@@ -442,6 +444,15 @@ impl<'q> sqlx::Encode<'q, sqlx::Postgres> for Amount {
}
#[test]
+fn constants() {
+ assert_eq!(format!("{}", Amount::zero(&Currency::KUDOS)), "KUDOS:0");
+ assert_eq!(
+ format!("{}", Amount::max(&Currency::KUDOS)),
+ "KUDOS:4503599627370496.99999999"
+ );
+}
+
+#[test]
fn test_amount_parse() {
const TALER_AMOUNT_FRAC_BASE: u32 = 100000000;
// https://git.taler.net/exchange.git/tree/src/util/test_amount.c
diff --git a/common/taler-common/src/types/timestamp.rs b/common/taler-common/src/types/timestamp.rs
@@ -14,7 +14,7 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-use std::{fmt::Display, ops::Add, time::Duration};
+use std::{fmt::Display, ops::Add, str::FromStr, time::Duration};
use jiff::{Timestamp, civil::Time, tz::TimeZone};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error, ser::SerializeStruct}; // codespell:ignore
@@ -27,6 +27,19 @@ pub enum TalerTimestamp {
Timestamp(Timestamp),
}
+impl FromStr for TalerTimestamp {
+ type Err = anyhow::Error;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ if s == "never" {
+ return Ok(Self::Never);
+ }
+ let s: i64 = s.parse()?;
+
+ Ok(Self::Timestamp(jiff::Timestamp::from_second(s)?))
+ }
+}
+
#[derive(Serialize, Deserialize)]
struct TimestampImpl {
t_s: Value,
@@ -144,6 +157,19 @@ pub enum RelativeTime {
Duration(Duration),
}
+impl FromStr for RelativeTime {
+ type Err = anyhow::Error;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ if s == "forever" {
+ return Ok(Self::Forever);
+ }
+ let micros: u64 = s.parse()?;
+
+ Ok(Self::Duration(Duration::from_micros(micros)))
+ }
+}
+
#[derive(Serialize, Deserialize)]
struct RelativeTimeImpl {
d_us: Value,
diff --git a/common/taler-macros/src/lib.rs b/common/taler-macros/src/lib.rs
@@ -95,7 +95,25 @@ pub fn derive_domain_code(input: TokenStream) -> TokenStream {
for variant in variants {
let v_ident = &variant.ident;
- let v_str = v_ident.to_string();
+ let v_str = variant
+ .attrs
+ .iter()
+ .find_map(|attr| {
+ if attr.path().is_ident("enum_meta") {
+ let mut res = None;
+ let _ = attr.parse_nested_meta(|meta| {
+ if meta.path.is_ident("rename") {
+ let value: LitStr = meta.value()?.parse()?;
+ res = Some(value.value());
+ }
+ Ok(())
+ });
+ res
+ } else {
+ None
+ }
+ })
+ .unwrap_or_else(|| v_ident.to_string());
if repr_type.is_some() {
if let Some((_, discriminant)) = &variant.discriminant {
diff --git a/taler-apns-relay/src/main.rs b/taler-apns-relay/src/main.rs
@@ -43,19 +43,19 @@ enum Command {
/// Initialize taler-apns-relay database
Dbinit {
/// Reset database (DANGEROUS: All existing data is lost)
- #[clap(long, short)]
+ #[arg(short, long)]
reset: bool,
},
/// Check taler-apns-relay config
Setup {
/// Remove all registered devices
- #[clap(long, short)]
+ #[arg(short, long)]
reset: bool,
},
/// Run taler-apns-relay worker
Worker {
/// Execute once and return
- #[clap(long, short)]
+ #[arg(short, long)]
transient: bool,
},
/// Run taler-apns-relay HTTP server
@@ -77,7 +77,7 @@ async fn run(cmd: Command, cfg: &Config) -> anyhow::Result<()> {
let pool = pool(cfg).await?;
let cfg = ServeCfg::parse(cfg)?;
let api = Arc::new(RelayApi::new(pool));
- router(api).serve(cfg.serve, None).await?;
+ router(api).serve(&cfg.serve, None).await?;
}
Command::Worker { transient } => {
let pool = pool(cfg).await?;
diff --git a/taler-cyclos/src/api.rs b/taler-cyclos/src/api.rs
@@ -480,7 +480,7 @@ mod test {
let (server, db) = &setup().await;
out_history_routine(
- &server,
+ server,
tasks!({ out_talerable(db).await }),
tasks!(
{ out_bounce(db).await },
@@ -502,7 +502,7 @@ mod test {
async fn in_history() {
let (server, db) = &setup().await;
in_history_routine(
- &server,
+ server,
&ACCOUNT,
true,
tasks!({ in_talerable(db).await }),
@@ -520,7 +520,7 @@ mod test {
async fn revenue() {
let (server, db) = &setup().await;
revenue_routine(
- &server,
+ server,
&ACCOUNT,
true,
tasks!({ in_malformed(db).await }, { in_talerable(db).await },),
diff --git a/taler-cyclos/src/bin/cyclos-harness.rs b/taler-cyclos/src/bin/cyclos-harness.rs
@@ -67,12 +67,12 @@ struct Args {
enum Command {
/// Run logic tests
Logic {
- #[clap(long, short)]
+ #[arg(short, long)]
reset: bool,
},
/// Run online tests
Online {
- #[clap(long, short)]
+ #[arg(short, long)]
reset: bool,
},
}
diff --git a/taler-cyclos/src/lib.rs b/taler-cyclos/src/lib.rs
@@ -43,6 +43,6 @@ pub async fn run_serve(cfg: &Config, pool: PgPool) -> anyhow::Result<()> {
if let Some(cfg) = cfg.revenue {
router = router.revenue(api, cfg.auth.method());
}
- router.serve(cfg.serve, None).await?;
+ router.serve(&cfg.serve, None).await?;
Ok(())
}
diff --git a/taler-cyclos/src/main.rs b/taler-cyclos/src/main.rs
@@ -43,26 +43,26 @@ enum Command {
/// Initialize taler-cyclos database
Dbinit {
/// Reset database (DANGEROUS: All existing data is lost)
- #[clap(long, short)]
+ #[arg(short, long)]
reset: bool,
},
/// Check taler-cyclos config
Setup {
/// Reset connection info and overwrite keys file
- #[clap(long, short)]
+ #[arg(short, long)]
reset: bool,
},
/// Run taler-cyclos worker
Worker {
/// Execute once and return
- #[clap(long, short)]
+ #[arg(short, long)]
transient: bool,
},
/// Run taler-cyclos HTTP server
Serve {
/// Check whether an API is in use (if it's useful to start the HTTP
/// server). Exit with 0 if at least one API is enabled, otherwise 1
- #[clap(long)]
+ #[arg(long)]
check: bool,
},
#[command(subcommand)]
diff --git a/taler-magnet-bank/src/api.rs b/taler-magnet-bank/src/api.rs
@@ -454,7 +454,7 @@ mod test {
let (server, db) = &setup().await;
out_history_routine(
- &server,
+ server,
tasks!({ out_talerable(db).await }),
tasks!(
{ out_bounce(db).await },
@@ -476,7 +476,7 @@ mod test {
async fn in_history() {
let (server, db) = &setup().await;
in_history_routine(
- &server,
+ server,
&ACCOUNT,
true,
tasks!({ in_talerable(db).await }),
@@ -494,7 +494,7 @@ mod test {
async fn revenue() {
let (server, db) = &setup().await;
revenue_routine(
- &server,
+ server,
&ACCOUNT,
true,
tasks!({ in_malformed(db).await }, { in_talerable(db).await },),
diff --git a/taler-magnet-bank/src/bin/magnet-bank-harness.rs b/taler-magnet-bank/src/bin/magnet-bank-harness.rs
@@ -66,12 +66,12 @@ struct Args {
enum Command {
/// Run logic tests
Logic {
- #[clap(long, short)]
+ #[arg(short, long)]
reset: bool,
},
/// Run online tests
Online {
- #[clap(long, short)]
+ #[arg(short, long)]
reset: bool,
},
}
diff --git a/taler-magnet-bank/src/dev.rs b/taler-magnet-bank/src/dev.rs
@@ -49,17 +49,17 @@ pub enum DevCmd {
Accounts,
Tx {
account: HuIban,
- #[clap(long, short, value_enum, default_value_t = DirArg::Both)]
+ #[arg(long, short, value_enum, default_value_t = DirArg::Both)]
direction: DirArg,
},
Transfer {
- #[clap(long)]
+ #[arg(long)]
debtor: HuPayto,
- #[clap(long)]
+ #[arg(long)]
creditor: TransferHuPayto,
- #[clap(long)]
+ #[arg(long)]
amount: Option<Amount>,
- #[clap(long)]
+ #[arg(long)]
subject: Option<String>,
},
}
diff --git a/taler-magnet-bank/src/lib.rs b/taler-magnet-bank/src/lib.rs
@@ -47,7 +47,7 @@ pub async fn run_serve(cfg: &Config, pool: PgPool) -> anyhow::Result<()> {
if let Some(cfg) = cfg.revenue {
router = router.revenue(api, cfg.auth.method());
}
- router.serve(cfg.serve, None).await?;
+ router.serve(&cfg.serve, None).await?;
Ok(())
}
diff --git a/taler-magnet-bank/src/main.rs b/taler-magnet-bank/src/main.rs
@@ -43,26 +43,26 @@ enum Command {
/// Initialize taler-magnet-bank database
Dbinit {
/// Reset database (DANGEROUS: All existing data is lost)
- #[clap(long, short)]
+ #[arg(short, long)]
reset: bool,
},
/// Setup taler-magnet-bank auth token and account settings for Wire Gateway use
Setup {
/// Reset connection info and overwrite keys file
- #[clap(long, short)]
+ #[arg(short, long)]
reset: bool,
},
/// Run taler-magnet-bank worker
Worker {
/// Execute once and return
- #[clap(long, short)]
+ #[arg(short, long)]
transient: bool,
},
/// Run taler-magnet-bank HTTP server
Serve {
/// Check whether an API is in use (if it's useful to start the HTTP
/// server). Exit with 0 if at least one API is enabled, otherwise 1
- #[clap(long)]
+ #[arg(long)]
check: bool,
},
#[command(subcommand)]