diff --git a/Cargo.lock b/Cargo.lock index cad429c..a688ac7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -986,6 +986,7 @@ dependencies = [ "nekoton-proto", "nekoton-utils", "serde", + "serde_json", "ton_block", "tracing", ] @@ -2973,11 +2974,12 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "4ab380d7d9f22ef3f21ad3e6c1ebe8e4fc7a2000ccba2e4d71fc96f15b2cb609" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -3540,7 +3542,7 @@ dependencies = [ [[package]] name = "ton_block" version = "1.9.73" -source = "git+https://github.com/broxus/ton-labs-block#44824b29903dfd556332e720fa932eabe1917f39" +source = "git+https://github.com/broxus/ton-labs-block.git#44824b29903dfd556332e720fa932eabe1917f39" dependencies = [ "anyhow", "base64 0.13.1", diff --git a/models/Cargo.toml b/models/Cargo.toml index 7780ee4..9b08c1d 100644 --- a/models/Cargo.toml +++ b/models/Cargo.toml @@ -17,3 +17,7 @@ ton_block = { git = "https://github.com/broxus/ton-labs-block.git" } nekoton-abi = { git = "https://github.com/broxus/nekoton.git", default-features = false } nekoton-proto = { git = "https://github.com/broxus/nekoton.git", default-features = false } nekoton-utils = { git = "https://github.com/broxus/nekoton.git", default-features = false } + +[dev-dependencies] +serde_json = "1.0.121" + diff --git a/models/src/jrpc.rs b/models/src/jrpc.rs index aef80e7..9383150 100644 --- a/models/src/jrpc.rs +++ b/models/src/jrpc.rs @@ -2,6 +2,10 @@ use nekoton_utils::*; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; +use serde::de::{self, MapAccess, Visitor}; +use serde::Deserializer; +use std::fmt; + pub trait Request: Serialize { type ResponseContainer: DeserializeOwned; type Response: From; @@ -393,7 +397,7 @@ impl Request for GetTimingsRequest { type Response = GetTimingsResponse; } -#[derive(Debug, Copy, Clone, Serialize, Deserialize, Default, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Serialize, Default, Eq, PartialEq)] pub struct GetTimingsResponse { pub last_mc_block_seqno: u32, pub last_shard_client_mc_block_seqno: u32, @@ -403,6 +407,157 @@ pub struct GetTimingsResponse { pub smallest_known_lt: Option, } +impl<'de> Deserialize<'de> for GetTimingsResponse { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + enum Field { + LastMcBlockSeqno, + LastShardClientMcBlockSeqno, + LastMcUtime, + McTimeDiff, + ShardClientTimeDiff, + SmallestKnownLt, + } + + impl<'de> Deserialize<'de> for Field { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct FieldVisitor; + + impl<'de> Visitor<'de> for FieldVisitor { + type Value = Field; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("field identifier") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + match value { + "last_mc_block_seqno" | "lastMcBlockSeqno" => { + Ok(Field::LastMcBlockSeqno) + } + "last_shard_client_mc_block_seqno" | "lastShardClientMcBlockSeqno" => { + Ok(Field::LastShardClientMcBlockSeqno) + } + "last_mc_utime" | "lastMcUtime" => Ok(Field::LastMcUtime), + "mc_time_diff" | "mcTimeDiff" => Ok(Field::McTimeDiff), + "shard_client_time_diff" | "shardClientTimeDiff" => { + Ok(Field::ShardClientTimeDiff) + } + "smallest_known_lt" | "smallestKnownLt" => Ok(Field::SmallestKnownLt), + _ => Err(de::Error::unknown_field(value, FIELDS)), + } + } + } + + deserializer.deserialize_identifier(FieldVisitor) + } + } + + struct GetTimingsResponseVisitor; + + impl<'de> Visitor<'de> for GetTimingsResponseVisitor { + type Value = GetTimingsResponse; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("struct GetTimingsResponse") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut last_mc_block_seqno = None; + let mut last_shard_client_mc_block_seqno = None; + let mut last_mc_utime = None; + let mut mc_time_diff = None; + let mut shard_client_time_diff = None; + let mut smallest_known_lt = None; + + while let Some(key) = map.next_key()? { + match key { + Field::LastMcBlockSeqno => { + if last_mc_block_seqno.is_some() { + return Err(de::Error::duplicate_field("last_mc_block_seqno")); + } + last_mc_block_seqno = Some(map.next_value()?); + } + Field::LastShardClientMcBlockSeqno => { + if last_shard_client_mc_block_seqno.is_some() { + return Err(de::Error::duplicate_field( + "last_shard_client_mc_block_seqno", + )); + } + last_shard_client_mc_block_seqno = Some(map.next_value()?); + } + Field::LastMcUtime => { + if last_mc_utime.is_some() { + return Err(de::Error::duplicate_field("last_mc_utime")); + } + last_mc_utime = Some(map.next_value()?); + } + Field::McTimeDiff => { + if mc_time_diff.is_some() { + return Err(de::Error::duplicate_field("mc_time_diff")); + } + mc_time_diff = Some(map.next_value()?); + } + Field::ShardClientTimeDiff => { + if shard_client_time_diff.is_some() { + return Err(de::Error::duplicate_field("shard_client_time_diff")); + } + shard_client_time_diff = Some(map.next_value()?); + } + Field::SmallestKnownLt => { + if smallest_known_lt.is_some() { + return Err(de::Error::duplicate_field("smallest_known_lt")); + } + smallest_known_lt = Some(map.next_value()?); + } + } + } + + let last_mc_block_seqno = last_mc_block_seqno + .ok_or_else(|| de::Error::missing_field("last_mc_block_seqno"))?; + let last_shard_client_mc_block_seqno = + last_shard_client_mc_block_seqno.unwrap_or(last_mc_block_seqno); + let last_mc_utime = + last_mc_utime.ok_or_else(|| de::Error::missing_field("last_mc_utime"))?; + let mc_time_diff = + mc_time_diff.ok_or_else(|| de::Error::missing_field("mc_time_diff"))?; + let shard_client_time_diff = shard_client_time_diff.unwrap_or(mc_time_diff); + + Ok(GetTimingsResponse { + last_mc_block_seqno, + last_shard_client_mc_block_seqno, + last_mc_utime, + mc_time_diff, + shard_client_time_diff, + smallest_known_lt, + }) + } + } + + const FIELDS: &'static [&'static str] = &[ + "last_mc_block_seqno", + "last_shard_client_mc_block_seqno", + "last_mc_utime", + "mc_time_diff", + "shard_client_time_diff", + "smallest_known_lt", + ]; + + deserializer.deserialize_struct("GetTimingsResponse", FIELDS, GetTimingsResponseVisitor) + } +} + #[cfg(test)] mod test { use super::*; @@ -424,4 +579,129 @@ mod test { assert!(!Timings::from(metrics).has_state_for(now() as u32 - 99)); assert!(Timings::from(metrics).has_state_for(now() as u32 - 101)); } + + use serde_json::json; + + #[test] + fn test_deserialize_snake_case() { + let json = json!({ + "last_mc_block_seqno": 123, + "last_shard_client_mc_block_seqno": 456, + "last_mc_utime": 789, + "mc_time_diff": 1000, + "shard_client_time_diff": 2000, + "smallest_known_lt": 3000 + }); + + let response: GetTimingsResponse = serde_json::from_value(json).unwrap(); + + assert_eq!(response.last_mc_block_seqno, 123); + assert_eq!(response.last_shard_client_mc_block_seqno, 456); + assert_eq!(response.last_mc_utime, 789); + assert_eq!(response.mc_time_diff, 1000); + assert_eq!(response.shard_client_time_diff, 2000); + assert_eq!(response.smallest_known_lt, Some(3000)); + } + + #[test] + fn test_deserialize_camel_case() { + let json = json!({ + "lastMcBlockSeqno": 123, + "lastShardClientMcBlockSeqno": 456, + "lastMcUtime": 789, + "mcTimeDiff": 1000, + "shardClientTimeDiff": 2000, + "smallestKnownLt": 3000 + }); + + let response: GetTimingsResponse = serde_json::from_value(json).unwrap(); + + assert_eq!(response.last_mc_block_seqno, 123); + assert_eq!(response.last_shard_client_mc_block_seqno, 456); + assert_eq!(response.last_mc_utime, 789); + assert_eq!(response.mc_time_diff, 1000); + assert_eq!(response.shard_client_time_diff, 2000); + assert_eq!(response.smallest_known_lt, Some(3000)); + } + + #[test] + fn test_deserialize_mixed_case() { + let json = json!({ + "last_mc_block_seqno": 123, + "lastShardClientMcBlockSeqno": 456, + "last_mc_utime": 789, + "mcTimeDiff": 1000, + "shard_client_time_diff": 2000, + "smallestKnownLt": 3000 + }); + + let response: GetTimingsResponse = serde_json::from_value(json).unwrap(); + + assert_eq!(response.last_mc_block_seqno, 123); + assert_eq!(response.last_shard_client_mc_block_seqno, 456); + assert_eq!(response.last_mc_utime, 789); + assert_eq!(response.mc_time_diff, 1000); + assert_eq!(response.shard_client_time_diff, 2000); + assert_eq!(response.smallest_known_lt, Some(3000)); + } + + #[test] + fn test_deserialize_missing_optional_field() { + let json = json!({ + "last_mc_block_seqno": 123, + "last_shard_client_mc_block_seqno": 456, + "last_mc_utime": 789, + "mc_time_diff": 1000, + "shard_client_time_diff": 2000 + }); + + let response: GetTimingsResponse = serde_json::from_value(json).unwrap(); + + assert_eq!(response.last_mc_block_seqno, 123); + assert_eq!(response.last_shard_client_mc_block_seqno, 456); + assert_eq!(response.last_mc_utime, 789); + assert_eq!(response.mc_time_diff, 1000); + assert_eq!(response.shard_client_time_diff, 2000); + assert_eq!(response.smallest_known_lt, None); + } + + #[test] + fn test_deserialize_fallback_values() { + let json = json!({ + "last_mc_block_seqno": 123, + "last_mc_utime": 789, + "mc_time_diff": 1000, + "smallest_known_lt": 3000 + }); + + let response: GetTimingsResponse = serde_json::from_value(json).unwrap(); + + assert_eq!(response.last_mc_block_seqno, 123); + assert_eq!(response.last_shard_client_mc_block_seqno, 123); // Fallback to last_mc_block_seqno + assert_eq!(response.last_mc_utime, 789); + assert_eq!(response.mc_time_diff, 1000); + assert_eq!(response.shard_client_time_diff, 1000); // Fallback to mc_time_diff + assert_eq!(response.smallest_known_lt, Some(3000)); + } + + #[test] + fn test_deserialize_with_all_fields() { + let json = json!({ + "last_mc_block_seqno": 123, + "last_shard_client_mc_block_seqno": 456, + "last_mc_utime": 789, + "mc_time_diff": 1000, + "shard_client_time_diff": 2000, + "smallest_known_lt": 3000 + }); + + let response: GetTimingsResponse = serde_json::from_value(json).unwrap(); + + assert_eq!(response.last_mc_block_seqno, 123); + assert_eq!(response.last_shard_client_mc_block_seqno, 456); + assert_eq!(response.last_mc_utime, 789); + assert_eq!(response.mc_time_diff, 1000); + assert_eq!(response.shard_client_time_diff, 2000); + assert_eq!(response.smallest_known_lt, Some(3000)); + } }