exchangerateapi_rust/
lib.rs1use reqwest::Url;
2use serde::Deserialize;
3use thiserror::Error;
4
5#[derive(Debug, Error)]
6pub enum Error {
7 #[error("http: {0}")]
8 Http(#[from] reqwest::Error),
9 #[error("api: {0}")]
10 Api(String),
11 #[error("url: {0}")]
12 Url(#[from] url::ParseError),
13}
14
15#[derive(Debug, Deserialize)]
16pub struct RatesResponse {
17 #[serde(default)]
18 pub base: Option<String>,
19 #[serde(default)]
20 pub date: Option<String>,
21 #[serde(default)]
22 pub rates: serde_json::Value,
23 #[serde(default)]
24 pub error: Option<serde_json::Value>,
25}
26
27pub struct Client {
28 api_key: String,
29 base_url: Url,
30 http: reqwest::Client,
31}
32
33impl Client {
34 pub fn new<S: Into<String>>(api_key: S) -> Result<Self, Error> {
35 let api_key = api_key.into();
36 let base_url = Url::parse("https://api.exchangerateapi.net/v1")?;
37 Ok(Self { api_key, base_url, http: reqwest::Client::new() })
38 }
39
40 pub fn with_base_url<S: Into<String>>(api_key: S, base_url: S) -> Result<Self, Error> {
41 let api_key = api_key.into();
42 let base_url = Url::parse(&base_url.into())?;
43 Ok(Self { api_key, base_url, http: reqwest::Client::new() })
44 }
45
46 pub async fn latest(&self, base: &str, symbols: Option<&[&str]>) -> Result<RatesResponse, Error> {
47 let mut url = self.base_url.join("latest")?;
48 {
49 let mut qp = url.query_pairs_mut();
50 qp.append_pair("base", base);
51 qp.append_pair("apikey", &self.api_key);
52 if let Some(s) = symbols { if !s.is_empty() { qp.append_pair("symbols", &s.join(",")); } }
53 }
54 self.get(url).await
55 }
56
57 pub async fn historical(&self, date: &str, base: &str, symbols: Option<&[&str]>) -> Result<RatesResponse, Error> {
58 let mut url = self.base_url.join("historical")?;
59 {
60 let mut qp = url.query_pairs_mut();
61 qp.append_pair("date", date);
62 qp.append_pair("base", base);
63 qp.append_pair("apikey", &self.api_key);
64 if let Some(s) = symbols { if !s.is_empty() { qp.append_pair("symbols", &s.join(",")); } }
65 }
66 self.get(url).await
67 }
68
69 async fn get(&self, url: Url) -> Result<RatesResponse, Error> {
70 let res = self.http.get(url).send().await?.error_for_status()?;
71 let data: RatesResponse = res.json().await?;
72 if let Some(err) = &data.error {
73 let msg = err.get("message").and_then(|m| m.as_str()).unwrap_or("API error");
74 return Err(Error::Api(msg.to_string()));
75 }
76 Ok(data)
77 }
78}