Skip to main content

ferric_fred/
series_search_request.rs

1use crate::{Client, OrderBy, Result, SearchType, SeriesSearchResults, SortOrder};
2
3/// A builder for a `series/search` request, returned by [`Client::search`].
4///
5/// Only parameters you set are sent; anything left unset uses FRED's default.
6/// Finish with [`send`](SeriesSearchRequest::send).
7///
8/// ```no_run
9/// # async fn run(client: &ferric_fred::Client) -> ferric_fred::Result<()> {
10/// use ferric_fred::{OrderBy, SortOrder};
11/// let results = client
12///     .search("unemployment rate")
13///     .order_by(OrderBy::Popularity)
14///     .sort_order(SortOrder::Descending)
15///     .limit(10)
16///     .send()
17///     .await?;
18/// println!("{} total matches", results.count);
19/// # Ok(())
20/// # }
21/// ```
22#[derive(Debug, Clone)]
23#[must_use = "a SeriesSearchRequest does nothing until you call `.send()`"]
24pub struct SeriesSearchRequest<'a> {
25    client: &'a Client,
26    search_text: String,
27    search_type: Option<SearchType>,
28    order_by: Option<OrderBy>,
29    sort_order: Option<SortOrder>,
30    limit: Option<u32>,
31    offset: Option<u32>,
32}
33
34impl<'a> SeriesSearchRequest<'a> {
35    pub(crate) fn new(client: &'a Client, search_text: String) -> Self {
36        Self {
37            client,
38            search_text,
39            search_type: None,
40            order_by: None,
41            sort_order: None,
42            limit: None,
43            offset: None,
44        }
45    }
46
47    /// How the search text is interpreted (`search_type`).
48    pub fn search_type(mut self, search_type: SearchType) -> Self {
49        self.search_type = Some(search_type);
50        self
51    }
52
53    /// Field to order results by (`order_by`).
54    pub fn order_by(mut self, order_by: OrderBy) -> Self {
55        self.order_by = Some(order_by);
56        self
57    }
58
59    /// Sort order of the results (`sort_order`).
60    pub fn sort_order(mut self, order: SortOrder) -> Self {
61        self.sort_order = Some(order);
62        self
63    }
64
65    /// Maximum number of results to return, `1..=1000` (`limit`).
66    pub fn limit(mut self, limit: u32) -> Self {
67        self.limit = Some(limit);
68        self
69    }
70
71    /// Number of results to skip from the start (`offset`), for paging.
72    pub fn offset(mut self, offset: u32) -> Self {
73        self.offset = Some(offset);
74        self
75    }
76
77    /// Run the search and return the matching series with pagination metadata.
78    ///
79    /// # Errors
80    ///
81    /// Returns an error if the request fails to send, FRED returns a non-success
82    /// status, or the response body cannot be deserialized.
83    pub async fn send(self) -> Result<SeriesSearchResults> {
84        self.client.execute_search(&self).await
85    }
86
87    /// Serialize the set parameters to FRED query key/value pairs. `api_key` and
88    /// `file_type` are added by the client, not here.
89    pub(crate) fn query_params(&self) -> Vec<(&'static str, String)> {
90        let mut params: Vec<(&'static str, String)> = Vec::new();
91        params.push(("search_text", self.search_text.clone()));
92        if let Some(search_type) = self.search_type {
93            params.push(("search_type", search_type.query_code().to_owned()));
94        }
95        if let Some(order_by) = self.order_by {
96            params.push(("order_by", order_by.query_code().to_owned()));
97        }
98        if let Some(order) = self.sort_order {
99            params.push(("sort_order", order.query_code().to_owned()));
100        }
101        if let Some(limit) = self.limit {
102            params.push(("limit", limit.to_string()));
103        }
104        if let Some(offset) = self.offset {
105            params.push(("offset", offset.to_string()));
106        }
107        params
108    }
109}
110
111impl crate::paginate::sealed::Sealed for SeriesSearchRequest<'_> {}
112impl crate::paginate::Paginate for SeriesSearchRequest<'_> {
113    type Page = SeriesSearchResults;
114    const MAX_PAGE: u32 = 1000;
115    fn requested_limit(&self) -> Option<u32> {
116        self.limit
117    }
118    fn requested_offset(&self) -> Option<u32> {
119        self.offset
120    }
121    fn with_paging(self, limit: u32, offset: u32) -> Self {
122        self.limit(limit).offset(offset)
123    }
124    fn send_page(self) -> impl std::future::Future<Output = Result<Self::Page>> + Send {
125        self.send()
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    fn test_client() -> Client {
134        Client::new("test-key").expect("client builds")
135    }
136
137    #[test]
138    fn defaults_send_only_the_search_text() {
139        let client = test_client();
140        let request = client.search("real gnp");
141        assert_eq!(
142            request.query_params(),
143            vec![("search_text", "real gnp".to_owned())]
144        );
145    }
146
147    #[test]
148    fn parameters_serialize_to_fred_codes() {
149        let client = test_client();
150        let request = client
151            .search("unemployment")
152            .search_type(SearchType::FullText)
153            .order_by(OrderBy::Popularity)
154            .sort_order(SortOrder::Descending)
155            .limit(25)
156            .offset(50);
157
158        let params = request.query_params();
159        for expected in [
160            ("search_text", "unemployment"),
161            ("search_type", "full_text"),
162            ("order_by", "popularity"),
163            ("sort_order", "desc"),
164            ("limit", "25"),
165            ("offset", "50"),
166        ] {
167            assert!(
168                params.contains(&(expected.0, expected.1.to_owned())),
169                "missing {expected:?} in {params:?}"
170            );
171        }
172    }
173}