armbankrate_parser/
lib.rs

1extern 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}