use std::borrow::Cow;
use derive_builder::Builder;
use crate::api::dividends::Dividends;
use crate::api::eod::Eod;
use crate::api::paged::PaginationError;
use crate::api::splits::Splits;
use crate::api::{endpoint_prelude::*, ApiError};
#[derive(Debug, Builder, Clone)]
#[builder(setter(strip_option), build_fn(validate = "Self::validate"))]
pub struct Tickers<'a> {
#[builder(setter(into), default)]
ticker: Option<Cow<'a, str>>,
#[builder(setter(into), default)]
exchange: Option<Cow<'a, str>>,
#[builder(setter(into), default)]
search: Option<Cow<'a, str>>,
#[builder(setter(name = "_limit"), default)]
limit: Option<PageLimit>,
#[builder(default)]
offset: Option<u64>,
#[builder(setter(into), default)]
eod: Option<Eod<'a>>,
#[builder(setter(into), default)]
splits: Option<Splits<'a>>,
#[builder(setter(into), default)]
dividends: Option<Dividends<'a>>,
}
impl<'a> Tickers<'a> {
pub fn builder() -> TickersBuilder<'a> {
TickersBuilder::default()
}
}
impl<'a> Endpoint for Tickers<'a> {
fn method(&self) -> Method {
Method::GET
}
fn endpoint(&self) -> Cow<'static, str> {
let mut endpoint = "tickers".to_owned();
if let Some(ticker) = &self.ticker {
endpoint.push_str(&format!("/{}", ticker));
if let Some(eod) = &self.eod {
endpoint.push_str(&format!("/{}", eod.endpoint().as_ref()));
}
if let Some(splits) = &self.splits {
endpoint.push_str(&format!("/{}", splits.endpoint().as_ref()));
}
if let Some(dividends) = &self.dividends {
endpoint.push_str(&format!("/{}", dividends.endpoint().as_ref()));
}
}
endpoint.into()
}
fn parameters(&self) -> QueryParams {
let mut params = QueryParams::default();
if let Some(eod) = &self.eod {
params = eod.parameters().clone();
}
if let Some(splits) = &self.splits {
params = splits.parameters().clone();
}
if let Some(dividends) = &self.dividends {
params = dividends.parameters().clone();
}
params
.push_opt("exchange", self.exchange.as_ref())
.push_opt("search", self.search.as_ref())
.push_opt("limit", self.limit.clone())
.push_opt("offset", self.offset);
params
}
}
impl<'a> TickersBuilder<'a> {
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> {
let active_fields = [
self.eod.is_some(),
self.splits.is_some(),
self.dividends.is_some(),
];
let count = active_fields.iter().filter(|x| **x).count();
if count > 1 {
Err("Invalid combinations of `eod`, `splits` or `dividends`".into())
} else {
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use chrono::NaiveDate;
use crate::api::common::SortOrder;
use crate::api::dividends::Dividends;
use crate::api::eod::Eod;
use crate::api::splits::Splits;
use crate::api::tickers::Tickers;
use crate::api::{self, Query};
use crate::test::client::{ExpectedUrl, SingleTestClient};
#[test]
fn tickers_defaults_are_sufficient() {
Tickers::builder().build().unwrap();
}
#[test]
fn tickers_ticker() {
let endpoint = ExpectedUrl::builder()
.endpoint("tickers/AAPL")
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Tickers::builder().ticker("AAPL").build().unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn tickers_eod_endpoint() {
let endpoint = ExpectedUrl::builder()
.endpoint("tickers/AAPL/eod")
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Tickers::builder()
.ticker("AAPL")
.eod(Eod::builder().build().unwrap())
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn tickers_eod_latest_endpoint() {
let endpoint = ExpectedUrl::builder()
.endpoint("tickers/AAPL/eod/latest")
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Tickers::builder()
.ticker("AAPL")
.eod(Eod::builder().latest(true).build().unwrap())
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn tickers_eod_date_endpoint() {
let endpoint = ExpectedUrl::builder()
.endpoint("tickers/AAPL/eod/2023-09-27")
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Tickers::builder()
.ticker("AAPL")
.eod(
Eod::builder()
.date(NaiveDate::from_ymd_opt(2023, 9, 27).unwrap())
.build()
.unwrap(),
)
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn tickers_eod_params() {
let endpoint = ExpectedUrl::builder()
.endpoint("tickers/AAPL/eod")
.add_query_params(&[("sort", "ASC")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Tickers::builder()
.ticker("AAPL")
.eod(Eod::builder().sort(SortOrder::Ascending).build().unwrap())
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn tickers_splits() {
let endpoint = ExpectedUrl::builder()
.endpoint("tickers/AAPL/splits")
.add_query_params(&[("date_from", "2023-09-27"), ("date_to", "2023-09-30")])
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Tickers::builder()
.ticker("AAPL")
.splits(
Splits::builder()
.date_from(NaiveDate::from_ymd_opt(2023, 9, 27).unwrap())
.date_to(NaiveDate::from_ymd_opt(2023, 9, 30).unwrap())
.build()
.unwrap(),
)
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn tickers_dividends() {
let endpoint = ExpectedUrl::builder()
.endpoint("tickers/AAPL/dividends")
.build()
.unwrap();
let client = SingleTestClient::new_raw(endpoint, "");
let endpoint = Tickers::builder()
.ticker("AAPL")
.dividends(Dividends::builder().build().unwrap())
.build()
.unwrap();
api::ignore(endpoint).query(&client).unwrap();
}
#[test]
fn tickers_validator() {
let endpoint = Tickers::builder()
.eod(Eod::builder().build().unwrap())
.splits(Splits::builder().build().unwrap())
.build();
assert!(endpoint.is_err());
assert!(endpoint.err().unwrap().to_string().contains("Invalid"));
}
}