//! Data decoding utilities for Polymarket client //! //! This module provides high-performance decoding functions for various //! data formats used in trading environments. use crate::errors::{PolyfillError, Result}; use crate::types::*; use alloy_primitives::{Address, U256}; use chrono::{DateTime, Utc}; use rust_decimal::Decimal; use serde::{Deserialize, Deserializer}; use serde_json::Value; use std::str::FromStr; /// Fast string to number deserializers pub mod deserializers { use super::*; use std::fmt::Display; /// Deserialize number from string or number pub fn number_from_string<'de, T, D>(deserializer: D) -> std::result::Result where D: Deserializer<'de>, T: FromStr + serde::Deserialize<'de> + Clone, ::Err: Display, { let value = serde_json::Value::deserialize(deserializer)?; match value { serde_json::Value::Number(n) => { if let Some(v) = n.as_u64() { T::deserialize(serde_json::Value::Number(serde_json::Number::from(v))) .map_err(|_| serde::de::Error::custom("Failed to deserialize number")) } else if let Some(v) = n.as_f64() { T::deserialize(serde_json::Value::Number(serde_json::Number::from_f64(v).unwrap())) .map_err(|_| serde::de::Error::custom("Failed to deserialize number")) } else { Err(serde::de::Error::custom("Invalid number format")) } } serde_json::Value::String(s) => { s.parse::().map_err(serde::de::Error::custom) } _ => Err(serde::de::Error::custom("Expected number or string")), } } /// Deserialize optional number from string pub fn optional_number_from_string<'de, T, D>( deserializer: D, ) -> std::result::Result, D::Error> where D: Deserializer<'de>, T: FromStr + serde::Deserialize<'de> + Clone, ::Err: Display, { let value = serde_json::Value::deserialize(deserializer)?; match value { serde_json::Value::Null => Ok(None), serde_json::Value::Number(n) => { if let Some(v) = n.as_u64() { T::deserialize(serde_json::Value::Number(serde_json::Number::from(v))) .map(Some) .map_err(|_| serde::de::Error::custom("Failed to deserialize number")) } else if let Some(v) = n.as_f64() { T::deserialize(serde_json::Value::Number(serde_json::Number::from_f64(v).unwrap())) .map(Some) .map_err(|_| serde::de::Error::custom("Failed to deserialize number")) } else { Err(serde::de::Error::custom("Invalid number format")) } } serde_json::Value::String(s) => { if s.is_empty() { Ok(None) } else { s.parse::() .map(Some) .map_err(serde::de::Error::custom) } } _ => Err(serde::de::Error::custom("Expected number, string, or null")), } } /// Deserialize DateTime from Unix timestamp pub fn datetime_from_timestamp<'de, D>(deserializer: D) -> std::result::Result, D::Error> where D: Deserializer<'de>, { let timestamp = number_from_string::(deserializer)?; DateTime::from_timestamp(timestamp as i64, 0) .ok_or_else(|| serde::de::Error::custom("Invalid timestamp")) } /// Deserialize optional DateTime from Unix timestamp pub fn optional_datetime_from_timestamp<'de, D>( deserializer: D, ) -> std::result::Result>, D::Error> where D: Deserializer<'de>, { match optional_number_from_string::(deserializer)? { Some(timestamp) => DateTime::from_timestamp(timestamp as i64, 0) .map(Some) .ok_or_else(|| serde::de::Error::custom("Invalid timestamp")), None => Ok(None), } } } /// Raw API response types for efficient parsing #[derive(Debug, Deserialize)] pub struct RawOrderBookResponse { pub market: String, pub asset_id: String, pub hash: String, #[serde(deserialize_with = "deserializers::number_from_string")] pub timestamp: u64, pub bids: Vec, pub asks: Vec, } #[derive(Debug, Deserialize)] pub struct RawBookLevel { #[serde(with = "rust_decimal::serde::str")] pub price: Decimal, #[serde(with = "rust_decimal::serde::str")] pub size: Decimal, } #[derive(Debug, Deserialize)] pub struct RawOrderResponse { pub id: String, pub status: String, pub market: String, pub asset_id: String, pub maker_address: String, pub owner: String, pub outcome: String, #[serde(rename = "type")] pub order_type: OrderType, pub side: Side, #[serde(with = "rust_decimal::serde::str")] pub original_size: Decimal, #[serde(with = "rust_decimal::serde::str")] pub price: Decimal, #[serde(with = "rust_decimal::serde::str")] pub size_matched: Decimal, #[serde(deserialize_with = "deserializers::number_from_string")] pub expiration: u64, #[serde(deserialize_with = "deserializers::number_from_string")] pub created_at: u64, } #[derive(Debug, Deserialize)] pub struct RawTradeResponse { pub id: String, pub market: String, pub asset_id: String, pub side: Side, #[serde(with = "rust_decimal::serde::str")] pub price: Decimal, #[serde(with = "rust_decimal::serde::str")] pub size: Decimal, pub maker_address: String, pub taker_address: String, #[serde(deserialize_with = "deserializers::number_from_string")] pub timestamp: u64, } #[derive(Debug, Deserialize)] pub struct RawMarketResponse { pub condition_id: String, pub tokens: [RawToken; 2], pub active: bool, pub closed: bool, pub question: String, pub description: String, pub category: Option, pub end_date_iso: Option, #[serde(with = "rust_decimal::serde::str")] pub minimum_order_size: Decimal, #[serde(with = "rust_decimal::serde::str")] pub minimum_tick_size: Decimal, } #[derive(Debug, Deserialize)] pub struct RawToken { pub token_id: String, pub outcome: String, } /// Decoder implementations for converting raw responses to client types pub trait Decoder { fn decode(&self) -> Result; } impl Decoder for RawOrderBookResponse { fn decode(&self) -> Result { let timestamp = chrono::DateTime::from_timestamp(self.timestamp as i64, 0) .ok_or_else(|| PolyfillError::parse("Invalid timestamp".to_string(), None))?; let bids = self .bids .iter() .map(|level| BookLevel { price: level.price, size: level.size, }) .collect(); let asks = self .asks .iter() .map(|level| BookLevel { price: level.price, size: level.size, }) .collect(); Ok(OrderBook { token_id: self.asset_id.clone(), timestamp, bids, asks, sequence: 0, // TODO: Get from response if available }) } } impl Decoder for RawOrderResponse { fn decode(&self) -> Result { let status = match self.status.as_str() { "LIVE" => OrderStatus::Live, "CANCELLED" => OrderStatus::Cancelled, "FILLED" => OrderStatus::Filled, "PARTIAL" => OrderStatus::Partial, "EXPIRED" => OrderStatus::Expired, _ => return Err(PolyfillError::parse( format!("Unknown order status: {}", self.status), None, )), }; let created_at = chrono::DateTime::from_timestamp(self.created_at as i64, 0) .ok_or_else(|| PolyfillError::parse("Invalid created_at timestamp".to_string(), None))?; let expiration = if self.expiration > 0 { Some(chrono::DateTime::from_timestamp(self.expiration as i64, 0) .ok_or_else(|| PolyfillError::parse("Invalid expiration timestamp".to_string(), None))?) } else { None }; Ok(Order { id: self.id.clone(), token_id: self.asset_id.clone(), side: self.side, price: self.price, original_size: self.original_size, filled_size: self.size_matched, remaining_size: self.original_size - self.size_matched, status, order_type: self.order_type, created_at, updated_at: created_at, // Use same as created for now expiration, client_id: None, }) } } impl Decoder for RawTradeResponse { fn decode(&self) -> Result { let timestamp = chrono::DateTime::from_timestamp(self.timestamp as i64, 0) .ok_or_else(|| PolyfillError::parse("Invalid trade timestamp".to_string(), None))?; let maker_address = Address::from_str(&self.maker_address) .map_err(|e| PolyfillError::parse(format!("Invalid maker address: {}", e), None))?; let taker_address = Address::from_str(&self.taker_address) .map_err(|e| PolyfillError::parse(format!("Invalid taker address: {}", e), None))?; Ok(FillEvent { id: self.id.clone(), order_id: "".to_string(), // TODO: Get from response if available token_id: self.asset_id.clone(), side: self.side, price: self.price, size: self.size, timestamp, maker_address, taker_address, fee: Decimal::ZERO, // TODO: Calculate or get from response }) } } impl Decoder for RawMarketResponse { fn decode(&self) -> Result { let tokens = [ Token { token_id: self.tokens[0].token_id.clone(), outcome: self.tokens[0].outcome.clone(), }, Token { token_id: self.tokens[1].token_id.clone(), outcome: self.tokens[1].outcome.clone(), }, ]; Ok(Market { condition_id: self.condition_id.clone(), tokens, rewards: crate::types::Rewards { rates: None, min_size: Decimal::ZERO, max_spread: Decimal::ONE, event_start_date: None, event_end_date: None, in_game_multiplier: None, reward_epoch: None, }, min_incentive_size: None, max_incentive_spread: None, active: self.active, closed: self.closed, question_id: self.condition_id.clone(), // Use condition_id as fallback minimum_order_size: self.minimum_order_size, minimum_tick_size: self.minimum_tick_size, description: self.description.clone(), category: self.category.clone(), end_date_iso: self.end_date_iso.clone(), game_start_time: None, question: self.question.clone(), market_slug: format!("market-{}", self.condition_id), // Generate a slug seconds_delay: Decimal::ZERO, icon: String::new(), fpmm: String::new(), }) } } /// WebSocket message parsing pub fn parse_stream_message(raw: &str) -> Result { let value: Value = serde_json::from_str(raw)?; let msg_type = value["type"] .as_str() .ok_or_else(|| PolyfillError::parse("Missing message type".to_string(), None))?; match msg_type { "book_update" => { let data = value["data"].clone(); let delta: OrderDelta = serde_json::from_value(data)?; Ok(StreamMessage::BookUpdate { data: delta }) } "trade" => { let data = value["data"].clone(); let raw_trade: RawTradeResponse = serde_json::from_value(data)?; let fill = raw_trade.decode()?; Ok(StreamMessage::Trade { data: fill }) } "order_update" => { let data = value["data"].clone(); let raw_order: RawOrderResponse = serde_json::from_value(data)?; let order = raw_order.decode()?; Ok(StreamMessage::OrderUpdate { data: order }) } "heartbeat" => { let timestamp = value["timestamp"] .as_str() .and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok()) .map(|dt| dt.with_timezone(&Utc)) .unwrap_or_else(|| Utc::now()); Ok(StreamMessage::Heartbeat { timestamp }) } _ => Err(PolyfillError::parse( format!("Unknown message type: {}", msg_type), None, )), } } /// Batch parsing utilities for high-throughput scenarios pub struct BatchDecoder { buffer: Vec, } impl BatchDecoder { pub fn new() -> Self { Self { buffer: Vec::with_capacity(8192), } } /// Parse multiple JSON objects from a byte stream pub fn parse_json_stream(&mut self, data: &[u8]) -> Result> where T: for<'de> serde::Deserialize<'de>, { self.buffer.extend_from_slice(data); let mut results = Vec::new(); let mut start = 0; while let Some(end) = self.find_json_boundary(start) { let json_slice = &self.buffer[start..end]; if let Ok(obj) = serde_json::from_slice::(json_slice) { results.push(obj); } start = end; } // Keep remaining incomplete data if start > 0 { self.buffer.drain(0..start); } Ok(results) } /// Find the end of a JSON object in the buffer fn find_json_boundary(&self, start: usize) -> Option { let mut depth = 0; let mut in_string = false; let mut escaped = false; for (i, &byte) in self.buffer[start..].iter().enumerate() { if escaped { escaped = false; continue; } match byte { b'\\' if in_string => escaped = true, b'"' => in_string = !in_string, b'{' if !in_string => depth += 1, b'}' if !in_string => { depth -= 1; if depth == 0 { return Some(start + i + 1); } } _ => {} } } None } } impl Default for BatchDecoder { fn default() -> Self { Self::new() } } /// Optimized parsers for common data types pub mod fast_parse { use super::*; /// Fast decimal parsing for prices #[inline] pub fn parse_decimal(s: &str) -> Result { Decimal::from_str(s) .map_err(|e| PolyfillError::parse(format!("Invalid decimal: {}", e), None)) } /// Fast address parsing #[inline] pub fn parse_address(s: &str) -> Result
{ Address::from_str(s) .map_err(|e| PolyfillError::parse(format!("Invalid address: {}", e), None)) } /// Fast U256 parsing #[inline] pub fn parse_u256(s: &str) -> Result { U256::from_str_radix(s, 10) .map_err(|e| PolyfillError::parse(format!("Invalid U256: {}", e), None)) } /// Parse Side enum #[inline] pub fn parse_side(s: &str) -> Result { match s.to_uppercase().as_str() { "BUY" => Ok(Side::BUY), "SELL" => Ok(Side::SELL), _ => Err(PolyfillError::parse(format!("Invalid side: {}", s), None)), } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_parse_decimal() { let result = fast_parse::parse_decimal("123.456").unwrap(); assert_eq!(result, Decimal::from_str("123.456").unwrap()); } #[test] fn test_parse_side() { assert_eq!(fast_parse::parse_side("BUY").unwrap(), Side::BUY); assert_eq!(fast_parse::parse_side("sell").unwrap(), Side::SELL); assert!(fast_parse::parse_side("invalid").is_err()); } #[test] fn test_batch_decoder() { let mut decoder = BatchDecoder::new(); let data = r#"{"test":1}{"test":2}"#.as_bytes(); let results: Vec = decoder.parse_json_stream(data).unwrap(); assert_eq!(results.len(), 2); } }