diff --git a/src/api.rs b/src/api.rs index 48fd279..ad7b362 100644 --- a/src/api.rs +++ b/src/api.rs @@ -55,6 +55,7 @@ mod raw; pub mod endpoint_prelude; pub mod common; +pub mod currencies; pub mod dividends; pub mod eod; pub mod paged; diff --git a/src/api/currencies.rs b/src/api/currencies.rs new file mode 100644 index 0000000..7d695c3 --- /dev/null +++ b/src/api/currencies.rs @@ -0,0 +1,112 @@ +//! Implementation of the `currencies` API endpoint. +//! +//! This endpoint is used to lookup the currencies supported by Marketstack. + +use derive_builder::Builder; + +use crate::api::paged::PaginationError; +use crate::api::{endpoint_prelude::*, ApiError}; + +/// Query for `currencies`. +#[derive(Debug, Clone, Builder)] +#[builder(setter(strip_option))] +pub struct Currencies { + /// Pagination limit for API request. + #[builder(setter(name = "_limit"), default)] + limit: Option, + /// Pagination offset value for API request. + #[builder(default)] + offset: Option, +} + +impl Currencies { + /// Create a builder for this endpoint. + pub fn builder() -> CurrenciesBuilder { + CurrenciesBuilder::default() + } +} + +impl CurrenciesBuilder { + /// Limit the number of results returned. + pub fn limit(&mut self, limit: u16) -> Result<&mut Self, ApiError> { + let new = self; + new.limit = Some(Some(PageLimit::new(limit)?)); + Ok(new) + } +} + +impl<'a> Endpoint for Currencies { + fn method(&self) -> Method { + Method::GET + } + + fn endpoint(&self) -> Cow<'static, str> { + "currencies".into() + } + + fn parameters(&self) -> QueryParams { + let mut params = QueryParams::default(); + + params + .push_opt("limit", self.limit.clone()) + .push_opt("offset", self.offset); + + params + } +} + +#[cfg(test)] +mod tests { + + use crate::api::currencies::Currencies; + use crate::api::{self, Query}; + use crate::test::client::{ExpectedUrl, SingleTestClient}; + + #[test] + fn currencies_defaults_are_sufficient() { + Currencies::builder().build().unwrap(); + } + + #[test] + fn currencies_endpoint() { + let endpoint = ExpectedUrl::builder() + .endpoint("currencies") + .build() + .unwrap(); + let client = SingleTestClient::new_raw(endpoint, ""); + + let endpoint = Currencies::builder().build().unwrap(); + api::ignore(endpoint).query(&client).unwrap(); + } + + #[test] + fn currencies_limit() { + let endpoint = ExpectedUrl::builder() + .endpoint("currencies") + .add_query_params(&[("limit", "50")]) + .build() + .unwrap(); + let client = SingleTestClient::new_raw(endpoint, ""); + + let endpoint = Currencies::builder().limit(50).unwrap().build().unwrap(); + api::ignore(endpoint).query(&client).unwrap(); + } + + #[test] + fn currencies_over_limit() { + assert!(Currencies::builder().limit(9999).is_err()); + } + + #[test] + fn currencies_offset() { + let endpoint = ExpectedUrl::builder() + .endpoint("currencies") + .add_query_params(&[("offset", "2")]) + .build() + .unwrap(); + let client = SingleTestClient::new_raw(endpoint, ""); + + let endpoint = Currencies::builder().offset(2).build().unwrap(); + api::ignore(endpoint).query(&client).unwrap(); + } +} diff --git a/src/types.rs b/src/types.rs index e49a94b..967ac37 100644 --- a/src/types.rs +++ b/src/types.rs @@ -101,11 +101,31 @@ pub struct DividendsData { pub data: Vec, } +/// Rust representation of single data item from Marketstack `currencies` response. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct CurrenciesDataItem { + /// 3-letter code of the given currency. + pub code: String, + /// Name of the given currency. + pub name: String, + /// Text symbol of the given currency. + pub symbol: String, +} + +/// Rust representation of the JSON response from `currencies` marketstack endpoint. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct CurrenciesData { + /// Corresponds to pagination entry from JSON response from marketstack. + pub pagination: PaginationInfo, + /// Corresponds to data entry from JSON response from marketstack. + pub data: Vec, +} + #[cfg(test)] mod tests { use chrono::NaiveDate; - use crate::{DividendsData, EodData, SplitsData}; + use crate::{CurrenciesData, DividendsData, EodData, SplitsData}; #[test] fn test_deserialize_eod() { @@ -243,4 +263,39 @@ mod tests { NaiveDate::from_ymd_opt(2023, 8, 11).unwrap() ); } + + #[test] + fn test_deserialize_currencies() { + let json_data = r#"{ + "pagination": { + "limit": 3, + "offset": 0, + "count": 3, + "total": 42 + }, + "data": [ + { + "code": "USD", + "symbol": "$", + "name": "US Dollar" + }, + { + "code": "ARS", + "symbol": "AR$", + "name": "Argentine Peso" + }, + { + "code": "EUR", + "symbol": "€", + "name": "Euro" + } + ] + }"#; + + let currencies_data: CurrenciesData = serde_json::from_str(json_data).unwrap(); + assert_eq!(currencies_data.pagination.limit, 3); + assert_eq!(currencies_data.data[0].code, "USD"); + assert_eq!(currencies_data.data[0].symbol, "$"); + assert_eq!(currencies_data.data[0].name, "US Dollar"); + } } diff --git a/tests/currencies.rs b/tests/currencies.rs new file mode 100644 index 0000000..05c8410 --- /dev/null +++ b/tests/currencies.rs @@ -0,0 +1,44 @@ +use marketstack::api::{currencies, AsyncQuery, Query}; +use marketstack::{AsyncMarketstack, CurrenciesData, Marketstack}; + +mod setup; + +#[test] +#[ignore] +fn test_currencies() { + let api_key = setup::setup_key(); + let client = Marketstack::new_insecure("api.marketstack.com", api_key).unwrap(); + + let endpoint = currencies::Currencies::builder() + .limit(3) + .unwrap() + .build() + .unwrap(); + let currencies_result: CurrenciesData = endpoint.query(&client).unwrap(); + + assert_eq!(currencies_result.pagination.limit, 3); + assert_eq!(currencies_result.pagination.offset, 0); + + assert_eq!(currencies_result.data.len(), 3); +} + +#[tokio::test] +#[ignore] +async fn test_async_currencies() { + let api_key = setup::setup_key(); + let client = AsyncMarketstack::new_insecure("api.marketstack.com", api_key) + .await + .unwrap(); + + let endpoint = currencies::Currencies::builder() + .limit(3) + .unwrap() + .build() + .unwrap(); + let currencies_result: CurrenciesData = endpoint.query_async(&client).await.unwrap(); + + assert_eq!(currencies_result.pagination.limit, 3); + assert_eq!(currencies_result.pagination.offset, 0); + + assert_eq!(currencies_result.data.len(), 3); +}