finance_query/adapters/fmp/
dividends_splits.rs1use serde::{Deserialize, Serialize};
4
5use crate::adapters::common::encode_path_segment;
6use crate::error::Result;
7
8use super::build_client;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
16#[non_exhaustive]
17pub struct DividendHistory {
18 pub date: Option<String>,
20 pub label: Option<String>,
22 #[serde(rename = "adjDividend")]
24 pub adj_dividend: Option<f64>,
25 pub dividend: Option<f64>,
27 #[serde(rename = "recordDate")]
29 pub record_date: Option<String>,
30 #[serde(rename = "paymentDate")]
32 pub payment_date: Option<String>,
33 #[serde(rename = "declarationDate")]
35 pub declaration_date: Option<String>,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
40#[non_exhaustive]
41pub struct DividendHistoryResponse {
42 pub symbol: Option<String>,
44 pub historical: Vec<DividendHistory>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50#[non_exhaustive]
51pub struct SplitHistory {
52 pub date: Option<String>,
54 pub label: Option<String>,
56 pub numerator: Option<f64>,
58 pub denominator: Option<f64>,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64#[non_exhaustive]
65pub struct SplitHistoryResponse {
66 pub symbol: Option<String>,
68 pub historical: Vec<SplitHistory>,
70}
71
72pub async fn historical_dividends(symbol: &str) -> Result<DividendHistoryResponse> {
78 let client = build_client()?;
79 let path = format!(
80 "/api/v3/historical-price-full/stock_dividend/{}",
81 encode_path_segment(symbol)
82 );
83 client.get(&path, &[]).await
84}
85
86pub async fn historical_splits(symbol: &str) -> Result<SplitHistoryResponse> {
88 let client = build_client()?;
89 let path = format!(
90 "/api/v3/historical-price-full/stock_split/{}",
91 encode_path_segment(symbol)
92 );
93 client.get(&path, &[]).await
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[tokio::test]
101 async fn test_historical_dividends_mock() {
102 let mut server = mockito::Server::new_async().await;
103 let _mock = server
104 .mock("GET", "/api/v3/historical-price-full/stock_dividend/AAPL")
105 .match_query(mockito::Matcher::AllOf(vec![mockito::Matcher::UrlEncoded(
106 "apikey".into(),
107 "test-key".into(),
108 )]))
109 .with_status(200)
110 .with_body(
111 serde_json::json!({
112 "symbol": "AAPL",
113 "historical": [
114 {
115 "date": "2024-02-09",
116 "label": "February 09, 24",
117 "adjDividend": 0.24,
118 "dividend": 0.24,
119 "recordDate": "2024-02-12",
120 "paymentDate": "2024-02-15",
121 "declarationDate": "2024-02-01"
122 }
123 ]
124 })
125 .to_string(),
126 )
127 .create_async()
128 .await;
129
130 let client = super::super::build_test_client(&server.url()).unwrap();
131 let path = "/api/v3/historical-price-full/stock_dividend/AAPL";
132 let resp: DividendHistoryResponse = client.get(path, &[]).await.unwrap();
133 assert_eq!(resp.symbol.as_deref(), Some("AAPL"));
134 assert_eq!(resp.historical.len(), 1);
135 assert!((resp.historical[0].adj_dividend.unwrap() - 0.24).abs() < 0.001);
136 }
137
138 #[tokio::test]
139 async fn test_historical_splits_mock() {
140 let mut server = mockito::Server::new_async().await;
141 let _mock = server
142 .mock("GET", "/api/v3/historical-price-full/stock_split/AAPL")
143 .match_query(mockito::Matcher::AllOf(vec![mockito::Matcher::UrlEncoded(
144 "apikey".into(),
145 "test-key".into(),
146 )]))
147 .with_status(200)
148 .with_body(
149 serde_json::json!({
150 "symbol": "AAPL",
151 "historical": [
152 {
153 "date": "2020-08-31",
154 "label": "August 31, 20",
155 "numerator": 4.0,
156 "denominator": 1.0
157 }
158 ]
159 })
160 .to_string(),
161 )
162 .create_async()
163 .await;
164
165 let client = super::super::build_test_client(&server.url()).unwrap();
166 let path = "/api/v3/historical-price-full/stock_split/AAPL";
167 let resp: SplitHistoryResponse = client.get(path, &[]).await.unwrap();
168 assert_eq!(resp.symbol.as_deref(), Some("AAPL"));
169 assert_eq!(resp.historical.len(), 1);
170 assert!((resp.historical[0].numerator.unwrap() - 4.0).abs() < 0.001);
171 }
172}