Skip to main content

dpp/identity/v0/conversion/
json.rs

1use crate::identity::conversion::json::IdentityJsonConversionMethodsV0;
2use crate::identity::conversion::platform_value::IdentityPlatformValueConversionMethodsV0;
3use crate::identity::{identity_public_key, IdentityV0, IDENTIFIER_FIELDS_RAW_OBJECT};
4use crate::ProtocolError;
5use platform_value::{ReplacementType, Value};
6use serde_json::Value as JsonValue;
7use std::convert::TryInto;
8
9impl IdentityJsonConversionMethodsV0 for IdentityV0 {
10    fn to_json_object(&self) -> Result<JsonValue, ProtocolError> {
11        self.to_cleaned_object()?
12            .try_into_validating_json()
13            .map_err(ProtocolError::ValueError)
14    }
15
16    fn to_json(&self) -> Result<JsonValue, ProtocolError> {
17        self.to_cleaned_object()?
18            .try_into()
19            .map_err(ProtocolError::ValueError)
20    }
21
22    /// Creates an identity from a json structure
23    fn from_json(json_object: JsonValue) -> Result<Self, ProtocolError> {
24        let mut platform_value: Value = json_object.into();
25
26        platform_value
27            .replace_at_paths(IDENTIFIER_FIELDS_RAW_OBJECT, ReplacementType::Identifier)?;
28
29        if let Some(public_keys_array) = platform_value.get_optional_array_mut_ref("publicKeys")? {
30            for public_key in public_keys_array.iter_mut() {
31                public_key.replace_at_paths(
32                    identity_public_key::BINARY_DATA_FIELDS,
33                    ReplacementType::BinaryBytes,
34                )?;
35            }
36        }
37
38        let identity: Self = platform_value::from_value(platform_value)?;
39
40        Ok(identity)
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47    use crate::identity::identity_public_key::v0::IdentityPublicKeyV0;
48    use crate::identity::{IdentityPublicKey, KeyType, Purpose, SecurityLevel};
49    use platform_value::{BinaryData, Identifier};
50    use std::collections::BTreeMap;
51
52    fn sample_identity_v0() -> IdentityV0 {
53        let mut keys: BTreeMap<u32, IdentityPublicKey> = BTreeMap::new();
54        keys.insert(
55            0,
56            IdentityPublicKey::V0(IdentityPublicKeyV0 {
57                id: 0,
58                purpose: Purpose::AUTHENTICATION,
59                security_level: SecurityLevel::MASTER,
60                contract_bounds: None,
61                key_type: KeyType::ECDSA_SECP256K1,
62                read_only: false,
63                data: BinaryData::new(vec![0x33; 33]),
64                disabled_at: None,
65            }),
66        );
67        IdentityV0 {
68            id: Identifier::from([9u8; 32]),
69            public_keys: keys,
70            balance: 42,
71            revision: 1,
72        }
73    }
74
75    #[test]
76    fn to_json_contains_expected_top_level_fields() {
77        let id = sample_identity_v0();
78        let json = id.to_json().expect("to_json");
79        let obj = json.as_object().expect("object");
80        assert!(obj.contains_key("id"));
81        assert!(obj.contains_key("publicKeys"));
82        assert!(obj.contains_key("balance"));
83        assert!(obj.contains_key("revision"));
84    }
85
86    // frozen: V0 consensus behavior
87    //
88    // `IdentityV0::to_json` produces a JSON form where the inner public keys carry the
89    // `$formatVersion` serde-adjacency tag and `data` is base64-encoded; but
90    // `IdentityV0::from_json` does the reverse mapping via `replace_at_paths` +
91    // `platform_value::from_value`. That combination does not round-trip for `IdentityV0`
92    // because the inner platform_value deserializer is inconsistent about
93    // `is_human_readable()` for nested BinaryData fields. Lock the observed failure in
94    // so the roundtrip pattern is not silently "fixed" in V0.
95    #[test]
96    fn to_json_then_from_json_fails_binary_data_roundtrip_v0_frozen() {
97        let id = sample_identity_v0();
98        let json = id.to_json().unwrap();
99        let back = IdentityV0::from_json(json);
100        assert!(
101            back.is_err(),
102            "V0 to_json -> from_json roundtrip is not expected to succeed; \
103             if this starts to pass, V0 consensus behavior may have changed"
104        );
105    }
106
107    #[test]
108    fn to_json_object_encodes_identifier_as_bytes_array() {
109        // to_json_object goes through try_into_validating_json, which represents
110        // identifiers (32 bytes) as a JSON array of numbers.
111        let id = sample_identity_v0();
112        let json = id.to_json_object().expect("to_json_object");
113        let obj = json.as_object().expect("object");
114        let id_field =
115            obj.get("id").expect("id").as_array().expect(
116                "to_json_object should render the identifier as a JSON array of byte values",
117            );
118        assert_eq!(id_field.len(), 32);
119    }
120
121    #[test]
122    fn from_json_fails_on_garbage_input() {
123        let json = serde_json::json!({ "id": "not-a-valid-identifier" });
124        let result = IdentityV0::from_json(json);
125        assert!(result.is_err());
126    }
127
128    // frozen: V0 consensus behavior
129    //
130    // The JSON fixture does not carry the inner-enum `$formatVersion` tag that
131    // `IdentityPublicKey` deserialization requires, so `from_json` fails on it.
132    // This is the canonical V0 shape of the fixture — the intent is to document
133    // that `from_json` cannot ingest the legacy fixture form directly.
134    #[test]
135    fn from_json_fixture_fails_missing_format_version_v0_frozen() {
136        use crate::tests::fixtures::identity_fixture_json;
137        let json = identity_fixture_json();
138        let result = IdentityV0::from_json(json);
139        match result {
140            Err(e) => {
141                let msg = format!("{:?}", e);
142                assert!(
143                    msg.contains("$formatVersion") || msg.contains("formatVersion"),
144                    "expected missing-formatVersion error, got {msg}"
145                );
146            }
147            Ok(_) => panic!("expected from_json on legacy fixture to fail"),
148        }
149    }
150
151    #[test]
152    fn from_json_errors_when_public_keys_field_is_not_array() {
153        // publicKeys is expected to be an array; using a string should fail early.
154        let json = serde_json::json!({
155            "id": "3bufpwQjL5qsvuP4fmCKgXJrKG852DDMYfi9J6XKqPAT",
156            "publicKeys": "oops",
157            "balance": 0,
158            "revision": 0,
159        });
160        let result = IdentityV0::from_json(json);
161        assert!(result.is_err());
162    }
163}