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