depolymerization

wire gateway for Bitcoin/Ethereum
Log | Files | Refs | Submodules | README | LICENSE

commit 5737be63081ea8258da93c9d7216d9761df92722
parent 9e917164e24d5b7d7e6461d5f21c3efd0e35963c
Author: Antoine A <>
Date:   Tue,  7 Jan 2025 19:48:05 +0100

Use new config parser

Diffstat:
MCargo.lock | 17+++++++++--------
Mcommon/Cargo.toml | 1+
Mcommon/src/config.rs | 182+++++++++++++++++++++++++++++++++++++------------------------------------------
Mwire-gateway/src/main.rs | 31+++++++++++++------------------
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(&currency) .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");