Skip to main content

finance_query/adapters/polygon/indices/
snapshots.rs

1//! Index snapshot endpoints.
2
3use serde::{Deserialize, Serialize};
4
5use crate::error::{FinanceError, Result};
6
7use super::super::build_client;
8
9/// Session data within an index snapshot.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11#[non_exhaustive]
12pub struct IndexSession {
13    /// Change from previous close.
14    pub change: Option<f64>,
15    /// Change percent from previous close.
16    pub change_percent: Option<f64>,
17    /// Close value.
18    pub close: Option<f64>,
19    /// High value.
20    pub high: Option<f64>,
21    /// Low value.
22    pub low: Option<f64>,
23    /// Open value.
24    pub open: Option<f64>,
25    /// Previous close value.
26    pub previous_close: Option<f64>,
27}
28
29/// A single index snapshot.
30#[derive(Debug, Clone, Serialize, Deserialize)]
31#[non_exhaustive]
32pub struct IndexSnapshot {
33    /// Current index value.
34    pub value: Option<f64>,
35    /// Name of the index.
36    pub name: Option<String>,
37    /// Type of the index.
38    #[serde(rename = "type")]
39    pub index_type: Option<String>,
40    /// Ticker symbol (e.g., `"I:SPX"`).
41    pub ticker: Option<String>,
42    /// Market status.
43    pub market_status: Option<String>,
44    /// Session data.
45    pub session: Option<IndexSession>,
46}
47
48/// Response wrapper for index snapshots.
49#[derive(Debug, Clone, Serialize, Deserialize)]
50#[non_exhaustive]
51pub struct IndexSnapshotResponse {
52    /// Response status.
53    pub status: Option<String>,
54    /// Request identifier.
55    pub request_id: Option<String>,
56    /// Index snapshot results.
57    pub results: Option<Vec<IndexSnapshot>>,
58}
59
60/// Fetch snapshot for a single index ticker.
61///
62/// * `ticker` - Index ticker symbol with `I:` prefix (e.g., `"I:SPX"`)
63pub async fn index_snapshot(ticker: &str) -> Result<IndexSnapshotResponse> {
64    let client = build_client()?;
65    let path = "/v3/snapshot/indices";
66    let params = [("ticker.any_of", ticker)];
67    let json = client.get_raw(path, &params).await?;
68    serde_json::from_value(json).map_err(|e| FinanceError::ResponseStructureError {
69        field: "index_snapshot".to_string(),
70        context: format!("Failed to parse index snapshot response: {e}"),
71    })
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[tokio::test]
79    async fn test_index_snapshot_mock() {
80        let mut server = mockito::Server::new_async().await;
81        let _mock = server
82            .mock("GET", "/v3/snapshot/indices")
83            .match_query(mockito::Matcher::AllOf(vec![
84                mockito::Matcher::UrlEncoded("apiKey".into(), "test-key".into()),
85                mockito::Matcher::UrlEncoded("ticker.any_of".into(), "I:SPX".into()),
86            ]))
87            .with_status(200)
88            .with_header("content-type", "application/json")
89            .with_body(
90                serde_json::json!({
91                    "status": "OK",
92                    "request_id": "abc123",
93                    "results": [
94                        {
95                            "value": 4790.0,
96                            "name": "S&P 500",
97                            "type": "indices",
98                            "ticker": "I:SPX",
99                            "market_status": "open",
100                            "session": {
101                                "change": 20.0,
102                                "change_percent": 0.42,
103                                "close": 4790.0,
104                                "high": 4800.0,
105                                "low": 4760.0,
106                                "open": 4770.0,
107                                "previous_close": 4770.0
108                            }
109                        }
110                    ]
111                })
112                .to_string(),
113            )
114            .create_async()
115            .await;
116
117        let client = super::super::super::build_test_client(&server.url()).unwrap();
118        let json = client
119            .get_raw("/v3/snapshot/indices", &[("ticker.any_of", "I:SPX")])
120            .await
121            .unwrap();
122
123        let resp: IndexSnapshotResponse = serde_json::from_value(json).unwrap();
124        assert_eq!(resp.status.as_deref(), Some("OK"));
125        let results = resp.results.unwrap();
126        assert_eq!(results.len(), 1);
127        assert_eq!(results[0].ticker.as_deref(), Some("I:SPX"));
128        assert!((results[0].value.unwrap() - 4790.0).abs() < 0.01);
129        let session = results[0].session.as_ref().unwrap();
130        assert!((session.change.unwrap() - 20.0).abs() < 0.01);
131    }
132}