1use crate::presentation::serialization::{string_as_bool_opt, string_as_float_opt};
2use lightstreamer_rs::subscription::ItemUpdate;
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::fmt;
6
7#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
9#[serde(rename_all = "UPPERCASE")]
10pub enum MarketState {
11 Closed,
13 #[default]
15 Offline,
16 Tradeable,
18 Edit,
20 Auction,
22 AuctionNoEdit,
24 Suspended,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize, Default)]
30pub struct MarketData {
31 item_name: String,
33 item_pos: i32,
35 fields: MarketFields,
37 changed_fields: MarketFields,
39 is_snapshot: bool,
41}
42
43impl MarketData {
44 pub fn from_item_update(item_update: &ItemUpdate) -> Result<Self, String> {
52 let item_name = item_update.item_name.clone().unwrap_or_default();
54
55 let item_pos = item_update.item_pos as i32;
57
58 let is_snapshot = item_update.is_snapshot;
60
61 let fields = Self::create_market_fields(&item_update.fields)?;
63
64 let mut changed_fields_map: HashMap<String, Option<String>> = HashMap::new();
66 for (key, value) in &item_update.changed_fields {
67 changed_fields_map.insert(key.clone(), Some(value.clone()));
68 }
69 let changed_fields = Self::create_market_fields(&changed_fields_map)?;
70
71 Ok(MarketData {
72 item_name,
73 item_pos,
74 fields,
75 changed_fields,
76 is_snapshot,
77 })
78 }
79
80 fn create_market_fields(
88 fields_map: &HashMap<String, Option<String>>,
89 ) -> Result<MarketFields, String> {
90 let get_field = |key: &str| -> Option<String> { fields_map.get(key).cloned().flatten() };
92
93 let market_state = match get_field("MARKET_STATE").as_deref() {
95 Some("closed") => Some(MarketState::Closed),
96 Some("offline") => Some(MarketState::Offline),
97 Some("tradeable") => Some(MarketState::Tradeable),
98 Some("edit") => Some(MarketState::Edit),
99 Some("auction") => Some(MarketState::Auction),
100 Some("auction_no_edit") => Some(MarketState::AuctionNoEdit),
101 Some("suspended") => Some(MarketState::Suspended),
102 Some(unknown) => return Err(format!("Unknown market state: {}", unknown)),
103 None => None,
104 };
105
106 let market_delay = match get_field("MARKET_DELAY").as_deref() {
108 Some("0") => Some(false),
109 Some("1") => Some(true),
110 Some(val) => return Err(format!("Invalid MARKET_DELAY value: {}", val)),
111 None => None,
112 };
113
114 let parse_float = |key: &str| -> Result<Option<f64>, String> {
116 match get_field(key) {
117 Some(val) if !val.is_empty() => val
118 .parse::<f64>()
119 .map(Some)
120 .map_err(|_| format!("Failed to parse {} as float: {}", key, val)),
121 _ => Ok(None),
122 }
123 };
124
125 Ok(MarketFields {
126 mid_open: parse_float("MID_OPEN")?,
127 high: parse_float("HIGH")?,
128 offer: parse_float("OFFER")?,
129 change: parse_float("CHANGE")?,
130 market_delay,
131 low: parse_float("LOW")?,
132 bid: parse_float("BID")?,
133 change_pct: parse_float("CHANGE_PCT")?,
134 market_state,
135 update_time: get_field("UPDATE_TIME"),
136 })
137 }
138}
139
140impl fmt::Display for MarketData {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 let json = serde_json::to_string(self).map_err(|_| fmt::Error)?;
143 write!(f, "{}", json)
144 }
145}
146
147impl From<&ItemUpdate> for MarketData {
148 fn from(item_update: &ItemUpdate) -> Self {
149 Self::from_item_update(item_update).unwrap_or_else(|_| MarketData {
150 item_name: String::new(),
151 item_pos: 0,
152 fields: MarketFields::default(),
153 changed_fields: MarketFields::default(),
154 is_snapshot: false,
155 })
156 }
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize, Default)]
161pub struct MarketFields {
162 #[serde(rename = "MID_OPEN")]
163 #[serde(with = "string_as_float_opt")]
164 #[serde(default)]
165 mid_open: Option<f64>,
166
167 #[serde(rename = "HIGH")]
168 #[serde(with = "string_as_float_opt")]
169 #[serde(default)]
170 high: Option<f64>,
171
172 #[serde(rename = "OFFER")]
173 #[serde(with = "string_as_float_opt")]
174 #[serde(default)]
175 offer: Option<f64>,
176
177 #[serde(rename = "CHANGE")]
178 #[serde(with = "string_as_float_opt")]
179 #[serde(default)]
180 change: Option<f64>,
181
182 #[serde(rename = "MARKET_DELAY")]
183 #[serde(with = "string_as_bool_opt")]
184 #[serde(default)]
185 market_delay: Option<bool>,
186
187 #[serde(rename = "LOW")]
188 #[serde(with = "string_as_float_opt")]
189 #[serde(default)]
190 low: Option<f64>,
191
192 #[serde(rename = "BID")]
193 #[serde(with = "string_as_float_opt")]
194 #[serde(default)]
195 bid: Option<f64>,
196
197 #[serde(rename = "CHANGE_PCT")]
198 #[serde(with = "string_as_float_opt")]
199 #[serde(default)]
200 change_pct: Option<f64>,
201
202 #[serde(rename = "MARKET_STATE")]
203 #[serde(default)]
204 market_state: Option<MarketState>,
205
206 #[serde(rename = "UPDATE_TIME")]
207 #[serde(default)]
208 update_time: Option<String>,
209}