marketstack/api/
tickers.rs1use std::borrow::Cow;
4
5use derive_builder::Builder;
6
7use crate::api::dividends::Dividends;
8use crate::api::eod::Eod;
9use crate::api::paged::PaginationError;
10use crate::api::splits::Splits;
11use crate::api::{endpoint_prelude::*, ApiError};
12
13#[derive(Debug, Builder, Clone)]
15#[builder(setter(strip_option), build_fn(validate = "Self::validate"))]
16pub struct Tickers<'a> {
17 #[builder(setter(into), default)]
19 ticker: Option<Cow<'a, str>>,
20 #[builder(setter(into), default)]
22 exchange: Option<Cow<'a, str>>,
23 #[builder(setter(into), default)]
25 search: Option<Cow<'a, str>>,
26 #[builder(setter(name = "_limit"), default)]
28 limit: Option<PageLimit>,
29 #[builder(default)]
31 offset: Option<u64>,
32 #[builder(setter(into), default)]
34 eod: Option<Eod<'a>>,
35 #[builder(setter(into), default)]
37 splits: Option<Splits<'a>>,
38 #[builder(setter(into), default)]
40 dividends: Option<Dividends<'a>>,
41}
42
43impl<'a> Tickers<'a> {
44 pub fn builder() -> TickersBuilder<'a> {
46 TickersBuilder::default()
47 }
48}
49
50impl<'a> Endpoint for Tickers<'a> {
51 fn method(&self) -> Method {
52 Method::GET
53 }
54
55 fn endpoint(&self) -> Cow<'static, str> {
56 let mut endpoint = "tickers".to_owned();
57 if let Some(ticker) = &self.ticker {
58 endpoint.push_str(&format!("/{}", ticker));
59
60 if let Some(eod) = &self.eod {
62 endpoint.push_str(&format!("/{}", eod.endpoint().as_ref()));
63 }
64 if let Some(splits) = &self.splits {
65 endpoint.push_str(&format!("/{}", splits.endpoint().as_ref()));
66 }
67 if let Some(dividends) = &self.dividends {
68 endpoint.push_str(&format!("/{}", dividends.endpoint().as_ref()));
69 }
70 }
71
72 endpoint.into()
73 }
74
75 fn parameters(&self) -> QueryParams {
76 let mut params = QueryParams::default();
77
78 if let Some(eod) = &self.eod {
82 params = eod.parameters().clone();
83 }
84 if let Some(splits) = &self.splits {
85 params = splits.parameters().clone();
86 }
87 if let Some(dividends) = &self.dividends {
88 params = dividends.parameters().clone();
89 }
90
91 params
93 .push_opt("exchange", self.exchange.as_ref())
94 .push_opt("search", self.search.as_ref())
95 .push_opt("limit", self.limit.clone())
96 .push_opt("offset", self.offset);
97
98 params
99 }
100}
101
102impl<'a> TickersBuilder<'a> {
103 pub fn limit(&mut self, limit: u16) -> Result<&mut Self, ApiError<PaginationError>> {
105 let new = self;
106 new.limit = Some(Some(PageLimit::new(limit)?));
107 Ok(new)
108 }
109
110 fn validate(&self) -> Result<(), String> {
112 let active_fields = [
113 self.eod.is_some(),
114 self.splits.is_some(),
115 self.dividends.is_some(),
116 ];
117 let count = active_fields.iter().filter(|x| **x).count();
118 if count > 1 {
119 Err("Invalid combinations of `eod`, `splits` or `dividends`".into())
120 } else {
121 Ok(())
122 }
123 }
124}
125
126#[cfg(test)]
127mod tests {
128
129 use chrono::NaiveDate;
130
131 use crate::api::common::SortOrder;
132 use crate::api::dividends::Dividends;
133 use crate::api::eod::Eod;
134 use crate::api::splits::Splits;
135 use crate::api::tickers::Tickers;
136 use crate::api::{self, Query};
137 use crate::test::client::{ExpectedUrl, SingleTestClient};
138
139 #[test]
140 fn tickers_defaults_are_sufficient() {
141 Tickers::builder().build().unwrap();
142 }
143
144 #[test]
145 fn tickers_ticker() {
146 let endpoint = ExpectedUrl::builder()
147 .endpoint("tickers/AAPL")
148 .build()
149 .unwrap();
150 let client = SingleTestClient::new_raw(endpoint, "");
151
152 let endpoint = Tickers::builder().ticker("AAPL").build().unwrap();
153 api::ignore(endpoint).query(&client).unwrap();
154 }
155
156 #[test]
157 fn tickers_eod_endpoint() {
158 let endpoint = ExpectedUrl::builder()
159 .endpoint("tickers/AAPL/eod")
160 .build()
161 .unwrap();
162 let client = SingleTestClient::new_raw(endpoint, "");
163
164 let endpoint = Tickers::builder()
165 .ticker("AAPL")
166 .eod(Eod::builder().build().unwrap())
167 .build()
168 .unwrap();
169 api::ignore(endpoint).query(&client).unwrap();
170 }
171
172 #[test]
173 fn tickers_eod_latest_endpoint() {
174 let endpoint = ExpectedUrl::builder()
175 .endpoint("tickers/AAPL/eod/latest")
176 .build()
177 .unwrap();
178 let client = SingleTestClient::new_raw(endpoint, "");
179
180 let endpoint = Tickers::builder()
181 .ticker("AAPL")
182 .eod(Eod::builder().latest(true).build().unwrap())
183 .build()
184 .unwrap();
185 api::ignore(endpoint).query(&client).unwrap();
186 }
187
188 #[test]
189 fn tickers_eod_date_endpoint() {
190 let endpoint = ExpectedUrl::builder()
191 .endpoint("tickers/AAPL/eod/2023-09-27")
192 .build()
193 .unwrap();
194 let client = SingleTestClient::new_raw(endpoint, "");
195
196 let endpoint = Tickers::builder()
197 .ticker("AAPL")
198 .eod(
199 Eod::builder()
200 .date(NaiveDate::from_ymd_opt(2023, 9, 27).unwrap())
201 .build()
202 .unwrap(),
203 )
204 .build()
205 .unwrap();
206 api::ignore(endpoint).query(&client).unwrap();
207 }
208
209 #[test]
210 fn tickers_eod_params() {
211 let endpoint = ExpectedUrl::builder()
212 .endpoint("tickers/AAPL/eod")
213 .add_query_params(&[("sort", "ASC")])
214 .build()
215 .unwrap();
216 let client = SingleTestClient::new_raw(endpoint, "");
217
218 let endpoint = Tickers::builder()
219 .ticker("AAPL")
220 .eod(Eod::builder().sort(SortOrder::Ascending).build().unwrap())
221 .build()
222 .unwrap();
223 api::ignore(endpoint).query(&client).unwrap();
224 }
225
226 #[test]
227 fn tickers_splits() {
228 let endpoint = ExpectedUrl::builder()
229 .endpoint("tickers/AAPL/splits")
230 .add_query_params(&[("date_from", "2023-09-27"), ("date_to", "2023-09-30")])
231 .build()
232 .unwrap();
233 let client = SingleTestClient::new_raw(endpoint, "");
234
235 let endpoint = Tickers::builder()
236 .ticker("AAPL")
237 .splits(
238 Splits::builder()
239 .date_from(NaiveDate::from_ymd_opt(2023, 9, 27).unwrap())
240 .date_to(NaiveDate::from_ymd_opt(2023, 9, 30).unwrap())
241 .build()
242 .unwrap(),
243 )
244 .build()
245 .unwrap();
246 api::ignore(endpoint).query(&client).unwrap();
247 }
248
249 #[test]
250 fn tickers_dividends() {
251 let endpoint = ExpectedUrl::builder()
252 .endpoint("tickers/AAPL/dividends")
253 .build()
254 .unwrap();
255 let client = SingleTestClient::new_raw(endpoint, "");
256
257 let endpoint = Tickers::builder()
258 .ticker("AAPL")
259 .dividends(Dividends::builder().build().unwrap())
260 .build()
261 .unwrap();
262 api::ignore(endpoint).query(&client).unwrap();
263 }
264
265 #[test]
266 fn tickers_validator() {
267 let endpoint = Tickers::builder()
268 .eod(Eod::builder().build().unwrap())
269 .splits(Splits::builder().build().unwrap())
270 .build();
271 assert!(endpoint.is_err());
272 assert!(endpoint.err().unwrap().to_string().contains("Invalid"));
273 }
274}