fmp_rs/endpoints/
indexes.rs1use crate::{
4 client::FmpClient,
5 error::Result,
6 models::indexes::{IndexConstituent, IndexHistorical, IndexQuote, IndexSymbol},
7};
8use serde::{Deserialize, Serialize};
9
10pub struct Indexes {
12 client: FmpClient,
13}
14
15impl Indexes {
16 pub(crate) fn new(client: FmpClient) -> Self {
17 Self { client }
18 }
19
20 pub async fn get_index_list(&self) -> Result<Vec<IndexSymbol>> {
38 #[derive(Serialize)]
39 struct Query<'a> {
40 apikey: &'a str,
41 }
42
43 let url = self.client.build_url("/symbol/available-indexes");
44 self.client
45 .get_with_query(
46 &url,
47 &Query {
48 apikey: self.client.api_key(),
49 },
50 )
51 .await
52 }
53
54 pub async fn get_index_quote(&self, symbol: &str) -> Result<Vec<IndexQuote>> {
76 #[derive(Serialize)]
77 struct Query<'a> {
78 apikey: &'a str,
79 }
80
81 let url = self.client.build_url(&format!("/quote/{}", symbol));
82 self.client
83 .get_with_query(
84 &url,
85 &Query {
86 apikey: self.client.api_key(),
87 },
88 )
89 .await
90 }
91
92 pub async fn get_index_historical(
115 &self,
116 symbol: &str,
117 from: Option<&str>,
118 to: Option<&str>,
119 ) -> Result<Vec<IndexHistorical>> {
120 #[derive(Serialize)]
121 struct Query<'a> {
122 #[serde(skip_serializing_if = "Option::is_none")]
123 from: Option<&'a str>,
124 #[serde(skip_serializing_if = "Option::is_none")]
125 to: Option<&'a str>,
126 apikey: &'a str,
127 }
128
129 let url = self
130 .client
131 .build_url(&format!("/historical-price-full/{}", symbol));
132
133 #[derive(Deserialize)]
134 struct Response {
135 historical: Vec<IndexHistorical>,
136 }
137
138 let response: Response = self
139 .client
140 .get_with_query(
141 &url,
142 &Query {
143 from,
144 to,
145 apikey: self.client.api_key(),
146 },
147 )
148 .await?;
149
150 Ok(response.historical)
151 }
152
153 pub async fn get_index_constituents(&self, symbol: &str) -> Result<Vec<IndexConstituent>> {
178 #[derive(Serialize)]
179 struct Query<'a> {
180 apikey: &'a str,
181 }
182
183 let url = self.client.build_url(&format!("{}_constituent", symbol));
184 self.client
185 .get_with_query(
186 &url,
187 &Query {
188 apikey: self.client.api_key(),
189 },
190 )
191 .await
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[tokio::test]
201 #[ignore = "requires FMP API key"]
202 async fn test_get_index_list() {
203 let client = FmpClient::new().unwrap();
204 let result = client.indexes().get_index_list().await;
205 assert!(result.is_ok());
206 let indexes = result.unwrap();
207 assert!(!indexes.is_empty());
208 }
209
210 #[tokio::test]
211 #[ignore = "requires FMP API key"]
212 async fn test_get_index_quote() {
213 let client = FmpClient::new().unwrap();
214 let result = client.indexes().get_index_quote("^GSPC").await;
215 assert!(result.is_ok());
216 let quotes = result.unwrap();
217 assert!(!quotes.is_empty());
218 assert!(quotes[0].price.is_some());
219 }
220
221 #[tokio::test]
222 #[ignore = "requires FMP API key"]
223 async fn test_get_index_historical() {
224 let client = FmpClient::new().unwrap();
225 let result = client
226 .indexes()
227 .get_index_historical("^GSPC", None, None)
228 .await;
229 assert!(result.is_ok());
230 let history = result.unwrap();
231 assert!(!history.is_empty());
232 }
233
234 #[tokio::test]
235 #[ignore = "requires FMP API key"]
236 async fn test_get_index_constituents() {
237 let client = FmpClient::new().unwrap();
238 let result = client.indexes().get_index_constituents("^GSPC").await;
239 assert!(result.is_ok());
240 let constituents = result.unwrap();
241 assert!(!constituents.is_empty());
242 }
243
244 #[tokio::test]
246 #[ignore = "requires FMP API key"]
247 async fn test_index_historical_with_dates() {
248 let client = FmpClient::new().unwrap();
249 let result = client
250 .indexes()
251 .get_index_historical("^GSPC", Some("2024-01-01"), Some("2024-01-31"))
252 .await;
253 assert!(result.is_ok());
254 }
255
256 #[tokio::test]
257 #[ignore = "requires FMP API key"]
258 async fn test_various_major_indexes() {
259 let client = FmpClient::new().unwrap();
260 for symbol in &["^GSPC", "^IXIC", "^DJI"] {
262 let result = client.indexes().get_index_quote(symbol).await;
263 assert!(result.is_ok());
264 }
265 }
266
267 #[tokio::test]
268 #[ignore = "requires FMP API key"]
269 async fn test_dow_jones_constituents() {
270 let client = FmpClient::new().unwrap();
271 let result = client.indexes().get_index_constituents("^DJI").await;
272 assert!(result.is_ok());
273 let constituents = result.unwrap();
274 assert!(constituents.len() <= 30);
276 }
277
278 #[tokio::test]
280 async fn test_invalid_api_key() {
281 let client = FmpClient::builder()
282 .api_key("invalid_key_12345")
283 .build()
284 .unwrap();
285 let result = client.indexes().get_index_list().await;
286 assert!(result.is_err());
287 }
288
289 #[tokio::test]
290 #[ignore = "requires FMP API key"]
291 async fn test_invalid_symbol() {
292 let client = FmpClient::new().unwrap();
293 let result = client.indexes().get_index_quote("INVALIDINDEX123").await;
294 match result {
296 Ok(quotes) => assert!(quotes.is_empty()),
297 Err(_) => {} }
299 }
300}