Skip to main content

dpp/
metadata.rs

1use bincode::Encode;
2use platform_serialization::de::Decode;
3use serde::{Deserialize, Serialize};
4
5#[cfg(feature = "json-conversion")]
6use crate::serialization::json_safe_fields;
7use crate::{errors::ProtocolError, prelude::TimestampMillis, util::deserializer::ProtocolVersion};
8
9#[cfg_attr(feature = "json-conversion", json_safe_fields)]
10#[derive(
11    Serialize, Deserialize, Encode, Decode, Debug, Default, Clone, Copy, PartialEq, PartialOrd, Eq,
12)]
13#[serde(rename_all = "camelCase")]
14pub struct Metadata {
15    #[serde(default)]
16    pub block_height: u64,
17    #[serde(default)]
18    pub core_chain_locked_height: u64,
19    #[serde(default)]
20    pub time_ms: TimestampMillis,
21    #[serde(default)]
22    pub protocol_version: ProtocolVersion,
23}
24
25impl std::convert::TryFrom<&str> for Metadata {
26    type Error = ProtocolError;
27
28    fn try_from(d: &str) -> Result<Metadata, Self::Error> {
29        serde_json::from_str(d).map_err(|e| ProtocolError::EncodingError(e.to_string()))
30    }
31}
32
33#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
34impl crate::serialization::JsonConvertible for Metadata {}
35
36#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
37impl crate::serialization::ValueConvertible for Metadata {}
38
39#[cfg(all(
40    test,
41    feature = "json-conversion",
42    feature = "value-conversion",
43    feature = "serde-conversion"
44))]
45mod json_convertible_tests {
46    use super::*;
47    use crate::serialization::{JsonConvertible, ValueConvertible};
48    use platform_value::platform_value;
49    use serde_json::json;
50
51    /// Non-default values per field. `Metadata` crosses to JS as the
52    /// `$metadata` field of `ExtendedDocument`, so its u64 fields go through
53    /// `json_safe_fields` (numbers below 2^53, strings above).
54    fn fixture() -> Metadata {
55        Metadata {
56            block_height: 1_234_567,
57            core_chain_locked_height: 2_222_333,
58            time_ms: 1_700_000_000_000,
59            protocol_version: 9,
60        }
61    }
62
63    #[test]
64    fn json_round_trip_with_full_wire_shape() {
65        let original = fixture();
66        let json = original.to_json().expect("to_json");
67        // Sized-int info erased in JSON; the value-path assertion locks the
68        // typed variants (u64 heights/time, u32 protocolVersion).
69        assert_eq!(
70            json,
71            json!({
72                "blockHeight": 1_234_567,
73                "coreChainLockedHeight": 2_222_333,
74                "timeMs": 1_700_000_000_000u64,
75                "protocolVersion": 9,
76            })
77        );
78        let recovered = Metadata::from_json(json).expect("from_json");
79        assert_eq!(original, recovered);
80    }
81
82    #[test]
83    fn value_round_trip_with_full_wire_shape() {
84        let original = fixture();
85        let value = original.to_object().expect("to_object");
86        assert_eq!(
87            value,
88            platform_value!({
89                "blockHeight": 1_234_567u64,
90                "coreChainLockedHeight": 2_222_333u64,
91                "timeMs": 1_700_000_000_000u64,
92                "protocolVersion": 9u32,
93            })
94        );
95        let recovered = Metadata::from_object(value).expect("from_object");
96        assert_eq!(original, recovered);
97    }
98
99    /// Values above JS `Number.MAX_SAFE_INTEGER` must stringify in JSON
100    /// (json_safe_fields contract) and still round-trip.
101    #[test]
102    fn json_stringifies_unsafe_u64() {
103        let original = Metadata {
104            block_height: u64::MAX,
105            ..fixture()
106        };
107        let json = original.to_json().expect("to_json");
108        assert_eq!(json["blockHeight"], json!(u64::MAX.to_string()));
109        let recovered = Metadata::from_json(json).expect("from_json");
110        assert_eq!(original, recovered);
111    }
112}