Skip to content

Commit

Permalink
fix `GetTimings' to parse both CamelCase and unix-case naming
Browse files Browse the repository at this point in the history
  • Loading branch information
0xdeafbeef committed Jul 29, 2024
1 parent 6d31565 commit 2049fe1
Show file tree
Hide file tree
Showing 3 changed files with 264 additions and 3 deletions.
6 changes: 4 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions models/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

257 changes: 256 additions & 1 deletion models/src/jrpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self::ResponseContainer>;
Expand Down Expand Up @@ -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,
Expand All @@ -403,6 +407,158 @@ pub struct GetTimingsResponse {
pub smallest_known_lt: Option<u64>,
}

impl<'de> Deserialize<'de> for GetTimingsResponse {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
enum Field {
LastMcBlockSeqno,
LastShardClientMcBlockSeqno,
LastMcUtime,
McTimeDiff,
ShardClientTimeDiff,
SmallestKnownLt,
}

impl<'de> Deserialize<'de> for Field {
fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
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<E>(self, value: &str) -> Result<Field, E>
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<V>(self, mut map: V) -> Result<GetTimingsResponse, V::Error>
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
.ok_or_else(|| de::Error::missing_field("last_shard_client_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
.ok_or_else(|| de::Error::missing_field("shard_client_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: &[&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::*;
Expand All @@ -424,4 +580,103 @@ 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 super::*;

Check warning on line 584 in models/src/jrpc.rs

View workflow job for this annotation

GitHub Actions / Test Suite

unused import: `super::*`

Check failure on line 584 in models/src/jrpc.rs

View workflow job for this annotation

GitHub Actions / Lints

unused import: `super::*`
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_error_missing_required_field() {
let json = json!({
"last_mc_block_seqno": 123,
"last_shard_client_mc_block_seqno": 456,
"last_mc_utime": 789,
"mc_time_diff": 1000
});

let result: Result<GetTimingsResponse, _> = serde_json::from_value(json);
assert!(result.is_err());
}
}

0 comments on commit 2049fe1

Please sign in to comment.