#![allow(dead_code)]
use serde::{Deserialize, Serialize};
use crate::error::Result;
use crate::models::indices::IndexQuote;
use super::super::build_client;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct IndexSessionDTO {
pub change: Option<f64>,
pub change_percent: Option<f64>,
pub close: Option<f64>,
pub high: Option<f64>,
pub low: Option<f64>,
pub open: Option<f64>,
pub previous_close: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct IndexSnapshotDTO {
pub value: Option<f64>,
pub name: Option<String>,
#[serde(rename = "type")]
pub index_type: Option<String>,
pub ticker: Option<String>,
pub market_status: Option<String>,
pub session: Option<IndexSessionDTO>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct IndexSnapshotResponseDTO {
pub status: Option<String>,
pub request_id: Option<String>,
pub results: Option<Vec<IndexSnapshotDTO>>,
}
pub async fn index_snapshot(ticker: &str) -> Result<IndexSnapshotResponseDTO> {
let client = build_client()?;
let path = "/v3/snapshot/indices";
let params = [("ticker.any_of", ticker)];
client
.get_as(path, ¶ms, "index_snapshot", "index snapshot response")
.await
}
pub async fn fetch_indices_quote_response(symbol: &str) -> Result<IndexQuote> {
let resp = index_snapshot(symbol).await?;
Ok(snapshot_to_quote(symbol, resp))
}
fn snapshot_to_quote(symbol: &str, resp: IndexSnapshotResponseDTO) -> IndexQuote {
let snap = resp.results.and_then(|mut v| {
if v.is_empty() {
None
} else {
Some(v.remove(0))
}
});
let session = snap.as_ref().and_then(|s| s.session.as_ref());
IndexQuote {
symbol: snap
.as_ref()
.and_then(|s| s.ticker.clone())
.unwrap_or_else(|| symbol.to_string()),
name: snap.as_ref().and_then(|s| s.name.clone()),
price: snap.as_ref().and_then(|s| s.value),
change: session.and_then(|s| s.change),
change_percent: session.and_then(|s| s.change_percent),
timestamp: None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_index_snapshot_mock() {
let mut server = mockito::Server::new_async().await;
let _mock = server
.mock("GET", "/v3/snapshot/indices")
.match_query(mockito::Matcher::AllOf(vec![
mockito::Matcher::UrlEncoded("apiKey".into(), "test-key".into()),
mockito::Matcher::UrlEncoded("ticker.any_of".into(), "I:SPX".into()),
]))
.with_status(200)
.with_header("content-type", "application/json")
.with_body(
serde_json::json!({
"status": "OK",
"request_id": "abc123",
"results": [
{
"value": 4790.0,
"name": "S&P 500",
"type": "indices",
"ticker": "I:SPX",
"market_status": "open",
"session": {
"change": 20.0,
"change_percent": 0.42,
"close": 4790.0,
"high": 4800.0,
"low": 4760.0,
"open": 4770.0,
"previous_close": 4770.0
}
}
]
})
.to_string(),
)
.create_async()
.await;
let client = super::super::super::build_test_client(&server.url()).unwrap();
let json = client
.get_raw("/v3/snapshot/indices", &[("ticker.any_of", "I:SPX")])
.await
.unwrap();
let resp: IndexSnapshotResponseDTO = serde_json::from_value(json).unwrap();
assert_eq!(resp.status.as_deref(), Some("OK"));
let results = resp.results.as_ref().unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].ticker.as_deref(), Some("I:SPX"));
assert!((results[0].value.unwrap() - 4790.0).abs() < 0.01);
let session = results[0].session.as_ref().unwrap();
assert!((session.change.unwrap() - 20.0).abs() < 0.01);
let quote = snapshot_to_quote("I:SPX", resp);
assert_eq!(quote.symbol, "I:SPX");
assert_eq!(quote.name.as_deref(), Some("S&P 500"));
assert_eq!(quote.price, Some(4790.0));
assert_eq!(quote.change, Some(20.0));
assert_eq!(quote.change_percent, Some(0.42));
}
#[test]
fn snapshot_to_quote_maps_value_and_session_fields() {
let resp: IndexSnapshotResponseDTO = serde_json::from_value(serde_json::json!({
"status": "OK",
"results": [{
"value": 4790.0,
"name": "S&P 500",
"ticker": "I:SPX",
"session": {"change": 20.0, "change_percent": 0.42}
}]
}))
.unwrap();
let quote = snapshot_to_quote("I:SPX", resp);
assert_eq!(quote.symbol, "I:SPX");
assert_eq!(quote.name.as_deref(), Some("S&P 500"));
assert_eq!(quote.price, Some(4790.0));
assert_eq!(quote.change, Some(20.0));
assert_eq!(quote.change_percent, Some(0.42));
}
#[test]
fn snapshot_to_quote_empty_results_falls_back_to_symbol() {
let resp: IndexSnapshotResponseDTO =
serde_json::from_value(serde_json::json!({"status": "OK", "results": []})).unwrap();
let quote = snapshot_to_quote("I:SPX", resp);
assert_eq!(quote.symbol, "I:SPX");
assert!(quote.price.is_none());
assert!(quote.change.is_none());
}
}