Skip to main content

dpp/
metadata.rs

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