taler-rust

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

commit 5746ba2e2be1ec3caf3b556227a938f43073ab6f
parent 410312e46a93b280f76c4c41287f4e94b4f7ca46
Author: Antoine A <>
Date:   Wed, 11 Dec 2024 19:27:16 +0100

taler-common: generic payto type

Diffstat:
MCargo.lock | 1+
Mtaler-api/src/db.rs | 4++++
Mtaler-api/tests/common/db.rs | 20++++++++++----------
Mtaler-api/tests/common/mod.rs | 8++++----
Mtaler-common/Cargo.toml | 1+
Mtaler-common/src/api_wire.rs | 26+++++++++++++-------------
Mtaler-common/src/lib.rs | 4+---
Ataler-common/src/payto.rs | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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)) + } +}