commit a21fa61c42d0c20807af46ef435f529f87889edd
parent 31a87a2793ddd720cf8163b49db12e49cd063a18
Author: Antoine A <>
Date: Tue, 21 Jan 2025 20:15:57 +0100
common: remove overenginered generic payto logic
Diffstat:
14 files changed, 164 insertions(+), 294 deletions(-)
diff --git a/common/taler-api/src/db.rs b/common/taler-api/src/db.rs
@@ -27,7 +27,7 @@ use taler_common::{
types::{
amount::{Amount, Decimal},
base32::Base32,
- payto::{Payto, PaytoImpl},
+ payto::Payto,
timestamp::Timestamp,
},
};
@@ -173,14 +173,7 @@ pub trait TypeHelper {
fn try_get_url<I: sqlx::ColumnIndex<Self>>(&self, index: I) -> sqlx::Result<Url> {
self.try_get_map(index, Url::parse)
}
- fn try_get_payto<
- E: 'static + std::error::Error + Sync + Send,
- P: PaytoImpl<ParseErr = E>,
- I: sqlx::ColumnIndex<Self>,
- >(
- &self,
- index: I,
- ) -> sqlx::Result<Payto<P>> {
+ fn try_get_payto<I: sqlx::ColumnIndex<Self>>(&self, index: I) -> sqlx::Result<Payto> {
self.try_get_map(index, |s: &str| s.parse())
}
fn try_get_amount(&self, index: &str, currency: &str) -> sqlx::Result<Amount>;
diff --git a/common/taler-api/src/lib.rs b/common/taler-api/src/lib.rs
@@ -49,10 +49,7 @@ use taler_common::{
TransferState, TransferStatus, WireConfig,
},
error_code::ErrorCode,
- types::{
- amount::Amount,
- payto::{AnyPayto, PaytoImpl},
- },
+ types::amount::Amount,
};
use tokio::{
net::{TcpListener, UnixListener},
@@ -183,38 +180,38 @@ where
}
}
-pub trait WireGatewayImpl<P: PaytoImpl>: Send + Sync {
+pub trait WireGatewayImpl: Send + Sync {
fn name(&self) -> &str;
fn currency(&self) -> &str;
fn implementation(&self) -> Option<&str>;
fn transfer(
&self,
- req: TransferRequest<P>,
+ req: TransferRequest,
) -> impl std::future::Future<Output = ApiResult<TransferResponse>> + Send;
fn transfer_page(
&self,
page: Page,
status: Option<TransferState>,
- ) -> impl std::future::Future<Output = ApiResult<TransferList<AnyPayto>>> + Send;
+ ) -> impl std::future::Future<Output = ApiResult<TransferList>> + Send;
fn transfer_by_id(
&self,
id: u64,
- ) -> impl std::future::Future<Output = ApiResult<Option<TransferStatus<AnyPayto>>>> + Send;
+ ) -> impl std::future::Future<Output = ApiResult<Option<TransferStatus>>> + Send;
fn outgoing_history(
&self,
params: History,
- ) -> impl std::future::Future<Output = ApiResult<OutgoingHistory<AnyPayto>>> + Send;
+ ) -> impl std::future::Future<Output = ApiResult<OutgoingHistory>> + Send;
fn incoming_history(
&self,
params: History,
- ) -> impl std::future::Future<Output = ApiResult<IncomingHistory<AnyPayto>>> + Send;
+ ) -> impl std::future::Future<Output = ApiResult<IncomingHistory>> + Send;
fn add_incoming_reserve(
&self,
- req: AddIncomingRequest<P>,
+ req: AddIncomingRequest,
) -> impl std::future::Future<Output = ApiResult<AddIncomingResponse>> + Send;
fn add_incoming_kyc(
&self,
- req: AddKycauthRequest<P>,
+ req: AddKycauthRequest,
) -> impl std::future::Future<Output = ApiResult<AddKycauthResponse>> + Send;
fn check_currency(&self, amount: &Amount) -> ApiResult<()> {
@@ -413,9 +410,7 @@ async fn logger_middleware(request: Request, next: Next) -> Response {
response
}
-pub fn wire_gateway_api<P: PaytoImpl + Send + 'static, I: WireGatewayImpl<P> + 'static>(
- wg: Arc<I>,
-) -> Router {
+pub fn wire_gateway_api<I: WireGatewayImpl + 'static>(wg: Arc<I>) -> Router {
Router::new()
.route(
"/config",
@@ -432,7 +427,7 @@ pub fn wire_gateway_api<P: PaytoImpl + Send + 'static, I: WireGatewayImpl<P> + '
.route(
"/transfer",
post(
- |State(state): State<Arc<I>>, Req(req): Req<TransferRequest<P>>| async move {
+ |State(state): State<Arc<I>>, Req(req): Req<TransferRequest>| async move {
state.check_currency(&req.amount)?;
ApiResult::Ok(Json(state.transfer(req).await?))
},
@@ -497,7 +492,7 @@ pub fn wire_gateway_api<P: PaytoImpl + Send + 'static, I: WireGatewayImpl<P> + '
.route(
"/admin/add-incoming",
post(
- |State(state): State<Arc<I>>, Req(req): Req<AddIncomingRequest<P>>| async move {
+ |State(state): State<Arc<I>>, Req(req): Req<AddIncomingRequest>| async move {
state.check_currency(&req.amount)?;
ApiResult::Ok(Json(state.add_incoming_reserve(req).await?))
},
@@ -506,7 +501,7 @@ pub fn wire_gateway_api<P: PaytoImpl + Send + 'static, I: WireGatewayImpl<P> + '
.route(
"/admin/add-kycauth",
post(
- |State(state): State<Arc<I>>, Req(req): Req<AddKycauthRequest<P>>| async move {
+ |State(state): State<Arc<I>>, Req(req): Req<AddKycauthRequest>| async move {
state.check_currency(&req.amount)?;
ApiResult::Ok(Json(state.add_incoming_kyc(req).await?))
},
diff --git a/common/taler-api/tests/api.rs b/common/taler-api/tests/api.rs
@@ -21,11 +21,7 @@ use taler_common::{
api_common::{HashCode, ShortHashCode},
api_wire::{OutgoingHistory, TransferResponse, TransferState},
error_code::ErrorCode,
- types::{
- amount::amount,
- payto::{payto, AnyPayto},
- url,
- },
+ types::{amount::amount, payto::payto, url},
};
use test_utils::{
axum_test::TestServer,
@@ -71,7 +67,7 @@ async fn config() {
#[tokio::test]
async fn transfer() {
let (server, _) = setup().await;
- transfer_routine::<AnyPayto>(&server, TransferState::success, &payto("payto://test")).await;
+ transfer_routine(&server, TransferState::success, &payto("payto://test")).await;
}
#[tokio::test]
@@ -79,7 +75,7 @@ async fn outgoing_history() {
let (server, _) = setup().await;
server.get("/history/outgoing").await.assert_no_content();
- routine_pagination::<OutgoingHistory<AnyPayto>, _>(
+ routine_pagination::<OutgoingHistory, _>(
&server,
"/history/outgoing",
|it| {
@@ -108,5 +104,5 @@ async fn outgoing_history() {
#[tokio::test]
async fn admin_add_incoming() {
let (server, _) = setup().await;
- admin_add_incoming_routine::<AnyPayto>(&server, &payto("payto://test")).await;
+ admin_add_incoming_routine(&server, &payto("payto://test")).await;
}
diff --git a/common/taler-api/tests/common/db.rs b/common/taler-api/tests/common/db.rs
@@ -23,7 +23,7 @@ use taler_common::{
IncomingBankTransaction, OutgoingBankTransaction, TransferListStatus, TransferRequest,
TransferResponse, TransferState, TransferStatus,
},
- types::{amount::Amount, payto::{AnyPayto, Payto}, timestamp::Timestamp},
+ types::{amount::Amount, payto::Payto, timestamp::Timestamp},
};
use tokio::sync::watch::{Receiver, Sender};
@@ -47,10 +47,7 @@ pub enum TransferResult {
RequestUidReuse,
}
-pub async fn transfer(
- db: &PgPool,
- transfer: TransferRequest<AnyPayto>,
-) -> sqlx::Result<TransferResult> {
+pub async fn transfer(db: &PgPool, transfer: TransferRequest) -> sqlx::Result<TransferResult> {
sqlx::query(
"
SELECT out_request_uid_reuse, out_tx_row_id, out_timestamp
@@ -83,7 +80,7 @@ pub async fn transfer_page(
status: &Option<TransferState>,
params: &Page,
currency: &str,
-) -> sqlx::Result<Vec<TransferListStatus<AnyPayto>>> {
+) -> sqlx::Result<Vec<TransferListStatus>> {
page(
db,
"transfer_id",
@@ -123,7 +120,7 @@ pub async fn transfer_by_id(
db: &PgPool,
id: u64,
currency: &str,
-) -> sqlx::Result<Option<TransferStatus<AnyPayto>>> {
+) -> sqlx::Result<Option<TransferStatus>> {
sqlx::query(
"
SELECT
@@ -159,7 +156,7 @@ pub async fn outgoing_page(
params: &History,
currency: &str,
listen: impl FnOnce() -> Receiver<i64>,
-) -> sqlx::Result<Vec<OutgoingBankTransaction<AnyPayto>>> {
+) -> sqlx::Result<Vec<OutgoingBankTransaction>> {
history(
db,
"transfer_id",
@@ -202,7 +199,7 @@ pub enum AddIncomingResult {
pub async fn add_incoming(
db: &PgPool,
amount: &Amount,
- debit_account: &Payto<AnyPayto>,
+ debit_account: &Payto,
subject: &str,
timestamp: &Timestamp,
kind: IncomingType,
@@ -217,7 +214,7 @@ pub async fn add_incoming(
.bind(key.as_slice())
.bind(subject)
.bind_amount(amount)
- .bind(debit_account .raw())
+ .bind(debit_account.raw())
.bind_timestamp(timestamp)
.bind(kind)
.try_map(|r: PgRow| {
@@ -236,7 +233,7 @@ pub async fn incoming_page(
params: &History,
currency: &str,
listen: impl FnOnce() -> Receiver<i64>,
-) -> sqlx::Result<Vec<IncomingBankTransaction<AnyPayto>>> {
+) -> sqlx::Result<Vec<IncomingBankTransaction>> {
history(
db,
"incoming_transaction_id",
diff --git a/common/taler-api/tests/common/mod.rs b/common/taler-api/tests/common/mod.rs
@@ -32,10 +32,7 @@ use taler_common::{
TransferState, TransferStatus,
},
error_code::ErrorCode,
- types::{
- payto::{payto, AnyPayto},
- timestamp::Timestamp,
- },
+ types::{payto::payto, timestamp::Timestamp},
};
use tokio::sync::watch::Sender;
@@ -49,7 +46,7 @@ pub struct SampleState {
incoming_channel: Sender<i64>,
}
-impl WireGatewayImpl<AnyPayto> for SampleState {
+impl WireGatewayImpl for SampleState {
fn name(&self) -> &str {
"taler-wire-gateway"
}
@@ -62,7 +59,7 @@ impl WireGatewayImpl<AnyPayto> for SampleState {
None
}
- async fn transfer(&self, req: TransferRequest<AnyPayto>) -> ApiResult<TransferResponse> {
+ async fn transfer(&self, req: TransferRequest) -> ApiResult<TransferResponse> {
let result = db::transfer(&self.pool, req).await?;
match result {
db::TransferResult::Success(transfer_response) => Ok(transfer_response),
@@ -77,18 +74,18 @@ impl WireGatewayImpl<AnyPayto> for SampleState {
&self,
page: Page,
status: Option<TransferState>,
- ) -> ApiResult<TransferList<AnyPayto>> {
+ ) -> ApiResult<TransferList> {
Ok(TransferList {
transfers: db::transfer_page(&self.pool, &status, &page, &self.currency).await?,
debit_account: payto("payto://test"),
})
}
- async fn transfer_by_id(&self, id: u64) -> ApiResult<Option<TransferStatus<AnyPayto>>> {
+ async fn transfer_by_id(&self, id: u64) -> ApiResult<Option<TransferStatus>> {
Ok(db::transfer_by_id(&self.pool, id, &self.currency).await?)
}
- async fn outgoing_history(&self, params: History) -> ApiResult<OutgoingHistory<AnyPayto>> {
+ async fn outgoing_history(&self, params: History) -> ApiResult<OutgoingHistory> {
let txs = db::outgoing_page(&self.pool, ¶ms, &self.currency, || {
self.outgoing_channel.subscribe()
})
@@ -99,7 +96,7 @@ impl WireGatewayImpl<AnyPayto> for SampleState {
})
}
- async fn incoming_history(&self, params: History) -> ApiResult<IncomingHistory<AnyPayto>> {
+ async fn incoming_history(&self, params: History) -> ApiResult<IncomingHistory> {
let txs = db::incoming_page(&self.pool, ¶ms, &self.currency, || {
self.incoming_channel.subscribe()
})
@@ -112,7 +109,7 @@ impl WireGatewayImpl<AnyPayto> for SampleState {
async fn add_incoming_reserve(
&self,
- req: AddIncomingRequest<AnyPayto>,
+ req: AddIncomingRequest,
) -> ApiResult<AddIncomingResponse> {
let timestamp = Timestamp::now();
let res = db::add_incoming(
@@ -134,10 +131,7 @@ impl WireGatewayImpl<AnyPayto> for SampleState {
}
}
- async fn add_incoming_kyc(
- &self,
- req: AddKycauthRequest<AnyPayto>,
- ) -> ApiResult<AddKycauthResponse> {
+ async fn add_incoming_kyc(&self, req: AddKycauthRequest) -> ApiResult<AddKycauthResponse> {
let timestamp = Timestamp::now();
let res = db::add_incoming(
&self.pool,
diff --git a/common/taler-common/src/api_wire.rs b/common/taler-common/src/api_wire.rs
@@ -18,11 +18,7 @@
use url::Url;
-use crate::types::{
- amount::Amount,
- payto::{Payto, PaytoImpl},
- timestamp::Timestamp,
-};
+use crate::types::{amount::Amount, payto::Payto, timestamp::Timestamp};
use super::api_common::{EddsaPublicKey, HashCode, SafeU64, ShortHashCode, WadId};
use serde::{Deserialize, Serialize};
@@ -45,85 +41,77 @@ pub struct TransferResponse {
/// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-TransferRequest>
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
-#[serde(bound = "")]
-pub struct TransferRequest<P: PaytoImpl> {
+pub struct TransferRequest {
pub request_uid: HashCode,
pub amount: Amount,
pub exchange_base_url: Url,
pub wtid: ShortHashCode,
- pub credit_account: Payto<P>,
+ pub credit_account: Payto,
}
/// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-TransferList>
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
-#[serde(bound = "")]
-pub struct TransferList<P: PaytoImpl> {
- pub transfers: Vec<TransferListStatus<P>>,
- pub debit_account: Payto<P>,
+pub struct TransferList {
+ pub transfers: Vec<TransferListStatus>,
+ pub debit_account: Payto,
}
/// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-TransferListStatus>
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
-#[serde(bound = "")]
-pub struct TransferListStatus<P: PaytoImpl> {
+pub struct TransferListStatus {
pub row_id: SafeU64,
pub status: TransferState,
pub amount: Amount,
- pub credit_account: Payto<P>,
+ pub credit_account: Payto,
pub timestamp: Timestamp,
}
/// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-TransfertSatus>
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
-#[serde(bound = "")]
-pub struct TransferStatus<P: PaytoImpl> {
+pub struct TransferStatus {
pub status: TransferState,
pub status_msg: Option<String>,
pub amount: Amount,
pub origin_exchange_url: String,
pub wtid: ShortHashCode,
- pub credit_account: Payto<P>,
+ pub credit_account: Payto,
pub timestamp: Timestamp,
}
/// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-OutgoingHistory>
#[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(bound = "")]
-pub struct OutgoingHistory<P: PaytoImpl> {
- pub outgoing_transactions: Vec<OutgoingBankTransaction<P>>,
- pub debit_account: Payto<P>,
+pub struct OutgoingHistory {
+ pub outgoing_transactions: Vec<OutgoingBankTransaction>,
+ pub debit_account: Payto,
}
/// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-OutgoingBankTransaction>
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
-#[serde(bound = "")]
-pub struct OutgoingBankTransaction<P: PaytoImpl> {
+pub struct OutgoingBankTransaction {
pub row_id: SafeU64,
pub date: Timestamp,
pub amount: Amount,
- pub credit_account: Payto<P>,
+ pub credit_account: Payto,
pub wtid: ShortHashCode,
pub exchange_base_url: Url,
}
/// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-IncomingHistory>
#[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(bound = "")]
-pub struct IncomingHistory<P: PaytoImpl> {
- pub credit_account: Payto<P>,
- pub incoming_transactions: Vec<IncomingBankTransaction<P>>,
+pub struct IncomingHistory {
+ pub credit_account: Payto,
+ pub incoming_transactions: Vec<IncomingBankTransaction>,
}
/// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-IncomingBankTransaction>
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
-#[serde(bound = "")]
-pub enum IncomingBankTransaction<P: PaytoImpl> {
+pub enum IncomingBankTransaction {
#[serde(rename = "RESERVE")]
Reserve {
row_id: SafeU64,
date: Timestamp,
amount: Amount,
- debit_account: Payto<P>,
+ debit_account: Payto,
reserve_pub: EddsaPublicKey,
},
#[serde(rename = "WAD")]
@@ -131,7 +119,7 @@ pub enum IncomingBankTransaction<P: PaytoImpl> {
row_id: SafeU64,
date: Timestamp,
amount: Amount,
- debit_account: Payto<P>,
+ debit_account: Payto,
origin_exchange_url: Url,
wad_id: WadId,
},
@@ -140,18 +128,17 @@ pub enum IncomingBankTransaction<P: PaytoImpl> {
row_id: SafeU64,
date: Timestamp,
amount: Amount,
- debit_account: Payto<P>,
+ debit_account: Payto,
account_pub: EddsaPublicKey,
},
}
/// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-AddIncomingRequest>
#[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(bound = "")]
-pub struct AddIncomingRequest<P: PaytoImpl> {
+pub struct AddIncomingRequest {
pub amount: Amount,
pub reserve_pub: EddsaPublicKey,
- pub debit_account: Payto<P>,
+ pub debit_account: Payto,
}
/// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-AddIncomingResponse>
@@ -163,11 +150,10 @@ pub struct AddIncomingResponse {
/// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-AddKycauthRequest>
#[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(bound = "")]
-pub struct AddKycauthRequest<P: PaytoImpl> {
+pub struct AddKycauthRequest {
pub amount: Amount,
pub account_pub: EddsaPublicKey,
- pub debit_account: Payto<P>,
+ pub debit_account: Payto,
}
/// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-AddKycauthResponse>
diff --git a/common/taler-common/src/config.rs b/common/taler-common/src/config.rs
@@ -26,7 +26,7 @@ use url::Url;
use crate::types::{
amount::{Amount, Currency},
- payto::{AnyPayto, Payto},
+ payto::Payto,
};
pub mod parser {
@@ -721,7 +721,7 @@ impl<'cfg, 'arg> Section<'cfg, 'arg> {
}
/** Access [option] as payto */
- pub fn payto(&self, option: &'arg str) -> Value<'arg, Payto<AnyPayto>> {
+ pub fn payto(&self, option: &'arg str) -> Value<'arg, Payto> {
self.parse("payto", option)
}
diff --git a/common/taler-common/src/types/payto.rs b/common/taler-common/src/types/payto.rs
@@ -14,158 +14,84 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-use serde::de::DeserializeOwned;
-use std::{fmt::Debug, ops::Deref, str::FromStr};
+use serde::{de::DeserializeOwned, Deserialize};
+use std::{fmt::Debug, str::FromStr};
use url::Url;
/// Parse a payto URI, panic if malformed
-pub fn payto<Impl: PaytoImpl>(url: impl AsRef<str>) -> Payto<Impl> {
+pub fn payto(url: impl AsRef<str>) -> Payto {
url.as_ref().parse().expect("invalid payto")
}
-/// A payto implementation
-pub trait PaytoImpl: Sized {
- type ParseErr: From<ParsePaytoErr> + std::error::Error + Sync + Send + 'static;
-
- fn parse(url: &Url) -> Result<Self, Self::ParseErr>;
-}
-
-/// A generic payto that accept any kind
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct AnyPayto {}
-
-impl PaytoImpl for AnyPayto {
- type ParseErr = ParsePaytoErr;
-
- fn parse(_url: &Url) -> Result<Self, Self::ParseErr> {
- Ok(Self {})
- }
-}
-
-/// RFC 8905 payto URI
+/// A generic RFC 8905 payto URI
#[derive(
Debug, Clone, PartialEq, Eq, serde_with::DeserializeFromStr, serde_with::SerializeDisplay,
)]
-pub struct Payto<Impl: PaytoImpl> {
- raw: Url,
- parsed: Impl,
-}
+pub struct Payto(Url);
-impl<Impl: PaytoImpl> Payto<Impl> {
+impl Payto {
pub fn raw(&self) -> &str {
- self.raw.as_str()
+ self.0.as_str()
}
- pub fn generic(self) -> Payto<AnyPayto> {
- Payto {
- raw: self.raw,
- parsed: AnyPayto {},
- }
+ pub fn query<Q: DeserializeOwned>(&self) -> Result<Q, PaytoErr> {
+ let query = self.0.query().unwrap_or_default().as_bytes();
+ let de = serde_urlencoded::Deserializer::new(url::form_urlencoded::parse(query));
+ serde_path_to_error::deserialize(de).map_err(PaytoErr::Query)
}
}
-impl<Impl: PaytoImpl> Deref for Payto<Impl> {
- type Target = Impl;
-
- fn deref(&self) -> &Self::Target {
- &self.parsed
+impl AsRef<Url> for Payto {
+ fn as_ref(&self) -> &Url {
+ &self.0
}
}
-impl<Impl: PaytoImpl> std::fmt::Display for Payto<Impl> {
+impl std::fmt::Display for Payto {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- std::fmt::Display::fmt(&self.raw, f)
- }
-}
-
-impl<Impl: PaytoImpl> AsRef<Url> for Payto<Impl> {
- fn as_ref(&self) -> &Url {
- &self.raw
+ std::fmt::Display::fmt(self.raw(), f)
}
}
#[derive(Debug, thiserror::Error)]
-pub enum ParsePaytoErr {
+pub enum PaytoErr {
#[error("invalid payto URI: {0}")]
Url(#[from] url::ParseError),
- #[error("malformed payto URI: {0}")]
- Malformed(#[from] serde_path_to_error::Error<serde_urlencoded::de::Error>),
+ #[error("malformed payto URI query: {0}")]
+ Query(#[from] serde_path_to_error::Error<serde_urlencoded::de::Error>),
#[error("expected a payto URI got {0}")]
NotPayto(String),
#[error("unsupported payto kind, expected {0} got {1}")]
UnsupportedKind(&'static str, String),
#[error("to much path segment for a {0} payto uri")]
TooLong(&'static str),
+ #[error(transparent)]
+ Custom(Box<dyn std::error::Error + Sync + Send + 'static>),
}
-fn parse_payto<Impl: PaytoImpl, Query: DeserializeOwned>(
- s: &str,
-) -> Result<(Payto<Impl>, Query), Impl::ParseErr> {
- // Parse url
- let raw: Url = s.parse().map_err(ParsePaytoErr::Url)?;
- // Check scheme
- if raw.scheme() != "payto" {
- Err(ParsePaytoErr::NotPayto(raw.scheme().to_owned()))?;
+impl PaytoErr {
+ pub fn custom<E: std::error::Error + Sync + Send + 'static>(e: E) -> Self {
+ Self::Custom(Box::new(e))
}
- // 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).map_err(ParsePaytoErr::Malformed)?;
-
- Ok((Payto { raw, parsed }, query))
}
-impl<Impl: PaytoImpl> FromStr for Payto<Impl> {
- type Err = Impl::ParseErr;
+impl FromStr for Payto {
+ type Err = PaytoErr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
- #[derive(serde::Deserialize)]
- struct Query {}
- let (payto, _) = parse_payto::<Impl, Query>(s)?;
- Ok(payto)
+ // Parse url
+ let url: Url = s.parse()?;
+ // Check scheme
+ if url.scheme() != "payto" {
+ return Err(PaytoErr::NotPayto(url.scheme().to_owned()));
+ }
+ Ok(Self(url))
}
}
/// RFC 8905 payto URI
-#[derive(
- Debug, Clone, PartialEq, Eq, serde_with::DeserializeFromStr, serde_with::SerializeDisplay,
-)]
-pub struct FullPayto<Impl: PaytoImpl> {
- payto: Payto<Impl>,
+#[derive(Debug, Clone, Deserialize)]
+pub struct FullPayto {
+ #[serde(rename = "receiver-name")]
pub receiver_name: String,
}
-
-impl<Impl: PaytoImpl> Deref for FullPayto<Impl> {
- type Target = Payto<Impl>;
-
- fn deref(&self) -> &Self::Target {
- &self.payto
- }
-}
-
-impl<Impl: PaytoImpl> std::fmt::Display for FullPayto<Impl> {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- std::fmt::Display::fmt(&self.payto, f)
- }
-}
-
-impl<Impl: PaytoImpl> FromStr for FullPayto<Impl> {
- type Err = Impl::ParseErr;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- #[derive(serde::Deserialize)]
- struct Query {
- #[serde(rename = "receiver-name")]
- receiver_name: String,
- }
- let (payto, query) = parse_payto::<Impl, Query>(s)?;
-
- Ok(Self {
- payto,
- receiver_name: query.receiver_name,
- })
- }
-}
diff --git a/common/test-utils/src/routine.rs b/common/test-utils/src/routine.rs
@@ -34,12 +34,7 @@ use taler_common::{
TransferStatus,
},
error_code::ErrorCode,
- types::{
- amount::amount,
- base32::Base32,
- payto::{Payto, PaytoImpl},
- url,
- },
+ types::{amount::amount, base32::Base32, payto::Payto, url},
};
use tokio::time::sleep;
@@ -268,10 +263,10 @@ async fn get_currency(server: &TestServer) -> String {
}
/// Test standard behavior of the transfer endpoints
-pub async fn transfer_routine<P: PaytoImpl + Eq + Debug>(
+pub async fn transfer_routine(
server: &TestServer,
default_status: TransferState,
- credit_account: &Payto<P>,
+ credit_account: &Payto,
) {
let currency = &get_currency(server).await;
let default_amount = amount(format!("{currency}:42"));
@@ -342,7 +337,7 @@ pub async fn transfer_routine<P: PaytoImpl + Eq + Debug>(
let tx = server
.get(&format!("/transfers/{}", resp.row_id))
.await
- .assert_ok_json::<TransferStatus<P>>();
+ .assert_ok_json::<TransferStatus>();
assert_eq!(default_status, tx.status);
assert_eq!(default_amount, tx.amount);
assert_eq!("http://exchange.taler/", tx.origin_exchange_url);
@@ -372,19 +367,19 @@ pub async fn transfer_routine<P: PaytoImpl + Eq + Debug>(
let list = server
.get("/transfers")
.await
- .assert_ok_json::<TransferList<P>>();
+ .assert_ok_json::<TransferList>();
assert_eq!(list.transfers.len(), 6);
assert_eq!(
list,
server
.get(&format!("/transfers?status={}", default_status.as_ref()))
.await
- .assert_ok_json::<TransferList<P>>()
+ .assert_ok_json::<TransferList>()
);
}
// Pagination test
- routine_pagination::<TransferList<P>, _>(
+ routine_pagination::<TransferList, _>(
server,
"/transfers",
|it| {
@@ -411,11 +406,11 @@ pub async fn transfer_routine<P: PaytoImpl + Eq + Debug>(
}
}
-async fn add_incoming_routine<P: PaytoImpl>(
+async fn add_incoming_routine(
server: &TestServer,
currency: &str,
kind: IncomingType,
- debit_acount: &Payto<P>,
+ debit_acount: &Payto,
) {
let (path, key) = match kind {
IncomingType::reserve => ("/admin/add-incoming", "reserve_pub"),
@@ -484,10 +479,7 @@ async fn add_incoming_routine<P: PaytoImpl>(
}
/// Test standard behavior of the admin add incoming endpoints
-pub async fn admin_add_incoming_routine<P: PaytoImpl>(
- server: &TestServer,
- debit_acount: &Payto<P>,
-) {
+pub async fn admin_add_incoming_routine(server: &TestServer, debit_acount: &Payto) {
let currency = &get_currency(server).await;
// History
@@ -495,7 +487,7 @@ pub async fn admin_add_incoming_routine<P: PaytoImpl>(
routine_history(
server,
"/history/incoming",
- |it: IncomingHistory<P>| {
+ |it: IncomingHistory| {
it.incoming_transactions
.into_iter()
.map(|it| match it {
diff --git a/wire-gateway/magnet-bank/src/db.rs b/wire-gateway/magnet-bank/src/db.rs
@@ -27,15 +27,11 @@ use taler_common::{
IncomingBankTransaction, OutgoingBankTransaction, TransferListStatus, TransferRequest,
TransferState, TransferStatus,
},
- types::{
- amount::Amount,
- payto::{AnyPayto, Payto},
- timestamp::Timestamp,
- },
+ types::{amount::Amount, payto::Payto, timestamp::Timestamp},
};
use tokio::sync::watch::{Receiver, Sender};
-use crate::{constant::CURRENCY, MagnetPayto};
+use crate::constant::CURRENCY;
pub async fn notification_listener(
pool: PgPool,
@@ -65,7 +61,7 @@ pub struct TxIn {
pub code: u64,
pub amount: Amount,
pub subject: String,
- pub debtor: Payto<MagnetPayto>,
+ pub debtor: Payto,
pub timestamp: Timestamp,
}
@@ -87,7 +83,7 @@ pub struct TxOut {
pub code: u64,
pub amount: Amount,
pub subject: String,
- pub creditor: Payto<MagnetPayto>,
+ pub creditor: Payto,
pub timestamp: Timestamp,
}
@@ -108,7 +104,7 @@ impl Display for TxOut {
pub struct TxInAdmin {
pub amount: Amount,
pub subject: String,
- pub debit_payto: Payto<MagnetPayto>,
+ pub debit_payto: Payto,
pub timestamp: Timestamp,
pub metadata: IncomingSubject,
}
@@ -131,7 +127,7 @@ pub struct Initiated {
pub id: u64,
pub amount: Amount,
pub subject: String,
- pub creditor: Payto<MagnetPayto>,
+ pub creditor: Payto,
}
impl Display for Initiated {
@@ -258,7 +254,7 @@ pub enum TransferResult {
pub async fn make_transfer<'a>(
db: impl PgExecutor<'a>,
- req: &TransferRequest<MagnetPayto>,
+ req: &TransferRequest,
timestamp: &Timestamp,
) -> sqlx::Result<TransferResult> {
let subject = format!("{} {}", req.wtid, req.exchange_base_url);
@@ -295,7 +291,7 @@ pub async fn transfer_page<'a>(
db: impl PgExecutor<'a>,
status: &Option<TransferState>,
params: &Page,
-) -> sqlx::Result<Vec<TransferListStatus<AnyPayto>>> {
+) -> sqlx::Result<Vec<TransferListStatus>> {
page(
db,
"initiated_id",
@@ -337,7 +333,7 @@ pub async fn outgoing_history(
db: &PgPool,
params: &History,
listen: impl FnOnce() -> Receiver<i64>,
-) -> sqlx::Result<Vec<OutgoingBankTransaction<AnyPayto>>> {
+) -> sqlx::Result<Vec<OutgoingBankTransaction>> {
history(
db,
"tx_out_id",
@@ -378,7 +374,7 @@ pub async fn incoming_history(
db: &PgPool,
params: &History,
listen: impl FnOnce() -> Receiver<i64>,
-) -> sqlx::Result<Vec<IncomingBankTransaction<AnyPayto>>> {
+) -> sqlx::Result<Vec<IncomingBankTransaction>> {
history(
db,
"tx_in_id",
@@ -429,7 +425,7 @@ pub async fn incoming_history(
pub async fn transfer_by_id<'a>(
db: impl PgExecutor<'a>,
id: u64,
-) -> sqlx::Result<Option<TransferStatus<AnyPayto>>> {
+) -> sqlx::Result<Option<TransferStatus>> {
sqlx::query(
"
SELECT
diff --git a/wire-gateway/magnet-bank/src/dev.rs b/wire-gateway/magnet-bank/src/dev.rs
@@ -49,15 +49,15 @@ pub enum DevCmd {
/// Print account info
Accounts,
Tx {
- account: Payto<MagnetPayto>,
+ account: Payto,
#[clap(long, short, value_enum, default_value_t = DirArg::Both)]
direction: DirArg,
},
Transfer {
#[clap(long)]
- debtor: Payto<MagnetPayto>,
+ debtor: Payto,
#[clap(long)]
- creditor: FullPayto<MagnetPayto>,
+ creditor: Payto,
#[clap(long)]
amount: Amount,
#[clap(long)]
@@ -84,6 +84,7 @@ pub async fn dev(cfg: Config, cmd: DevCmd) -> anyhow::Result<()> {
}
}
DevCmd::Tx { account, direction } => {
+ let account = MagnetPayto::try_from(&account)?;
let dir = match direction {
DirArg::Incoming => Direction::Incoming,
DirArg::Outgoing => Direction::Outgoing,
@@ -93,7 +94,7 @@ pub async fn dev(cfg: Config, cmd: DevCmd) -> anyhow::Result<()> {
let mut next = None;
loop {
let page = client
- .page_tx(dir, 5, &account.account, &next, &None)
+ .page_tx(dir, 5, &account.number, &next, &None)
.await?;
next = page.next;
for item in page.list {
@@ -131,7 +132,10 @@ pub async fn dev(cfg: Config, cmd: DevCmd) -> anyhow::Result<()> {
amount,
subject,
} => {
- let debtor = client.account(&debtor.account).await?;
+ let full: FullPayto = creditor.query()?;
+ let debtor = MagnetPayto::try_from(&debtor)?;
+ let creditor = MagnetPayto::try_from(&creditor)?;
+ let debtor = client.account(&debtor.number).await?;
let now = Zoned::now();
let date = now.date();
@@ -141,8 +145,8 @@ pub async fn dev(cfg: Config, cmd: DevCmd) -> anyhow::Result<()> {
amount.val as f64,
&subject,
date,
- &creditor.receiver_name,
- &creditor.account,
+ &full.receiver_name,
+ &creditor.number,
)
.await?;
client
@@ -152,7 +156,7 @@ pub async fn dev(cfg: Config, cmd: DevCmd) -> anyhow::Result<()> {
init.code,
init.amount,
date,
- &creditor.account,
+ &creditor.number,
)
.await?;
}
diff --git a/wire-gateway/magnet-bank/src/lib.rs b/wire-gateway/magnet-bank/src/lib.rs
@@ -14,8 +14,7 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-use reqwest::Url;
-use taler_common::types::payto::{ParsePaytoErr, PaytoImpl};
+use taler_common::types::payto::{Payto, PaytoErr};
pub mod config;
pub mod constant;
@@ -27,40 +26,39 @@ pub mod wire_gateway;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MagnetPayto {
- pub account: String,
+ pub number: String,
}
#[derive(Debug, thiserror::Error)]
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;
+impl TryFrom<&Payto> for MagnetPayto {
+ type Error = PaytoErr;
- fn parse(url: &Url) -> Result<Self, Self::ParseErr> {
+ fn try_from(value: &Payto) -> Result<Self, Self::Error> {
+ let url = value.as_ref();
if url.domain() != Some(MAGNET_BANK) {
- Err(ParsePaytoErr::UnsupportedKind(
+ return Err(PaytoErr::UnsupportedKind(
MAGNET_BANK,
url.domain().unwrap_or_default().to_owned(),
- ))?;
+ ));
}
let Some(mut segments) = url.path_segments() else {
- return Err(MagnetPaytoErr::MissingAccount);
+ return Err(PaytoErr::custom(MagnetPaytoErr::MissingAccount));
};
let Some(account) = segments.next() else {
- return Err(MagnetPaytoErr::MissingAccount);
+ return Err(PaytoErr::custom(MagnetPaytoErr::MissingAccount));
};
if segments.next().is_some() {
- Err(ParsePaytoErr::TooLong(MAGNET_BANK))?;
+ return Err(PaytoErr::TooLong(MAGNET_BANK));
}
Ok(Self {
- account: account.to_owned(),
+ number: account.to_owned(),
})
}
}
diff --git a/wire-gateway/magnet-bank/src/wire_gateway.rs b/wire-gateway/magnet-bank/src/wire_gateway.rs
@@ -28,22 +28,18 @@ use taler_common::{
TransferState, TransferStatus,
},
error_code::ErrorCode,
- types::{
- payto::{AnyPayto, Payto},
- timestamp::Timestamp,
- },
+ types::{payto::Payto, timestamp::Timestamp},
};
use tokio::sync::watch::Sender;
use crate::{
constant::CURRENCY,
db::{self, AddIncomingResult, TxInAdmin},
- MagnetPayto,
};
pub struct MagnetWireGateway {
pub pool: sqlx::PgPool,
- pub payto: Payto<MagnetPayto>,
+ pub payto: Payto,
pub in_channel: Sender<i64>,
pub taler_in_channel: Sender<i64>,
pub out_channel: Sender<i64>,
@@ -51,7 +47,7 @@ pub struct MagnetWireGateway {
}
impl MagnetWireGateway {
- pub async fn start(pool: sqlx::PgPool, payto: Payto<MagnetPayto>) -> Self {
+ pub async fn start(pool: sqlx::PgPool, payto: Payto) -> Self {
let in_channel = Sender::new(0);
let taler_in_channel = Sender::new(0);
let out_channel = Sender::new(0);
@@ -75,7 +71,7 @@ impl MagnetWireGateway {
}
}
-impl WireGatewayImpl<MagnetPayto> for MagnetWireGateway {
+impl WireGatewayImpl for MagnetWireGateway {
fn name(&self) -> &str {
"magnet-bank"
}
@@ -88,7 +84,7 @@ impl WireGatewayImpl<MagnetPayto> for MagnetWireGateway {
None
}
- async fn transfer(&self, req: TransferRequest<MagnetPayto>) -> ApiResult<TransferResponse> {
+ async fn transfer(&self, req: TransferRequest) -> ApiResult<TransferResponse> {
let result = db::make_transfer(&self.pool, &req, &Timestamp::now()).await?;
match result {
db::TransferResult::Success { id, timestamp } => Ok(TransferResponse {
@@ -107,40 +103,40 @@ impl WireGatewayImpl<MagnetPayto> for MagnetWireGateway {
&self,
page: Page,
status: Option<TransferState>,
- ) -> ApiResult<TransferList<AnyPayto>> {
+ ) -> ApiResult<TransferList> {
Ok(TransferList {
transfers: db::transfer_page(&self.pool, &status, &page).await?,
- debit_account: self.payto.clone().generic(),
+ debit_account: self.payto.clone(),
})
}
- async fn transfer_by_id(&self, id: u64) -> ApiResult<Option<TransferStatus<AnyPayto>>> {
+ async fn transfer_by_id(&self, id: u64) -> ApiResult<Option<TransferStatus>> {
Ok(db::transfer_by_id(&self.pool, id).await?)
}
- async fn outgoing_history(&self, params: History) -> ApiResult<OutgoingHistory<AnyPayto>> {
+ async fn outgoing_history(&self, params: History) -> ApiResult<OutgoingHistory> {
Ok(OutgoingHistory {
outgoing_transactions: db::outgoing_history(&self.pool, ¶ms, || {
self.taler_out_channel.subscribe()
})
.await?,
- debit_account: self.payto.clone().generic(),
+ debit_account: self.payto.clone(),
})
}
- async fn incoming_history(&self, params: History) -> ApiResult<IncomingHistory<AnyPayto>> {
+ async fn incoming_history(&self, params: History) -> ApiResult<IncomingHistory> {
Ok(IncomingHistory {
incoming_transactions: db::incoming_history(&self.pool, ¶ms, || {
self.taler_in_channel.subscribe()
})
.await?,
- credit_account: self.payto.clone().generic(),
+ credit_account: self.payto.clone(),
})
}
async fn add_incoming_reserve(
&self,
- req: AddIncomingRequest<MagnetPayto>,
+ req: AddIncomingRequest,
) -> ApiResult<AddIncomingResponse> {
let res = db::register_tx_in_admin(
&self.pool,
@@ -165,10 +161,7 @@ impl WireGatewayImpl<MagnetPayto> for MagnetWireGateway {
}
}
- async fn add_incoming_kyc(
- &self,
- req: AddKycauthRequest<MagnetPayto>,
- ) -> ApiResult<AddKycauthResponse> {
+ async fn add_incoming_kyc(&self, req: AddKycauthRequest) -> ApiResult<AddKycauthResponse> {
let res = db::register_tx_in_admin(
&self.pool,
&TxInAdmin {
diff --git a/wire-gateway/magnet-bank/tests/api.rs b/wire-gateway/magnet-bank/tests/api.rs
@@ -16,7 +16,7 @@
use std::sync::Arc;
-use magnet_bank::{db, wire_gateway::MagnetWireGateway, MagnetPayto};
+use magnet_bank::{db, wire_gateway::MagnetWireGateway};
use sqlx::PgPool;
use taler_api::{auth::AuthMethod, standard_layer, subject::OutgoingSubject};
use taler_common::{
@@ -47,7 +47,7 @@ async fn setup() -> (TestServer, PgPool) {
#[tokio::test]
async fn transfer() {
let (server, _) = setup().await;
- transfer_routine::<MagnetPayto>(
+ transfer_routine(
&server,
TransferState::pending,
&payto("payto://magnet-bank/todo"),
@@ -59,7 +59,7 @@ async fn transfer() {
async fn outgoing_history() {
let (server, pool) = setup().await;
server.get("/history/outgoing").await.assert_no_content();
- routine_pagination::<OutgoingHistory<MagnetPayto>, _>(
+ routine_pagination::<OutgoingHistory, _>(
&server,
"/history/outgoing",
|it| {
@@ -97,5 +97,5 @@ async fn outgoing_history() {
#[tokio::test]
async fn admin_add_incoming() {
let (server, _) = setup().await;
- admin_add_incoming_routine::<MagnetPayto>(&server, &payto("payto://magnet-bank/todo")).await;
+ admin_add_incoming_routine(&server, &payto("payto://magnet-bank/todo")).await;
}