1use crate::client::FmpClient;
4use crate::error::Result;
5use crate::models::common::Period;
6use crate::models::financials::{
7 BalanceSheet, CashFlowStatement, FinancialAsReported, FinancialGrowth, FinancialRatios,
8 IncomeStatement, KeyMetrics, RevenueGeographicSegmentation, RevenueProductSegmentation,
9};
10use serde::Serialize;
11
12pub struct Financials {
14 client: FmpClient,
15}
16
17impl Financials {
18 pub(crate) fn new(client: FmpClient) -> Self {
19 Self { client }
20 }
21
22 pub async fn get_income_statement(
24 &self,
25 symbol: &str,
26 period: Period,
27 limit: Option<u32>,
28 ) -> Result<Vec<IncomeStatement>> {
29 #[derive(Serialize)]
30 struct Query<'a> {
31 symbol: &'a str,
32 period: &'a str,
33 #[serde(skip_serializing_if = "Option::is_none")]
34 limit: Option<u32>,
35 apikey: &'a str,
36 }
37
38 let url = self.client.build_url("/income-statement");
39 self.client
40 .get_with_query(
41 &url,
42 &Query {
43 symbol,
44 period: &period.to_string(),
45 limit,
46 apikey: self.client.api_key(),
47 },
48 )
49 .await
50 }
51
52 pub async fn get_balance_sheet(
54 &self,
55 symbol: &str,
56 period: Period,
57 limit: Option<u32>,
58 ) -> Result<Vec<BalanceSheet>> {
59 #[derive(Serialize)]
60 struct Query<'a> {
61 symbol: &'a str,
62 period: &'a str,
63 #[serde(skip_serializing_if = "Option::is_none")]
64 limit: Option<u32>,
65 apikey: &'a str,
66 }
67
68 let url = self.client.build_url("/balance-sheet-statement");
69 self.client
70 .get_with_query(
71 &url,
72 &Query {
73 symbol,
74 period: &period.to_string(),
75 limit,
76 apikey: self.client.api_key(),
77 },
78 )
79 .await
80 }
81
82 pub async fn get_cash_flow_statement(
84 &self,
85 symbol: &str,
86 period: Period,
87 limit: Option<u32>,
88 ) -> Result<Vec<CashFlowStatement>> {
89 #[derive(Serialize)]
90 struct Query<'a> {
91 symbol: &'a str,
92 period: &'a str,
93 #[serde(skip_serializing_if = "Option::is_none")]
94 limit: Option<u32>,
95 apikey: &'a str,
96 }
97
98 let url = self.client.build_url("/cash-flow-statement");
99 self.client
100 .get_with_query(
101 &url,
102 &Query {
103 symbol,
104 period: &period.to_string(),
105 limit,
106 apikey: self.client.api_key(),
107 },
108 )
109 .await
110 }
111
112 pub async fn get_ratios(
114 &self,
115 symbol: &str,
116 period: Period,
117 limit: Option<u32>,
118 ) -> Result<Vec<FinancialRatios>> {
119 #[derive(Serialize)]
120 struct Query<'a> {
121 symbol: &'a str,
122 period: &'a str,
123 #[serde(skip_serializing_if = "Option::is_none")]
124 limit: Option<u32>,
125 apikey: &'a str,
126 }
127
128 let url = self.client.build_url("/ratios");
129 self.client
130 .get_with_query(
131 &url,
132 &Query {
133 symbol,
134 period: &period.to_string(),
135 limit,
136 apikey: self.client.api_key(),
137 },
138 )
139 .await
140 }
141
142 pub async fn get_key_metrics(
144 &self,
145 symbol: &str,
146 period: Period,
147 limit: Option<u32>,
148 ) -> Result<Vec<KeyMetrics>> {
149 #[derive(Serialize)]
150 struct Query<'a> {
151 symbol: &'a str,
152 period: &'a str,
153 #[serde(skip_serializing_if = "Option::is_none")]
154 limit: Option<u32>,
155 apikey: &'a str,
156 }
157
158 let url = self.client.build_url("/key-metrics");
159 self.client
160 .get_with_query(
161 &url,
162 &Query {
163 symbol,
164 period: &period.to_string(),
165 limit,
166 apikey: self.client.api_key(),
167 },
168 )
169 .await
170 }
171
172 pub async fn get_key_metrics_ttm(&self, symbol: &str) -> Result<Vec<KeyMetrics>> {
174 #[derive(Serialize)]
175 struct Query<'a> {
176 symbol: &'a str,
177 apikey: &'a str,
178 }
179
180 let url = self.client.build_url("/key-metrics-ttm");
181 self.client
182 .get_with_query(
183 &url,
184 &Query {
185 symbol,
186 apikey: self.client.api_key(),
187 },
188 )
189 .await
190 }
191
192 pub async fn get_ratios_ttm(&self, symbol: &str) -> Result<Vec<FinancialRatios>> {
194 #[derive(Serialize)]
195 struct Query<'a> {
196 symbol: &'a str,
197 apikey: &'a str,
198 }
199
200 let url = self.client.build_url("/ratios-ttm");
201 self.client
202 .get_with_query(
203 &url,
204 &Query {
205 symbol,
206 apikey: self.client.api_key(),
207 },
208 )
209 .await
210 }
211
212 pub async fn get_financial_growth(
236 &self,
237 symbol: &str,
238 period: Period,
239 limit: Option<u32>,
240 ) -> Result<Vec<FinancialGrowth>> {
241 #[derive(Serialize)]
242 struct Query<'a> {
243 symbol: &'a str,
244 period: &'a str,
245 #[serde(skip_serializing_if = "Option::is_none")]
246 limit: Option<u32>,
247 apikey: &'a str,
248 }
249
250 let url = self.client.build_url("/financial-growth");
251 self.client
252 .get_with_query(
253 &url,
254 &Query {
255 symbol,
256 period: &period.to_string(),
257 limit,
258 apikey: self.client.api_key(),
259 },
260 )
261 .await
262 }
263
264 pub async fn get_financial_as_reported(
287 &self,
288 symbol: &str,
289 period: Period,
290 limit: Option<u32>,
291 ) -> Result<Vec<FinancialAsReported>> {
292 #[derive(Serialize)]
293 struct Query<'a> {
294 symbol: &'a str,
295 period: &'a str,
296 #[serde(skip_serializing_if = "Option::is_none")]
297 limit: Option<u32>,
298 apikey: &'a str,
299 }
300
301 let url = self
302 .client
303 .build_url("/financial-statement-full-as-reported");
304 self.client
305 .get_with_query(
306 &url,
307 &Query {
308 symbol,
309 period: &period.to_string(),
310 limit,
311 apikey: self.client.api_key(),
312 },
313 )
314 .await
315 }
316
317 pub async fn get_revenue_product_segmentation(
340 &self,
341 symbol: &str,
342 period: Period,
343 structure: Option<&str>,
344 ) -> Result<Vec<RevenueProductSegmentation>> {
345 #[derive(Serialize)]
346 struct Query<'a> {
347 symbol: &'a str,
348 period: &'a str,
349 #[serde(skip_serializing_if = "Option::is_none")]
350 structure: Option<&'a str>,
351 apikey: &'a str,
352 }
353
354 let url = self.client.build_url("/revenue-product-segmentation");
355 self.client
356 .get_with_query(
357 &url,
358 &Query {
359 symbol,
360 period: &period.to_string(),
361 structure,
362 apikey: self.client.api_key(),
363 },
364 )
365 .await
366 }
367
368 pub async fn get_revenue_geographic_segmentation(
391 &self,
392 symbol: &str,
393 period: Period,
394 structure: Option<&str>,
395 ) -> Result<Vec<RevenueGeographicSegmentation>> {
396 #[derive(Serialize)]
397 struct Query<'a> {
398 symbol: &'a str,
399 period: &'a str,
400 #[serde(skip_serializing_if = "Option::is_none")]
401 structure: Option<&'a str>,
402 apikey: &'a str,
403 }
404
405 let url = self.client.build_url("/revenue-geographic-segmentation");
406 self.client
407 .get_with_query(
408 &url,
409 &Query {
410 symbol,
411 period: &period.to_string(),
412 structure,
413 apikey: self.client.api_key(),
414 },
415 )
416 .await
417 }
418
419 pub async fn get_financial_full_as_reported(
443 &self,
444 symbol: &str,
445 period: Period,
446 ) -> Result<Vec<FinancialAsReported>> {
447 #[derive(Serialize)]
448 struct Query<'a> {
449 symbol: &'a str,
450 period: &'a str,
451 apikey: &'a str,
452 }
453
454 let url = self
455 .client
456 .build_url("/financial-statement-full-as-reported");
457 self.client
458 .get_with_query(
459 &url,
460 &Query {
461 symbol,
462 period: &period.to_string(),
463 apikey: self.client.api_key(),
464 },
465 )
466 .await
467 }
468}
469
470#[cfg(test)]
471mod tests {
472 use super::*;
473
474 #[test]
475 fn test_new() {
476 let client = FmpClient::builder().api_key("test_key").build().unwrap();
477 let _ = Financials::new(client);
478 }
479
480 #[tokio::test]
482 #[ignore = "requires FMP API key"]
483 async fn test_get_income_statement() {
484 let client = FmpClient::new().unwrap();
485 let result = client
486 .financials()
487 .get_income_statement("AAPL", Period::Annual, Some(5))
488 .await;
489 assert!(result.is_ok());
490 let statements = result.unwrap();
491 assert!(!statements.is_empty());
492 assert!(statements.len() <= 5);
493 }
494
495 #[tokio::test]
496 #[ignore = "requires FMP API key"]
497 async fn test_get_balance_sheet() {
498 let client = FmpClient::new().unwrap();
499 let result = client
500 .financials()
501 .get_balance_sheet("AAPL", Period::Annual, Some(5))
502 .await;
503 assert!(result.is_ok());
504 let statements = result.unwrap();
505 assert!(!statements.is_empty());
506 }
507
508 #[tokio::test]
509 #[ignore = "requires FMP API key"]
510 async fn test_get_cash_flow_statement() {
511 let client = FmpClient::new().unwrap();
512 let result = client
513 .financials()
514 .get_cash_flow_statement("AAPL", Period::Annual, Some(5))
515 .await;
516 assert!(result.is_ok());
517 let statements = result.unwrap();
518 assert!(!statements.is_empty());
519 }
520
521 #[tokio::test]
522 #[ignore = "requires FMP API key"]
523 async fn test_get_ratios() {
524 let client = FmpClient::new().unwrap();
525 let result = client
526 .financials()
527 .get_ratios("AAPL", Period::Annual, Some(5))
528 .await;
529 assert!(result.is_ok());
530 let ratios = result.unwrap();
531 assert!(!ratios.is_empty());
532 }
533
534 #[tokio::test]
535 #[ignore = "requires FMP API key"]
536 async fn test_get_key_metrics() {
537 let client = FmpClient::new().unwrap();
538 let result = client
539 .financials()
540 .get_key_metrics("AAPL", Period::Annual, Some(5))
541 .await;
542 assert!(result.is_ok());
543 let metrics = result.unwrap();
544 assert!(!metrics.is_empty());
545 }
546
547 #[tokio::test]
548 #[ignore = "requires FMP API key"]
549 async fn test_get_key_metrics_ttm() {
550 let client = FmpClient::new().unwrap();
551 let result = client.financials().get_key_metrics_ttm("AAPL").await;
552 assert!(result.is_ok());
553 let metrics = result.unwrap();
554 assert!(!metrics.is_empty());
555 }
556
557 #[tokio::test]
558 #[ignore = "requires FMP API key"]
559 async fn test_get_ratios_ttm() {
560 let client = FmpClient::new().unwrap();
561 let result = client.financials().get_ratios_ttm("AAPL").await;
562 assert!(result.is_ok());
563 let ratios = result.unwrap();
564 assert!(!ratios.is_empty());
565 }
566
567 #[tokio::test]
568 #[ignore = "requires FMP API key"]
569 async fn test_get_financial_growth() {
570 let client = FmpClient::new().unwrap();
571 let result = client
572 .financials()
573 .get_financial_growth("AAPL", Period::Annual, Some(5))
574 .await;
575 assert!(result.is_ok());
576 let growth = result.unwrap();
577 assert!(!growth.is_empty());
578 assert!(growth.len() <= 5);
579 }
580
581 #[tokio::test]
582 #[ignore = "requires FMP API key"]
583 async fn test_get_financial_as_reported() {
584 let client = FmpClient::new().unwrap();
585 let result = client
586 .financials()
587 .get_financial_as_reported("AAPL", Period::Annual, Some(1))
588 .await;
589 assert!(result.is_ok());
590 let statements = result.unwrap();
591 assert!(!statements.is_empty());
592 }
593
594 #[tokio::test]
595 #[ignore = "requires FMP API key"]
596 async fn test_get_revenue_product_segmentation() {
597 let client = FmpClient::new().unwrap();
598 let result = client
599 .financials()
600 .get_revenue_product_segmentation("AAPL", Period::Annual, None)
601 .await;
602 assert!(result.is_ok());
603 let segments = result.unwrap();
604 assert!(!segments.is_empty());
605 }
606
607 #[tokio::test]
608 #[ignore = "requires FMP API key"]
609 async fn test_get_revenue_geographic_segmentation() {
610 let client = FmpClient::new().unwrap();
611 let result = client
612 .financials()
613 .get_revenue_geographic_segmentation("AAPL", Period::Annual, None)
614 .await;
615 assert!(result.is_ok());
616 let segments = result.unwrap();
617 assert!(!segments.is_empty());
618 }
619
620 #[tokio::test]
621 #[ignore = "requires FMP API key"]
622 async fn test_get_financial_full_as_reported() {
623 let client = FmpClient::new().unwrap();
624 let result = client
625 .financials()
626 .get_financial_full_as_reported("AAPL", Period::Annual)
627 .await;
628 assert!(result.is_ok());
629 let statements = result.unwrap();
630 assert!(!statements.is_empty());
631 assert!(!statements[0].data.is_empty());
633 }
634
635 #[tokio::test]
637 #[ignore = "requires FMP API key"]
638 async fn test_get_income_statement_quarterly() {
639 let client = FmpClient::new().unwrap();
640 let result = client
641 .financials()
642 .get_income_statement("AAPL", Period::Quarter, Some(4))
643 .await;
644 assert!(result.is_ok());
645 let statements = result.unwrap();
646 assert!(!statements.is_empty());
647 assert!(statements.len() <= 4);
648 }
649
650 #[tokio::test]
651 #[ignore = "requires FMP API key"]
652 async fn test_get_financial_growth_quarterly() {
653 let client = FmpClient::new().unwrap();
654 let result = client
655 .financials()
656 .get_financial_growth("AAPL", Period::Quarter, Some(4))
657 .await;
658 assert!(result.is_ok());
659 }
660
661 #[tokio::test]
662 #[ignore = "requires FMP API key"]
663 async fn test_get_financial_full_as_reported_quarterly() {
664 let client = FmpClient::new().unwrap();
665 let result = client
666 .financials()
667 .get_financial_full_as_reported("AAPL", Period::Quarter)
668 .await;
669 assert!(result.is_ok());
670 let statements = result.unwrap();
671 assert!(!statements.is_empty());
672 }
673
674 #[tokio::test]
676 #[ignore = "requires FMP API key"]
677 async fn test_get_income_statement_invalid_symbol() {
678 let client = FmpClient::new().unwrap();
679 let result = client
680 .financials()
681 .get_income_statement("INVALID_SYMBOL_XYZ123", Period::Annual, Some(5))
682 .await;
683 if let Ok(statements) = result {
685 assert!(statements.is_empty());
686 }
687 }
688
689 #[tokio::test]
690 #[ignore = "requires FMP API key"]
691 async fn test_get_financial_growth_zero_limit() {
692 let client = FmpClient::new().unwrap();
693 let result = client
694 .financials()
695 .get_financial_growth("AAPL", Period::Annual, Some(0))
696 .await;
697 assert!(result.is_ok());
699 }
700
701 #[tokio::test]
702 #[ignore = "requires FMP API key"]
703 async fn test_get_revenue_segmentation_with_structure() {
704 let client = FmpClient::new().unwrap();
705 let result = client
706 .financials()
707 .get_revenue_product_segmentation("AAPL", Period::Annual, Some("flat"))
708 .await;
709 assert!(result.is_ok());
710 }
711
712 #[tokio::test]
713 #[ignore = "requires FMP API key"]
714 async fn test_get_revenue_geographic_with_structure() {
715 let client = FmpClient::new().unwrap();
716 let result = client
717 .financials()
718 .get_revenue_geographic_segmentation("AAPL", Period::Annual, Some("flat"))
719 .await;
720 assert!(result.is_ok());
721 }
722
723 #[tokio::test]
724 #[ignore = "requires FMP API key"]
725 async fn test_get_balance_sheet_no_limit() {
726 let client = FmpClient::new().unwrap();
727 let result = client
728 .financials()
729 .get_balance_sheet("AAPL", Period::Annual, None)
730 .await;
731 assert!(result.is_ok());
732 let statements = result.unwrap();
733 assert!(!statements.is_empty());
734 }
735
736 #[tokio::test]
737 #[ignore = "requires FMP API key"]
738 async fn test_get_cash_flow_quarterly() {
739 let client = FmpClient::new().unwrap();
740 let result = client
741 .financials()
742 .get_cash_flow_statement("AAPL", Period::Quarter, Some(8))
743 .await;
744 assert!(result.is_ok());
745 let statements = result.unwrap();
746 assert!(!statements.is_empty());
747 assert!(statements.len() <= 8);
748 }
749
750 #[tokio::test]
752 async fn test_invalid_api_key() {
753 let client = FmpClient::builder()
754 .api_key("invalid_key_12345")
755 .build()
756 .unwrap();
757 let result = client
758 .financials()
759 .get_income_statement("AAPL", Period::Annual, Some(5))
760 .await;
761 assert!(result.is_err());
762 }
763
764 #[tokio::test]
765 async fn test_empty_symbol() {
766 let client = FmpClient::builder().api_key("test_key").build().unwrap();
767 let result = client
768 .financials()
769 .get_income_statement("", Period::Annual, Some(5))
770 .await;
771 assert!(result.is_err() || result.unwrap().is_empty());
773 }
774
775 #[tokio::test]
776 #[ignore = "requires FMP API key"]
777 async fn test_company_without_segmentation() {
778 let client = FmpClient::new().unwrap();
779 let result = client
781 .financials()
782 .get_revenue_product_segmentation("TSLA", Period::Annual, None)
783 .await;
784 if let Ok(_segments) = result {
786 }
788 }
789
790 #[tokio::test]
791 #[ignore = "requires FMP API key"]
792 async fn test_get_financial_full_as_reported_invalid_symbol() {
793 let client = FmpClient::new().unwrap();
794 let result = client
795 .financials()
796 .get_financial_full_as_reported("INVALID_SYMBOL_XYZ123", Period::Annual)
797 .await;
798 assert!(result.is_ok());
800 if let Ok(statements) = result {
801 assert!(statements.is_empty());
802 }
803 }
804}