From c9af5851140d26641db05bb235dea6e97710514f Mon Sep 17 00:00:00 2001 From: Reuben Wong Date: Sat, 21 Oct 2023 18:10:45 +0800 Subject: [PATCH] Implement timezones endpoint --- Cargo.toml | 2 +- src/api.rs | 1 + src/api/timezones.rs | 112 +++++++++++++++++++++++++++++++++++++++++++ src/types.rs | 56 +++++++++++++++++++++- tests/timezones.rs | 44 +++++++++++++++++ 5 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 src/api/timezones.rs create mode 100644 tests/timezones.rs diff --git a/Cargo.toml b/Cargo.toml index b99e286..15143fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "marketstack" -version = "0.0.6" +version = "0.0.7" edition = "2021" license = "MIT" description = "Rust bindings for Marketstack REST API" diff --git a/src/api.rs b/src/api.rs index ad7b362..c473448 100644 --- a/src/api.rs +++ b/src/api.rs @@ -60,6 +60,7 @@ pub mod dividends; pub mod eod; pub mod paged; pub mod splits; +pub mod timezones; pub use self::client::AsyncClient; pub use self::client::Client; diff --git a/src/api/timezones.rs b/src/api/timezones.rs new file mode 100644 index 0000000..95fba1a --- /dev/null +++ b/src/api/timezones.rs @@ -0,0 +1,112 @@ +//! Implementation of the `timezones` API endpoint. +//! +//! This endpoint is used to lookup the timezones supported by Marketstack. + +use derive_builder::Builder; + +use crate::api::paged::PaginationError; +use crate::api::{endpoint_prelude::*, ApiError}; + +/// Query for `timezones`. +#[derive(Debug, Clone, Builder)] +#[builder(setter(strip_option))] +pub struct Timezones { + /// Pagination limit for API request. + #[builder(setter(name = "_limit"), default)] + limit: Option, + /// Pagination offset value for API request. + #[builder(default)] + offset: Option, +} + +impl Timezones { + /// Create a builder for this endpoint. + pub fn builder() -> TimezonesBuilder { + TimezonesBuilder::default() + } +} + +impl TimezonesBuilder { + /// 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 Endpoint for Timezones { + fn method(&self) -> Method { + Method::GET + } + + fn endpoint(&self) -> Cow<'static, str> { + "timezones".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::timezones::Timezones; + use crate::api::{self, Query}; + use crate::test::client::{ExpectedUrl, SingleTestClient}; + + #[test] + fn timezones_defaults_are_sufficient() { + Timezones::builder().build().unwrap(); + } + + #[test] + fn timezones_endpoint() { + let endpoint = ExpectedUrl::builder() + .endpoint("timezones") + .build() + .unwrap(); + let client = SingleTestClient::new_raw(endpoint, ""); + + let endpoint = Timezones::builder().build().unwrap(); + api::ignore(endpoint).query(&client).unwrap(); + } + + #[test] + fn timezones_limit() { + let endpoint = ExpectedUrl::builder() + .endpoint("timezones") + .add_query_params(&[("limit", "50")]) + .build() + .unwrap(); + let client = SingleTestClient::new_raw(endpoint, ""); + + let endpoint = Timezones::builder().limit(50).unwrap().build().unwrap(); + api::ignore(endpoint).query(&client).unwrap(); + } + + #[test] + fn timezones_over_limit() { + assert!(Timezones::builder().limit(9999).is_err()); + } + + #[test] + fn timezones_offset() { + let endpoint = ExpectedUrl::builder() + .endpoint("timezones") + .add_query_params(&[("offset", "2")]) + .build() + .unwrap(); + let client = SingleTestClient::new_raw(endpoint, ""); + + let endpoint = Timezones::builder().offset(2).build().unwrap(); + api::ignore(endpoint).query(&client).unwrap(); + } +} diff --git a/src/types.rs b/src/types.rs index 967ac37..727e674 100644 --- a/src/types.rs +++ b/src/types.rs @@ -121,11 +121,31 @@ pub struct CurrenciesData { pub data: Vec, } +/// Rust representation of single data item from Marketstack `timezones` response. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TimezonesDataItem { + /// Name of the given timezone. + pub timezone: String, + /// Abbreviation of the given timezone. + pub abbr: String, + /// Summer time abbreviation of the given timezone. + pub abbr_dst: String, +} + +/// Rust representation of the JSON response from `timezones` marketstack endpoint. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TimezonesData { + /// 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::{CurrenciesData, DividendsData, EodData, SplitsData}; + use crate::{CurrenciesData, DividendsData, EodData, SplitsData, TimezonesData}; #[test] fn test_deserialize_eod() { @@ -298,4 +318,38 @@ mod tests { assert_eq!(currencies_data.data[0].symbol, "$"); assert_eq!(currencies_data.data[0].name, "US Dollar"); } + + #[test] + fn test_deserialize_timezones() { + let json_data = r#"{ + "pagination": { + "limit": 3, + "offset": 0, + "count": 3, + "total": 57 + }, + "data": [ + { + "timezone": "America/New_York", + "abbr": "EST", + "abbr_dst": "EDT" + }, + { + "timezone": "America/Argentina/Buenos_Aires", + "abbr": "-03", + "abbr_dst": "-03" + }, + { + "timezone": "Europe/Vienna", + "abbr": "CET", + "abbr_dst": "CEST" + } + ] + }"#; + + let timezones_data: TimezonesData = serde_json::from_str(json_data).unwrap(); + assert_eq!(timezones_data.data[0].timezone, "America/New_York"); + assert_eq!(timezones_data.data[0].abbr, "EST"); + assert_eq!(timezones_data.data[0].abbr_dst, "EDT"); + } } diff --git a/tests/timezones.rs b/tests/timezones.rs new file mode 100644 index 0000000..88ac486 --- /dev/null +++ b/tests/timezones.rs @@ -0,0 +1,44 @@ +use marketstack::api::{timezones, AsyncQuery, Query}; +use marketstack::{AsyncMarketstack, Marketstack, TimezonesData}; + +mod setup; + +#[test] +#[ignore] +fn test_timezones() { + let api_key = setup::setup_key(); + let client = Marketstack::new_insecure("api.marketstack.com", api_key).unwrap(); + + let endpoint = timezones::Timezones::builder() + .limit(3) + .unwrap() + .build() + .unwrap(); + let timezones_result: TimezonesData = endpoint.query(&client).unwrap(); + + assert_eq!(timezones_result.pagination.limit, 3); + assert_eq!(timezones_result.pagination.offset, 0); + + assert_eq!(timezones_result.data.len(), 3); +} + +#[tokio::test] +#[ignore] +async fn test_async_timezones() { + let api_key = setup::setup_key(); + let client = AsyncMarketstack::new_insecure("api.marketstack.com", api_key) + .await + .unwrap(); + + let endpoint = timezones::Timezones::builder() + .limit(3) + .unwrap() + .build() + .unwrap(); + let timezones_result: TimezonesData = endpoint.query_async(&client).await.unwrap(); + + assert_eq!(timezones_result.pagination.limit, 3); + assert_eq!(timezones_result.pagination.offset, 0); + + assert_eq!(timezones_result.data.len(), 3); +}