finance_query/adapters/fmp/
indexes.rs1use serde::{Deserialize, Serialize};
4
5use crate::adapters::common::encode_path_segment;
6use crate::error::Result;
7
8use super::build_client;
9use super::models::{FmpQuote, HistoricalPriceResponse};
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13#[non_exhaustive]
14pub struct IndexConstituent {
15 pub symbol: Option<String>,
17 pub name: Option<String>,
19 pub sector: Option<String>,
21 #[serde(rename = "subSector")]
23 pub sub_sector: Option<String>,
24 #[serde(rename = "headQuarter")]
26 pub head_quarter: Option<String>,
27 #[serde(rename = "dateFirstAdded")]
29 pub date_first_added: Option<String>,
30 pub cik: Option<String>,
32 pub founded: Option<String>,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38#[non_exhaustive]
39pub struct HistoricalConstituent {
40 pub date: Option<String>,
42 pub symbol: Option<String>,
44 #[serde(rename = "addedSecurity")]
46 pub added_security: Option<String>,
47 #[serde(rename = "removedTicker")]
49 pub removed_ticker: Option<String>,
50 #[serde(rename = "removedSecurity")]
52 pub removed_security: Option<String>,
53 pub reason: Option<String>,
55}
56
57pub async fn major_indexes_quote() -> Result<Vec<FmpQuote>> {
59 let client = build_client()?;
60 client.get("/api/v3/quotes/index", &[]).await
61}
62
63pub async fn sp500_constituents() -> Result<Vec<IndexConstituent>> {
65 let client = build_client()?;
66 client.get("/api/v3/sp500_constituent", &[]).await
67}
68
69pub async fn nasdaq_constituents() -> Result<Vec<IndexConstituent>> {
71 let client = build_client()?;
72 client.get("/api/v3/nasdaq_constituent", &[]).await
73}
74
75pub async fn dow_constituents() -> Result<Vec<IndexConstituent>> {
77 let client = build_client()?;
78 client.get("/api/v3/dowjones_constituent", &[]).await
79}
80
81pub async fn historical_sp500() -> Result<Vec<HistoricalConstituent>> {
83 let client = build_client()?;
84 client
85 .get("/api/v3/historical/sp500_constituent", &[])
86 .await
87}
88
89pub async fn index_historical(
94 symbol: &str,
95 params: &[(&str, &str)],
96) -> Result<HistoricalPriceResponse> {
97 let client = build_client()?;
98 let path = format!(
99 "/api/v3/historical-price-full/{}",
100 encode_path_segment(symbol)
101 );
102 client.get(&path, params).await
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[tokio::test]
110 async fn test_sp500_constituents_mock() {
111 let mut server = mockito::Server::new_async().await;
112 let _mock = server
113 .mock("GET", "/api/v3/sp500_constituent")
114 .match_query(mockito::Matcher::AllOf(vec![mockito::Matcher::UrlEncoded(
115 "apikey".into(),
116 "test-key".into(),
117 )]))
118 .with_status(200)
119 .with_body(
120 serde_json::json!([
121 {
122 "symbol": "AAPL",
123 "name": "Apple Inc.",
124 "sector": "Information Technology",
125 "subSector": "Technology Hardware",
126 "headQuarter": "Cupertino, CA",
127 "dateFirstAdded": "1982-11-30",
128 "cik": "0000320193",
129 "founded": "1976"
130 }
131 ])
132 .to_string(),
133 )
134 .create_async()
135 .await;
136
137 let client = super::super::build_test_client(&server.url()).unwrap();
138 let result: Vec<IndexConstituent> =
139 client.get("/api/v3/sp500_constituent", &[]).await.unwrap();
140 assert_eq!(result.len(), 1);
141 assert_eq!(result[0].symbol.as_deref(), Some("AAPL"));
142 assert_eq!(result[0].sector.as_deref(), Some("Information Technology"));
143 }
144}