commit 5737be63081ea8258da93c9d7216d9761df92722
parent 9e917164e24d5b7d7e6461d5f21c3efd0e35963c
Author: Antoine A <>
Date: Tue, 7 Jan 2025 19:48:05 +0100
Use new config parser
Diffstat:
4 files changed, 109 insertions(+), 122 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -373,9 +373,9 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.5.23"
+version = "4.5.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
+checksum = "9560b07a799281c7e0958b9296854d6fafd4c5f31444a7e5bb1ad6dde5ccf1bd"
dependencies = [
"clap_builder",
"clap_derive",
@@ -383,9 +383,9 @@ dependencies = [
[[package]]
name = "clap_builder"
-version = "4.5.23"
+version = "4.5.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
+checksum = "874e0dd3eb68bf99058751ac9712f622e61e6f393a94f7128fa26e3f02f5c7cd"
dependencies = [
"anstream",
"anstyle",
@@ -395,9 +395,9 @@ dependencies = [
[[package]]
name = "clap_derive"
-version = "4.5.18"
+version = "4.5.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
+checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c"
dependencies = [
"heck",
"proc-macro2",
@@ -451,6 +451,7 @@ dependencies = [
"serde_json",
"serde_with",
"sqlx",
+ "taler-api",
"taler-common",
"thiserror",
"uri-pack",
@@ -2905,7 +2906,7 @@ dependencies = [
[[package]]
name = "taler-api"
version = "0.1.0"
-source = "git+https://git.taler.net/taler-rust.git/#517e6d3e02180b4f9a58fc74716fa96c96c9b9fb"
+source = "git+https://git.taler.net/taler-rust.git/#88fad287f9047d4ab097253f39bc44e82729a623"
dependencies = [
"axum",
"dashmap",
@@ -2928,7 +2929,7 @@ dependencies = [
[[package]]
name = "taler-common"
version = "0.1.0"
-source = "git+https://git.taler.net/taler-rust.git/#517e6d3e02180b4f9a58fc74716fa96c96c9b9fb"
+source = "git+https://git.taler.net/taler-rust.git/#88fad287f9047d4ab097253f39bc44e82729a623"
dependencies = [
"fastrand",
"glob",
diff --git a/common/Cargo.toml b/common/Cargo.toml
@@ -34,4 +34,5 @@ uri-pack = { path = "../uri-pack" }
# Exponential backoff generator
exponential-backoff = "1.2.0"
taler-common.workspace = true
+taler-api.workspace = true
sqlx.workspace = true
diff --git a/common/src/config.rs b/common/src/config.rs
@@ -13,25 +13,27 @@
You should have received a copy of the GNU Affero General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-use ini::{Ini, Properties};
use sqlx::postgres::PgConnectOptions;
use std::{
path::{Path, PathBuf},
process::Command,
str::FromStr,
};
-use taler_common::types::payto::Payto;
+use taler_api::auth::AuthMethod;
+use taler_common::{
+ config::{parser::ConfigErr, Config, Section},
+ types::payto::Payto,
+};
use url::Url;
use crate::{
currency::Currency,
log::{fail, OrFail},
};
-pub use ini;
// Depolymerizer taler config
pub struct TalerConfig {
- conf: Ini,
+ cfg: Config,
section_name: &'static str,
pub currency: Currency,
}
@@ -54,11 +56,11 @@ impl TalerConfig {
String::from_utf8_lossy(&output.stderr)
));
}
+
// Parse ini config
- let conf = ini::Ini::load_from_str(&String::from_utf8_lossy(&output.stdout))
+ let conf = Config::from_mem(&String::from_utf8_lossy(&output.stdout))
.or_fail(|e| format!("config format: {}", e));
- let taler = section(&conf, "taler");
- let currency = required(taler, "CURRENCY", string);
+ let currency = conf.section("taler").str("currency").require().unwrap();
let currency = Currency::from_str(¤cy)
.or_fail(|_| format!("config CURRENCY={} is an unsupported currency", currency));
let section_name = match currency {
@@ -67,18 +69,22 @@ impl TalerConfig {
};
Self {
- conf,
+ cfg: conf,
section_name,
currency,
}
}
- fn section(&self) -> &Properties {
- section(&self.conf, self.section_name)
+ fn section(&self) -> Section {
+ self.cfg.section(self.section_name)
}
fn non_zero_option(&self, name: &str) -> Option<u32> {
- nb(self.section(), name).filter(|nb| *nb != 0)
+ self.section()
+ .number::<u32>(name)
+ .opt()
+ .unwrap()
+ .filter(|it| *it == 0)
}
}
@@ -86,29 +92,36 @@ impl TalerConfig {
/* ----- Common ----- */
pub fn db_config(&self) -> postgres::Config {
- required(self.section(), "DB_URL", postgres)
- }
-
- pub fn db_config_sqlx(&self) -> PgConnectOptions {
- required(self.section(), "DB_URL", postgres_sqlx)
+ self.section()
+ .parse("Postgres", "DB_URL")
+ .require()
+ .unwrap()
}
pub fn base_url(&self) -> Url {
- required(section(&self.conf, "exchange"), "BASE_URL", url)
+ self.cfg
+ .section("exchange")
+ .url("BASE_URL")
+ .require()
+ .unwrap()
}
/* ----- Wire Gateway ----- */
pub fn payto(&self) -> Payto {
- required(self.section(), "PAYTO", payto)
+ self.section().payto("PAYTO").require().unwrap()
}
pub fn port(&self) -> u16 {
- nb(self.section(), "PORT").unwrap_or(8080)
+ self.section().number("PORT").default(8080).unwrap()
}
pub fn unix_path(&self) -> Option<PathBuf> {
- path(self.section(), "UNIXPATH")
+ self.section()
+ .path("UNIXPATH")
+ .opt()
+ .unwrap()
+ .map(|e| e.into())
}
pub fn http_lifetime(&self) -> Option<u32> {
@@ -116,25 +129,31 @@ impl TalerConfig {
}
pub fn auth_method(&self) -> AuthMethod {
- let section = self.section();
- match required(section, "AUTH_METHOD", string).as_str() {
+ let sect = self.section();
+ let kind = sect
+ .value("auth method", "AUTH_METHOD", |s| match s {
+ "none" | "basic" => Ok(s),
+ unknown => Err(format!(
+ "unknown config auth method AUTH_METHOD={unknown} expected 'none' or 'basic'"
+ )),
+ })
+ .require()
+ .unwrap();
+ match kind {
"none" => AuthMethod::None,
- "basic" => AuthMethod::Basic(required(section, "AUTH_TOKEN", string)),
- it => fail(format!(
- "unknown config auth method AUTH_METHOD={} expected 'none' or 'basic'",
- it
- )),
+ "basic" => AuthMethod::Basic(sect.str("AUTH_TOKEN").require().unwrap()),
+ _ => unreachable!(),
}
}
/* ----- Wire Common ----- */
pub fn confirmation(&self) -> Option<u16> {
- nb(self.section(), "CONFIRMATION")
+ self.section().number("CONFIRMATION").opt().unwrap()
}
pub fn bounce_fee(&self) -> Option<String> {
- string(self.section(), "BOUNCE_FEE")
+ self.section().str("BOUNCE_FEE").opt().unwrap()
}
pub fn wire_lifetime(&self) -> Option<u32> {
@@ -148,76 +167,47 @@ impl TalerConfig {
/* ----- Custom ----- */
pub fn path(&self, name: &str) -> Option<PathBuf> {
- path(self.section(), name)
+ self.section().path(name).opt().unwrap().map(|e| e.into())
}
}
-/* ----- Auth Method ----- */
-
-pub enum AuthMethod {
- Basic(String),
- None,
-}
-
-/* ----- Helper parsing functions ----- */
-
-pub fn section<'a>(ini: &'a Ini, name: &str) -> &'a Properties {
- ini.section(Some(name))
- .or_fail(|_| format!("missing config section {}", name))
-}
-
-pub fn required<T>(
- properties: &Properties,
- name: &str,
- lambda: fn(properties: &Properties, name: &str) -> Option<T>,
-) -> T {
- expect_config(lambda(properties, name), name)
-}
-
-pub fn expect_config<T>(value: Option<T>, name: &str) -> T {
- value.or_fail(|_| format!("missing config value {}", name))
-}
-
-pub fn string(properties: &Properties, name: &str) -> Option<String> {
- properties.get(name).map(|s| s.to_string())
-}
-
-pub fn path(properties: &Properties, name: &str) -> Option<PathBuf> {
- properties.get(name).map(|s| {
- PathBuf::from_str(s).or_fail(|_| format!("config {}={} is not a valid path", name, s))
- })
-}
-
-pub fn nb<T: FromStr>(properties: &Properties, name: &str) -> Option<T> {
- properties.get(name).map(|s| {
- s.parse()
- .or_fail(|_| format!("config {}={} is not a number", name, s))
- })
-}
-
-pub fn url(properties: &Properties, name: &str) -> Option<Url> {
- properties.get(name).map(|s| {
- Url::parse(s).or_fail(|e| format!("config {}={} is not a valid url: {}", name, s, e))
- })
-}
-
-pub fn payto(properties: &Properties, name: &str) -> Option<Payto> {
- properties.get(name).map(|s| {
- s.parse()
- .or_fail(|e| format!("config {}={} is not a valid payto: {}", name, s, e))
- })
-}
-
-pub fn postgres(properties: &Properties, name: &str) -> Option<postgres::Config> {
- properties.get(name).map(|s| {
- postgres::Config::from_str(s)
- .or_fail(|e| format!("config {}={} is not a valid postgres url: {}", name, s, e))
- })
+pub struct WireGatewayCfg {
+ pub auth: AuthMethod,
+ pub http_lifetime: Option<u32>,
+ pub db: PgConnectOptions,
+ pub payto: Payto,
+ pub currency: Currency,
+ pub unix: Option<String>,
+ pub port: u16,
}
-pub fn postgres_sqlx(properties: &Properties, name: &str) -> Option<PgConnectOptions> {
- properties.get(name).map(|s| {
- PgConnectOptions::from_str(s)
- .or_fail(|e| format!("config {}={} is not a valid postgres url: {}", name, s, e))
- })
+impl WireGatewayCfg {
+ pub fn parse(path: Option<&Path>) -> Result<Self, ConfigErr> {
+ let tmp = TalerConfig::load(path);
+ let sect = tmp.section();
+ let auth = {
+ let kind = sect
+ .value("auth method", "AUTH_METHOD", |s| match s {
+ "none" | "basic" => Ok(s),
+ unknown => Err(format!(
+ "unknown config auth method AUTH_METHOD={unknown} expected 'none' or 'basic'"
+ )),
+ })
+ .require()?;
+ match kind {
+ "none" => AuthMethod::None,
+ "basic" => AuthMethod::Basic(sect.str("AUTH_TOKEN").require()?),
+ _ => unreachable!(),
+ }
+ };
+ Ok(Self {
+ auth,
+ http_lifetime: tmp.non_zero_option("HTTP_LIFETIME"),
+ db: sect.postgres("DB_URL").require()?,
+ payto: sect.payto("PAYTO").require()?,
+ currency: tmp.currency,
+ unix: sect.path("UNIXPATH").opt()?,
+ port: sect.number("PORT").default(8080)?,
+ })
+ }
}
diff --git a/wire-gateway/src/main.rs b/wire-gateway/src/main.rs
@@ -21,11 +21,12 @@ use axum::{
};
use clap::Parser;
use common::{
- config::{AuthMethod, TalerConfig},
+ config::WireGatewayCfg,
currency::Currency,
log::{
fail,
log::{error, info},
+ OrFail,
},
};
use listenfd::ListenFd;
@@ -236,24 +237,23 @@ struct Args {
#[tokio::main]
async fn main() {
common::log::init();
- let args = Args::parse();
- let taler_config = TalerConfig::load(args.config.as_deref());
-
#[cfg(feature = "test")]
common::log::log::warn!("Running with test admin endpoint unsuitable for production");
+ let args = Args::parse();
+
+ let config = WireGatewayCfg::parse(args.config.as_deref()).or_fail(|e| e.to_string());
+
// Parse postgres url
- let db_config = taler_config.db_config_sqlx();
- let pool = PgPool::connect_with(db_config).await.unwrap();
+ let pool = PgPool::connect_with(config.db).await.unwrap();
- let payto = taler_config.payto();
let state = Arc::new(ServerState {
pool,
status: AtomicBool::new(true),
- payto,
- currency: taler_config.currency,
+ payto: config.payto,
+ currency: config.currency,
});
- let lifetime = taler_config.http_lifetime().map(AtomicU32::new);
+ let lifetime = config.http_lifetime.map(AtomicU32::new);
let router = wire_gateway_api(state.clone()).layer(middleware::from_fn_with_state(
state.clone(),
@@ -262,11 +262,6 @@ async fn main() {
tokio::spawn(status_watcher(state));
- let auth: taler_api::auth::AuthMethod = match taler_config.auth_method() {
- AuthMethod::Basic(a) => taler_api::auth::AuthMethod::Basic(a),
- AuthMethod::None => taler_api::auth::AuthMethod::None,
- };
-
let mut listenfd = ListenFd::from_env();
let listener = if let Some(listener) = listenfd.take_tcp_listener(0).unwrap() {
@@ -275,7 +270,7 @@ async fn main() {
listener.local_addr().unwrap()
);
tokio::net::TcpListener::from_std(listener).unwrap()
- } else if let Some(path) = taler_config.unix_path() {
+ } else if let Some(path) = config.unix {
info!("Server listening on unix domain socket {:?}", path);
if let Err(err) = std::fs::remove_file(&path) {
if err.kind() != std::io::ErrorKind::NotFound {
@@ -285,11 +280,11 @@ async fn main() {
//Listener::Unix(tokio::net::UnixListener::bind(path).unwrap())
unimplemented!("UNIX domain socket TODO")
} else {
- let addr: SocketAddr = ([0, 0, 0, 0], taler_config.port()).into();
+ let addr: SocketAddr = ([0, 0, 0, 0], config.port).into();
info!("Server listening on http://{}", &addr);
tokio::net::TcpListener::bind(addr).await.unwrap()
};
- taler_api::server(listener, router, auth, lifetime)
+ taler_api::server(listener, router, config.auth, lifetime)
.await
.unwrap();
info!("wire-gateway stopped");