marketstack/api/
dividends.rs

1//! Implementation of the `dividends` API endpoint.
2
3use std::collections::BTreeSet;
4
5use chrono::NaiveDate;
6use derive_builder::Builder;
7
8use crate::api::common::SortOrder;
9use crate::api::paged::PaginationError;
10use crate::api::{endpoint_prelude::*, ApiError};
11
12/// Query for `dividends`.
13#[derive(Debug, Builder, Clone)]
14#[builder(setter(strip_option))]
15pub struct Dividends<'a> {
16    /// Search for `dividends` for a symbol.
17    #[builder(setter(name = "_symbols"), default)]
18    symbols: BTreeSet<Cow<'a, str>>,
19    /// The sort order for the return results.
20    #[builder(default)]
21    sort: Option<SortOrder>,
22    /// Date to query EOD data from.
23    #[builder(default)]
24    date_from: Option<NaiveDate>,
25    /// Date to query EOD date to.
26    #[builder(default)]
27    date_to: Option<NaiveDate>,
28    /// Pagination limit for API request.
29    #[builder(setter(name = "_limit"), default)]
30    limit: Option<PageLimit>,
31    /// Pagination offset value for API request.
32    #[builder(default)]
33    offset: Option<u64>,
34}
35
36impl<'a> Dividends<'a> {
37    /// Create a builder for this endpoint.
38    pub fn builder() -> DividendsBuilder<'a> {
39        DividendsBuilder::default()
40    }
41}
42
43impl<'a> DividendsBuilder<'a> {
44    /// Search the given symbol.
45    ///
46    /// This provides sane defaults for the user to call symbol()
47    /// on the builder without needing to wrap his symbol in a
48    /// BTreeSet beforehand.
49    pub fn symbol(&mut self, symbol: &'a str) -> &mut Self {
50        self.symbols
51            .get_or_insert_with(BTreeSet::new)
52            .insert(symbol.into());
53        self
54    }
55
56    /// Search the given symbols.
57    pub fn symbols<I, V>(&mut self, iter: I) -> &mut Self
58    where
59        I: Iterator<Item = V>,
60        V: Into<Cow<'a, str>>,
61    {
62        self.symbols
63            .get_or_insert_with(BTreeSet::new)
64            .extend(iter.map(|v| v.into()));
65        self
66    }
67
68    /// Limit the number of results returned.
69    pub fn limit(&mut self, limit: u16) -> Result<&mut Self, ApiError<PaginationError>> {
70        let new = self;
71        new.limit = Some(Some(PageLimit::new(limit)?));
72        Ok(new)
73    }
74}
75
76impl<'a> Endpoint for Dividends<'a> {
77    fn method(&self) -> Method {
78        Method::GET
79    }
80
81    fn endpoint(&self) -> Cow<'static, str> {
82        "dividends".into()
83    }
84
85    fn parameters(&self) -> QueryParams {
86        let mut params = QueryParams::default();
87
88        params
89            .extend(self.symbols.iter().map(|value| ("symbols", value)))
90            .push_opt("sort", self.sort)
91            .push_opt("date_from", self.date_from)
92            .push_opt("date_to", self.date_to)
93            .push_opt("limit", self.limit.clone())
94            .push_opt("offset", self.offset);
95
96        params
97    }
98}
99
100#[cfg(test)]
101mod tests {
102
103    use chrono::NaiveDate;
104
105    use crate::api::common::SortOrder;
106    use crate::api::dividends::Dividends;
107    use crate::api::{self, Query};
108    use crate::test::client::{ExpectedUrl, SingleTestClient};
109
110    #[test]
111    fn dividends_defaults_are_sufficient() {
112        Dividends::builder().build().unwrap();
113    }
114
115    #[test]
116    fn dividends_endpoint() {
117        let endpoint = ExpectedUrl::builder()
118            .endpoint("dividends")
119            .build()
120            .unwrap();
121        let client = SingleTestClient::new_raw(endpoint, "");
122
123        let endpoint = Dividends::builder().build().unwrap();
124        api::ignore(endpoint).query(&client).unwrap();
125    }
126
127    #[test]
128    fn dividends_symbol() {
129        let endpoint = ExpectedUrl::builder()
130            .endpoint("dividends")
131            .add_query_params(&[("symbols", "AAPL")])
132            .build()
133            .unwrap();
134        let client = SingleTestClient::new_raw(endpoint, "");
135
136        let endpoint = Dividends::builder().symbol("AAPL").build().unwrap();
137        api::ignore(endpoint).query(&client).unwrap();
138    }
139
140    #[test]
141    fn dividends_symbols() {
142        let endpoint = ExpectedUrl::builder()
143            .endpoint("dividends")
144            .add_query_params(&[("symbols", "AAPL"), ("symbols", "GOOG")])
145            .build()
146            .unwrap();
147        let client = SingleTestClient::new_raw(endpoint, "");
148
149        let endpoint = Dividends::builder()
150            .symbol("AAPL")
151            .symbols(["AAPL", "GOOG"].iter().copied())
152            .build()
153            .unwrap();
154        api::ignore(endpoint).query(&client).unwrap();
155    }
156
157    #[test]
158    fn dividends_sort() {
159        let endpoint = ExpectedUrl::builder()
160            .endpoint("dividends")
161            .add_query_params(&[("sort", "ASC")])
162            .build()
163            .unwrap();
164        let client = SingleTestClient::new_raw(endpoint, "");
165
166        let endpoint = Dividends::builder()
167            .sort(SortOrder::Ascending)
168            .build()
169            .unwrap();
170        api::ignore(endpoint).query(&client).unwrap();
171    }
172
173    #[test]
174    fn dividends_date_from() {
175        let endpoint = ExpectedUrl::builder()
176            .endpoint("dividends")
177            .add_query_params(&[("date_from", "2020-01-01")])
178            .build()
179            .unwrap();
180        let client = SingleTestClient::new_raw(endpoint, "");
181
182        let endpoint = Dividends::builder()
183            .date_from(NaiveDate::from_ymd_opt(2020, 1, 1).unwrap())
184            .build()
185            .unwrap();
186        api::ignore(endpoint).query(&client).unwrap();
187    }
188
189    #[test]
190    fn dividends_date_to() {
191        let endpoint = ExpectedUrl::builder()
192            .endpoint("dividends")
193            .add_query_params(&[("date_to", "2020-01-01")])
194            .build()
195            .unwrap();
196        let client = SingleTestClient::new_raw(endpoint, "");
197
198        let endpoint = Dividends::builder()
199            .date_to(NaiveDate::from_ymd_opt(2020, 1, 1).unwrap())
200            .build()
201            .unwrap();
202        api::ignore(endpoint).query(&client).unwrap();
203    }
204
205    #[test]
206    fn dividends_limit() {
207        let endpoint = ExpectedUrl::builder()
208            .endpoint("dividends")
209            .add_query_params(&[("limit", "50")])
210            .build()
211            .unwrap();
212        let client = SingleTestClient::new_raw(endpoint, "");
213
214        let endpoint = Dividends::builder().limit(50).unwrap().build().unwrap();
215        api::ignore(endpoint).query(&client).unwrap();
216    }
217
218    #[test]
219    fn dividends_over_limit() {
220        assert!(Dividends::builder().limit(9999).is_err());
221    }
222
223    #[test]
224    fn dividends_offset() {
225        let endpoint = ExpectedUrl::builder()
226            .endpoint("dividends")
227            .add_query_params(&[("offset", "2")])
228            .build()
229            .unwrap();
230        let client = SingleTestClient::new_raw(endpoint, "");
231
232        let endpoint = Dividends::builder().offset(2).build().unwrap();
233        api::ignore(endpoint).query(&client).unwrap();
234    }
235}