iadb_api/
utils.rs

1use std::fmt;
2use reqwest::{Client, Response};
3use csv::{Reader, ReaderBuilder, StringRecord};
4use crate::error::Error;
5use crate::{BASE_URL, schemas::{IADBSeries, IADBDataPoint}};
6
7
8#[derive(Debug)]
9pub enum CSVF {
10    TT,
11    TN,
12    CT,
13    CN,
14}
15
16impl fmt::Display for CSVF {
17    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18        match self {
19            CSVF::TT => write!(f, "TT"),
20            CSVF::TN => write!(f, "TN"),
21            CSVF::CT => write!(f, "CT"),
22            CSVF::CN => write!(f, "CN"),
23        }
24    }
25}
26
27
28#[derive(Debug)]
29pub enum VPD {
30    Y,
31    N,
32}
33
34impl fmt::Display for VPD {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        match self {
37            VPD::Y => write!(f, "Y"),
38            VPD::N => write!(f, "N"),
39        }
40    }
41}
42
43
44#[derive(Debug)]
45pub enum Param<'a> {
46    DateFrom { v: &'a String },
47    DateTo { v: &'a String },
48    CSVF { v: &'a CSVF },
49    UsingCodes { v: &'a String },
50    VPD { v: &'a VPD },
51    VFD { v: &'a String },
52}
53
54impl<'a> Param<'a> {
55    fn add_param_to_url(&self, url: &mut String) -> () {
56        let url_param: String = match self {
57            Param::DateFrom { v } => format!("&Datefrom={}", v),
58            Param::DateTo { v } => format!("&Dateto={}", v),
59            Param::CSVF { v } => format!("&CSVF={}", v.to_string()),
60            Param::UsingCodes { v } => format!("&UsingCodes={}", v),
61            Param::VPD { v } => format!("&VPD={}", v.to_string()),
62            Param::VFD { v } => format!("&VFD={}", v),
63        };
64        url.push_str(&url_param);
65    }
66}
67
68
69/// Make a request to the provided URL, validate the status code of the response, and return deserialized data.
70async fn process_request(url: String) -> Result<Vec<IADBDataPoint>, Error> {
71    let user_agent: &str = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.90 Safari/537.36";
72    let client: Client = Client::builder().user_agent(user_agent).build()?;
73    let response: Response = client.get(url).send().await?;
74    let response_body: String = response.text().await?;
75    // Deserialize the CSV
76    let mut rdr: Reader<&[u8]> = ReaderBuilder::new().has_headers(true).from_reader(response_body.as_bytes());
77    // Replace default CSV headers
78    let _: &StringRecord = rdr.headers()?;
79    rdr.set_headers(StringRecord::from(vec!["date", "value"]));
80    // Deserialize data
81    let mut data: Vec<IADBDataPoint> = Vec::<IADBDataPoint>::new();
82    for entry in rdr.deserialize() {
83        let entry: IADBDataPoint = entry?;
84        data.push(entry);
85    }
86    Ok(data)
87}
88
89
90/// Constructs a URL for API request, sends the request, and returns the deserialzied response.
91///
92/// # Input
93/// - `series_code`: Code of the time series in the IADB.
94/// - `params`: List of parameters expected by the IADB API endpoint
95/// - `additional_params`: Additional parameters to add to the request
96pub async fn call_api_endpoint<'a>(series_code: &String, description: &Option<String>, params: Vec<Param<'a>>, additional_params: Option<String>) -> Result<IADBSeries, Error> {
97    // Set up a URL for the API endpoint
98    let mut url: String = String::from(BASE_URL);
99    // Add parameters to the URL
100    url.push_str(&format!("?csv.x=yes&SeriesCodes={}", series_code));
101    for param in params {
102        param.add_param_to_url(&mut url);
103    }
104    // Add additional parameters to the URL
105    match additional_params {
106        Some(v) => url.push_str(&v),
107        None => (),
108    }
109    // Process API response
110    let description: String = match description {
111        Some(v) => v.clone(),
112        None => String::from(""),
113    };
114    let data: Vec<IADBDataPoint> = process_request(url).await?;
115    Ok(IADBSeries { name: series_code.to_string(), description: description, data, })
116}
117
118
119#[cfg(test)]
120mod tests {
121    use tokio;
122
123    #[tokio::test]
124    async fn unit_test_request() -> () {
125        use reqwest::{Client, Response};
126        let user_agent: &str = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.90 Safari/537.36";
127        let client: Client = Client::builder().user_agent(user_agent).build().unwrap();
128        // Request
129        let url: String = String::from("http://www.bankofengland.co.uk/boeapps/iadb/fromshowcolumns.asp?csv.x=yes&Datefrom=01/Jan/2000&Dateto=01/Oct/2018&SeriesCodes=IUMBV34,IUMBV37,IUMBV42,IUMBV45&CSVF=TT&UsingCodes=Y&VPD=Y&VFD=N");
130        let response: Response = client.get(url).send().await.unwrap();
131        let response_body: String = response.text().await.unwrap();
132        println!("{}", response_body)
133    }
134}