Skip to main content

alpaca_data/news/
request.rs

1use alpaca_core::{QueryWriter, pagination::PaginatedRequest};
2
3use crate::Error;
4
5use super::Sort;
6
7#[derive(Clone, Debug, Default)]
8pub struct ListRequest {
9    pub start: Option<String>,
10    pub end: Option<String>,
11    pub sort: Option<Sort>,
12    pub symbols: Option<Vec<String>>,
13    pub limit: Option<u32>,
14    pub include_content: Option<bool>,
15    pub exclude_contentless: Option<bool>,
16    pub page_token: Option<String>,
17}
18
19impl ListRequest {
20    pub(crate) fn validate(&self) -> Result<(), Error> {
21        validate_limit(self.limit, 1, 50)?;
22
23        if let Some(symbols) = &self.symbols {
24            validate_symbols(symbols)?;
25        }
26
27        Ok(())
28    }
29
30    pub(crate) fn into_query(self) -> Vec<(String, String)> {
31        let mut query = QueryWriter::default();
32        query.push_opt("start", self.start);
33        query.push_opt("end", self.end);
34        query.push_opt("sort", self.sort);
35        if let Some(symbols) = self.symbols {
36            query.push_csv("symbols", symbols);
37        }
38        query.push_opt("limit", self.limit);
39        query.push_opt("include_content", self.include_content);
40        query.push_opt("exclude_contentless", self.exclude_contentless);
41        query.push_opt("page_token", self.page_token);
42        query.finish()
43    }
44}
45
46impl PaginatedRequest for ListRequest {
47    fn with_page_token(&self, page_token: Option<String>) -> Self {
48        let mut next = self.clone();
49        next.page_token = page_token;
50        next
51    }
52}
53
54fn validate_symbols(symbols: &[String]) -> Result<(), Error> {
55    if symbols.is_empty() {
56        return Err(Error::InvalidRequest(
57            "symbols are invalid: must not be empty when provided".to_owned(),
58        ));
59    }
60
61    if symbols.iter().any(|symbol| symbol.trim().is_empty()) {
62        return Err(Error::InvalidRequest(
63            "symbols are invalid: must not contain empty or whitespace-only entries".to_owned(),
64        ));
65    }
66
67    Ok(())
68}
69
70fn validate_limit(limit: Option<u32>, min: u32, max: u32) -> Result<(), Error> {
71    if let Some(limit) = limit
72        && !(min..=max).contains(&limit)
73    {
74        return Err(Error::InvalidRequest(format!(
75            "limit must be between {min} and {max}"
76        )));
77    }
78
79    Ok(())
80}