1use crate::document::fields::property_names;
2use crate::document::serialization_traits::{
3 DocumentJsonMethodsV0, DocumentPlatformValueMethodsV0,
4};
5use crate::document::DocumentV0;
6use crate::util::json_value::JsonValueExt;
7use crate::ProtocolError;
8use platform_value::{Identifier, Value};
9use platform_version::version::PlatformVersion;
10use serde::Deserialize;
11use serde_json::{json, Value as JsonValue};
12use std::convert::TryInto;
13
14impl DocumentJsonMethodsV0<'_> for DocumentV0 {
15 fn to_json_with_identifiers_using_bytes(
16 &self,
17 _platform_version: &PlatformVersion,
18 ) -> Result<JsonValue, ProtocolError> {
19 let mut value = json!({
20 property_names::ID: self.id,
21 property_names::OWNER_ID: self.owner_id,
22 });
23 let value_mut = value.as_object_mut().unwrap();
24 if let Some(created_at) = self.created_at {
25 value_mut.insert(
26 property_names::CREATED_AT.to_string(),
27 JsonValue::Number(created_at.into()),
28 );
29 }
30 if let Some(updated_at) = self.updated_at {
31 value_mut.insert(
32 property_names::UPDATED_AT.to_string(),
33 JsonValue::Number(updated_at.into()),
34 );
35 }
36 if let Some(created_at_block_height) = self.created_at_block_height {
37 value_mut.insert(
38 property_names::CREATED_AT_BLOCK_HEIGHT.to_string(),
39 JsonValue::Number(created_at_block_height.into()),
40 );
41 }
42
43 if let Some(updated_at_block_height) = self.updated_at_block_height {
44 value_mut.insert(
45 property_names::UPDATED_AT_BLOCK_HEIGHT.to_string(),
46 JsonValue::Number(updated_at_block_height.into()),
47 );
48 }
49
50 if let Some(created_at_core_block_height) = self.created_at_core_block_height {
51 value_mut.insert(
52 property_names::CREATED_AT_CORE_BLOCK_HEIGHT.to_string(),
53 JsonValue::Number(created_at_core_block_height.into()),
54 );
55 }
56
57 if let Some(updated_at_core_block_height) = self.updated_at_core_block_height {
58 value_mut.insert(
59 property_names::UPDATED_AT_CORE_BLOCK_HEIGHT.to_string(),
60 JsonValue::Number(updated_at_core_block_height.into()),
61 );
62 }
63 if let Some(transferred_at) = self.transferred_at {
64 value_mut.insert(
65 property_names::TRANSFERRED_AT.to_string(),
66 JsonValue::Number(transferred_at.into()),
67 );
68 }
69 if let Some(transferred_at_block_height) = self.transferred_at_block_height {
70 value_mut.insert(
71 property_names::TRANSFERRED_AT_BLOCK_HEIGHT.to_string(),
72 JsonValue::Number(transferred_at_block_height.into()),
73 );
74 }
75 if let Some(transferred_at_core_block_height) = self.transferred_at_core_block_height {
76 value_mut.insert(
77 property_names::TRANSFERRED_AT_CORE_BLOCK_HEIGHT.to_string(),
78 JsonValue::Number(transferred_at_core_block_height.into()),
79 );
80 }
81 if let Some(creator_id) = self.creator_id {
82 value_mut.insert(property_names::CREATOR_ID.to_string(), json!(creator_id));
83 }
84 if let Some(revision) = self.revision {
85 value_mut.insert(
86 property_names::REVISION.to_string(),
87 JsonValue::Number(revision.into()),
88 );
89 }
90
91 self.properties
92 .iter()
93 .try_for_each(|(key, property_value)| {
94 let serde_value: JsonValue = property_value.try_to_validating_json()?;
95 value_mut.insert(key.to_string(), serde_value);
96 Ok::<(), ProtocolError>(())
97 })?;
98
99 Ok(value)
100 }
101
102 fn to_json(&self, _platform_version: &PlatformVersion) -> Result<JsonValue, ProtocolError> {
103 self.to_object()
104 .map(|v| v.try_into().map_err(ProtocolError::ValueError))?
105 }
106
107 fn from_json_value<S, E>(
108 mut document_value: JsonValue,
109 _platform_version: &PlatformVersion,
110 ) -> Result<Self, ProtocolError>
111 where
112 for<'de> S: Deserialize<'de> + TryInto<Identifier, Error = E>,
113 E: Into<ProtocolError>,
114 {
115 let mut document = Self {
116 ..Default::default()
117 };
118
119 if let Ok(value) = document_value.remove(property_names::ID) {
120 if !value.is_null() {
121 let data: S = serde_json::from_value(value)?;
122 document.id = data.try_into().map_err(Into::into)?;
123 }
124 }
125 if let Ok(value) = document_value.remove(property_names::OWNER_ID) {
126 if !value.is_null() {
127 let data: S = serde_json::from_value(value)?;
128 document.owner_id = data.try_into().map_err(Into::into)?;
129 }
130 }
131 if let Ok(value) = document_value.remove(property_names::REVISION) {
132 document.revision = serde_json::from_value(value)?
133 }
134 if let Ok(value) = document_value.remove(property_names::CREATED_AT) {
135 document.created_at = serde_json::from_value(value)?
136 }
137 if let Ok(value) = document_value.remove(property_names::UPDATED_AT) {
138 document.updated_at = serde_json::from_value(value)?
139 }
140 if let Ok(value) = document_value.remove(property_names::CREATED_AT_BLOCK_HEIGHT) {
141 document.created_at_block_height = serde_json::from_value(value)?;
142 }
143 if let Ok(value) = document_value.remove(property_names::UPDATED_AT_BLOCK_HEIGHT) {
144 document.updated_at_block_height = serde_json::from_value(value)?;
145 }
146 if let Ok(value) = document_value.remove(property_names::CREATED_AT_CORE_BLOCK_HEIGHT) {
147 document.created_at_core_block_height = serde_json::from_value(value)?;
148 }
149 if let Ok(value) = document_value.remove(property_names::UPDATED_AT_CORE_BLOCK_HEIGHT) {
150 document.updated_at_core_block_height = serde_json::from_value(value)?;
151 }
152 if let Ok(value) = document_value.remove(property_names::TRANSFERRED_AT) {
153 document.transferred_at = serde_json::from_value(value)?;
154 }
155 if let Ok(value) = document_value.remove(property_names::TRANSFERRED_AT_BLOCK_HEIGHT) {
156 document.transferred_at_block_height = serde_json::from_value(value)?;
157 }
158 if let Ok(value) = document_value.remove(property_names::TRANSFERRED_AT_CORE_BLOCK_HEIGHT) {
159 document.transferred_at_core_block_height = serde_json::from_value(value)?;
160 }
161 if let Ok(value) = document_value.remove(property_names::CREATOR_ID) {
162 if !value.is_null() {
163 let data: S = serde_json::from_value(value)?;
164 document.creator_id = Some(data.try_into().map_err(Into::into)?);
165 }
166 }
167
168 let platform_value: Value = document_value.into();
169
170 document.properties = platform_value
171 .into_btree_string_map()
172 .map_err(ProtocolError::ValueError)?;
173 Ok(document)
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180 use crate::data_contract::accessors::v0::DataContractV0Getters;
181 use crate::data_contract::document_type::random_document::CreateRandomDocument;
182 use crate::document::serialization_traits::DocumentJsonMethodsV0;
183 use crate::tests::json_document::json_document_to_contract;
184 use platform_version::version::PlatformVersion;
185 use std::collections::BTreeMap;
186
187 fn make_document_v0_with_all_timestamps() -> DocumentV0 {
188 let mut properties = BTreeMap::new();
189 properties.insert("label".to_string(), Value::Text("test-label".to_string()));
190 DocumentV0 {
191 id: Identifier::new([1u8; 32]),
192 owner_id: Identifier::new([2u8; 32]),
193 properties,
194 revision: Some(3),
195 created_at: Some(1_700_000_000_000),
196 updated_at: Some(1_700_000_100_000),
197 transferred_at: Some(1_700_000_200_000),
198 created_at_block_height: Some(100),
199 updated_at_block_height: Some(200),
200 transferred_at_block_height: Some(300),
201 created_at_core_block_height: Some(50),
202 updated_at_core_block_height: Some(60),
203 transferred_at_core_block_height: Some(70),
204 creator_id: Some(Identifier::new([9u8; 32])),
205 }
206 }
207
208 fn make_minimal_document_v0() -> DocumentV0 {
209 DocumentV0 {
210 id: Identifier::new([0xAA; 32]),
211 owner_id: Identifier::new([0xBB; 32]),
212 properties: BTreeMap::new(),
213 revision: None,
214 created_at: None,
215 updated_at: None,
216 transferred_at: None,
217 created_at_block_height: None,
218 updated_at_block_height: None,
219 transferred_at_block_height: None,
220 created_at_core_block_height: None,
221 updated_at_core_block_height: None,
222 transferred_at_core_block_height: None,
223 creator_id: None,
224 }
225 }
226
227 #[test]
232 fn to_json_includes_id_and_owner_id() {
233 let platform_version = PlatformVersion::latest();
234 let doc = make_minimal_document_v0();
235 let json = doc
236 .to_json(platform_version)
237 .expect("to_json should succeed");
238 let obj = json.as_object().expect("should be an object");
239 assert!(
240 obj.contains_key(property_names::ID),
241 "JSON should contain $id"
242 );
243 assert!(
244 obj.contains_key(property_names::OWNER_ID),
245 "JSON should contain $ownerId"
246 );
247 }
248
249 #[test]
250 fn to_json_represents_none_timestamps_as_null() {
251 let platform_version = PlatformVersion::latest();
252 let doc = make_minimal_document_v0();
253 let json = doc
254 .to_json(platform_version)
255 .expect("to_json should succeed");
256 let obj = json.as_object().expect("should be an object");
257
258 if let Some(val) = obj.get(property_names::CREATED_AT) {
260 assert!(
261 val.is_null(),
262 "$createdAt should be null when None, got: {:?}",
263 val
264 );
265 }
266 if let Some(val) = obj.get(property_names::UPDATED_AT) {
267 assert!(
268 val.is_null(),
269 "$updatedAt should be null when None, got: {:?}",
270 val
271 );
272 }
273 if let Some(val) = obj.get(property_names::REVISION) {
274 assert!(
275 val.is_null(),
276 "$revision should be null when None, got: {:?}",
277 val
278 );
279 }
280 }
281
282 #[test]
287 fn to_json_with_identifiers_using_bytes_includes_all_timestamp_fields() {
288 let platform_version = PlatformVersion::latest();
289 let doc = make_document_v0_with_all_timestamps();
290 let json = doc
291 .to_json_with_identifiers_using_bytes(platform_version)
292 .expect("to_json_with_identifiers_using_bytes should succeed");
293 let obj = json.as_object().expect("should be an object");
294
295 assert!(obj.contains_key(property_names::ID));
296 assert!(obj.contains_key(property_names::OWNER_ID));
297 assert!(obj.contains_key(property_names::REVISION));
298 assert!(obj.contains_key(property_names::CREATED_AT));
299 assert!(obj.contains_key(property_names::UPDATED_AT));
300 assert!(obj.contains_key(property_names::TRANSFERRED_AT));
301 assert!(obj.contains_key(property_names::CREATED_AT_BLOCK_HEIGHT));
302 assert!(obj.contains_key(property_names::UPDATED_AT_BLOCK_HEIGHT));
303 assert!(obj.contains_key(property_names::TRANSFERRED_AT_BLOCK_HEIGHT));
304 assert!(obj.contains_key(property_names::CREATED_AT_CORE_BLOCK_HEIGHT));
305 assert!(obj.contains_key(property_names::UPDATED_AT_CORE_BLOCK_HEIGHT));
306 assert!(obj.contains_key(property_names::TRANSFERRED_AT_CORE_BLOCK_HEIGHT));
307 assert!(obj.contains_key(property_names::CREATOR_ID));
308
309 assert_eq!(obj[property_names::REVISION].as_u64(), Some(3));
311 assert_eq!(
312 obj[property_names::CREATED_AT].as_u64(),
313 Some(1_700_000_000_000)
314 );
315 assert_eq!(
316 obj[property_names::UPDATED_AT].as_u64(),
317 Some(1_700_000_100_000)
318 );
319 assert_eq!(
320 obj[property_names::TRANSFERRED_AT].as_u64(),
321 Some(1_700_000_200_000)
322 );
323 assert_eq!(
324 obj[property_names::CREATED_AT_BLOCK_HEIGHT].as_u64(),
325 Some(100)
326 );
327 assert_eq!(
328 obj[property_names::UPDATED_AT_BLOCK_HEIGHT].as_u64(),
329 Some(200)
330 );
331 assert_eq!(
332 obj[property_names::TRANSFERRED_AT_BLOCK_HEIGHT].as_u64(),
333 Some(300)
334 );
335 assert_eq!(
336 obj[property_names::CREATED_AT_CORE_BLOCK_HEIGHT].as_u64(),
337 Some(50)
338 );
339 assert_eq!(
340 obj[property_names::UPDATED_AT_CORE_BLOCK_HEIGHT].as_u64(),
341 Some(60)
342 );
343 assert_eq!(
344 obj[property_names::TRANSFERRED_AT_CORE_BLOCK_HEIGHT].as_u64(),
345 Some(70)
346 );
347 }
348
349 #[test]
350 fn to_json_with_identifiers_using_bytes_includes_custom_properties() {
351 let platform_version = PlatformVersion::latest();
352 let doc = make_document_v0_with_all_timestamps();
353 let json = doc
354 .to_json_with_identifiers_using_bytes(platform_version)
355 .expect("should succeed");
356 let obj = json.as_object().expect("should be an object");
357 assert_eq!(
358 obj.get("label").and_then(|v| v.as_str()),
359 Some("test-label")
360 );
361 }
362
363 #[test]
370 fn json_round_trip_with_random_dashpay_profile() {
371 let platform_version = PlatformVersion::latest();
372 let contract = json_document_to_contract(
373 "../rs-drive/tests/supporting_files/contract/dashpay/dashpay-contract.json",
374 false,
375 platform_version,
376 )
377 .expect("expected to load dashpay contract");
378
379 let document_type = contract
380 .document_type_for_name("profile")
381 .expect("expected profile document type");
382
383 for seed in 0..5u64 {
384 let document = document_type
385 .random_document(Some(seed), platform_version)
386 .expect("expected random document");
387
388 let doc_v0 = match &document {
389 crate::document::Document::V0(d) => d,
390 };
391
392 let json_val = doc_v0
393 .to_json(platform_version)
394 .expect("to_json should succeed");
395
396 let recovered = DocumentV0::from_json_value::<String, _>(json_val, platform_version)
397 .expect("from_json_value should succeed");
398
399 assert_eq!(doc_v0.id, recovered.id, "id mismatch for seed {seed}");
400 assert_eq!(
401 doc_v0.owner_id, recovered.owner_id,
402 "owner_id mismatch for seed {seed}"
403 );
404 assert_eq!(
405 doc_v0.revision, recovered.revision,
406 "revision mismatch for seed {seed}"
407 );
408 }
409 }
410
411 #[test]
416 fn from_json_value_extracts_timestamps_and_revision() {
417 let platform_version = PlatformVersion::latest();
418 let id = Identifier::new([1u8; 32]);
419 let owner = Identifier::new([2u8; 32]);
420 let creator = Identifier::new([9u8; 32]);
421
422 let json_val = json!({
423 "$id": bs58::encode(id.to_buffer()).into_string(),
424 "$ownerId": bs58::encode(owner.to_buffer()).into_string(),
425 "$revision": 5,
426 "$createdAt": 1_000_000u64,
427 "$updatedAt": 2_000_000u64,
428 "$createdAtBlockHeight": 100u64,
429 "$updatedAtBlockHeight": 200u64,
430 "$createdAtCoreBlockHeight": 50u32,
431 "$updatedAtCoreBlockHeight": 60u32,
432 "$transferredAt": 3_000_000u64,
433 "$transferredAtBlockHeight": 300u64,
434 "$transferredAtCoreBlockHeight": 70u32,
435 "$creatorId": bs58::encode(creator.to_buffer()).into_string(),
436 "customProp": "hello"
437 });
438
439 let doc = DocumentV0::from_json_value::<String, _>(json_val, platform_version)
440 .expect("from_json_value should succeed");
441
442 assert_eq!(doc.id, id);
443 assert_eq!(doc.owner_id, owner);
444 assert_eq!(doc.revision, Some(5));
445 assert_eq!(doc.created_at, Some(1_000_000));
446 assert_eq!(doc.updated_at, Some(2_000_000));
447 assert_eq!(doc.created_at_block_height, Some(100));
448 assert_eq!(doc.updated_at_block_height, Some(200));
449 assert_eq!(doc.created_at_core_block_height, Some(50));
450 assert_eq!(doc.updated_at_core_block_height, Some(60));
451 assert_eq!(doc.transferred_at, Some(3_000_000));
452 assert_eq!(doc.transferred_at_block_height, Some(300));
453 assert_eq!(doc.transferred_at_core_block_height, Some(70));
454 assert_eq!(doc.creator_id, Some(creator));
455 assert_eq!(
457 doc.properties.get("customProp"),
458 Some(&Value::Text("hello".to_string()))
459 );
460 }
461
462 #[test]
463 fn from_json_value_handles_missing_optional_fields() {
464 let platform_version = PlatformVersion::latest();
465 let id = Identifier::new([3u8; 32]);
466 let owner = Identifier::new([4u8; 32]);
467 let json_val = json!({
468 "$id": bs58::encode(id.to_buffer()).into_string(),
469 "$ownerId": bs58::encode(owner.to_buffer()).into_string(),
470 });
471
472 let doc = DocumentV0::from_json_value::<String, _>(json_val, platform_version)
473 .expect("from_json_value should succeed with minimal fields");
474
475 assert_eq!(doc.id, id);
476 assert_eq!(doc.owner_id, owner);
477 assert_eq!(doc.revision, None);
478 assert_eq!(doc.created_at, None);
479 assert_eq!(doc.updated_at, None);
480 assert_eq!(doc.transferred_at, None);
481 assert_eq!(doc.created_at_block_height, None);
482 assert_eq!(doc.updated_at_block_height, None);
483 assert_eq!(doc.transferred_at_block_height, None);
484 assert_eq!(doc.created_at_core_block_height, None);
485 assert_eq!(doc.updated_at_core_block_height, None);
486 assert_eq!(doc.transferred_at_core_block_height, None);
487 assert_eq!(doc.creator_id, None);
488 }
489
490 #[test]
495 fn from_json_value_parses_creator_id() {
496 let platform_version = PlatformVersion::latest();
497 let creator = Identifier::new([0xCC; 32]);
498 let json_val = json!({
499 "$id": bs58::encode([1u8; 32]).into_string(),
500 "$ownerId": bs58::encode([2u8; 32]).into_string(),
501 "$creatorId": bs58::encode(creator.to_buffer()).into_string(),
502 });
503
504 let doc = DocumentV0::from_json_value::<String, _>(json_val, platform_version)
505 .expect("from_json_value with creator_id should succeed");
506
507 assert_eq!(doc.creator_id, Some(creator));
508 }
509}