taler-rust

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

commit 31a87a2793ddd720cf8163b49db12e49cd063a18
parent 301a9af06c7d39bf5b71116501c7970202bfd063
Author: Antoine A <>
Date:   Tue, 21 Jan 2025 18:27:32 +0100

common: simplify payto

Diffstat:
Mcommon/taler-common/src/types/payto.rs | 64+++++++++++++++-------------------------------------------------
Mwire-gateway/magnet-bank/src/lib.rs | 33++++++++++++++++++++++-----------
Mwire-gateway/magnet-bank/src/magnet/error.rs | 20++++++++++++--------
3 files changed, 49 insertions(+), 68 deletions(-)

diff --git a/common/taler-common/src/types/payto.rs b/common/taler-common/src/types/payto.rs @@ -18,8 +18,6 @@ use serde::de::DeserializeOwned; use std::{fmt::Debug, ops::Deref, str::FromStr}; use url::Url; -use super::url; - /// Parse a payto URI, panic if malformed pub fn payto<Impl: PaytoImpl>(url: impl AsRef<str>) -> Payto<Impl> { url.as_ref().parse().expect("invalid payto") @@ -27,13 +25,9 @@ pub fn payto<Impl: PaytoImpl>(url: impl AsRef<str>) -> Payto<Impl> { /// A payto implementation pub trait PaytoImpl: Sized { - type ParseErr: std::error::Error; - - fn kind() -> &'static str; - - fn parse(path: &mut std::str::Split<'_, char>) -> Result<Self, Self::ParseErr>; + type ParseErr: From<ParsePaytoErr> + std::error::Error + Sync + Send + 'static; - fn path(&self) -> String; + fn parse(url: &Url) -> Result<Self, Self::ParseErr>; } /// A generic payto that accept any kind @@ -41,20 +35,11 @@ pub trait PaytoImpl: Sized { pub struct AnyPayto {} impl PaytoImpl for AnyPayto { - type ParseErr = std::convert::Infallible; + type ParseErr = ParsePaytoErr; - fn parse(path: &mut std::str::Split<'_, char>) -> Result<Self, Self::ParseErr> { - for _ in path {} + fn parse(_url: &Url) -> Result<Self, Self::ParseErr> { Ok(Self {}) } - - fn kind() -> &'static str { - "" - } - - fn path(&self) -> String { - "".to_owned() - } } /// RFC 8905 payto URI @@ -99,15 +84,8 @@ impl<Impl: PaytoImpl> AsRef<Url> for Payto<Impl> { } } -impl<Impl: PaytoImpl> From<Impl> for Payto<Impl> { - fn from(parsed: Impl) -> Self { - let raw = url(&format!("payto://{}{}", Impl::kind(), parsed.path())); - Self { raw, parsed } - } -} - #[derive(Debug, thiserror::Error)] -pub enum ParsePaytoErr<E> { +pub enum ParsePaytoErr { #[error("invalid payto URI: {0}")] Url(#[from] url::ParseError), #[error("malformed payto URI: {0}")] @@ -118,47 +96,35 @@ pub enum ParsePaytoErr<E> { UnsupportedKind(&'static str, String), #[error("to much path segment for a {0} payto uri")] TooLong(&'static str), - #[error(transparent)] - Custom(E), } fn parse_payto<Impl: PaytoImpl, Query: DeserializeOwned>( s: &str, -) -> Result<(Payto<Impl>, Query), ParsePaytoErr<Impl::ParseErr>> { +) -> Result<(Payto<Impl>, Query), Impl::ParseErr> { // Parse url - let raw: Url = s.parse()?; + let raw: Url = s.parse().map_err(ParsePaytoErr::Url)?; // Check scheme if raw.scheme() != "payto" { - return Err(ParsePaytoErr::NotPayto(raw.scheme().to_owned())); - } - // Check domain - let domain = raw.domain().unwrap_or_default(); - let kind = Impl::kind(); - if !kind.is_empty() && domain != kind { - return Err(ParsePaytoErr::UnsupportedKind(kind, domain.to_owned())); - } - // Parse path - let mut segments = raw.path_segments().unwrap_or_else(|| "".split('/')); - let parsed = Impl::parse(&mut segments).map_err(ParsePaytoErr::Custom)?; - if segments.next().is_some() { - return Err(ParsePaytoErr::TooLong(kind)); + Err(ParsePaytoErr::NotPayto(raw.scheme().to_owned()))?; } + // Parse implementation + let parsed = Impl::parse(&raw)?; // Parse query let de = serde_urlencoded::Deserializer::new(url::form_urlencoded::parse( raw.query().unwrap_or_default().as_bytes(), )); - let query: Query = serde_path_to_error::deserialize(de)?; + let query: Query = serde_path_to_error::deserialize(de).map_err(ParsePaytoErr::Malformed)?; Ok((Payto { raw, parsed }, query)) } impl<Impl: PaytoImpl> FromStr for Payto<Impl> { - type Err = ParsePaytoErr<Impl::ParseErr>; + type Err = Impl::ParseErr; fn from_str(s: &str) -> Result<Self, Self::Err> { #[derive(serde::Deserialize)] struct Query {} - let (payto, _): (_, Query) = parse_payto(s)?; + let (payto, _) = parse_payto::<Impl, Query>(s)?; Ok(payto) } } @@ -187,7 +153,7 @@ impl<Impl: PaytoImpl> std::fmt::Display for FullPayto<Impl> { } impl<Impl: PaytoImpl> FromStr for FullPayto<Impl> { - type Err = ParsePaytoErr<Impl::ParseErr>; + type Err = Impl::ParseErr; fn from_str(s: &str) -> Result<Self, Self::Err> { #[derive(serde::Deserialize)] @@ -195,7 +161,7 @@ impl<Impl: PaytoImpl> FromStr for FullPayto<Impl> { #[serde(rename = "receiver-name")] receiver_name: String, } - let (payto, query): (_, Query) = parse_payto(s)?; + let (payto, query) = parse_payto::<Impl, Query>(s)?; Ok(Self { payto, diff --git a/wire-gateway/magnet-bank/src/lib.rs b/wire-gateway/magnet-bank/src/lib.rs @@ -14,7 +14,8 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -use taler_common::types::payto::PaytoImpl; +use reqwest::Url; +use taler_common::types::payto::{ParsePaytoErr, PaytoImpl}; pub mod config; pub mod constant; @@ -33,23 +34,33 @@ pub struct MagnetPayto { pub enum MagnetPaytoErr { #[error("missing Magnet Bank account number in path")] MissingAccount, + #[error(transparent)] + Other(#[from] ParsePaytoErr), } +const MAGNET_BANK: &str = "magnet-bank"; + impl PaytoImpl for MagnetPayto { type ParseErr = MagnetPaytoErr; - fn kind() -> &'static str { - "magnet-bank" - } - - fn parse(path: &mut std::str::Split<'_, char>) -> Result<Self, Self::ParseErr> { - let account = path.next().ok_or(MagnetPaytoErr::MissingAccount)?; + fn parse(url: &Url) -> Result<Self, Self::ParseErr> { + if url.domain() != Some(MAGNET_BANK) { + Err(ParsePaytoErr::UnsupportedKind( + MAGNET_BANK, + url.domain().unwrap_or_default().to_owned(), + ))?; + } + let Some(mut segments) = url.path_segments() else { + return Err(MagnetPaytoErr::MissingAccount); + }; + let Some(account) = segments.next() else { + return Err(MagnetPaytoErr::MissingAccount); + }; + if segments.next().is_some() { + Err(ParsePaytoErr::TooLong(MAGNET_BANK))?; + } Ok(Self { account: account.to_owned(), }) } - - fn path(&self) -> String { - format!("/{}", self.account) - } } diff --git a/wire-gateway/magnet-bank/src/magnet/error.rs b/wire-gateway/magnet-bank/src/magnet/error.rs @@ -17,7 +17,7 @@ use reqwest::{header, Response, StatusCode}; use serde::{de::DeserializeOwned, Deserialize}; use thiserror::Error; -use tracing::error; +use tracing::{error, Level}; #[derive(Deserialize, Debug)] struct Header { @@ -109,21 +109,25 @@ async fn error_handling(res: reqwest::Result<Response>) -> ApiResult<String> { Err(ApiError::StatusCause(status, cause.to_string())) } _ => { - dbg!(&res); - let body = res.text().await?; - dbg!(body); + if tracing::enabled!(Level::DEBUG) { + tracing::debug!("unexpected error: {:?}", &res); + let body = res.text().await; + tracing::debug!("unexpected error body: {:?}", body); + } Err(ApiError::Status(status)) } } } +/** Parse JSON and track error path */ +fn parse<T: DeserializeOwned>(str: &str) -> ApiResult<T> { + let deserializer = &mut serde_json::Deserializer::from_str(str); + serde_path_to_error::deserialize(deserializer).map_err(ApiError::Json) +} + /** Parse magnet JSON response */ async fn magnet_json<T: DeserializeOwned>(res: reqwest::Result<Response>) -> ApiResult<T> { let body = error_handling(res).await?; - fn parse<T: DeserializeOwned>(str: &str) -> ApiResult<T> { - let deserializer = &mut serde_json::Deserializer::from_str(str); - serde_path_to_error::deserialize(deserializer).map_err(ApiError::Json) - } let header: Header = parse(&body)?; match header.error_code { Some(_) => Err(ApiError::Magnet(parse(&body)?)),