use std::collections::BTreeSet;
use chrono::NaiveDate;
use derive_builder::Builder;
use crate::api::common::SortOrder;
use crate::api::paged::PaginationError;
use crate::api::{endpoint_prelude::*, ApiError};
#[derive(Debug, Builder, Clone)]
#[builder(setter(strip_option), build_fn(validate = "Self::validate"))]
pub struct Eod<'a> {
#[builder(setter(name = "_symbols"), default)]
symbols: BTreeSet<Cow<'a, str>>,
#[builder(setter(into), default)]
exchange: Option<Cow<'a, str>>,
#[builder(default)]
sort: Option<SortOrder>,
#[builder(default)]
date_from: Option<NaiveDate>,
#[builder(default)]
date_to: Option<NaiveDate>,
#[builder(setter(name = "_limit"), default)]
limit: Option<PageLimit>,
#[builder(default)]
offset: Option<u64>,
#[builder(default)]
latest: Option<bool>,
#[builder(default)]
date: Option<NaiveDate>,
}
impl<'a> Eod<'a> {
pub fn builder() -> EodBuilder<'a> {
EodBuilder::default()
}
}
impl<'a> EodBuilder<'a> {
pub fn symbol(&mut self, symbol: &'a str) -> &mut Self {
self.symbols
.get_or_insert_with(BTreeSet::new)
.insert(symbol.into());
self
}
pub fn symbols<I, V>(&mut self, iter: I) -> &mut Self
where
I: Iterator<Item = V>,
V: Into<Cow<'a, str>>,
{
self.symbols
.get_or_insert_with(BTreeSet::new)
.extend(iter.map(|v| v.into()));
self
}
pub fn limit(&mut self, limit: u16) -> Result<&mut Self, ApiError<PaginationError>> {
let new = self;
new.limit = Some(Some(PageLimit::new(limit)?));
Ok(new)
}
fn validate(&self) -> Result<(), String> {
if self.date.is_some() && self.latest.is_some() {
Err("Cannot use both `date` and `latest`".into())
} else {
Ok(())
}
}
}
impl<'a> Endpoint for Eod<'a> {
fn method(&self) -> Method {
Method::GET
}
fn endpoint(&self) -> Cow<'static, str> {
if self.latest.is_some() {
"eod/latest".into()
} else if self.date.is_some() {
format!("eod/{}", self.date.unwrap()).into()
} else {
"eod".into()
}
}
fn parameters(&self) -> QueryParams {
let mut params = QueryParams::default();
params
.extend(self.symbols.iter().map(|value| ("symbols", value)))
.push_opt("exchange", self.exchange.as_ref())
.push_opt("sort", self.sort)
.push_opt("date_from", self.date_from)
.push_opt("date_to", self.date_to)
.push_opt("limit", self.limit.clone())
.push_opt("offset", self.offset);
params
}
}
#[cfg(test)]
mod tests {
use chrono::NaiveDate;
use crate::api::common::SortOrder;
use crate::api::eod::Eod;
use crate::api::{self, Query};
use crate::test::client::{ExpectedUrl, SingleTestClient};
#[test]
fn eod_defaults_are_sufficient() {
Eod::builder().build().unwrap();
}
#[test]
fn eod_endpoint() {
let endpoint = ExpectedUrl::builder().endpoint("eod").build().unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Eod::builder().build().unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn eod_symbol() {
let endpoint = ExpectedUrl::builder()
.endpoint("eod")
.add_query_params(&[("symbols", "AAPL")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Eod::builder().symbol("AAPL").build().unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn eod_symbols() {
let endpoint = ExpectedUrl::builder()
.endpoint("eod")
.add_query_params(&[("symbols", "AAPL"), ("symbols", "GOOG")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Eod::builder()
.symbol("AAPL")
.symbols(["AAPL", "GOOG"].iter().copied())
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn eod_exchange() {
let endpoint = ExpectedUrl::builder()
.endpoint("eod")
.add_query_params(&[("exchange", "NYSE")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Eod::builder().exchange("NYSE").build().unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn eod_sort() {
let endpoint = ExpectedUrl::builder()
.endpoint("eod")
.add_query_params(&[("sort", "ASC")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Eod::builder().sort(SortOrder::Ascending).build().unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn eod_date_from() {
let endpoint = ExpectedUrl::builder()
.endpoint("eod")
.add_query_params(&[("date_from", "2020-01-01")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Eod::builder()
.date_from(NaiveDate::from_ymd_opt(2020, 1, 1).unwrap())
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn eod_date_to() {
let endpoint = ExpectedUrl::builder()
.endpoint("eod")
.add_query_params(&[("date_to", "2020-01-01")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Eod::builder()
.date_to(NaiveDate::from_ymd_opt(2020, 1, 1).unwrap())
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn eod_limit() {
let endpoint = ExpectedUrl::builder()
.endpoint("eod")
.add_query_params(&[("limit", "50")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Eod::builder().limit(50).unwrap().build().unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn eod_over_limit() {
assert!(Eod::builder().limit(9999).is_err());
}
#[test]
fn eod_offset() {
let endpoint = ExpectedUrl::builder()
.endpoint("eod")
.add_query_params(&[("offset", "2")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Eod::builder().offset(2).build().unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn eod_latest_defaults_are_sufficient() {
Eod::builder().latest(true).build().unwrap();
}
#[test]
fn eod_latest_endpoint() {
let endpoint = ExpectedUrl::builder()
.endpoint("eod/latest")
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Eod::builder().latest(true).build().unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn eod_latest_symbol() {
let endpoint = ExpectedUrl::builder()
.endpoint("eod/latest")
.add_query_params(&[("symbols", "AAPL")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Eod::builder().latest(true).symbol("AAPL").build().unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn eod_latest_symbols() {
let endpoint = ExpectedUrl::builder()
.endpoint("eod/latest")
.add_query_params(&[("symbols", "AAPL"), ("symbols", "GOOG")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Eod::builder()
.latest(true)
.symbol("AAPL")
.symbols(["AAPL", "GOOG"].iter().copied())
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn eod_latest_exchange() {
let endpoint = ExpectedUrl::builder()
.endpoint("eod/latest")
.add_query_params(&[("exchange", "NYSE")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Eod::builder()
.latest(true)
.exchange("NYSE")
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn eod_latest_sort() {
let endpoint = ExpectedUrl::builder()
.endpoint("eod/latest")
.add_query_params(&[("sort", "ASC")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Eod::builder()
.latest(true)
.sort(SortOrder::Ascending)
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn eod_latest_limit() {
let endpoint = ExpectedUrl::builder()
.endpoint("eod/latest")
.add_query_params(&[("limit", "50")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Eod::builder()
.latest(true)
.limit(50)
.unwrap()
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn eod_latest_over_limit() {
assert!(Eod::builder().latest(true).limit(9999).is_err());
}
#[test]
fn eod_latest_offset() {
let endpoint = ExpectedUrl::builder()
.endpoint("eod/latest")
.add_query_params(&[("offset", "2")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Eod::builder().latest(true).offset(2).build().unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn eod_date_endpoint() {
let endpoint = ExpectedUrl::builder()
.endpoint("eod/2022-01-01")
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Eod::builder()
.date(NaiveDate::from_ymd_opt(2022, 1, 1).unwrap())
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn eod_date_symbol() {
let endpoint = ExpectedUrl::builder()
.endpoint("eod/2022-01-01")
.add_query_params(&[("symbols", "AAPL")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Eod::builder()
.symbol("AAPL")
.date(NaiveDate::from_ymd_opt(2022, 1, 1).unwrap())
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn eod_date_symbols() {
let endpoint = ExpectedUrl::builder()
.endpoint("eod/2022-01-01")
.add_query_params(&[("symbols", "AAPL"), ("symbols", "GOOG")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Eod::builder()
.symbol("AAPL")
.date(NaiveDate::from_ymd_opt(2022, 1, 1).unwrap())
.symbols(["AAPL", "GOOG"].iter().copied())
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn eod_date_exchange() {
let endpoint = ExpectedUrl::builder()
.endpoint("eod/2022-01-01")
.add_query_params(&[("exchange", "NYSE")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Eod::builder()
.exchange("NYSE")
.date(NaiveDate::from_ymd_opt(2022, 1, 1).unwrap())
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn eod_date_sort() {
let endpoint = ExpectedUrl::builder()
.endpoint("eod/2022-01-01")
.add_query_params(&[("sort", "ASC")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Eod::builder()
.sort(SortOrder::Ascending)
.date(NaiveDate::from_ymd_opt(2022, 1, 1).unwrap())
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn eod_date_limit() {
let endpoint = ExpectedUrl::builder()
.endpoint("eod/2022-01-01")
.add_query_params(&[("limit", "50")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Eod::builder()
.date(NaiveDate::from_ymd_opt(2022, 1, 1).unwrap())
.limit(50)
.unwrap()
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn eod_date_over_limit() {
assert!(Eod::builder()
.date(NaiveDate::from_ymd_opt(2022, 1, 1).unwrap())
.limit(9999)
.is_err());
}
#[test]
fn eod_date_offset() {
let endpoint = ExpectedUrl::builder()
.endpoint("eod/2022-01-01")
.add_query_params(&[("offset", "2")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Eod::builder()
.date(NaiveDate::from_ymd_opt(2022, 1, 1).unwrap())
.offset(2)
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn eod_date_latest_mutually_exclusive() {
let endpoint = Eod::builder()
.latest(true)
.date(NaiveDate::from_ymd_opt(2022, 1, 1).unwrap())
.build();
assert!(endpoint.is_err());
assert!(endpoint
.err()
.unwrap()
.to_string()
.contains("Cannot use both"));
}
}