commit 31a87a2793ddd720cf8163b49db12e49cd063a18
parent 301a9af06c7d39bf5b71116501c7970202bfd063
Author: Antoine A <>
Date: Tue, 21 Jan 2025 18:27:32 +0100
common: simplify payto
Diffstat:
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)?)),