libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

config.rs (11009B)


      1 /*
      2 * This file is part of LibEuFin.
      3 * Copyright (C) 2026 Taler Systems S.A.
      4 
      5 * LibEuFin is free software; you can redistribute it and/or modify
      6 * it under the terms of the GNU Affero General Public License as
      7 * published by the Free Software Foundation; either version 3, or
      8 * (at your option) any later version.
      9 
     10 * LibEuFin is distributed in the hope that it will be useful, but
     11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
     12 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
     13 * Public License for more details.
     14 
     15 * You should have received a copy of the GNU Affero General Public
     16 * License along with LibEuFin; see the file COPYING.  If not, see
     17 * <http://www.gnu.org/licenses/>
     18 */
     19 
     20 use std::{cell::OnceCell, time::Duration};
     21 
     22 use compact_str::CompactString;
     23 use jiff::{
     24     Timestamp,
     25     civil::{Date, Time},
     26 };
     27 use libeufin_ebics::{
     28     config::{EbicsHostCfg, EbicsKeysCfg, EbicsSetupCfg},
     29     dialect::Dialect,
     30     keys::{BankKeys, ClientKeys},
     31 };
     32 use regex::Regex;
     33 use taler_api::{
     34     Serve,
     35     config::{ApiCfg, DbCfg},
     36 };
     37 use taler_common::{
     38     config::{CfgErr, Config},
     39     types::{
     40         amount::{Amount, Currency},
     41         iban::IBAN,
     42         payto::{BankID, FullIbanPayto},
     43         utils::date_to_utc_ts,
     44     },
     45 };
     46 use taler_macros::EnumMeta;
     47 
     48 pub fn parse_db_cfg(cfg: &Config) -> Result<DbCfg, CfgErr> {
     49     DbCfg::parse(cfg.section("libeufin-nexusdb-postgres"))
     50 }
     51 
     52 pub struct NexusKeysCfg {
     53     pub bank: String,
     54     pub client: String,
     55 }
     56 
     57 impl NexusKeysCfg {
     58     pub fn parse(cfg: &Config) -> Result<Self, CfgErr> {
     59         let s = cfg.section("nexus-ebics");
     60         Ok(Self {
     61             bank: s.path("bank_public_keys_file").require()?,
     62             client: s.path("client_private_keys_file").require()?,
     63         })
     64     }
     65 
     66     pub fn ebics<'a>(&'a self) -> EbicsKeysCfg<'a> {
     67         EbicsKeysCfg {
     68             bank: self.bank.as_str(),
     69             client: self.client.as_str(),
     70         }
     71     }
     72 }
     73 
     74 #[derive(Clone)]
     75 pub struct NexusHostCfg {
     76     pub base_url: url::Url,
     77     pub unix_path: Option<String>,
     78     pub host_id: CompactString,
     79     pub user_id: CompactString,
     80     pub partner_id: CompactString,
     81 }
     82 
     83 impl NexusHostCfg {
     84     pub fn parse(cfg: &Config) -> Result<Self, CfgErr> {
     85         let s = cfg.section("nexus-ebics");
     86         Ok(Self {
     87             base_url: s.url("host_base_url").require()?,
     88             unix_path: s.path("UNIXPATH").opt()?,
     89             host_id: s.cstr("host_id").require()?,
     90             user_id: s.cstr("user_id").require()?,
     91             partner_id: s.cstr("partner_id").require()?,
     92         })
     93     }
     94 
     95     pub fn ebics<'a>(&'a self) -> EbicsHostCfg<'a> {
     96         EbicsHostCfg {
     97             base_url: self.base_url.as_str(),
     98             unix_path: self.unix_path.as_deref(),
     99             host_id: &self.host_id,
    100             user_id: &self.user_id,
    101             partner_id: &self.partner_id,
    102         }
    103     }
    104 }
    105 
    106 #[derive(Debug, Clone, Copy, EnumMeta)]
    107 #[enum_meta(Str)]
    108 #[allow(non_camel_case_types)]
    109 pub enum AccountType {
    110     exchange,
    111     normal,
    112 }
    113 
    114 pub struct NexusIngestCfg {
    115     pub account_type: AccountType,
    116     pub ignore_txs_before: Timestamp,
    117     pub ignore_bounces_before: Timestamp,
    118     pub restriction_payto_regex: Option<Regex>,
    119     pub bounce_deduce_fee: bool,
    120     pub bounce_fee: Amount,
    121     pub currency: Currency,
    122 }
    123 
    124 impl NexusIngestCfg {
    125     pub const fn simple(account_type: AccountType, currency: &Currency) -> Self {
    126         Self {
    127             account_type,
    128             ignore_txs_before: Timestamp::UNIX_EPOCH,
    129             ignore_bounces_before: Timestamp::UNIX_EPOCH,
    130             restriction_payto_regex: None,
    131             bounce_deduce_fee: false,
    132             bounce_fee: Amount::zero(currency),
    133             currency: Currency::KUDOS,
    134         }
    135     }
    136 }
    137 
    138 pub struct NexusFetchCfg {
    139     pub frequency: Duration,
    140     pub frequency_raw: String,
    141     pub checkpoint_time: Time,
    142     pub ignore_txs_before: Timestamp,
    143     pub ignore_bounces_before: Timestamp,
    144     pub restriction_payto_regex: Option<Regex>,
    145     pub bounce_deduce_fee: bool,
    146     pub bounce_fee: Amount,
    147 }
    148 
    149 impl NexusFetchCfg {
    150     pub fn parse(cfg: &Config, currency: &Currency) -> Result<Self, CfgErr> {
    151         let s = cfg.section("nexus-fetch");
    152 
    153         Ok(Self {
    154             frequency: s.duration("frequency").require()?,
    155             frequency_raw: s.str("frequency").require()?,
    156             checkpoint_time: s.time("checkpoint_time_of_day").require()?,
    157             ignore_txs_before: date_to_utc_ts(
    158                 &s.date("ignore_transactions_before").default(Date::ZERO)?,
    159             ),
    160             ignore_bounces_before: date_to_utc_ts(
    161                 &s.date("ignore_bounces_before").default(Date::ZERO)?,
    162             ),
    163             restriction_payto_regex: s.regex("restriction_payto_regex").opt()?,
    164             bounce_deduce_fee: s.boolean("bounce_deduce_fee").default(false)?,
    165             bounce_fee: s
    166                 .amount("bounce_fee", currency)
    167                 .default(Amount::zero(currency))?,
    168         })
    169     }
    170 }
    171 
    172 pub struct NexusSubmitCfg {
    173     pub frequency: Duration,
    174     pub frequency_raw: String,
    175     pub require_ack: bool,
    176 }
    177 
    178 impl NexusSubmitCfg {
    179     pub fn parse(cfg: &Config) -> Result<Self, CfgErr> {
    180         let s = cfg.section("nexus-submit");
    181 
    182         Ok(Self {
    183             frequency: s.duration("frequency").require()?,
    184             frequency_raw: s.str("frequency").require()?,
    185             require_ack: s.boolean("manual_ack").default(false)?,
    186         })
    187     }
    188 }
    189 
    190 #[derive(Debug, Clone)]
    191 pub struct NexusEbicsConfig {
    192     pub account: FullIbanPayto,
    193     pub qr_iban: Option<IBAN>,
    194     pub dialect: Dialect,
    195 }
    196 
    197 impl NexusEbicsConfig {
    198     pub fn parse(cfg: &Config) -> Result<Self, CfgErr> {
    199         let s = cfg.section("nexus-ebics");
    200         Ok(Self {
    201             account: FullIbanPayto::new(
    202                 BankID {
    203                     iban: s.parse("IBAN", "iban").require()?,
    204                     bic: Some(s.parse("BIC", "bic").require()?),
    205                 },
    206                 &s.str("name").require()?,
    207             ),
    208             dialect: s.parse("bank dialect", "bank_dialect").require()?,
    209             qr_iban: s.parse("IBAN", "qr_iban").opt()?,
    210         })
    211     }
    212 }
    213 
    214 pub struct NexusSetupConfig {
    215     pub enc: Option<Vec<u8>>,
    216     pub auth: Option<Vec<u8>>,
    217 }
    218 
    219 impl NexusSetupConfig {
    220     pub fn parse(cfg: &Config) -> Result<Self, CfgErr> {
    221         let s = cfg.section("nexus-setup");
    222         Ok(Self {
    223             enc: s.hex("bank_encryption_pub_key_hash").opt()?,
    224             auth: s.hex("bank_authentication_pub_key_hash").opt()?,
    225         })
    226     }
    227 }
    228 
    229 pub struct NexusCfg {
    230     pub cfg: Config,
    231     pub currency: Currency,
    232     pub account_type: AccountType,
    233     pub keys: OnceCell<NexusKeysCfg>,
    234     pub host: OnceCell<NexusHostCfg>,
    235     pub fetch: OnceCell<NexusFetchCfg>,
    236     pub submit: OnceCell<NexusSubmitCfg>,
    237     pub ebics: OnceCell<NexusEbicsConfig>,
    238     pub setup: OnceCell<NexusSetupConfig>,
    239     pub wire_cfg: Option<ApiCfg>,
    240     pub revenue_cfg: Option<ApiCfg>,
    241     pub observability_cfg: Option<ApiCfg>,
    242     pub db_cfg: DbCfg,
    243     pub serve_cfg: Serve,
    244 }
    245 
    246 impl<'a> NexusCfg {
    247     pub fn parse(cfg: &Config) -> Result<Self, CfgErr> {
    248         let s = cfg.section("nexus-ebics");
    249         Ok(Self {
    250             currency: s.currency("currency").require()?,
    251             account_type: s.parse("account type", "ACCOUNT_TYPE").require()?,
    252             wire_cfg: ApiCfg::parse(cfg.section("nexus-httpd-wire-gateway-api"))?,
    253             revenue_cfg: ApiCfg::parse(cfg.section("nexus-httpd-revenue-api"))?,
    254             observability_cfg: ApiCfg::parse(cfg.section("nexus-httpd-observability-api"))?,
    255             serve_cfg: Serve::parse(&cfg.section("nexus-httpd"))?,
    256             keys: OnceCell::new(),
    257             host: OnceCell::new(),
    258             fetch: OnceCell::new(),
    259             submit: OnceCell::new(),
    260             ebics: OnceCell::new(),
    261             setup: OnceCell::new(),
    262             db_cfg: parse_db_cfg(cfg)?,
    263             cfg: cfg.clone(),
    264         })
    265     }
    266 
    267     pub fn keys(&self) -> Result<&NexusKeysCfg, CfgErr> {
    268         // TODO use get_or_try_init when stable
    269         if let Some(keys) = self.keys.get() {
    270             return Ok(keys);
    271         }
    272         let keys = NexusKeysCfg::parse(&self.cfg)?;
    273         self.keys.set(keys).ok();
    274         Ok(self.keys.get().unwrap())
    275     }
    276 
    277     pub fn host(&self) -> Result<&NexusHostCfg, CfgErr> {
    278         // TODO use get_or_try_init when stable
    279         if let Some(host) = self.host.get() {
    280             return Ok(host);
    281         }
    282         let host = NexusHostCfg::parse(&self.cfg)?;
    283         self.host.set(host).ok();
    284         Ok(self.host.get().unwrap())
    285     }
    286 
    287     pub fn fetch(&self) -> Result<&NexusFetchCfg, CfgErr> {
    288         // TODO use get_or_try_init when stable
    289         if let Some(fetch) = self.fetch.get() {
    290             return Ok(fetch);
    291         }
    292         let fetch = NexusFetchCfg::parse(&self.cfg, &self.currency)?;
    293         self.fetch.set(fetch).ok();
    294         Ok(self.fetch.get().unwrap())
    295     }
    296 
    297     pub fn submit(&self) -> Result<&NexusSubmitCfg, CfgErr> {
    298         // TODO use get_or_try_init when stable
    299         if let Some(submit) = self.submit.get() {
    300             return Ok(submit);
    301         }
    302         let submit = NexusSubmitCfg::parse(&self.cfg)?;
    303         self.submit.set(submit).ok();
    304         Ok(self.submit.get().unwrap())
    305     }
    306 
    307     pub fn ebics(&self) -> Result<&NexusEbicsConfig, CfgErr> {
    308         // TODO use get_or_try_init when stable
    309         if let Some(ebics) = self.ebics.get() {
    310             return Ok(ebics);
    311         }
    312         let ebics = NexusEbicsConfig::parse(&self.cfg)?;
    313         self.ebics.set(ebics).ok();
    314         Ok(self.ebics.get().unwrap())
    315     }
    316 
    317     pub fn setup(&self) -> Result<&NexusSetupConfig, CfgErr> {
    318         // TODO use get_or_try_init when stable
    319         if let Some(setup) = self.setup.get() {
    320             return Ok(setup);
    321         }
    322         let setup = NexusSetupConfig::parse(&self.cfg)?;
    323         self.setup.set(setup).ok();
    324         Ok(self.setup.get().unwrap())
    325     }
    326 
    327     pub fn ingest(&self) -> Result<NexusIngestCfg, CfgErr> {
    328         let fetch = self.fetch()?;
    329         Ok(NexusIngestCfg {
    330             account_type: self.account_type,
    331             ignore_txs_before: fetch.ignore_txs_before,
    332             ignore_bounces_before: fetch.ignore_bounces_before,
    333             restriction_payto_regex: fetch.restriction_payto_regex.clone(),
    334             bounce_deduce_fee: fetch.bounce_deduce_fee,
    335             bounce_fee: fetch.bounce_fee,
    336             currency: self.currency,
    337         })
    338     }
    339 
    340     pub fn ebics_setup(&'a self) -> Result<EbicsSetupCfg<'a>, CfgErr> {
    341         let setup = self.setup()?;
    342         Ok(EbicsSetupCfg {
    343             keys: self.keys()?.ebics(),
    344             host: self.host()?.ebics(),
    345             enc: setup.enc.as_deref(),
    346             auth: setup.auth.as_deref(),
    347         })
    348     }
    349 
    350     pub fn expect_full_keys(&self) -> Result<(ClientKeys, BankKeys), CfgErr> {
    351         libeufin_ebics::keys::expect_full_keys(&self.keys()?.ebics(), "libeufin-nexus ebics-setup")
    352     }
    353 }