taler-rust

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

payto.rs (3635B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 
     17 use std::{fmt::Display, num::ParseIntError, ops::Deref, str::FromStr};
     18 
     19 use compact_str::CompactString;
     20 use taler_common::types::payto::{FullPayto, Payto, PaytoErr, PaytoImpl, PaytoURI, TransferPayto};
     21 
     22 #[derive(Debug, Clone, PartialEq, Eq)]
     23 pub struct CyclosAccount {
     24     pub id: CyclosId,
     25     pub root: CompactString,
     26 }
     27 
     28 #[derive(
     29     Debug, Clone, Copy, PartialEq, Eq, serde_with::DeserializeFromStr, serde_with::SerializeDisplay,
     30 )]
     31 pub struct CyclosId(pub i64);
     32 
     33 impl Deref for CyclosId {
     34     type Target = i64;
     35 
     36     fn deref(&self) -> &Self::Target {
     37         &self.0
     38     }
     39 }
     40 
     41 impl Display for CyclosId {
     42     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
     43         self.0.fmt(f)
     44     }
     45 }
     46 
     47 impl FromStr for CyclosId {
     48     type Err = ParseIntError;
     49 
     50     fn from_str(s: &str) -> Result<Self, Self::Err> {
     51         Ok(Self(i64::from_str(s)?))
     52     }
     53 }
     54 
     55 const CYCLOS: &str = "cyclos";
     56 
     57 impl PaytoImpl for CyclosAccount {
     58     fn as_payto(&self) -> PaytoURI {
     59         PaytoURI::from_parts(CYCLOS, format_args!("/{}/{}", self.root, self.id))
     60     }
     61 
     62     fn parse(raw: &PaytoURI) -> Result<Self, PaytoErr> {
     63         let url = raw.as_ref();
     64         if url.domain() != Some(CYCLOS) {
     65             return Err(PaytoErr::UnsupportedKind(
     66                 CYCLOS,
     67                 url.domain().unwrap_or_default().into(),
     68             ));
     69         }
     70         let Some((root, id)) = url.path().trim_start_matches('/').rsplit_once('/') else {
     71             return Err(PaytoErr::MissingSegment("cyclos account id"));
     72         };
     73 
     74         Ok(CyclosAccount {
     75             id: CyclosId::from_str(id)
     76                 .map_err(|e| PaytoErr::malformed_segment("cyclos account id", e))?,
     77             root: CompactString::new(root),
     78         })
     79     }
     80 }
     81 
     82 /// Parse a cyclos payto URI, panic if malformed
     83 pub fn cyclos_payto(url: impl AsRef<str>) -> FullCyclosPayto {
     84     url.as_ref().parse().expect("invalid cyclos payto")
     85 }
     86 
     87 // TODO should we check the root url ?
     88 
     89 pub type CyclosPayto = Payto<CyclosAccount>;
     90 pub type FullCyclosPayto = FullPayto<CyclosAccount>;
     91 pub type TransferCyclosPayto = TransferPayto<CyclosAccount>;
     92 
     93 #[cfg(test)]
     94 mod test {
     95     use crate::payto::cyclos_payto;
     96 
     97     #[test]
     98     pub fn parse() {
     99         let simple = "payto://cyclos/demo.cyclos.org/7762070814194619199?receiver-name=John+Smith";
    100         let payto = cyclos_payto(simple);
    101 
    102         assert_eq!(*payto.id, 7762070814194619199);
    103         assert_eq!(payto.name, "John Smith");
    104         assert_eq!(payto.root, "demo.cyclos.org");
    105 
    106         assert_eq!(payto.to_string(), simple);
    107 
    108         let complex = "payto://cyclos/communities.cyclos.org/utrecht/7762070814194619199?receiver-name=John+Smith";
    109         let payto = cyclos_payto(complex);
    110 
    111         assert_eq!(*payto.id, 7762070814194619199);
    112         assert_eq!(payto.name, "John Smith");
    113         assert_eq!(payto.root, "communities.cyclos.org/utrecht");
    114 
    115         assert_eq!(payto.to_string(), complex);
    116     }
    117 }