use crate::{
Client, Result,
endpoints::{BinaryResponse, EndpointSpec, QueryEncoder, QueryParameters},
query::{FiscalPeriod, Year},
responses::statements::{FinancialReportDate, FinancialReportJson},
types::Ticker,
};
pub const FINANCIAL_REPORTS_XLSX_CONTENT_TYPES: &[&str] = &[
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/octet-stream",
];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FinancialReportsDatesQuery {
symbol: Ticker,
}
impl FinancialReportsDatesQuery {
pub fn new(symbol: Ticker) -> Self {
Self { symbol }
}
pub fn symbol(&self) -> &Ticker {
&self.symbol
}
}
impl From<Ticker> for FinancialReportsDatesQuery {
fn from(symbol: Ticker) -> Self {
Self::new(symbol)
}
}
impl From<&Ticker> for FinancialReportsDatesQuery {
fn from(symbol: &Ticker) -> Self {
Self::new(symbol.clone())
}
}
impl QueryParameters for FinancialReportsDatesQuery {
fn encode(&self, encoder: &mut QueryEncoder<'_>) {
encoder.required("symbol", &self.symbol);
}
}
macro_rules! financial_report_query {
($docs:literal, $query:ident) => {
#[doc = $docs]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct $query {
symbol: Ticker,
year: Year,
period: FiscalPeriod,
}
impl $query {
pub fn new(symbol: Ticker, year: Year, period: FiscalPeriod) -> Self {
Self {
symbol,
year,
period,
}
}
pub fn symbol(&self) -> &Ticker {
&self.symbol
}
pub const fn year(&self) -> Year {
self.year
}
pub const fn period(&self) -> FiscalPeriod {
self.period
}
}
impl QueryParameters for $query {
fn encode(&self, encoder: &mut QueryEncoder<'_>) {
encoder.required("symbol", &self.symbol);
encoder.required("year", self.year);
encoder.required("period", self.period);
}
}
};
}
financial_report_query!(
"Required query for a dynamic JSON financial report.",
FinancialReportsJsonQuery
);
financial_report_query!(
"Required query for an XLSX financial report download.",
FinancialReportsXlsxQuery
);
pub fn financial_reports_dates(
query: FinancialReportsDatesQuery,
) -> EndpointSpec<FinancialReportsDatesQuery, Vec<FinancialReportDate>> {
EndpointSpec::get("financial-reports-dates", "financial-reports-dates", query)
}
pub fn financial_reports_json(
query: FinancialReportsJsonQuery,
) -> EndpointSpec<FinancialReportsJsonQuery, Vec<FinancialReportJson>> {
EndpointSpec::get("financial-reports-json", "financial-reports-json", query)
}
pub fn financial_reports_xlsx(
query: FinancialReportsXlsxQuery,
) -> EndpointSpec<FinancialReportsXlsxQuery, BinaryResponse> {
EndpointSpec::get_binary(
"financial-reports-xlsx",
"financial-reports-xlsx",
query,
FINANCIAL_REPORTS_XLSX_CONTENT_TYPES,
)
}
impl Client {
pub async fn financial_reports_dates(
&self,
query: impl Into<FinancialReportsDatesQuery>,
) -> Result<Vec<FinancialReportDate>> {
self.execute(&financial_reports_dates(query.into())).await
}
pub async fn financial_reports_json(
&self,
query: FinancialReportsJsonQuery,
) -> Result<Vec<FinancialReportJson>> {
self.execute(&financial_reports_json(query)).await
}
pub async fn financial_reports_xlsx(
&self,
query: FinancialReportsXlsxQuery,
) -> Result<BinaryResponse> {
self.execute(&financial_reports_xlsx(query)).await
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pairs(query: &impl QueryParameters) -> Vec<(String, String)> {
let mut pairs = Vec::new();
query.encode(&mut QueryEncoder::new(&mut |name, value| {
pairs.push((name.to_owned(), value.to_owned()));
}));
pairs
}
#[test]
fn endpoint_owned_queries_encode_exact_required_order_and_five_periods() {
let symbol = Ticker::new("BRK.B / Class A").unwrap();
let dates: FinancialReportsDatesQuery = (&symbol).into();
assert_eq!(dates.symbol(), &symbol);
assert_eq!(
pairs(&dates),
[("symbol".to_owned(), "BRK.B / Class A".to_owned())]
);
let periods = [
FiscalPeriod::Q1,
FiscalPeriod::Q2,
FiscalPeriod::Q3,
FiscalPeriod::Q4,
FiscalPeriod::FullYear,
];
for period in periods {
let json = FinancialReportsJsonQuery::new(symbol.clone(), Year(2022), period);
let xlsx = FinancialReportsXlsxQuery::new(symbol.clone(), Year(2022), period);
assert_eq!(json.symbol(), &symbol);
assert_eq!(json.year(), Year(2022));
assert_eq!(json.period(), period);
assert_eq!(xlsx.symbol(), &symbol);
assert_eq!(xlsx.year(), Year(2022));
assert_eq!(xlsx.period(), period);
let expected = [
("symbol".to_owned(), "BRK.B / Class A".to_owned()),
("year".to_owned(), "2022".to_owned()),
("period".to_owned(), period.to_string()),
];
assert_eq!(pairs(&json), expected);
assert_eq!(pairs(&xlsx), expected);
}
}
#[test]
fn xlsx_descriptor_inherits_the_finite_client_response_limit() {
let query = FinancialReportsXlsxQuery::new(
Ticker::new("AAPL").unwrap(),
Year(2025),
FiscalPeriod::FullYear,
);
assert_eq!(
financial_reports_xlsx(query).max_response_body_bytes(),
None
);
}
}