use super::super::Number;
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ChartEquityField {
Key = 0,
OpenPrice = 1,
HighPrice = 2,
LowPrice = 3,
ClosePrice = 4,
Volume = 5,
Sequence = 6,
ChartTime = 7,
ChartDay = 8,
}
impl ChartEquityField {
pub fn index(&self) -> u32 {
*self as u32
}
pub fn all() -> &'static [ChartEquityField] {
use ChartEquityField::*;
&[
Key, OpenPrice, HighPrice, LowPrice, ClosePrice, Volume, Sequence, ChartTime, ChartDay,
]
}
}
#[allow(missing_docs)]
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ChartEquity {
pub key: Option<String>,
pub delayed: Option<bool>,
pub asset_main_type: Option<String>,
pub asset_sub_type: Option<String>,
pub cusip: Option<String>,
pub symbol: Option<String>,
pub open_price: Option<Number>,
pub high_price: Option<Number>,
pub low_price: Option<Number>,
pub close_price: Option<Number>,
pub volume: Option<i64>,
pub sequence: Option<i64>,
pub chart_time: Option<i64>,
pub chart_day: Option<i64>,
}
fn parse_num(v: &serde_json::Value) -> Option<Number> {
serde_json::from_value::<Number>(v.clone()).ok()
}
impl ChartEquity {
pub(crate) fn from_value(value: &serde_json::Value) -> Option<Self> {
let map = value.as_object()?;
let mut s = Self {
key: map.get("key").and_then(|v| v.as_str()).map(String::from),
delayed: map.get("delayed").and_then(|v| v.as_bool()),
asset_main_type: map
.get("assetMainType")
.and_then(|v| v.as_str())
.map(String::from),
asset_sub_type: map
.get("assetSubType")
.and_then(|v| v.as_str())
.map(String::from),
cusip: map.get("cusip").and_then(|v| v.as_str()).map(String::from),
..Self::default()
};
for (key, val) in map {
match key.as_str() {
"0" => s.symbol = val.as_str().map(String::from),
"1" => s.open_price = parse_num(val),
"2" => s.high_price = parse_num(val),
"3" => s.low_price = parse_num(val),
"4" => s.close_price = parse_num(val),
"5" => s.volume = val.as_i64(),
"6" => s.sequence = val.as_i64(),
"7" => s.chart_time = val.as_i64(),
"8" => s.chart_day = val.as_i64(),
_ => {}
}
}
Some(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn field_index_first() {
assert_eq!(ChartEquityField::Key.index(), 0);
}
#[test]
fn field_index_last() {
assert_eq!(ChartEquityField::ChartDay.index(), 8);
}
#[test]
fn all_fields_count() {
assert_eq!(ChartEquityField::all().len(), 9);
}
#[test]
fn all_fields_sequential_indices() {
for (i, field) in ChartEquityField::all().iter().enumerate() {
assert_eq!(
field.index() as usize,
i,
"field at position {i} has wrong index"
);
}
}
#[test]
fn from_value_parses_sample() {
let input = json!({
"key": "AAPL",
"1": 150.25,
"2": 151.0,
"5": 1000000,
"7": 1234567890000_i64
});
let chart = ChartEquity::from_value(&input).expect("should parse JSON object");
assert_eq!(chart.key, Some("AAPL".to_string()));
assert_eq!(chart.open_price, Some("150.25".parse().unwrap()));
assert_eq!(chart.high_price, Some("151".parse().unwrap()));
assert_eq!(chart.volume, Some(1000000));
assert_eq!(chart.chart_time, Some(1234567890000));
assert_eq!(chart.symbol, None);
assert_eq!(chart.low_price, None);
}
#[test]
fn from_value_returns_none_for_non_object() {
assert!(ChartEquity::from_value(&json!(42)).is_none());
assert!(ChartEquity::from_value(&json!("text")).is_none());
assert!(ChartEquity::from_value(&json!(null)).is_none());
}
}