1use chrono::NaiveDate;
2
3use crate::{
4 AggregationMethod, Client, Frequency, Observation, Result, SeriesId, SortOrder, Units,
5};
6
7#[derive(Debug, Clone)]
27#[must_use = "an ObservationsRequest does nothing until you call `.send()`"]
28pub struct ObservationsRequest<'a> {
29 client: &'a Client,
30 series_id: SeriesId,
31 observation_start: Option<NaiveDate>,
32 observation_end: Option<NaiveDate>,
33 units: Option<Units>,
34 frequency: Option<Frequency>,
35 aggregation_method: Option<AggregationMethod>,
36 sort_order: Option<SortOrder>,
37 limit: Option<u32>,
38 offset: Option<u32>,
39 realtime: Option<(NaiveDate, NaiveDate)>,
42 vintage_dates: Vec<NaiveDate>,
43}
44
45impl<'a> ObservationsRequest<'a> {
46 pub(crate) fn new(client: &'a Client, series_id: SeriesId) -> Self {
47 Self {
48 client,
49 series_id,
50 observation_start: None,
51 observation_end: None,
52 units: None,
53 frequency: None,
54 aggregation_method: None,
55 sort_order: None,
56 limit: None,
57 offset: None,
58 realtime: None,
59 vintage_dates: Vec::new(),
60 }
61 }
62
63 pub fn observation_start(mut self, date: NaiveDate) -> Self {
65 self.observation_start = Some(date);
66 self
67 }
68
69 pub fn observation_end(mut self, date: NaiveDate) -> Self {
71 self.observation_end = Some(date);
72 self
73 }
74
75 pub fn date_range(self, start: NaiveDate, end: NaiveDate) -> Self {
77 self.observation_start(start).observation_end(end)
78 }
79
80 pub fn units(mut self, units: Units) -> Self {
82 self.units = Some(units);
83 self
84 }
85
86 pub fn frequency(mut self, frequency: Frequency) -> Self {
89 self.frequency = Some(frequency);
90 self
91 }
92
93 pub fn aggregation_method(mut self, method: AggregationMethod) -> Self {
96 self.aggregation_method = Some(method);
97 self
98 }
99
100 pub fn sort_order(mut self, order: SortOrder) -> Self {
102 self.sort_order = Some(order);
103 self
104 }
105
106 pub fn limit(mut self, limit: u32) -> Self {
108 self.limit = Some(limit);
109 self
110 }
111
112 pub fn offset(mut self, offset: u32) -> Self {
114 self.offset = Some(offset);
115 self
116 }
117
118 pub fn realtime(mut self, start: NaiveDate, end: NaiveDate) -> Self {
125 self.realtime = Some((start, end));
126 self
127 }
128
129 pub fn vintage_dates(mut self, dates: impl IntoIterator<Item = NaiveDate>) -> Self {
133 self.vintage_dates = dates.into_iter().collect();
134 self
135 }
136
137 pub async fn send(self) -> Result<Vec<Observation>> {
144 self.client.execute_observations(&self).await
145 }
146
147 pub(crate) fn query_params(&self) -> Vec<(&'static str, String)> {
150 let mut params: Vec<(&'static str, String)> = Vec::new();
151 params.push(("series_id", self.series_id.as_str().to_owned()));
152 if let Some(date) = self.observation_start {
153 params.push(("observation_start", date.to_string()));
154 }
155 if let Some(date) = self.observation_end {
156 params.push(("observation_end", date.to_string()));
157 }
158 if let Some(units) = self.units {
159 params.push(("units", units.query_code().to_owned()));
160 }
161 if let Some(frequency) = &self.frequency {
162 params.push(("frequency", frequency.query_code().to_owned()));
163 }
164 if let Some(method) = self.aggregation_method {
165 params.push(("aggregation_method", method.query_code().to_owned()));
166 }
167 if let Some(order) = self.sort_order {
168 params.push(("sort_order", order.query_code().to_owned()));
169 }
170 if let Some(limit) = self.limit {
171 params.push(("limit", limit.to_string()));
172 }
173 if let Some(offset) = self.offset {
174 params.push(("offset", offset.to_string()));
175 }
176 if let Some((start, end)) = self.realtime {
177 params.push(("realtime_start", start.to_string()));
178 params.push(("realtime_end", end.to_string()));
179 }
180 if !self.vintage_dates.is_empty() {
181 let joined = self
182 .vintage_dates
183 .iter()
184 .map(NaiveDate::to_string)
185 .collect::<Vec<_>>()
186 .join(",");
187 params.push(("vintage_dates", joined));
188 }
189 params
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 fn test_client() -> Client {
198 Client::new("test-key").expect("client builds")
199 }
200
201 #[test]
202 fn defaults_send_only_the_series_id() {
203 let client = test_client();
204 let request = client.observations(&SeriesId::new("GNPCA"));
205 assert_eq!(
206 request.query_params(),
207 vec![("series_id", "GNPCA".to_owned())]
208 );
209 }
210
211 #[test]
212 fn parameters_serialize_to_fred_codes() {
213 let client = test_client();
214 let request = client
215 .observations(&SeriesId::new("GNPCA"))
216 .date_range(
217 NaiveDate::from_ymd_opt(2000, 1, 1).unwrap(),
218 NaiveDate::from_ymd_opt(2010, 12, 31).unwrap(),
219 )
220 .units(Units::PercentChange)
221 .frequency(Frequency::Quarterly)
222 .aggregation_method(AggregationMethod::Sum)
223 .sort_order(SortOrder::Descending)
224 .limit(50)
225 .offset(5);
226
227 let params = request.query_params();
228 for expected in [
229 ("series_id", "GNPCA"),
230 ("observation_start", "2000-01-01"),
231 ("observation_end", "2010-12-31"),
232 ("units", "pch"),
233 ("frequency", "q"),
234 ("aggregation_method", "sum"),
235 ("sort_order", "desc"),
236 ("limit", "50"),
237 ("offset", "5"),
238 ] {
239 assert!(
240 params.contains(&(expected.0, expected.1.to_owned())),
241 "missing {expected:?} in {params:?}"
242 );
243 }
244 }
245
246 #[test]
247 fn realtime_and_vintage_dates_serialize() {
248 let client = test_client();
249 let request = client
250 .observations(&SeriesId::new("GNPCA"))
251 .realtime(
252 NaiveDate::from_ymd_opt(2020, 1, 1).unwrap(),
253 NaiveDate::from_ymd_opt(2020, 1, 1).unwrap(),
254 )
255 .vintage_dates([
256 NaiveDate::from_ymd_opt(2020, 3, 26).unwrap(),
257 NaiveDate::from_ymd_opt(2021, 3, 25).unwrap(),
258 ]);
259
260 let params = request.query_params();
261 for expected in [
262 ("realtime_start", "2020-01-01"),
263 ("realtime_end", "2020-01-01"),
264 ("vintage_dates", "2020-03-26,2021-03-25"),
265 ] {
266 assert!(
267 params.contains(&(expected.0, expected.1.to_owned())),
268 "missing {expected:?} in {params:?}"
269 );
270 }
271 }
272}