taler-rust

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

payto.rs (3821B)


      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 #[derive(Debug, thiserror::Error)]
     48 #[error("malformed cyclos id: {0}")]
     49 pub struct CyclosIdError(ParseIntError);
     50 
     51 impl FromStr for CyclosId {
     52     type Err = CyclosIdError;
     53 
     54     fn from_str(s: &str) -> Result<Self, Self::Err> {
     55         Ok(Self(i64::from_str(s).map_err(CyclosIdError)?))
     56     }
     57 }
     58 
     59 const CYCLOS: &str = "cyclos";
     60 
     61 #[derive(Debug, thiserror::Error)]
     62 #[error("missing cyclos root and account id in path")]
     63 pub struct MissingParts;
     64 
     65 impl PaytoImpl for CyclosAccount {
     66     fn as_payto(&self) -> PaytoURI {
     67         PaytoURI::from_parts(CYCLOS, format_args!("/{}/{}", self.root, self.id))
     68     }
     69 
     70     fn parse(raw: &PaytoURI) -> Result<Self, PaytoErr> {
     71         let url = raw.as_ref();
     72         if url.domain() != Some(CYCLOS) {
     73             return Err(PaytoErr::UnsupportedKind(
     74                 CYCLOS,
     75                 url.domain().unwrap_or_default().to_owned(),
     76             ));
     77         }
     78         let Some((root, id)) = url.path().trim_start_matches("/").rsplit_once('/') else {
     79             return Err(PaytoErr::custom(MissingParts));
     80         };
     81 
     82         Ok(CyclosAccount {
     83             id: CyclosId::from_str(id).map_err(PaytoErr::custom)?,
     84             root: CompactString::new(root),
     85         })
     86     }
     87 }
     88 
     89 /// Parse a cyclos payto URI, panic if malformed
     90 pub fn cyclos_payto(url: impl AsRef<str>) -> FullCyclosPayto {
     91     url.as_ref().parse().expect("invalid cyclos payto")
     92 }
     93 
     94 // TODO should we check the root url ?
     95 
     96 pub type CyclosPayto = Payto<CyclosAccount>;
     97 pub type FullCyclosPayto = FullPayto<CyclosAccount>;
     98 pub type TransferCyclosPayto = TransferPayto<CyclosAccount>;
     99 
    100 #[cfg(test)]
    101 mod test {
    102     use crate::payto::cyclos_payto;
    103 
    104     #[test]
    105     pub fn parse() {
    106         let simple = "payto://cyclos/demo.cyclos.org/7762070814194619199?receiver-name=John+Smith";
    107         let payto = cyclos_payto(simple);
    108 
    109         assert_eq!(*payto.id, 7762070814194619199);
    110         assert_eq!(payto.name, "John Smith");
    111         assert_eq!(payto.root, "demo.cyclos.org");
    112 
    113         assert_eq!(payto.to_string(), simple);
    114 
    115         let complex = "payto://cyclos/communities.cyclos.org/utrecht/7762070814194619199?receiver-name=John+Smith";
    116         let payto = cyclos_payto(complex);
    117 
    118         assert_eq!(*payto.id, 7762070814194619199);
    119         assert_eq!(payto.name, "John Smith");
    120         assert_eq!(payto.root, "communities.cyclos.org/utrecht");
    121 
    122         assert_eq!(payto.to_string(), complex);
    123     }
    124 }