config.rs (5183B)
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::time::Duration; 18 19 use compact_str::CompactString; 20 use taler_api::{ 21 Serve, 22 config::{ApiCfg, DbCfg}, 23 }; 24 use taler_common::{ 25 config::{Config, ValueErr}, 26 map_config, 27 types::{ 28 amount::Currency, 29 payto::{ACH, BankID}, 30 }, 31 }; 32 33 use crate::payto::WiseAccount; 34 35 #[derive(Debug, Clone, Copy)] 36 pub enum AccountType { 37 Exchange, 38 Normal, 39 } 40 41 pub fn parse_db_cfg(cfg: &Config) -> Result<DbCfg, ValueErr> { 42 DbCfg::parse(cfg.section("wisedb-postgres")) 43 } 44 45 pub struct WiseBalance { 46 pub id: u32, 47 pub currency: Currency, 48 pub payto: WiseAccount, 49 } 50 51 fn balances(cfg: &Config) -> Result<Vec<WiseBalance>, ValueErr> { 52 cfg.sections() 53 .filter_map(|s| { 54 (|| { 55 Ok(if s.name.starts_with("wise-balance") { 56 Some(WiseBalance { 57 id: s.number("id").require()?, 58 currency: s.currency("currency").require()?, 59 payto: map_config!(s, "payto type", "PAYTO_TYPE", 60 "iban" => { 61 WiseAccount::IBAN(BankID { 62 iban: s.parse("IBAN", "iban").require()?, 63 bic: s.parse("BIC", "bic").opt()? 64 }) 65 }, 66 "ach" => { 67 WiseAccount::ACH(ACH { 68 routing_number: s.parse("ACH routing number", "routing_number").require()?, 69 account_number: s.parse("ACH account_number", "account_number").require()? 70 }) 71 }, 72 ) 73 .require()?, 74 }) 75 } else { 76 None 77 }) 78 })() 79 .transpose() 80 }) 81 .collect() 82 } 83 84 /// taler-wise setup config 85 pub struct SetupCfg { 86 pub token: String, 87 pub profile_id: Option<u64>, 88 pub name: Option<CompactString>, 89 pub balances: Vec<WiseBalance>, 90 } 91 92 impl SetupCfg { 93 pub fn parse(cfg: &Config) -> Result<Self, ValueErr> { 94 let s = cfg.section("wise-worker"); 95 Ok(Self { 96 profile_id: s.number("PROFILE_ID").opt()?, 97 token: s.str("TOKEN").require()?, 98 name: cfg.section("wise").cstr("NAME").opt()?, 99 balances: balances(cfg)?, 100 }) 101 } 102 } 103 104 /// taler-wise httpd config 105 pub struct ServeCfg { 106 pub name: CompactString, 107 pub serve: Serve, 108 pub balances: Vec<WiseBalance>, 109 pub wire_gateway: Option<ApiCfg>, 110 pub revenue: Option<ApiCfg>, 111 pub observability: Option<ApiCfg>, 112 } 113 114 impl ServeCfg { 115 pub fn parse(cfg: &Config) -> Result<Self, ValueErr> { 116 let s = cfg.section("wise"); 117 Ok(Self { 118 name: s.cstr("NAME").require()?, 119 serve: Serve::parse(&cfg.section("wise-httpd"))?, 120 wire_gateway: ApiCfg::parse(cfg.section("wise-httpd-wire-gateway-api"))?, 121 revenue: ApiCfg::parse(cfg.section("wise-httpd-revenue-api"))?, 122 observability: ApiCfg::parse(cfg.section("wise-httpd-observability-api"))?, 123 balances: balances(cfg)?, 124 }) 125 } 126 } 127 128 /// taler-wise worker config 129 pub struct WorkerCfg { 130 pub profile_id: u64, 131 pub token: String, 132 pub frequency: Duration, 133 pub balances: Vec<WiseBalance>, 134 } 135 136 impl WorkerCfg { 137 pub fn parse(cfg: &Config) -> Result<Self, ValueErr> { 138 let s = cfg.section("wise-worker"); 139 let cfg = Self { 140 profile_id: s.number("PROFILE_ID").require()?, 141 token: s.str("TOKEN").require()?, 142 frequency: s.duration("FREQUENCY").require()?, 143 balances: balances(cfg)?, 144 }; 145 if cfg.balances.is_empty() { 146 return Err(ValueErr::Missing { 147 ty: "balance ID".into(), 148 section: "wise-balance-*".into(), 149 option: "ID".into(), 150 }); 151 } 152 Ok(cfg) 153 } 154 } 155 156 #[cfg(test)] 157 mod tests { 158 use super::*; 159 160 #[test] 161 fn worker_requires_a_balance() { 162 let cfg = 163 Config::from_mem("[wise-worker]\nPROFILE_ID=1\nTOKEN=test\nFREQUENCY=1 min\n").unwrap(); 164 assert!(matches!( 165 WorkerCfg::parse(&cfg), 166 Err(ValueErr::Missing { section, option, .. }) 167 if section == "wise-balance-*" && option == "ID" 168 )); 169 } 170 }