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 crate::document::Document::V0(doc_v0) = &document;
389
390 let json_val = doc_v0
391 .to_json(platform_version)
392 .expect("to_json should succeed");
393
394 let recovered = DocumentV0::from_json_value::<String, _>(json_val, platform_version)
395 .expect("from_json_value should succeed");
396
397 assert_eq!(doc_v0.id, recovered.id, "id mismatch for seed {seed}");
398 assert_eq!(
399 doc_v0.owner_id, recovered.owner_id,
400 "owner_id mismatch for seed {seed}"
401 );
402 assert_eq!(
403 doc_v0.revision, recovered.revision,
404 "revision mismatch for seed {seed}"
405 );
406 }
407 }
408
409 #[test]
414 fn from_json_value_extracts_timestamps_and_revision() {
415 let platform_version = PlatformVersion::latest();
416 let id = Identifier::new([1u8; 32]);
417 let owner = Identifier::new([2u8; 32]);
418 let creator = Identifier::new([9u8; 32]);
419
420 let json_val = json!({
421 "$id": bs58::encode(id.to_buffer()).into_string(),
422 "$ownerId": bs58::encode(owner.to_buffer()).into_string(),
423 "$revision": 5,
424 "$createdAt": 1_000_000u64,
425 "$updatedAt": 2_000_000u64,
426 "$createdAtBlockHeight": 100u64,
427 "$updatedAtBlockHeight": 200u64,
428 "$createdAtCoreBlockHeight": 50u32,
429 "$updatedAtCoreBlockHeight": 60u32,
430 "$transferredAt": 3_000_000u64,
431 "$transferredAtBlockHeight": 300u64,
432 "$transferredAtCoreBlockHeight": 70u32,
433 "$creatorId": bs58::encode(creator.to_buffer()).into_string(),
434 "customProp": "hello"
435 });
436
437 let doc = DocumentV0::from_json_value::<String, _>(json_val, platform_version)
438 .expect("from_json_value should succeed");
439
440 assert_eq!(doc.id, id);
441 assert_eq!(doc.owner_id, owner);
442 assert_eq!(doc.revision, Some(5));
443 assert_eq!(doc.created_at, Some(1_000_000));
444 assert_eq!(doc.updated_at, Some(2_000_000));
445 assert_eq!(doc.created_at_block_height, Some(100));
446 assert_eq!(doc.updated_at_block_height, Some(200));
447 assert_eq!(doc.created_at_core_block_height, Some(50));
448 assert_eq!(doc.updated_at_core_block_height, Some(60));
449 assert_eq!(doc.transferred_at, Some(3_000_000));
450 assert_eq!(doc.transferred_at_block_height, Some(300));
451 assert_eq!(doc.transferred_at_core_block_height, Some(70));
452 assert_eq!(doc.creator_id, Some(creator));
453 assert_eq!(
455 doc.properties.get("customProp"),
456 Some(&Value::Text("hello".to_string()))
457 );
458 }
459
460 #[test]
461 fn from_json_value_handles_missing_optional_fields() {
462 let platform_version = PlatformVersion::latest();
463 let id = Identifier::new([3u8; 32]);
464 let owner = Identifier::new([4u8; 32]);
465 let json_val = json!({
466 "$id": bs58::encode(id.to_buffer()).into_string(),
467 "$ownerId": bs58::encode(owner.to_buffer()).into_string(),
468 });
469
470 let doc = DocumentV0::from_json_value::<String, _>(json_val, platform_version)
471 .expect("from_json_value should succeed with minimal fields");
472
473 assert_eq!(doc.id, id);
474 assert_eq!(doc.owner_id, owner);
475 assert_eq!(doc.revision, None);
476 assert_eq!(doc.created_at, None);
477 assert_eq!(doc.updated_at, None);
478 assert_eq!(doc.transferred_at, None);
479 assert_eq!(doc.created_at_block_height, None);
480 assert_eq!(doc.updated_at_block_height, None);
481 assert_eq!(doc.transferred_at_block_height, None);
482 assert_eq!(doc.created_at_core_block_height, None);
483 assert_eq!(doc.updated_at_core_block_height, None);
484 assert_eq!(doc.transferred_at_core_block_height, None);
485 assert_eq!(doc.creator_id, None);
486 }
487
488 #[test]
494 fn to_json_with_identifiers_using_bytes_minimal_document_has_only_id_and_owner() {
495 let platform_version = PlatformVersion::latest();
496 let doc = make_minimal_document_v0();
497 let json = doc
498 .to_json_with_identifiers_using_bytes(platform_version)
499 .expect("to_json_with_identifiers_using_bytes should succeed");
500 let obj = json.as_object().expect("object");
501 assert!(obj.contains_key(property_names::ID));
502 assert!(obj.contains_key(property_names::OWNER_ID));
503 assert!(!obj.contains_key(property_names::CREATED_AT));
505 assert!(!obj.contains_key(property_names::UPDATED_AT));
506 assert!(!obj.contains_key(property_names::TRANSFERRED_AT));
507 assert!(!obj.contains_key(property_names::CREATED_AT_BLOCK_HEIGHT));
508 assert!(!obj.contains_key(property_names::UPDATED_AT_BLOCK_HEIGHT));
509 assert!(!obj.contains_key(property_names::TRANSFERRED_AT_BLOCK_HEIGHT));
510 assert!(!obj.contains_key(property_names::CREATED_AT_CORE_BLOCK_HEIGHT));
511 assert!(!obj.contains_key(property_names::UPDATED_AT_CORE_BLOCK_HEIGHT));
512 assert!(!obj.contains_key(property_names::TRANSFERRED_AT_CORE_BLOCK_HEIGHT));
513 assert!(!obj.contains_key(property_names::CREATOR_ID));
514 assert!(!obj.contains_key(property_names::REVISION));
515 }
516
517 #[test]
523 fn to_json_with_identifiers_using_bytes_emits_base58_identifiers() {
524 let platform_version = PlatformVersion::latest();
525 let doc = make_minimal_document_v0();
526 let json = doc
527 .to_json_with_identifiers_using_bytes(platform_version)
528 .expect("should succeed");
529 let obj = json.as_object().expect("object");
530 let id_val = obj.get(property_names::ID).expect("id present");
533 assert!(id_val.is_string(), "expected base58 string for $id");
534 let owner_val = obj.get(property_names::OWNER_ID).expect("owner present");
535 assert!(owner_val.is_string(), "expected base58 string for $ownerId");
536 }
537
538 #[test]
543 fn from_json_value_with_null_creator_id_stays_none() {
544 let platform_version = PlatformVersion::latest();
545 let json_val = json!({
546 "$id": bs58::encode([1u8; 32]).into_string(),
547 "$ownerId": bs58::encode([2u8; 32]).into_string(),
548 "$creatorId": JsonValue::Null,
549 });
550 let doc = DocumentV0::from_json_value::<String, _>(json_val, platform_version)
551 .expect("from_json_value should succeed with null creator_id");
552 assert_eq!(doc.creator_id, None);
553 }
554
555 #[test]
560 fn from_json_value_with_null_id_leaves_default() {
561 let platform_version = PlatformVersion::latest();
562 let json_val = json!({
563 "$id": JsonValue::Null,
564 "$ownerId": bs58::encode([2u8; 32]).into_string(),
565 });
566 let doc = DocumentV0::from_json_value::<String, _>(json_val, platform_version)
567 .expect("from_json_value should succeed with null $id");
568 assert_eq!(doc.id, Identifier::new([0u8; 32]));
570 }
571
572 #[test]
578 fn to_json_with_identifiers_using_bytes_with_multiple_properties() {
579 let platform_version = PlatformVersion::latest();
580 let mut props = BTreeMap::new();
581 props.insert("a".to_string(), Value::U64(1));
582 props.insert("b".to_string(), Value::Text("two".to_string()));
583 props.insert("c".to_string(), Value::Bool(true));
584 let doc = DocumentV0 {
585 id: Identifier::new([1u8; 32]),
586 owner_id: Identifier::new([2u8; 32]),
587 properties: props,
588 revision: None,
589 created_at: None,
590 updated_at: None,
591 transferred_at: None,
592 created_at_block_height: None,
593 updated_at_block_height: None,
594 transferred_at_block_height: None,
595 created_at_core_block_height: None,
596 updated_at_core_block_height: None,
597 transferred_at_core_block_height: None,
598 creator_id: None,
599 };
600 let json = doc
601 .to_json_with_identifiers_using_bytes(platform_version)
602 .expect("should succeed");
603 let obj = json.as_object().expect("object");
604 assert_eq!(obj.get("a").and_then(|v| v.as_u64()), Some(1));
605 assert_eq!(obj.get("b").and_then(|v| v.as_str()), Some("two"));
606 assert_eq!(obj.get("c").and_then(|v| v.as_bool()), Some(true));
607 }
608
609 #[test]
614 fn from_json_value_empty_object_returns_default_document() {
615 let platform_version = PlatformVersion::latest();
616 let doc = DocumentV0::from_json_value::<String, _>(json!({}), platform_version)
617 .expect("from_json_value should succeed with empty object");
618 assert_eq!(doc.id, Identifier::new([0u8; 32]));
619 assert_eq!(doc.owner_id, Identifier::new([0u8; 32]));
620 assert_eq!(doc.revision, None);
621 assert!(doc.properties.is_empty());
622 }
623
624 #[test]
629 fn from_json_value_parses_creator_id() {
630 let platform_version = PlatformVersion::latest();
631 let creator = Identifier::new([0xCC; 32]);
632 let json_val = json!({
633 "$id": bs58::encode([1u8; 32]).into_string(),
634 "$ownerId": bs58::encode([2u8; 32]).into_string(),
635 "$creatorId": bs58::encode(creator.to_buffer()).into_string(),
636 });
637
638 let doc = DocumentV0::from_json_value::<String, _>(json_val, platform_version)
639 .expect("from_json_value with creator_id should succeed");
640
641 assert_eq!(doc.creator_id, Some(creator));
642 }
643}