use serde::Deserialize;
use serde::Deserializer;
use std::collections::BTreeMap;
#[derive(Deserialize)]
pub struct ChartEnvelope {
pub(crate) chart: Option<ChartNode>,
}
#[derive(Deserialize)]
pub struct ChartNode {
pub(crate) result: Option<Vec<ChartResult>>,
pub(crate) error: Option<ChartError>,
}
#[derive(Deserialize)]
pub struct ChartError {
pub(crate) code: String,
pub(crate) description: String,
}
#[derive(Deserialize)]
pub struct ChartResult {
#[serde(default)]
pub(crate) meta: Option<MetaNode>,
#[serde(default)]
pub(crate) timestamp: Option<Vec<i64>>,
pub(crate) indicators: Indicators,
#[serde(default)]
pub(crate) events: Option<Events>,
}
#[derive(Deserialize, Clone)]
pub struct MetaNode {
#[serde(default)]
pub(crate) timezone: Option<String>,
#[serde(default)]
pub(crate) gmtoffset: Option<i64>,
#[serde(default)]
pub(crate) currency: Option<String>,
}
#[derive(Deserialize)]
pub struct Indicators {
#[serde(default)]
pub(crate) quote: Vec<QuoteBlock>,
#[serde(default)]
pub(crate) adjclose: Vec<AdjCloseBlock>,
}
#[derive(Deserialize, Clone)]
pub struct QuoteBlock {
#[serde(default)]
pub(crate) open: Vec<Option<f64>>,
#[serde(default)]
pub(crate) high: Vec<Option<f64>>,
#[serde(default)]
pub(crate) low: Vec<Option<f64>>,
#[serde(default)]
pub(crate) close: Vec<Option<f64>>,
#[serde(default)]
pub(crate) volume: Vec<Option<u64>>,
}
#[derive(Deserialize, Clone)]
pub struct AdjCloseBlock {
#[serde(default)]
pub(crate) adjclose: Vec<Option<f64>>,
}
#[derive(Deserialize, Default, Clone)]
pub struct Events {
#[serde(default)]
pub(crate) dividends: Option<BTreeMap<String, DividendEvent>>,
#[serde(default)]
pub(crate) splits: Option<BTreeMap<String, SplitEvent>>,
#[serde(default, rename = "capitalGains")]
pub(crate) capital_gains: Option<BTreeMap<String, CapitalGainEvent>>,
}
#[derive(Deserialize, Clone)]
pub struct DividendEvent {
pub(crate) amount: Option<f64>,
pub(crate) date: Option<i64>,
}
#[derive(Deserialize, Clone)]
pub struct SplitEvent {
#[serde(default, deserialize_with = "de_opt_f64_from_mixed")]
pub(crate) numerator: Option<f64>,
#[serde(default, deserialize_with = "de_opt_f64_from_mixed")]
pub(crate) denominator: Option<f64>,
#[serde(rename = "splitRatio")]
pub(crate) split_ratio: Option<String>,
pub(crate) date: Option<i64>,
}
#[derive(Deserialize, Clone)]
pub struct CapitalGainEvent {
pub(crate) amount: Option<f64>,
pub(crate) date: Option<i64>,
}
fn de_opt_f64_from_mixed<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error;
use serde_json::Value;
let v = Option::<Value>::deserialize(deserializer)?;
let some = match v {
None | Some(Value::Null) => return Ok(None),
Some(Value::Number(n)) => {
let Some(f) = n.as_f64() else {
return Err(D::Error::custom("unsupported number type for split field"));
};
if !f.is_finite() {
return Err(D::Error::custom("non-finite number for split field"));
}
Some(f)
}
Some(Value::String(s)) => {
let s = s.trim();
if s.is_empty() {
None
} else {
let f = s.parse::<f64>().map_err(|_| {
D::Error::custom(format!("invalid numeric string '{s}' for split field"))
})?;
if !f.is_finite() {
return Err(D::Error::custom("non-finite string for split field"));
}
Some(f)
}
}
Some(other) => {
return Err(D::Error::custom(format!(
"unexpected JSON type for split field: {other}"
)));
}
};
Ok(some)
}