commit 5746ba2e2be1ec3caf3b556227a938f43073ab6f
parent 410312e46a93b280f76c4c41287f4e94b4f7ca46
Author: Antoine A <>
Date: Wed, 11 Dec 2024 19:27:16 +0100
taler-common: generic payto type
Diffstat:
8 files changed, 97 insertions(+), 30 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -2122,6 +2122,7 @@ dependencies = [
"rand",
"serde",
"serde_json",
+ "serde_urlencoded",
"serde_with",
"sqlx",
"thiserror 2.0.6",
diff --git a/taler-api/src/db.rs b/taler-api/src/db.rs
@@ -22,6 +22,7 @@ use taler_common::{
amount::{Amount, Decimal},
api_common::{Base32, SafeU64, Timestamp},
api_params::{History, Page},
+ payto::Payto,
};
use tokio::sync::watch::Receiver;
use url::Url;
@@ -165,6 +166,9 @@ 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<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>;
fn try_get_amount_i(&self, index: usize, currency: &str) -> sqlx::Result<Amount>;
}
diff --git a/taler-api/tests/common/db.rs b/taler-api/tests/common/db.rs
@@ -30,10 +30,10 @@ use taler_common::{
IncomingBankTransaction, OutgoingBankTransaction, TransferListStatus, TransferRequest,
TransferResponse, TransferState, TransferStatus,
},
+ payto::Payto,
};
use tokio::sync::watch::{Receiver, Sender};
use tracing::debug;
-use url::Url;
pub async fn notification_listener(
pool: PgPool,
@@ -80,7 +80,7 @@ pub async fn transfer(db: &PgPool, transfer: TransferRequest) -> ApiResult<Trans
.bind_amount(&transfer.amount)
.bind(transfer.exchange_base_url.as_str())
.bind(format!("{} {}", transfer.wtid, transfer.exchange_base_url))
- .bind(transfer.credit_account.as_str())
+ .bind(transfer.credit_account.raw())
.bind(transfer.request_uid.as_slice())
.bind(transfer.wtid.as_slice())
.bind_timestamp(&Timestamp::now())
@@ -131,7 +131,7 @@ pub async fn transfer_page(
row_id: r.try_get_safeu64("transfer_id")?,
status: r.try_get("status")?,
amount: r.try_get_amount("amount", currency)?,
- credit_account: r.try_get_url("credit_payto")?,
+ credit_account: r.try_get_payto("credit_payto")?,
timestamp: r.try_get_timestamp("transfer_time")?,
})
},
@@ -166,7 +166,7 @@ pub async fn transfer_by_id(
amount: r.try_get_amount("amount", currency)?,
origin_exchange_url: r.try_get("exchange_base_url")?,
wtid: r.try_get_base32("wtid")?,
- credit_account: r.try_get_url("credit_payto")?,
+ credit_account: r.try_get_payto("credit_payto")?,
timestamp: r.try_get_timestamp("transfer_time")?,
})
})
@@ -204,7 +204,7 @@ pub async fn outgoing_page(
Ok(OutgoingBankTransaction {
amount: r.try_get_amount("amount", currency)?,
wtid: r.try_get_base32("wtid")?,
- credit_account: r.try_get_url("credit_payto")?,
+ credit_account: r.try_get_payto("credit_payto")?,
row_id: r.try_get_safeu64("transfer_id")?,
date: r.try_get_timestamp("transfer_time")?,
exchange_base_url: r.try_get_url("exchange_base_url")?,
@@ -222,7 +222,7 @@ pub enum AddIncomingResult {
pub async fn add_incoming(
db: &PgPool,
amount: &Amount,
- debit_account: &Url,
+ debit_account: &Payto,
subject: &str,
timestamp: &Timestamp,
kind: IncomingType,
@@ -237,7 +237,7 @@ pub async fn add_incoming(
.bind(key.as_slice())
.bind(subject)
.bind_amount(amount)
- .bind(debit_account.as_str())
+ .bind(debit_account.raw())
.bind_timestamp(timestamp)
.bind(kind)
.try_map(|r: PgRow| {
@@ -287,21 +287,21 @@ pub async fn incoming_page(
row_id: r.try_get_safeu64("incoming_transaction_id")?,
date: r.try_get_timestamp("creation_time")?,
amount: r.try_get_amount("amount", currency)?,
- debit_account: r.try_get_url("debit_payto")?,
+ debit_account: r.try_get_payto("debit_payto")?,
reserve_pub: r.try_get_base32("reserve_pub")?,
},
IncomingType::kyc => IncomingBankTransaction::IncomingKycAuthTransaction {
row_id: r.try_get_safeu64("incoming_transaction_id")?,
date: r.try_get_timestamp("creation_time")?,
amount: r.try_get_amount("amount", currency)?,
- debit_account: r.try_get_url("debit_payto")?,
+ debit_account: r.try_get_payto("debit_payto")?,
account_pub: r.try_get_base32("account_pub")?,
},
IncomingType::wad => IncomingBankTransaction::IncomingWadTransaction {
row_id: r.try_get_safeu64("incoming_transaction_id")?,
date: r.try_get_timestamp("creation_time")?,
amount: r.try_get_amount("amount", currency)?,
- debit_account: r.try_get_url("debit_payto")?,
+ debit_account: r.try_get_payto("debit_payto")?,
origin_exchange_url: r.try_get_url("origin_exchange_url")?,
wad_id: r.try_get_base32("wa_id")?,
},
diff --git a/taler-api/tests/common/mod.rs b/taler-api/tests/common/mod.rs
@@ -33,9 +33,9 @@ use taler_common::{
TransferState, TransferStatus,
},
error_code::ErrorCode,
+ payto::payto,
};
use tokio::sync::watch::Sender;
-use url::Url;
pub mod db;
@@ -74,7 +74,7 @@ impl WireGatewayImpl for SampleState {
) -> ApiResult<TransferList> {
Ok(TransferList {
transfers: db::transfer_page(&self.pool, &status, &page, &self.currency).await?,
- debit_account: Url::parse("payto://todo").unwrap(),
+ debit_account: payto("payto://todo"),
})
}
@@ -89,7 +89,7 @@ impl WireGatewayImpl for SampleState {
.await?;
Ok(OutgoingHistory {
outgoing_transactions: txs,
- debit_account: Url::parse("payto://todo").unwrap(),
+ debit_account: payto("payto://todo"),
})
}
@@ -100,7 +100,7 @@ impl WireGatewayImpl for SampleState {
.await?;
Ok(IncomingHistory {
incoming_transactions: txs,
- credit_account: Url::parse("payto://todo").unwrap(),
+ credit_account: payto("payto://todo"),
})
}
diff --git a/taler-common/Cargo.toml b/taler-common/Cargo.toml
@@ -8,6 +8,7 @@ base32 = "0.5.1"
serde_with = "3.11.0"
rand = "0.8"
fastrand = "2.2.0"
+serde_urlencoded = "0.7"
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, features = ["raw_value"] }
url.workspace = true
diff --git a/taler-common/src/api_wire.rs b/taler-common/src/api_wire.rs
@@ -18,7 +18,7 @@
use url::Url;
-use crate::amount::Amount;
+use crate::{amount::Amount, payto::Payto};
use super::api_common::{EddsaPublicKey, HashCode, SafeU64, ShortHashCode, Timestamp, WadId};
use serde::{Deserialize, Serialize};
@@ -46,14 +46,14 @@ pub struct TransferRequest {
pub amount: Amount,
pub exchange_base_url: Url,
pub wtid: ShortHashCode,
- pub credit_account: Url,
+ pub credit_account: Payto,
}
/// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-TransferList>
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct TransferList {
pub transfers: Vec<TransferListStatus>,
- pub debit_account: Url,
+ pub debit_account: Payto,
}
/// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-TransferListStatus>
@@ -62,7 +62,7 @@ pub struct TransferListStatus {
pub row_id: SafeU64,
pub status: TransferState,
pub amount: Amount,
- pub credit_account: Url,
+ pub credit_account: Payto,
pub timestamp: Timestamp,
}
@@ -74,7 +74,7 @@ pub struct TransferStatus {
pub amount: Amount,
pub origin_exchange_url: String,
pub wtid: ShortHashCode,
- pub credit_account: Url,
+ pub credit_account: Payto,
pub timestamp: Timestamp,
}
@@ -82,7 +82,7 @@ pub struct TransferStatus {
/// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-OutgoingHistory>
pub struct OutgoingHistory {
pub outgoing_transactions: Vec<OutgoingBankTransaction>,
- pub debit_account: Url,
+ pub debit_account: Payto,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -91,7 +91,7 @@ pub struct OutgoingBankTransaction {
pub row_id: SafeU64,
pub date: Timestamp,
pub amount: Amount,
- pub credit_account: Url,
+ pub credit_account: Payto,
pub wtid: ShortHashCode,
pub exchange_base_url: Url,
}
@@ -99,7 +99,7 @@ pub struct OutgoingBankTransaction {
#[derive(Debug, Clone, Serialize, Deserialize)]
/// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-IncomingHistory>
pub struct IncomingHistory {
- pub credit_account: Url,
+ pub credit_account: Payto,
pub incoming_transactions: Vec<IncomingBankTransaction>,
}
@@ -112,7 +112,7 @@ pub enum IncomingBankTransaction {
row_id: SafeU64,
date: Timestamp,
amount: Amount,
- debit_account: Url,
+ debit_account: Payto,
reserve_pub: EddsaPublicKey,
},
#[serde(rename = "WAD")]
@@ -120,7 +120,7 @@ pub enum IncomingBankTransaction {
row_id: SafeU64,
date: Timestamp,
amount: Amount,
- debit_account: Url,
+ debit_account: Payto,
origin_exchange_url: Url,
wad_id: WadId,
},
@@ -129,7 +129,7 @@ pub enum IncomingBankTransaction {
row_id: SafeU64,
date: Timestamp,
amount: Amount,
- debit_account: Url,
+ debit_account: Payto,
account_pub: EddsaPublicKey,
},
}
@@ -139,7 +139,7 @@ pub enum IncomingBankTransaction {
pub struct AddIncomingRequest {
pub amount: Amount,
pub reserve_pub: EddsaPublicKey,
- pub debit_account: Url,
+ pub debit_account: Payto,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -154,7 +154,7 @@ pub struct AddIncomingResponse {
pub struct AddKycauthRequest {
pub amount: Amount,
pub account_pub: EddsaPublicKey,
- pub debit_account: Url,
+ pub debit_account: Payto,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
diff --git a/taler-common/src/lib.rs b/taler-common/src/lib.rs
@@ -22,6 +22,4 @@ pub mod error_code;
pub mod config {
// TODO
}
-pub mod payto {
- // TODO
-}
+pub mod payto;
diff --git a/taler-common/src/payto.rs b/taler-common/src/payto.rs
@@ -0,0 +1,63 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ 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 std::{fmt::Debug, str::FromStr};
+use url::Url;
+
+#[derive(PartialEq, Eq, Clone, serde_with::DeserializeFromStr, serde_with::SerializeDisplay)]
+pub struct Payto(Url);
+
+impl Payto {
+ pub fn raw(&self) -> &str {
+ self.0.as_str()
+ }
+}
+
+impl std::fmt::Display for Payto {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ std::fmt::Display::fmt(&self.0, f)
+ }
+}
+
+impl std::fmt::Debug for Payto {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ std::fmt::Debug::fmt(&self.0, f)
+ }
+}
+
+pub fn payto(url: impl AsRef<str>) -> Payto {
+ url.as_ref().parse().expect("invalid payto")
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum ParsePaytoError {
+ #[error("invalid URI: {0}")]
+ Url(#[from] url::ParseError),
+ #[error("not a payto URI")]
+ NotPayto,
+}
+
+impl FromStr for Payto {
+ type Err = ParsePaytoError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ let url: Url = s.parse()?;
+ if url.scheme() != "payto" {
+ return Err(ParsePaytoError::NotPayto);
+ }
+ Ok(Self(url))
+ }
+}