fred_rs/series/
observation.rs

1//! Get the observations or data values for an economic data series
2//! 
3//! [https://research.stlouisfed.org/docs/api/fred/series_observations.html](https://research.stlouisfed.org/docs/api/fred/series_observations.html)
4//! 
5//! ```
6//! use fred_rs::client::FredClient;
7//! use fred_rs::series::observation::{Builder, Units, Frequency, Response};
8//! 
9//! // Create the client object
10//! let mut c = match FredClient::new() {
11//!     Ok(c) => c,
12//!     Err(msg) => {
13//!         println!("{}", msg);
14//!         return
15//!     },
16//! };
17//! 
18//! // Create the argument builder
19//! let mut builder = Builder::new();
20//! 
21//! // Set the arguments for the builder
22//! builder
23//!     .observation_start("2000-01-01")
24//!     .units(Units::PCH)
25//!     .frequency(Frequency::M);
26//! 
27//! // Make the request and pass in the builder to apply the arguments
28//! let resp: Response = match c.series_observation("GNPCA", Some(builder)) {
29//!     Ok(resp) => resp,
30//!     Err(msg) => {
31//!         println!("{}", msg);
32//!         return
33//!     },
34//! };
35//! ```
36
37use serde::Deserialize;
38use std::fmt::{self, Display, Formatter};
39
40#[derive(Deserialize, Clone, Debug, Default)]
41/// Response data structure for the fred/series/observation endpoint
42/// 
43/// [https://research.stlouisfed.org/docs/api/fred/series_observations.html](https://research.stlouisfed.org/docs/api/fred/series_observations.html)
44pub struct Response {
45    /// The realtime start of the request
46    pub realtime_start: String,
47    /// The realtiem end of the request
48    pub realtime_end: String,
49    /// The start of the observation period
50    pub observation_start: String,
51    /// The end of the observation period
52    pub observation_end: String,
53    /// The units of the observation (e.g. Billions of Chained 2009 Dollars)
54    pub units: String,
55    /// The output type [Link](enum.OutputType.html)
56    pub output_type: usize,
57    /// The file type (will always be JSON for fred-rs)
58    pub file_type: String,
59    /// On what metric the data are order
60    pub order_by: String,
61    /// Ascending (asc) of descending (desc)
62    pub sort_order: String,
63    /// The number of data items returned
64    pub count: usize,
65    /// The first result returned
66    pub offset: usize,
67    /// The maximum number of results requested
68    pub limit: usize,
69    /// The data values returned
70    pub observations: Vec<DataPoint>,
71}
72
73impl Display for Response {
74    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
75        for item in self.observations.iter() {
76            match item.fmt(f) {
77                Ok(_) => (),
78                Err(e) => return Err(e),
79            }
80            match writeln!(f, "") {
81                Ok(_) => (),
82                Err(e) => return Err(e),
83            }
84        }
85        Ok(())
86    }
87}
88
89#[derive(Deserialize, Clone, Debug, Default)]
90/// A single observation datapoint
91/// 
92/// [https://research.stlouisfed.org/docs/api/fred/series_observations.html](https://research.stlouisfed.org/docs/api/fred/series_observations.html)
93pub struct DataPoint {
94    pub realtime_start: String,
95    pub realtime_end: String,
96    /// Date of the data point
97    pub date: String,
98    /// String encoded data point
99    pub value: String,
100}
101
102impl Display for DataPoint {
103    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
104        write!(f, "({}: {})", self.date, self.value)
105    }
106}
107
108/// Sort order options for the fred/series/observation endpoint
109/// 
110/// [https://research.stlouisfed.org/docs/api/fred/series_observations.html#sort_order](https://research.stlouisfed.org/docs/api/fred/series_observations.html#sort_order)
111pub enum SortOrder {
112    /// Dates returned in ascending order (default)
113    Ascending,    
114    /// Dates returned in descending order
115    Descending,   
116}
117
118/// Data transformation options for the fred/series/observation endpoint
119/// 
120/// [https://research.stlouisfed.org/docs/api/fred/series_observations.html#units](https://research.stlouisfed.org/docs/api/fred/series_observations.html#units)
121pub enum Units {
122    /// Linear: no transform applied (default)
123    LIN,
124    /// Change: returns the period over period change of the observation
125    CHG,
126    /// 1 Year Change: Returns the YoY change of the observation
127    CH1,
128    /// Percent Change: Returns the period over period percent change of the observation
129    PCH,
130    /// 1 Year Percent Change: Returns the YoY percent change of the observation
131    PC1,
132    /// Compounded Annual Rate of Change
133    PCA,
134    /// Continuously Compounded Rate of Change
135    CCH,
136    /// Continuously Compounded Annual Rate of Change
137    CCA,
138    /// Natual Log: Returns the natural logarithm of the observation
139    LOG,
140}
141
142/// Options for data series frequency
143/// 
144/// The frequency cannot exceed the native frequency of the data series.
145/// 
146/// [https://research.stlouisfed.org/docs/api/fred/series_observations.html#frequency](https://research.stlouisfed.org/docs/api/fred/series_observations.html#frequency)
147pub enum Frequency {
148    /// Daily (fastest)
149    D,
150    /// Weekly
151    W,
152    /// Bi-Weekly
153    BW,
154    /// Monthly
155    M,
156    /// Quarterly
157    Q,
158    /// Semi-Annualy
159    SA,
160    /// Annual (slowest)
161    A,
162    /// Weekly, Ending Friday
163    WEF,    
164    /// Weekly, Ending Thursday
165    WETH,   
166    /// Weekly, Ending Wednesday
167    WEW,    
168    /// Weekly, Ending Tuesday
169    WETU,  
170    /// Weekly, Ending Monday 
171    WEM,   
172    /// Weekly, Ending Sunday 
173    WESU,  
174    /// Weekly, Ending Saturday 
175    WESA,  
176    /// Bi-Weekly, Ending Wednesday 
177    BWEW,   
178    /// Bi-Weekly, Ending Monday
179    BWEM,   
180}
181
182/// Provides an aggregation method for frequency aggregation
183/// 
184/// This argument should be used in conjunction with the frequency argument if the default aggregation method (AVG) is not preferred.
185/// 
186/// [https://research.stlouisfed.org/docs/api/fred/series_observations.html#aggregation_method](https://research.stlouisfed.org/docs/api/fred/series_observations.html#aggregation_method)
187pub enum AggregationMethod {
188    /// Average (default): intermediate datapoints are averaged to produce the aggregate
189    AVG,
190    /// Sum: intermediate datapoints are summed to produce the aggregate
191    SUM,
192    /// End of Period: The final result in the period is returned
193    EOP
194}
195
196/// Specifies the data output type
197/// 
198/// [https://research.stlouisfed.org/docs/api/fred/series_observations.html#output_type](https://research.stlouisfed.org/docs/api/fred/series_observations.html#output_type)
199pub enum OutputType {
200    /// Observations by Real Time Period
201    RT,
202    /// Observations by Vintage Date, All Observations
203    VDALL,
204    /// Observations by Vintage Date, New and Revised Observations Only
205    VDNEW,
206    /// Observations, Initial Release Only
207    INITIAL
208}
209
210/// Argument builder for the fred/series/observation endpoint.
211/// 
212/// Each method adds an argument to the builder which can then be passed to the client used to fetch the data to apply the arguments.
213pub struct Builder {
214    option_string: String,
215    vintage_dates: String,
216}
217
218
219impl Builder {
220    /// Initializes a new observation::Builder that can be used to add commands to an API request
221    /// 
222    /// The builder does not do validity checking of the arguments nor does it check for duplicates.
223    /// 
224    /// ```
225    /// use fred_rs::series::observation::{Builder, Units, SortOrder};
226    /// // Create a new builder
227    /// let mut builder = Builder::new();
228    /// // add arguments to the builder
229    /// builder
230    ///     .limit(100)
231    ///     .units(Units::LOG)
232    ///     .sort_order(SortOrder::Descending);
233    /// ```
234    pub fn new() -> Builder {
235        Builder {
236            option_string: String::new(),
237            vintage_dates: String::new(),
238        }
239    }
240
241    /// Returns the current arguments as a URL formatted string
242    pub(crate) fn build(mut self) -> String {
243        if self.vintage_dates.len() > 0 {
244            self.option_string += format!("&vintage_dates={}", self.vintage_dates).as_str()
245        }
246
247        self.option_string
248    }
249
250    /// Adds a realtime_start argument to the builder
251    /// 
252    /// # Arguments
253    /// * `start_date` - date formatted as YYYY-MM-DD
254    /// 
255    /// [https://research.stlouisfed.org/docs/api/fred/series_observations.html#realtime_start](https://research.stlouisfed.org/docs/api/fred/series_observations.html#realtime_start)
256    pub fn realtime_start(&mut self, start_date: &str) -> &mut Builder {
257        self.option_string += format!("&realtime_start={}", start_date).as_str();
258        self
259    }
260
261    /// Adds a realtime_end argument to the builder
262    /// 
263    /// # Arguments
264    /// * `end_date` - date formatted as YYYY-MM-DD
265    /// 
266    /// [https://research.stlouisfed.org/docs/api/fred/series_observations.html#realtime_end](https://research.stlouisfed.org/docs/api/fred/series_observations.html#realtime_end)
267    pub fn realtime_end(&mut self, end_date: &str) -> &mut Builder {
268        self.option_string += format!("&realtime_end={}", end_date).as_str();
269        self
270    }
271
272    /// Adds a limit argument to the builder
273    /// 
274    /// The limit argument specifies a maximum number of observations to return.
275    /// 
276    /// # Arguments
277    /// * `num_points` - Maximum number of data points to return
278    /// 
279    /// [https://research.stlouisfed.org/docs/api/fred/series_observations.html#limit](https://research.stlouisfed.org/docs/api/fred/series_observations.html#limit)
280    pub fn limit(&mut self, num_points: usize) -> &mut Builder {
281        let num_points = if num_points > 1000000 { // max value is 1000
282            1000000
283        } else {
284            num_points
285        };
286        self.option_string += format!("&limit={}", num_points).as_str();
287        self
288    }
289
290    /// Adds an offset argument to the builder
291    /// 
292    /// Adding an offset shifts the starting result number.  For example, if limit is 5 and offset is 0 then results 1-5 will be returned, but if offset was 5 then results 6-10 would be returned.
293    /// 
294    /// # Arguments
295    /// * `ofs` - the offset amount
296    /// 
297    /// [https://research.stlouisfed.org/docs/api/fred/series_observations.html#offset](https://research.stlouisfed.org/docs/api/fred/series_observations.html#offset)
298    pub fn offset(&mut self, ofs: usize) -> &mut Builder {
299        self.option_string += format!("&offset={}", ofs).as_str();
300        self
301    }
302
303    /// Change the sort order of the data
304    /// 
305    /// # Arguments
306    /// * `order` - Data sort order enum
307    /// 
308    /// [https://research.stlouisfed.org/docs/api/fred/series_observations.html#sort_order](https://research.stlouisfed.org/docs/api/fred/series_observations.html#sort_order)
309    pub fn sort_order(&mut self, order: SortOrder) -> &mut Builder {
310        match order {
311            SortOrder::Descending => {
312                self.option_string += format!("&sort_order=desc").as_str()
313            },
314            _ => () // Ascending is the default so do nothing
315        }
316        self
317    }
318
319    /// Set the start year for data points
320    /// 
321    /// # Arguments
322    /// * `start_date` - date formatted as YYYY-MM-DD
323    /// 
324    /// [https://research.stlouisfed.org/docs/api/fred/series_observations.html#observation_start](https://research.stlouisfed.org/docs/api/fred/series_observations.html#observation_start)
325    pub fn observation_start(&mut self, start_date: &str) -> &mut Builder {
326        self.option_string += format!("&observation_start={}", start_date).as_str();
327        self
328    }
329
330    /// Set the end year for data points
331    /// 
332    /// # Arguments
333    /// * `end_date` - date formatted as YYYY-MM-DD
334    /// 
335    /// [https://research.stlouisfed.org/docs/api/fred/series_observations.html#observation_end](https://research.stlouisfed.org/docs/api/fred/series_observations.html#observation_end)
336    pub fn observation_end(&mut self, end_date: &str) -> &mut Builder {
337        self.option_string += format!("&observation_end={}", end_date).as_str();
338        self
339    }
340
341    /// Set the units of the data series
342    /// 
343    /// # Arguments
344    /// * `units` - Data units to apply to the data set (see ObservationUnits)
345    /// 
346    /// [https://research.stlouisfed.org/docs/api/fred/series_observations.html#units](https://research.stlouisfed.org/docs/api/fred/series_observations.html#units)
347    pub fn units(&mut self, units: Units) -> &mut Builder {
348        match units {
349            Units::CHG => {
350                self.option_string += format!("&units=chg").as_str()
351            },
352            Units::CH1 => {
353                self.option_string += format!("&units=ch1").as_str()
354            },
355            Units::PCH => {
356                self.option_string += format!("&units=pch").as_str()
357            },
358            Units::PC1 => {
359                self.option_string += format!("&units=pc1").as_str()
360            },
361            Units::PCA => {
362                self.option_string += format!("&units=pca").as_str()
363            },
364            Units::CCH => {
365                self.option_string += format!("&units=cch").as_str()
366            },
367            Units::CCA => {
368                self.option_string += format!("&units=cca").as_str()
369            },
370            Units::LOG => {
371                self.option_string += format!("&units=log").as_str()
372            },
373            _ => (), // lin is the default
374        }
375        self
376    }
377
378    /// Set the frequency of the data series
379    /// 
380    /// The requested frequency must be less than or equal to the native frequency for the data set.
381    /// 
382    /// # Arguments
383    /// * `freq` - Frequency of data observations to return
384    /// 
385    /// [https://research.stlouisfed.org/docs/api/fred/series_observations.html#frequency](https://research.stlouisfed.org/docs/api/fred/series_observations.html#frequency)
386    pub fn frequency(&mut self, freq: Frequency) -> &mut Builder {
387        match freq {
388            Frequency::D => {
389                self.option_string += format!("&frequency=d").as_str()
390            },
391            Frequency::W => {
392                self.option_string += format!("&frequency=w").as_str()
393            },
394            Frequency::BW => {
395                self.option_string += format!("&frequency=bw").as_str()
396            },
397            Frequency::M => {
398                self.option_string += format!("&frequency=m").as_str()
399            },
400            Frequency::Q => {
401                self.option_string += format!("&frequency=q").as_str()
402            },
403            Frequency::SA => {
404                self.option_string += format!("&frequency=sa").as_str()
405            },
406            Frequency::A => {
407                self.option_string += format!("&frequency=a").as_str()
408            },
409            Frequency::WEF => {
410                self.option_string += format!("&frequency=wef").as_str()
411            },
412            Frequency::WETH => {
413                self.option_string += format!("&frequency=weth").as_str()
414            },
415            Frequency::WEW => {
416                self.option_string += format!("&frequency=wew").as_str()
417            },
418            Frequency::WETU => {
419                self.option_string += format!("&frequency=d").as_str()
420            },
421            Frequency::WEM => {
422                self.option_string += format!("&frequency=wem").as_str()
423            },
424            Frequency::WESU => {
425                self.option_string += format!("&frequency=wesu").as_str()
426            },
427            Frequency::WESA => {
428                self.option_string += format!("&frequency=wesa").as_str()
429            },
430            Frequency::BWEW => {
431                self.option_string += format!("&frequency=bwew").as_str()
432            },
433            Frequency::BWEM => {
434                self.option_string += format!("&frequency=bwem").as_str()
435            },
436        }
437        self
438    }
439
440    /// Set the aggregation method of the data series
441    /// 
442    /// # Arguments
443    /// * `method` - See `ObservationAggregationMethod`
444    /// 
445    /// [https://research.stlouisfed.org/docs/api/fred/series_observations.html#aggregation_method](https://research.stlouisfed.org/docs/api/fred/series_observations.html#aggregation_method)
446    pub fn aggregation_method(&mut self, method: AggregationMethod) -> &mut Builder {
447        match method {
448            AggregationMethod::SUM => {
449                self.option_string += format!("&aggregation_method=sum").as_str()
450            },
451            AggregationMethod::EOP => {
452                self.option_string += format!("&aggregation_method=eop").as_str()
453            },
454            _ => () // AVG is the default so do nothing
455        }
456        self
457    }
458
459    /// Set the datapoint output type
460    /// 
461    /// # Arguments
462    /// * `otype` - [OutputType](enum.OutputType.hmtl)
463    /// 
464    /// [https://research.stlouisfed.org/docs/api/fred/series_observations.html#output_type](https://research.stlouisfed.org/docs/api/fred/series_observations.html#output_type)
465    pub fn output_type(&mut self, otype: OutputType) -> &mut Builder {
466        match otype {
467            OutputType::VDALL => {
468                self.option_string += format!("&output_type=2").as_str()
469            },
470            OutputType::VDNEW => {
471                self.option_string += format!("&output_type=3").as_str()
472            },
473            OutputType::INITIAL => {
474                self.option_string += format!("&output_type=4").as_str()
475            },
476            _ => () // AVG is the default so do nothing
477        }
478        self
479    }
480
481    /// Add a vintage date argument
482    /// 
483    /// This is the only parameter that could be added mroe than once.
484    /// 
485    /// The API accepts a comma separated list of vintage dates for which to return data.
486    /// 
487    /// [https://research.stlouisfed.org/docs/api/fred/series_observations.html#vintage_dates](https://research.stlouisfed.org/docs/api/fred/series_observations.html#vintage_dates)
488    /// 
489    /// # Arguments
490    /// * `date` - date formatted as YYYY-MM-DD
491    pub fn vintage_date(&mut self, date: &str) -> &mut Builder {
492        if self.vintage_dates.len() != 0 {
493            self.vintage_dates.push(',');
494        } 
495        self.vintage_dates += date;
496        self
497    }
498}
499
500#[cfg(test)]
501mod tests {
502    use super::*;
503    use crate::client::FredClient;
504
505    #[test]
506    fn series_observation_with_options() {
507        let mut c = match FredClient::new() {
508            Ok(c) => c,
509            Err(msg) => {
510                println!("{}", msg);
511                assert_eq!(2, 1);
512                return
513            },
514        };
515
516        let mut builder = Builder::new();
517        builder
518            .limit(5)
519            .sort_order(SortOrder::Descending);
520
521        let resp: Response = match c.series_observation("GNPCA", Some(builder)) {
522            Ok(resp) => resp,
523            Err(msg) => {
524                println!("{}", msg);
525                assert_eq!(2, 1);
526                return
527            },
528        };
529
530        for item in resp.observations {
531            println!("{}: {}", item.date, item.value.parse::<f64>().unwrap());
532        }
533        //assert_eq!(resp.observations[0].value, String::from("1120.076"));
534    }
535}