marketstack/api/
intraday.rs1use std::collections::BTreeSet;
62
63use chrono::NaiveDate;
64use derive_builder::Builder;
65
66use crate::api::common::{Interval, SortOrder};
67use crate::api::paged::PaginationError;
68use crate::api::{endpoint_prelude::*, ApiError};
69
70#[derive(Debug, Clone, Builder)]
72#[builder(setter(strip_option), build_fn(validate = "Self::validate"))]
73pub struct Intraday<'a> {
74 #[builder(setter(name = "_symbols"), default)]
76 symbols: BTreeSet<Cow<'a, str>>,
77 #[builder(setter(into), default)]
79 exchange: Option<Cow<'a, str>>,
80 #[builder(default)]
82 interval: Option<Interval>,
83 #[builder(default)]
85 sort: Option<SortOrder>,
86 #[builder(default)]
88 date_from: Option<NaiveDate>,
89 #[builder(default)]
91 date_to: Option<NaiveDate>,
92 #[builder(setter(name = "_limit"), default)]
94 limit: Option<PageLimit>,
95 #[builder(default)]
97 offset: Option<u64>,
98 #[builder(default)]
100 latest: Option<bool>,
101 #[builder(default)]
103 date: Option<NaiveDate>,
104}
105
106impl<'a> Intraday<'a> {
107 pub fn builder() -> IntradayBuilder<'a> {
109 IntradayBuilder::default()
110 }
111}
112
113impl<'a> IntradayBuilder<'a> {
114 pub fn symbol(&mut self, symbol: &'a str) -> &mut Self {
120 self.symbols
121 .get_or_insert_with(BTreeSet::new)
122 .insert(symbol.into());
123 self
124 }
125
126 pub fn symbols<I, V>(&mut self, iter: I) -> &mut Self
128 where
129 I: Iterator<Item = V>,
130 V: Into<Cow<'a, str>>,
131 {
132 self.symbols
133 .get_or_insert_with(BTreeSet::new)
134 .extend(iter.map(|v| v.into()));
135 self
136 }
137
138 pub fn limit(&mut self, limit: u16) -> Result<&mut Self, ApiError<PaginationError>> {
140 let new = self;
141 new.limit = Some(Some(PageLimit::new(limit)?));
142 Ok(new)
143 }
144
145 fn validate(&self) -> Result<(), String> {
147 if self.date.is_some() && self.latest.is_some() {
148 Err("Cannot use both `date` and `latest`".into())
149 } else {
150 Ok(())
151 }
152 }
153}
154
155impl<'a> Endpoint for Intraday<'a> {
156 fn method(&self) -> Method {
157 Method::GET
158 }
159
160 fn endpoint(&self) -> Cow<'static, str> {
161 if self.latest.is_some() {
162 "intraday/latest".into()
163 } else if self.date.is_some() {
164 format!("intraday/{}", self.date.unwrap()).into()
166 } else {
167 "intraday".into()
168 }
169 }
170
171 fn parameters(&self) -> QueryParams {
172 let mut params = QueryParams::default();
173
174 params
175 .extend(self.symbols.iter().map(|value| ("symbols", value)))
176 .push_opt("exchange", self.exchange.as_ref())
177 .push_opt("interval", self.interval.clone())
178 .push_opt("sort", self.sort)
179 .push_opt("date_from", self.date_from)
180 .push_opt("date_to", self.date_to)
181 .push_opt("limit", self.limit.clone())
182 .push_opt("offset", self.offset);
183
184 params
185 }
186}
187
188#[cfg(test)]
189mod tests {
190
191 use chrono::NaiveDate;
192
193 use crate::api::common::{Interval, SortOrder};
194 use crate::api::intraday::Intraday;
195 use crate::api::{self, Query};
196 use crate::test::client::{ExpectedUrl, SingleTestClient};
197
198 #[test]
199 fn intraday_defaults_are_sufficient() {
200 Intraday::builder().build().unwrap();
201 }
202
203 #[test]
204 fn intraday_endpoint() {
205 let endpoint = ExpectedUrl::builder().endpoint("intraday").build().unwrap();
206 let client = SingleTestClient::new_raw(endpoint, "");
207
208 let endpoint = Intraday::builder().build().unwrap();
209 api::ignore(endpoint).query(&client).unwrap();
210 }
211
212 #[test]
213 fn intraday_symbol() {
214 let endpoint = ExpectedUrl::builder()
215 .endpoint("intraday")
216 .add_query_params(&[("symbols", "AAPL")])
217 .build()
218 .unwrap();
219 let client = SingleTestClient::new_raw(endpoint, "");
220
221 let endpoint = Intraday::builder().symbol("AAPL").build().unwrap();
222 api::ignore(endpoint).query(&client).unwrap();
223 }
224
225 #[test]
226 fn intraday_symbols() {
227 let endpoint = ExpectedUrl::builder()
228 .endpoint("intraday")
229 .add_query_params(&[("symbols", "AAPL"), ("symbols", "MSFT")])
230 .build()
231 .unwrap();
232 let client = SingleTestClient::new_raw(endpoint, "");
233
234 let endpoint = Intraday::builder()
235 .symbols(["AAPL", "MSFT"].iter().copied())
236 .build()
237 .unwrap();
238 api::ignore(endpoint).query(&client).unwrap();
239 }
240
241 #[test]
242 fn intraday_exchange() {
243 let endpoint = ExpectedUrl::builder()
244 .endpoint("intraday")
245 .add_query_params(&[("exchange", "NYSE")])
246 .build()
247 .unwrap();
248 let client = SingleTestClient::new_raw(endpoint, "");
249
250 let endpoint = Intraday::builder().exchange("NYSE").build().unwrap();
251 api::ignore(endpoint).query(&client).unwrap();
252 }
253
254 #[test]
255 fn intraday_interval() {
256 let endpoint = ExpectedUrl::builder()
257 .endpoint("intraday")
258 .add_query_params(&[("interval", "5min")])
259 .build()
260 .unwrap();
261 let client = SingleTestClient::new_raw(endpoint, "");
262
263 let endpoint = Intraday::builder()
264 .interval(Interval::FiveMinutes)
265 .build()
266 .unwrap();
267 api::ignore(endpoint).query(&client).unwrap();
268 }
269
270 #[test]
271 fn intraday_sort() {
272 let endpoint = ExpectedUrl::builder()
273 .endpoint("intraday")
274 .add_query_params(&[("sort", "ASC")])
275 .build()
276 .unwrap();
277 let client = SingleTestClient::new_raw(endpoint, "");
278
279 let endpoint = Intraday::builder()
280 .sort(SortOrder::Ascending)
281 .build()
282 .unwrap();
283 api::ignore(endpoint).query(&client).unwrap();
284 }
285
286 #[test]
287 fn intraday_date_from_and_to() {
288 let endpoint = ExpectedUrl::builder()
289 .endpoint("intraday")
290 .add_query_params(&[("date_from", "2019-01-01"), ("date_to", "2019-01-02")])
291 .build()
292 .unwrap();
293 let client = SingleTestClient::new_raw(endpoint, "");
294
295 let endpoint = Intraday::builder()
296 .date_from(NaiveDate::from_ymd_opt(2019, 1, 1).unwrap())
297 .date_to(NaiveDate::from_ymd_opt(2019, 1, 2).unwrap())
298 .build()
299 .unwrap();
300 api::ignore(endpoint).query(&client).unwrap();
301 }
302
303 #[test]
304 fn intraday_limit_and_offset() {
305 let endpoint = ExpectedUrl::builder()
306 .endpoint("intraday")
307 .add_query_params(&[("limit", "5"), ("offset", "3")])
308 .build()
309 .unwrap();
310 let client = SingleTestClient::new_raw(endpoint, "");
311
312 let endpoint = Intraday::builder()
313 .limit(5)
314 .unwrap()
315 .offset(3)
316 .build()
317 .unwrap();
318 api::ignore(endpoint).query(&client).unwrap();
319 }
320
321 #[test]
322 fn intraday_over_limit() {
323 assert!(Intraday::builder().limit(5000).is_err());
324 }
325
326 #[test]
327 fn intraday_latest_defaults_are_sufficient() {
328 Intraday::builder().latest(true).build().unwrap();
329 }
330
331 #[test]
332 fn intraday_latest_endpoint() {
333 let endpoint = ExpectedUrl::builder()
334 .endpoint("intraday/latest")
335 .build()
336 .unwrap();
337 let client = SingleTestClient::new_raw(endpoint, "");
338
339 let endpoint = Intraday::builder().latest(true).build().unwrap();
340 api::ignore(endpoint).query(&client).unwrap();
341 }
342
343 #[test]
344 fn intraday_date_defaults_are_sufficient() {
345 Intraday::builder()
346 .date(NaiveDate::from_ymd_opt(2019, 1, 1).unwrap())
347 .build()
348 .unwrap();
349 }
350
351 #[test]
352 fn intraday_date_endpoint() {
353 let endpoint = ExpectedUrl::builder()
354 .endpoint("intraday/2019-01-01")
355 .build()
356 .unwrap();
357 let client = SingleTestClient::new_raw(endpoint, "");
358
359 let endpoint = Intraday::builder()
360 .date(NaiveDate::from_ymd_opt(2019, 1, 1).unwrap())
361 .build()
362 .unwrap();
363 api::ignore(endpoint).query(&client).unwrap();
364 }
365}