1use chrono::TimeZone;
2use serde::de::DeserializeOwned;
3use reqwest::{Client, Response, StatusCode};
4use crate::error::Error;
5use crate::query::{Resource, Query};
6use crate::parameter::{data::DataParameter, metadata::MetadataParameter};
7
8
9pub struct ECBDataPortal;
18
19impl ECBDataPortal {
20 fn process_status_code(status_code: &StatusCode) -> Result<(), Error> {
22 match status_code.as_u16() {
23 400 => Err(Error::SC400),
24 404 => Err(Error::SC404),
25 406 => Err(Error::SC406),
26 500 => Err(Error::SC500),
27 501 => Err(Error::SC501),
28 503 => Err(Error::SC503),
29 _ => Ok((),)
30 }
31 }
32
33 async fn process_request(url: &str) -> Result<String, Error> {
35 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";
36 let client: Client = Client::builder().user_agent(user_agent).build()?;
37 let response: Response = client.get(url).send().await?;
38 let status_code: StatusCode = response.status();
40 Self::process_status_code(&status_code)?;
41 if status_code.as_u16() == 304 {
43 return Ok("[]".to_owned()); }
45 let response_body: String = response.text().await?;
47 Ok(response_body)
48 }
49
50 pub async fn get_data<Tz, T>(q: &Query, parameters: Option<Vec<DataParameter<Tz>>>) -> Result<T, Error>
86 where
87 Tz: TimeZone,
88 <Tz as TimeZone>::Offset: std::fmt::Display,
89 T: DeserializeOwned,
90 {
91 q.validate_query(Resource::all_data_resources())?;
92 let mut url: String = q.generate_url()? + "?";
94 if let Some(params) = parameters {
95 url += ¶ms.iter().map(|p| p.to_string() ).collect::<Vec<String>>().join("&");
96 }
97 let response_body: String = Self::process_request(&url).await?;
98 let ecb_response: T = serde_json::from_str(&response_body)?;
99 Ok(ecb_response)
100 }
101
102 pub async fn get_schema(q: &Query) -> Result<String, Error> {
131 q.validate_query(Resource::all_schema_resources())?;
132 let url: String = q.generate_url()?;
133 Self::process_request(&url).await
134 }
135
136 pub async fn get_metadata(q: &Query, parameters: Option<Vec<MetadataParameter>>) -> Result<String, Error> {
164 q.validate_query(Resource::all_metadata_resources())?;
165 let mut url: String = q.generate_url()? + "?";
167 if let Some(params) = parameters {
168 url += ¶ms.iter().map(|p| p.to_string() ).collect::<Vec<String>>().join("&");
169 }
170 Self::process_request(&url).await
171 }
172}
173
174
175#[cfg(test)]
176mod tests {
177 use chrono::{FixedOffset, DateTime, TimeZone};
178 use crate::backend::ECBDataPortal;
179 use crate::query;
180 use crate::parameter::data as pd;
181 use crate::schemas;
182
183 async fn unit_test_set_up(flow_id: &str, series_key: &str) -> schemas::ECBResponse {
185 let q: query::Query = query::Query::new()
187 .flow_ref(query::FlowRef { agency_id: None, flow_id: flow_id.to_owned(), version: None, })
188 .series_key(series_key);
189 let hour: i32 = 3600;
191 let datetime: DateTime<FixedOffset> = FixedOffset::east_opt(1 * hour).unwrap()
192 .with_ymd_and_hms(2009, 05, 15, 14, 15, 0).unwrap();
193 let parameters: Option<Vec<pd::DataParameter<FixedOffset>>> = Some(vec![
194 pd::DataParameter::UpdatedAfter { datetime, },
195 pd::DataParameter::Detail { detail: pd::Detail::DataOnly, },
196 pd::DataParameter::Format { format: pd::Format::JSONData, }
197 ]);
198 ECBDataPortal::get_data(&q, parameters).await.unwrap()
200 }
201
202 #[tokio::test]
203 async fn unit_test_get_data_1() -> () {
204 let ecb_response: schemas::ECBResponse = unit_test_set_up("EXR", "M.USD.EUR.SP00.A").await;
205 assert!(0 < ecb_response.datasets[0].series.iter().last().unwrap().1.observations.as_ref().unwrap().len());
206 assert_eq!(ecb_response.structure.name, "Exchange Rates".to_owned());
207 }
208
209 #[tokio::test]
210 async fn unit_test_get_data_2() -> () {
211 let ecb_response: schemas::ECBResponse = unit_test_set_up("FM", "B.U2.EUR.4F.KR.MLFR.LEV").await;
212 assert!(0 < ecb_response.datasets[0].series.iter().last().unwrap().1.observations.as_ref().unwrap().len());
213 assert_eq!(ecb_response.structure.name, "Financial market data".to_owned());
214 }
215
216 #[tokio::test]
217 async fn unit_test_get_data_3() -> () {
218 let ecb_response: schemas::ECBResponse = unit_test_set_up("CBD2", "Q.B0.W0.11._Z._Z.A.A.A0000._X.ALL.CA._Z.LE._T.EUR").await;
219 assert!(0 < ecb_response.datasets[0].series.iter().last().unwrap().1.observations.as_ref().unwrap().len());
220 assert_eq!(ecb_response.structure.name, "Consolidated Banking data".to_owned());
221 }
222
223 #[tokio::test]
224 async fn unit_test_get_data_4() -> () {
225 let ecb_response: schemas::ECBResponse = unit_test_set_up("PDD", "H.B0.W0.1._T.DDS_ALL._T._Z.N.PN").await;
226 assert!(0 < ecb_response.datasets[0].series.iter().last().unwrap().1.observations.as_ref().unwrap().len());
227 assert_eq!(ecb_response.structure.name, "PDD".to_owned());
228 }
229
230 #[tokio::test]
231 async fn unit_test_get_data_5() -> () {
232 let ecb_response: schemas::ECBResponse = unit_test_set_up("TGB", "M.U4.N.A094T.U2.EUR.A").await;
233 assert!(0 < ecb_response.datasets[0].series.iter().last().unwrap().1.observations.as_ref().unwrap().len());
234 assert_eq!(ecb_response.structure.name, "Target Balances".to_owned());
235 }
236
237 #[tokio::test]
238 async fn unit_test_get_schema() -> () {
239 let q: query::Query = query::Query::new()
241 .resource(query::Resource::Schema)
242 .context(query::Context::DataStructure)
243 .agency_id("ECB")
244 .resource_id("ECB_EXR1")
245 .version("1.0");
246 let schema: String = ECBDataPortal::get_schema(&q).await.unwrap();
248 assert!(!schema.is_empty())
249 }
250
251 #[tokio::test]
252 async fn unit_test_get_metadata() -> () {
253 let q: query::Query = query::Query::new()
255 .resource(query::Resource::MetadataDataStructure)
256 .agency_id("ECB")
257 .resource_id("ECB_EXR1")
258 .version("latest");
259 let metadata: String = ECBDataPortal::get_metadata(&q, None).await.unwrap();
261 assert!(!metadata.is_empty())
262 }
263}