armbankrate_parser/
lib.rs1extern crate core;
2
3mod ardshinbank;
4mod conversebank;
5mod error;
6mod evocabank;
7mod idbank;
8mod inecobank;
9mod unibank;
10pub mod sort;
11
12use std::collections::HashMap;
13use std::fmt::Debug;
14
15use crate::ardshinbank::Ardshinbank;
16use crate::conversebank::Conversebank;
17use crate::evocabank::Evocabank;
18use async_trait::async_trait;
19use enum_dispatch::enum_dispatch;
20use scraper::Html;
21use serde::Serialize;
22use std::str::FromStr;
23use futures::stream::FuturesUnordered;
24use once_cell::sync::Lazy;
25use reqwest::Client;
26use futures::StreamExt;
27
28use crate::error::Error;
29use crate::Error::BankParseFail;
30
31use crate::idbank::Idbank;
32use crate::inecobank::Inecobank;
33
34use crate::unibank::Unibank;
35
36static CLIENT: Lazy<Client> = Lazy::new(|| {
37 Client::builder()
38 .user_agent("Some")
39 .build()
40 .unwrap()
41});
42
43pub async fn parse<T: ToString>(banks: &[T]) -> Result<Vec<Bank>, Error> {
44 let mut banks: Vec<Bank> = banks
45 .iter()
46 .map(|bank| bank_from_str(bank.to_string()))
47 .collect::<Result<Vec<Bank>, Error>>()?;
48
49 parse_banks(&mut banks).await;
50
51 Ok(banks)
52}
53
54pub async fn parse_all() -> Result<Vec<Bank>, Error> {
55 let mut banks: Vec<Bank> = get_bank_vec();
56
57 parse_banks(&mut banks).await;
58
59 Ok(banks)
60}
61
62pub async fn parse_json<T: ToString>(banks: &[T]) -> Result<String, Error> {
63 let mut banks: Vec<Bank> = banks
64 .iter()
65 .map(|bank| bank_from_str(bank.to_string()))
66 .collect::<Result<Vec<Bank>, Error>>()?;
67
68 parse_banks(&mut banks).await;
69
70 json_from(&banks)
71}
72
73pub async fn parse_all_json() -> Result<String, Error> {
74 let mut banks = get_bank_vec();
75
76 parse_banks(&mut banks).await;
77
78 json_from(&banks)
79}
80
81async fn parse_banks(banks: &mut Vec<Bank>) {
82 let futures = FuturesUnordered::new();
83 for bank in banks {
84 futures.push(bank.parse());
85 }
86 futures.collect::<Vec<Result<_, _>>>().await;
87}
88
89fn json_from(banks: &Vec<Bank>) -> Result<String, Error> {
90 let mut bank_map: HashMap<&String, &Bank> = HashMap::with_capacity(banks.len());
91
92 for bank in banks {
93 let bank_name = bank.get_name();
94 bank_map.insert(bank_name, bank);
95 }
96
97 Ok(serde_json::to_string(&bank_map)?)
98}
99
100fn bank_from_str<T: ToString>(s: T) -> Result<Bank, Error> {
101 let s = s.to_string().to_lowercase();
102
103 match s.as_str() {
104 "ardshinbank" => Ok(Ardshinbank::default().into()),
105 "conversebank" => Ok(Conversebank::default().into()),
106 "evocabank" => Ok(Evocabank::default().into()),
107 "idbank" => Ok(Idbank::default().into()),
108 "inecobank" => Ok(Inecobank::default().into()),
109 "unibank" => Ok(Unibank::default().into()),
110 _ => Err(Error::BankNotFound(s)),
111 }
112}
113
114fn get_bank_vec() -> Vec<Bank> {
115 vec![
116 Unibank::default().into(),
117 Conversebank::default().into(),
118 Idbank::default().into(),
119 Evocabank::default().into(),
120 Inecobank::default().into(),
121 Ardshinbank::default().into(),
122 ]
123}
124
125#[async_trait]
126#[enum_dispatch]
127pub trait BankImpl: Send {
128 async fn parse(&mut self) -> Result<(), Error> {
129 let url = self.get_url();
130
131 let response = CLIENT.get(url).send().await?.text().await?;
132 let document = Html::parse_document(&response);
133
134 self.parse_cash(&document)?;
135 self.parse_no_cash(&document)?;
136
137 Ok(())
138 }
139
140 fn parse_cash(&mut self, document: &Html) -> Result<(), Error>;
141 fn parse_no_cash(&mut self, document: &Html) -> Result<(), Error>;
142
143 fn cash_currencies(&self) -> &CurrencyBody;
144 fn no_cash_currencies(&self) -> &CurrencyBody;
145
146 fn get_name(&self) -> &String;
147 fn get_url(&self) -> &String;
148}
149
150#[derive(Default, Debug, Serialize)]
151pub struct Currency {
152 name: CurrencyName,
153 buy: Option<f64>,
154 sell: Option<f64>,
155}
156
157#[derive(Default, Debug)]
158struct BankBody {
159 name: String,
160 url: String,
161}
162
163impl Currency {
164 pub fn new(name: CurrencyName, buy: Option<f64>, sell: Option<f64>) -> Self {
165 Self { name, buy, sell }
166 }
167
168 pub fn buy(&self) -> &Option<f64> {
169 &self.buy
170 }
171
172 pub fn sell(&self) -> &Option<f64> {
173 &self.sell
174 }
175}
176
177#[derive(Debug, Clone, Serialize)]
178pub enum CurrencyName {
179 USD,
180 GBP,
181 EUR,
182 RUB,
183}
184
185impl Default for CurrencyName {
186 fn default() -> Self {
187 Self::RUB
188 }
189}
190
191impl FromStr for CurrencyName {
192 type Err = error::Error;
193
194 fn from_str(value: &str) -> Result<Self, Self::Err> {
195 let value = value.to_uppercase();
196
197 match value.as_str() {
198 "USD" => Ok(CurrencyName::USD),
199 "RUR" | "RUB" => Ok(CurrencyName::RUB),
200 "GBP" => Ok(CurrencyName::GBP),
201 "EUR" => Ok(CurrencyName::EUR),
202 _ => Err(Error::CurrencyNotFound(value)),
203 }
204 }
205}
206
207#[derive(Default, Debug, Serialize)]
208pub struct CurrencyBody {
209 usd: Currency,
210 gbp: Currency,
211 eur: Currency,
212 rub: Currency,
213}
214
215impl CurrencyBody {
216 pub fn get_usd_rate(&self) -> &Currency {
217 &self.usd
218 }
219
220 pub fn get_gbp_rate(&self) -> &Currency {
221 &self.gbp
222 }
223
224 pub fn get_eur_rate(&self) -> &Currency {
225 &self.eur
226 }
227
228 pub fn get_rub_rate(&self) -> &Currency {
229 &self.rub
230 }
231
232 pub fn fill_from_currency(&mut self, currency: Currency) {
233 match currency.name {
234 CurrencyName::USD => self.usd = currency,
235 CurrencyName::GBP => self.gbp = currency,
236 CurrencyName::EUR => self.eur = currency,
237 CurrencyName::RUB => self.rub = currency,
238 }
239 }
240}
241
242#[enum_dispatch(BankImpl)]
243#[derive(Debug, Serialize)]
244#[serde(untagged)]
245pub enum Bank {
246 Ardshinbank,
247 Inecobank,
248 Evocabank,
249 Idbank,
250 Conversebank,
251 Unibank,
252}
253
254#[derive(Debug, Clone)]
255pub enum CurrencyType {
256 Cash,
257 Noncash,
258}