fmp_rs/endpoints/
economics.rs

1//! Economics and indicators endpoints
2
3use crate::{
4    client::FmpClient,
5    error::Result,
6    models::economics::{EconomicIndicator, MarketRiskPremium, TreasuryRate},
7};
8use serde::Serialize;
9
10/// Economics API endpoints
11pub struct Economics {
12    client: FmpClient,
13}
14
15impl Economics {
16    pub(crate) fn new(client: FmpClient) -> Self {
17        Self { client }
18    }
19
20    /// Get treasury rates
21    ///
22    /// Returns US treasury rates for various maturities (1mo to 30yr).
23    ///
24    /// # Arguments
25    /// * `from` - Start date (optional, format: YYYY-MM-DD)
26    /// * `to` - End date (optional, format: YYYY-MM-DD)
27    ///
28    /// # Example
29    /// ```no_run
30    /// # use fmp_rs::FmpClient;
31    /// # #[tokio::main]
32    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
33    /// let client = FmpClient::new()?;
34    /// let rates = client.economics().get_treasury_rates(None, None).await?;
35    /// for rate in rates.iter().take(5) {
36    ///     println!("{}: 10Y = {:.2}%, 2Y = {:.2}%",
37    ///         rate.date,
38    ///         rate.year_10.unwrap_or(0.0),
39    ///         rate.year_2.unwrap_or(0.0));
40    /// }
41    /// # Ok(())
42    /// # }
43    /// ```
44    pub async fn get_treasury_rates(
45        &self,
46        from: Option<&str>,
47        to: Option<&str>,
48    ) -> Result<Vec<TreasuryRate>> {
49        #[derive(Serialize)]
50        struct Query<'a> {
51            #[serde(skip_serializing_if = "Option::is_none")]
52            from: Option<&'a str>,
53            #[serde(skip_serializing_if = "Option::is_none")]
54            to: Option<&'a str>,
55            apikey: &'a str,
56        }
57
58        let url = self.client.build_url("/treasury");
59        self.client
60            .get_with_query(
61                &url,
62                &Query {
63                    from,
64                    to,
65                    apikey: self.client.api_key(),
66                },
67            )
68            .await
69    }
70
71    /// Get GDP (Gross Domestic Product)
72    ///
73    /// Returns historical GDP data.
74    ///
75    /// # Example
76    /// ```no_run
77    /// # use fmp_rs::FmpClient;
78    /// # #[tokio::main]
79    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
80    /// let client = FmpClient::new()?;
81    /// let gdp = client.economics().get_gdp().await?;
82    /// for record in gdp.iter().take(5) {
83    ///     println!("{}: ${:.2}T", record.date, record.value.unwrap_or(0.0) / 1_000_000_000_000.0);
84    /// }
85    /// # Ok(())
86    /// # }
87    /// ```
88    pub async fn get_gdp(&self) -> Result<Vec<EconomicIndicator>> {
89        #[derive(Serialize)]
90        struct Query<'a> {
91            apikey: &'a str,
92        }
93
94        let url = self.client.build_url("/economic");
95        self.client
96            .get_with_query(
97                &url,
98                &Query {
99                    apikey: self.client.api_key(),
100                },
101            )
102            .await
103    }
104
105    /// Get Real GDP
106    ///
107    /// Returns inflation-adjusted GDP data.
108    ///
109    /// # Example
110    /// ```no_run
111    /// # use fmp_rs::FmpClient;
112    /// # #[tokio::main]
113    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
114    /// let client = FmpClient::new()?;
115    /// let real_gdp = client.economics().get_real_gdp().await?;
116    /// println!("Latest Real GDP: ${:.2}T",
117    ///     real_gdp.first().and_then(|r| r.value).unwrap_or(0.0) / 1_000_000_000_000.0);
118    /// # Ok(())
119    /// # }
120    /// ```
121    pub async fn get_real_gdp(&self) -> Result<Vec<EconomicIndicator>> {
122        #[derive(Serialize)]
123        struct Query<'a> {
124            apikey: &'a str,
125        }
126
127        let url = self.client.build_url("/economic_indicator/GDP");
128        self.client
129            .get_with_query(
130                &url,
131                &Query {
132                    apikey: self.client.api_key(),
133                },
134            )
135            .await
136    }
137
138    /// Get GDP per capita
139    ///
140    /// Returns GDP per capita (GDP divided by population).
141    ///
142    /// # Example
143    /// ```no_run
144    /// # use fmp_rs::FmpClient;
145    /// # #[tokio::main]
146    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
147    /// let client = FmpClient::new()?;
148    /// let gdp_per_capita = client.economics().get_gdp_per_capita().await?;
149    /// println!("Latest GDP per capita: ${:.2}",
150    ///     gdp_per_capita.first().and_then(|r| r.value).unwrap_or(0.0));
151    /// # Ok(())
152    /// # }
153    /// ```
154    pub async fn get_gdp_per_capita(&self) -> Result<Vec<EconomicIndicator>> {
155        #[derive(Serialize)]
156        struct Query<'a> {
157            apikey: &'a str,
158        }
159
160        let url = self.client.build_url("/economic_indicator/GDP_PER_CAPITA");
161        self.client
162            .get_with_query(
163                &url,
164                &Query {
165                    apikey: self.client.api_key(),
166                },
167            )
168            .await
169    }
170
171    /// Get CPI (Consumer Price Index)
172    ///
173    /// Returns historical CPI data used to measure inflation.
174    ///
175    /// # Example
176    /// ```no_run
177    /// # use fmp_rs::FmpClient;
178    /// # #[tokio::main]
179    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
180    /// let client = FmpClient::new()?;
181    /// let cpi = client.economics().get_cpi().await?;
182    /// for record in cpi.iter().take(12) {
183    ///     println!("{}: CPI = {:.2}", record.date, record.value.unwrap_or(0.0));
184    /// }
185    /// # Ok(())
186    /// # }
187    /// ```
188    pub async fn get_cpi(&self) -> Result<Vec<EconomicIndicator>> {
189        #[derive(Serialize)]
190        struct Query<'a> {
191            apikey: &'a str,
192        }
193
194        let url = self.client.build_url("/economic_indicator/CPI");
195        self.client
196            .get_with_query(
197                &url,
198                &Query {
199                    apikey: self.client.api_key(),
200                },
201            )
202            .await
203    }
204
205    /// Get inflation rate
206    ///
207    /// Returns year-over-year inflation rate (percentage change in CPI).
208    ///
209    /// # Example
210    /// ```no_run
211    /// # use fmp_rs::FmpClient;
212    /// # #[tokio::main]
213    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
214    /// let client = FmpClient::new()?;
215    /// let inflation = client.economics().get_inflation_rate().await?;
216    /// println!("Current inflation: {:.2}%",
217    ///     inflation.first().and_then(|r| r.value).unwrap_or(0.0));
218    /// # Ok(())
219    /// # }
220    /// ```
221    pub async fn get_inflation_rate(&self) -> Result<Vec<EconomicIndicator>> {
222        #[derive(Serialize)]
223        struct Query<'a> {
224            apikey: &'a str,
225        }
226
227        let url = self.client.build_url("/economic_indicator/INFLATION");
228        self.client
229            .get_with_query(
230                &url,
231                &Query {
232                    apikey: self.client.api_key(),
233                },
234            )
235            .await
236    }
237
238    /// Get unemployment rate
239    ///
240    /// Returns historical unemployment rate data.
241    ///
242    /// # Example
243    /// ```no_run
244    /// # use fmp_rs::FmpClient;
245    /// # #[tokio::main]
246    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
247    /// let client = FmpClient::new()?;
248    /// let unemployment = client.economics().get_unemployment_rate().await?;
249    /// println!("Current unemployment: {:.1}%",
250    ///     unemployment.first().and_then(|r| r.value).unwrap_or(0.0));
251    /// # Ok(())
252    /// # }
253    /// ```
254    pub async fn get_unemployment_rate(&self) -> Result<Vec<EconomicIndicator>> {
255        #[derive(Serialize)]
256        struct Query<'a> {
257            apikey: &'a str,
258        }
259
260        let url = self.client.build_url("/economic_indicator/UNEMPLOYMENT");
261        self.client
262            .get_with_query(
263                &url,
264                &Query {
265                    apikey: self.client.api_key(),
266                },
267            )
268            .await
269    }
270
271    /// Get federal funds rate
272    ///
273    /// Returns the federal funds effective rate set by the Federal Reserve.
274    ///
275    /// # Example
276    /// ```no_run
277    /// # use fmp_rs::FmpClient;
278    /// # #[tokio::main]
279    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
280    /// let client = FmpClient::new()?;
281    /// let fed_rate = client.economics().get_federal_funds_rate().await?;
282    /// println!("Current Fed rate: {:.2}%",
283    ///     fed_rate.first().and_then(|r| r.value).unwrap_or(0.0));
284    /// # Ok(())
285    /// # }
286    /// ```
287    pub async fn get_federal_funds_rate(&self) -> Result<Vec<EconomicIndicator>> {
288        #[derive(Serialize)]
289        struct Query<'a> {
290            apikey: &'a str,
291        }
292
293        let url = self
294            .client
295            .build_url("/economic_indicator/FEDERAL_FUNDS_RATE");
296        self.client
297            .get_with_query(
298                &url,
299                &Query {
300                    apikey: self.client.api_key(),
301                },
302            )
303            .await
304    }
305
306    /// Get market risk premium
307    ///
308    /// Returns equity risk premium data by country.
309    ///
310    /// # Example
311    /// ```no_run
312    /// # use fmp_rs::FmpClient;
313    /// # #[tokio::main]
314    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
315    /// let client = FmpClient::new()?;
316    /// let risk_premiums = client.economics().get_market_risk_premium().await?;
317    /// for premium in risk_premiums.iter().take(10) {
318    ///     println!("{}: Total ERP = {:.2}%",
319    ///         premium.country,
320    ///         premium.total_equity_risk_premium.unwrap_or(0.0));
321    /// }
322    /// # Ok(())
323    /// # }
324    /// ```
325    pub async fn get_market_risk_premium(&self) -> Result<Vec<MarketRiskPremium>> {
326        #[derive(Serialize)]
327        struct Query<'a> {
328            apikey: &'a str,
329        }
330
331        let url = self.client.build_url("/market_risk_premium");
332        self.client
333            .get_with_query(
334                &url,
335                &Query {
336                    apikey: self.client.api_key(),
337                },
338            )
339            .await
340    }
341}
342
343#[cfg(test)]
344mod tests {
345    use super::*;
346
347    // Golden path tests
348    #[tokio::test]
349    #[ignore = "requires FMP API key"]
350    async fn test_get_treasury_rates() {
351        let client = FmpClient::new().unwrap();
352        let result = client.economics().get_treasury_rates(None, None).await;
353        assert!(result.is_ok());
354        let rates = result.unwrap();
355        assert!(!rates.is_empty());
356        assert!(rates[0].year_10.is_some());
357    }
358
359    #[tokio::test]
360    #[ignore = "requires FMP API key"]
361    async fn test_get_gdp() {
362        let client = FmpClient::new().unwrap();
363        let result = client.economics().get_gdp().await;
364        assert!(result.is_ok());
365        let gdp = result.unwrap();
366        assert!(!gdp.is_empty());
367    }
368
369    #[tokio::test]
370    #[ignore = "requires FMP API key"]
371    async fn test_get_real_gdp() {
372        let client = FmpClient::new().unwrap();
373        let result = client.economics().get_real_gdp().await;
374        assert!(result.is_ok());
375        let real_gdp = result.unwrap();
376        assert!(!real_gdp.is_empty());
377    }
378
379    #[tokio::test]
380    #[ignore = "requires FMP API key"]
381    async fn test_get_gdp_per_capita() {
382        let client = FmpClient::new().unwrap();
383        let result = client.economics().get_gdp_per_capita().await;
384        assert!(result.is_ok());
385        let gdp_pc = result.unwrap();
386        assert!(!gdp_pc.is_empty());
387    }
388
389    #[tokio::test]
390    #[ignore = "requires FMP API key"]
391    async fn test_get_cpi() {
392        let client = FmpClient::new().unwrap();
393        let result = client.economics().get_cpi().await;
394        assert!(result.is_ok());
395        let cpi = result.unwrap();
396        assert!(!cpi.is_empty());
397    }
398
399    #[tokio::test]
400    #[ignore = "requires FMP API key"]
401    async fn test_get_inflation_rate() {
402        let client = FmpClient::new().unwrap();
403        let result = client.economics().get_inflation_rate().await;
404        assert!(result.is_ok());
405        let inflation = result.unwrap();
406        assert!(!inflation.is_empty());
407    }
408
409    #[tokio::test]
410    #[ignore = "requires FMP API key"]
411    async fn test_get_unemployment_rate() {
412        let client = FmpClient::new().unwrap();
413        let result = client.economics().get_unemployment_rate().await;
414        assert!(result.is_ok());
415        let unemployment = result.unwrap();
416        assert!(!unemployment.is_empty());
417    }
418
419    #[tokio::test]
420    #[ignore = "requires FMP API key"]
421    async fn test_get_federal_funds_rate() {
422        let client = FmpClient::new().unwrap();
423        let result = client.economics().get_federal_funds_rate().await;
424        assert!(result.is_ok());
425        let fed_rate = result.unwrap();
426        assert!(!fed_rate.is_empty());
427    }
428
429    #[tokio::test]
430    #[ignore = "requires FMP API key"]
431    async fn test_get_market_risk_premium() {
432        let client = FmpClient::new().unwrap();
433        let result = client.economics().get_market_risk_premium().await;
434        assert!(result.is_ok());
435        let premiums = result.unwrap();
436        assert!(!premiums.is_empty());
437    }
438
439    // Edge case tests
440    #[tokio::test]
441    #[ignore = "requires FMP API key"]
442    async fn test_treasury_rates_with_dates() {
443        let client = FmpClient::new().unwrap();
444        let result = client
445            .economics()
446            .get_treasury_rates(Some("2024-01-01"), Some("2024-12-31"))
447            .await;
448        assert!(result.is_ok());
449    }
450
451    // Error handling tests
452    #[tokio::test]
453    async fn test_invalid_api_key() {
454        let client = FmpClient::builder()
455            .api_key("invalid_key_12345")
456            .build()
457            .unwrap();
458        let result = client.economics().get_gdp().await;
459        assert!(result.is_err());
460    }
461
462    // Additional edge case tests
463    #[tokio::test]
464    #[ignore = "requires FMP API key"]
465    async fn test_get_inflation_rate() {
466        let client = FmpClient::new().unwrap();
467        let result = client.economics().get_inflation_rate().await;
468        assert!(result.is_ok());
469        let inflation = result.unwrap();
470        assert!(!inflation.is_empty());
471        // Inflation rate should be a percentage
472        if let Some(value) = inflation[0].value {
473            assert!(value >= -10.0 && value <= 20.0); // Reasonable range for inflation
474        }
475    }
476
477    #[tokio::test]
478    #[ignore = "requires FMP API key"]
479    async fn test_get_unemployment_rate() {
480        let client = FmpClient::new().unwrap();
481        let result = client.economics().get_unemployment_rate().await;
482        assert!(result.is_ok());
483        let unemployment = result.unwrap();
484        assert!(!unemployment.is_empty());
485        // Unemployment rate should be a reasonable percentage
486        if let Some(value) = unemployment[0].value {
487            assert!(value >= 0.0 && value <= 50.0); // Reasonable range
488        }
489    }
490
491    #[tokio::test]
492    #[ignore = "requires FMP API key"]
493    async fn test_get_federal_funds_rate() {
494        let client = FmpClient::new().unwrap();
495        let result = client.economics().get_federal_funds_rate().await;
496        assert!(result.is_ok());
497        let fed_rate = result.unwrap();
498        assert!(!fed_rate.is_empty());
499        // Fed funds rate should be a reasonable percentage
500        if let Some(value) = fed_rate[0].value {
501            assert!(value >= 0.0 && value <= 25.0); // Reasonable range
502        }
503    }
504
505    #[tokio::test]
506    #[ignore = "requires FMP API key"]
507    async fn test_treasury_rates_invalid_dates() {
508        let client = FmpClient::new().unwrap();
509        // Test with invalid date format
510        let result = client
511            .economics()
512            .get_treasury_rates(Some("invalid-date"), Some("2024-12-31"))
513            .await;
514        // Should handle invalid dates gracefully
515        match result {
516            Ok(_) => {}  // API might handle it gracefully
517            Err(_) => {} // Or return an error, both are acceptable
518        }
519    }
520
521    #[tokio::test]
522    #[ignore = "requires FMP API key"]
523    async fn test_treasury_rates_future_dates() {
524        let client = FmpClient::new().unwrap();
525        // Test with future dates
526        let result = client
527            .economics()
528            .get_treasury_rates(Some("2030-01-01"), Some("2030-12-31"))
529            .await;
530        assert!(result.is_ok());
531        let rates = result.unwrap();
532        // Future dates should return empty or current data
533        assert!(rates.is_empty() || !rates.is_empty());
534    }
535}