1pub use fields::{property_names, IDENTIFIER_FIELDS};
2
3mod accessors;
4pub mod document_event;
5#[cfg(feature = "client")]
6mod document_facade;
7#[cfg(feature = "factories")]
8pub mod document_factory;
9pub mod document_methods;
10mod document_patch;
11pub mod errors;
12#[cfg(feature = "extended-document")]
13pub mod extended_document;
14mod fields;
15pub mod generate_document_id;
16pub mod serialization_traits;
17#[cfg(feature = "factories")]
18pub mod specialized_document_factory;
19pub mod transfer;
20mod v0;
21
22pub use accessors::*;
23pub use v0::*;
24
25#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
26use crate::serialization::JsonConvertible;
27#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
28use crate::serialization::ValueConvertible;
29#[cfg(feature = "extended-document")]
30pub use extended_document::property_names as extended_document_property_names;
31#[cfg(feature = "extended-document")]
32pub use extended_document::ExtendedDocument;
33#[cfg(feature = "extended-document")]
34pub use extended_document::IDENTIFIER_FIELDS as EXTENDED_DOCUMENT_IDENTIFIER_FIELDS;
35
36pub const INITIAL_REVISION: u64 = 1;
38
39use crate::data_contract::document_type::DocumentTypeRef;
40use crate::data_contract::DataContract;
41use crate::document::document_methods::{
42 DocumentGetRawForContractV0, DocumentGetRawForDocumentTypeV0, DocumentHashV0Method,
43 DocumentIsEqualIgnoringTimestampsV0, DocumentMethodsV0,
44};
45use crate::document::errors::DocumentError;
46use crate::version::PlatformVersion;
47use crate::ProtocolError;
48use derive_more::From;
49
50use std::fmt;
51use std::fmt::Formatter;
52
53#[derive(Clone, Debug, PartialEq, From)]
54#[cfg_attr(
55 feature = "serde-conversion",
56 derive(serde::Serialize, serde::Deserialize),
57 serde(tag = "$formatVersion")
58)]
59pub enum Document {
60 #[cfg_attr(feature = "serde-conversion", serde(rename = "0"))]
61 V0(DocumentV0),
62}
63
64#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
65impl JsonConvertible for Document {}
66
67#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
68impl ValueConvertible for Document {}
69
70impl fmt::Display for Document {
71 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
72 match self {
73 Document::V0(v0) => {
74 write!(f, "v0 : {} ", v0)?;
75 }
76 }
77 Ok(())
78 }
79}
80
81impl DocumentMethodsV0 for Document {
82 fn get_raw_for_contract(
84 &self,
85 key: &str,
86 document_type_name: &str,
87 contract: &DataContract,
88 owner_id: Option<[u8; 32]>,
89 platform_version: &PlatformVersion,
90 ) -> Result<Option<Vec<u8>>, ProtocolError> {
91 match self {
92 Document::V0(document_v0) => {
93 match platform_version
94 .dpp
95 .document_versions
96 .document_method_versions
97 .get_raw_for_contract
98 {
99 0 => document_v0.get_raw_for_contract_v0(
100 key,
101 document_type_name,
102 contract,
103 owner_id,
104 platform_version,
105 ),
106 version => Err(ProtocolError::UnknownVersionMismatch {
107 method: "DocumentMethodV0::get_raw_for_contract".to_string(),
108 known_versions: vec![0],
109 received: version,
110 }),
111 }
112 }
113 }
114 }
115
116 fn get_raw_for_document_type(
118 &self,
119 key_path: &str,
120 document_type: DocumentTypeRef,
121 owner_id: Option<[u8; 32]>,
122 platform_version: &PlatformVersion,
123 ) -> Result<Option<Vec<u8>>, ProtocolError> {
124 match self {
125 Document::V0(document_v0) => {
126 match platform_version
127 .dpp
128 .document_versions
129 .document_method_versions
130 .get_raw_for_document_type
131 {
132 0 => document_v0.get_raw_for_document_type_v0(
133 key_path,
134 document_type,
135 owner_id,
136 platform_version,
137 ),
138 version => Err(ProtocolError::UnknownVersionMismatch {
139 method: "DocumentMethodV0::get_raw_for_document_type".to_string(),
140 known_versions: vec![0],
141 received: version,
142 }),
143 }
144 }
145 }
146 }
147
148 fn hash(
149 &self,
150 contract: &DataContract,
151 document_type: DocumentTypeRef,
152 platform_version: &PlatformVersion,
153 ) -> Result<Vec<u8>, ProtocolError> {
154 match self {
155 Document::V0(document_v0) => {
156 match platform_version
157 .dpp
158 .document_versions
159 .document_method_versions
160 .hash
161 {
162 0 => document_v0.hash_v0(contract, document_type, platform_version),
163 version => Err(ProtocolError::UnknownVersionMismatch {
164 method: "DocumentMethodV0::hash".to_string(),
165 known_versions: vec![0],
166 received: version,
167 }),
168 }
169 }
170 }
171 }
172
173 fn increment_revision(&mut self) -> Result<(), ProtocolError> {
174 let Some(revision) = self.revision() else {
175 return Err(ProtocolError::Document(Box::new(
176 DocumentError::DocumentNoRevisionError {
177 document: Box::new(self.clone()),
178 },
179 )));
180 };
181
182 let new_revision = revision
183 .checked_add(1)
184 .ok_or(ProtocolError::Overflow("overflow when adding 1"))?;
185
186 self.set_revision(Some(new_revision));
187
188 Ok(())
189 }
190
191 fn is_equal_ignoring_time_based_fields(
192 &self,
193 rhs: &Self,
194 also_ignore_fields: Option<Vec<&str>>,
195 platform_version: &PlatformVersion,
196 ) -> Result<bool, ProtocolError> {
197 match (self, rhs) {
198 (Document::V0(document_v0), Document::V0(rhs_v0)) => {
199 match platform_version
200 .dpp
201 .document_versions
202 .document_method_versions
203 .is_equal_ignoring_timestamps
204 {
205 0 => Ok(document_v0
206 .is_equal_ignoring_time_based_fields_v0(rhs_v0, also_ignore_fields)),
207 version => Err(ProtocolError::UnknownVersionMismatch {
208 method: "DocumentMethodV0::is_equal_ignoring_time_based_fields".to_string(),
209 known_versions: vec![0],
210 received: version,
211 }),
212 }
213 }
214 }
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221 use crate::data_contract::accessors::v0::DataContractV0Getters;
222 use crate::data_contract::document_type::random_document::CreateRandomDocument;
223 use crate::document::serialization_traits::DocumentPlatformConversionMethodsV0;
224 use crate::tests::json_document::json_document_to_contract;
225
226 use regex::Regex;
227
228 #[test]
229 fn test_document_display() {
230 let platform_version = PlatformVersion::first();
231 let contract = json_document_to_contract(
232 "../rs-drive/tests/supporting_files/contract/dashpay/dashpay-contract.json",
233 false,
234 platform_version,
235 )
236 .expect("expected to get contract");
237
238 let document_type = contract
239 .document_type_for_name("profile")
240 .expect("expected to get profile document type");
241 let document = document_type
242 .random_document(Some(3333), platform_version)
243 .expect("expected to get a random document");
244
245 let document_string = format!("{}", document);
246 let pattern = r"v\d+ : id:45ZNwGcxeMpLpYmiVEKKBKXbZfinrhjZLkau1GWizPFX owner_id:2vq574DjKi7ZD8kJ6dMHxT5wu6ZKD2bW5xKAyKAGW7qZ created_at:(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) updated_at:(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) avatarUrl:string y8RD1DbW18RuyblDX7hx\[...\(670\)\] displayName:string y94Itl6mn1yBE publicMessage:string SvAQrzsslj0ESc15GQBQ\[...\(105\)\] .*";
247 let re = Regex::new(pattern).unwrap();
248 assert!(
249 re.is_match(document_string.as_str()),
250 "pattern: {} does not match {}",
251 pattern,
252 document_string
253 );
254 }
255
256 #[test]
257 fn test_serialization_and_deserialization() {
258 let platform_version = PlatformVersion::latest();
259 let contract = json_document_to_contract(
260 "../rs-drive/tests/supporting_files/contract/dpns/dpns-contract.json",
261 false,
262 platform_version,
263 )
264 .expect("expected to get contract");
265
266 let document_type = contract
267 .document_type_for_name("domain")
268 .expect("expected to get document type");
269 for _ in 0..20 {
270 let document = document_type
271 .random_document(None, platform_version)
272 .expect("expected a document");
273 let serialized = <Document as DocumentPlatformConversionMethodsV0>::serialize(
274 &document,
275 document_type,
276 &contract,
277 platform_version,
278 )
279 .expect("should serialize");
280 let _deserialized = Document::from_bytes(&serialized, document_type, platform_version)
281 .expect("expected to deserialize domain document");
282 }
283 }
284
285 #[test]
286 fn test_serialize_deserialize_over_different_versions_of_document_type() {
287 let platform_version = PlatformVersion::latest();
288 let contract = json_document_to_contract(
289 "../rs-drive/tests/supporting_files/contract/dpns/dpns-contract.json",
290 false,
291 platform_version,
292 )
293 .expect("expected to get contract");
294
295 let updated_contract = json_document_to_contract(
296 "../rs-drive/tests/supporting_files/contract/dpns/dpns-contract-update-v2-test.json",
297 false,
298 platform_version,
299 )
300 .expect("expected to get contract");
301
302 let document_type = contract
303 .document_type_for_name("domain")
304 .expect("expected to get document type");
305
306 let updated_document_type = updated_contract
307 .document_type_for_name("domain")
308 .expect("expected to get document type");
309
310 for _ in 0..20 {
312 let document = document_type
313 .random_document(None, platform_version)
314 .expect("expected a document");
315 let serialized = <Document as DocumentPlatformConversionMethodsV0>::serialize(
316 &document,
317 document_type,
318 &contract,
319 platform_version,
320 )
321 .expect("should serialize");
322 let _deserialized =
323 Document::from_bytes(&serialized, updated_document_type, platform_version)
324 .expect("expected to deserialize domain document");
325 }
326
327 for _ in 0..20 {
329 let document = updated_document_type
330 .random_document(None, platform_version)
331 .expect("expected a document");
332 let serialized = <Document as DocumentPlatformConversionMethodsV0>::serialize(
333 &document,
334 document_type,
335 &contract,
336 platform_version,
337 )
338 .expect("should serialize");
339 let _deserialized = Document::from_bytes(&serialized, document_type, platform_version)
340 .expect("expected to deserialize domain document");
341 }
342 }
343
344 #[test]
349 fn display_document_with_no_properties() {
350 let doc = Document::V0(DocumentV0 {
351 contract_version: None,
352 id: platform_value::Identifier::new([0xAA; 32]),
353 owner_id: platform_value::Identifier::new([0xBB; 32]),
354 properties: Default::default(),
355 revision: None,
356 created_at: None,
357 updated_at: None,
358 transferred_at: None,
359 created_at_block_height: None,
360 updated_at_block_height: None,
361 transferred_at_block_height: None,
362 created_at_core_block_height: None,
363 updated_at_core_block_height: None,
364 transferred_at_core_block_height: None,
365 creator_id: None,
366 moderated_at: None,
367 moderated_by: None,
368 });
369
370 let s = format!("{}", doc);
371 assert!(
372 s.contains("no properties"),
373 "should say 'no properties' when the BTreeMap is empty, got: {}",
374 s
375 );
376 }
377
378 #[test]
379 fn display_document_shows_transferred_at_fields() {
380 let doc = Document::V0(DocumentV0 {
381 contract_version: None,
382 id: platform_value::Identifier::new([1u8; 32]),
383 owner_id: platform_value::Identifier::new([2u8; 32]),
384 properties: Default::default(),
385 revision: None,
386 created_at: None,
387 updated_at: None,
388 transferred_at: Some(1_700_000_000_000),
389 created_at_block_height: None,
390 updated_at_block_height: None,
391 transferred_at_block_height: Some(500),
392 created_at_core_block_height: None,
393 updated_at_core_block_height: None,
394 transferred_at_core_block_height: Some(42),
395 creator_id: None,
396 moderated_at: None,
397 moderated_by: None,
398 });
399
400 let s = format!("{}", doc);
401 assert!(
402 s.contains("transferred_at:"),
403 "should contain transferred_at, got: {}",
404 s
405 );
406 assert!(
407 s.contains("transferred_at_block_height:500"),
408 "should contain transferred_at_block_height:500, got: {}",
409 s
410 );
411 assert!(
412 s.contains("transferred_at_core_block_height:42"),
413 "should contain transferred_at_core_block_height:42, got: {}",
414 s
415 );
416 }
417
418 #[test]
419 fn display_document_shows_creator_id() {
420 let creator = platform_value::Identifier::new([0xCC; 32]);
421 let doc = Document::V0(DocumentV0 {
422 contract_version: None,
423 id: platform_value::Identifier::new([1u8; 32]),
424 owner_id: platform_value::Identifier::new([2u8; 32]),
425 properties: Default::default(),
426 revision: None,
427 created_at: None,
428 updated_at: None,
429 transferred_at: None,
430 created_at_block_height: None,
431 updated_at_block_height: None,
432 transferred_at_block_height: None,
433 created_at_core_block_height: None,
434 updated_at_core_block_height: None,
435 transferred_at_core_block_height: None,
436 creator_id: Some(creator),
437 moderated_at: None,
438 moderated_by: None,
439 });
440
441 let s = format!("{}", doc);
442 assert!(
443 s.contains("creator_id:"),
444 "should contain creator_id, got: {}",
445 s
446 );
447 }
448
449 #[test]
450 fn display_document_shows_block_height_fields() {
451 let doc = Document::V0(DocumentV0 {
452 contract_version: None,
453 id: platform_value::Identifier::new([1u8; 32]),
454 owner_id: platform_value::Identifier::new([2u8; 32]),
455 properties: Default::default(),
456 revision: None,
457 created_at: None,
458 updated_at: None,
459 transferred_at: None,
460 created_at_block_height: Some(100),
461 updated_at_block_height: Some(200),
462 transferred_at_block_height: None,
463 created_at_core_block_height: Some(50),
464 updated_at_core_block_height: Some(60),
465 transferred_at_core_block_height: None,
466 creator_id: None,
467 moderated_at: None,
468 moderated_by: None,
469 });
470
471 let s = format!("{}", doc);
472 assert!(s.contains("created_at_block_height:100"), "got: {}", s);
473 assert!(s.contains("updated_at_block_height:200"), "got: {}", s);
474 assert!(s.contains("created_at_core_block_height:50"), "got: {}", s);
475 assert!(s.contains("updated_at_core_block_height:60"), "got: {}", s);
476 }
477
478 #[test]
483 fn increment_revision_works_on_mutable_document() {
484 let mut doc = Document::V0(DocumentV0 {
485 contract_version: None,
486 id: platform_value::Identifier::new([1u8; 32]),
487 owner_id: platform_value::Identifier::new([2u8; 32]),
488 properties: Default::default(),
489 revision: Some(1),
490 created_at: None,
491 updated_at: None,
492 transferred_at: None,
493 created_at_block_height: None,
494 updated_at_block_height: None,
495 transferred_at_block_height: None,
496 created_at_core_block_height: None,
497 updated_at_core_block_height: None,
498 transferred_at_core_block_height: None,
499 creator_id: None,
500 moderated_at: None,
501 moderated_by: None,
502 });
503
504 doc.increment_revision()
505 .expect("increment_revision should succeed");
506 assert_eq!(doc.revision(), Some(2));
507 }
508
509 #[test]
510 fn increment_revision_fails_when_no_revision() {
511 let mut doc = Document::V0(DocumentV0 {
512 contract_version: None,
513 id: platform_value::Identifier::new([1u8; 32]),
514 owner_id: platform_value::Identifier::new([2u8; 32]),
515 properties: Default::default(),
516 revision: None,
517 created_at: None,
518 updated_at: None,
519 transferred_at: None,
520 created_at_block_height: None,
521 updated_at_block_height: None,
522 transferred_at_block_height: None,
523 created_at_core_block_height: None,
524 updated_at_core_block_height: None,
525 transferred_at_core_block_height: None,
526 creator_id: None,
527 moderated_at: None,
528 moderated_by: None,
529 });
530
531 let result = doc.increment_revision();
532 assert!(
533 result.is_err(),
534 "increment_revision should fail when revision is None"
535 );
536 }
537
538 #[test]
543 fn is_equal_ignoring_time_based_fields_dispatches_correctly() {
544 let platform_version = PlatformVersion::latest();
545 let contract = json_document_to_contract(
546 "../rs-drive/tests/supporting_files/contract/dashpay/dashpay-contract.json",
547 false,
548 platform_version,
549 )
550 .expect("expected to get contract");
551
552 let document_type = contract
553 .document_type_for_name("profile")
554 .expect("expected to get profile document type");
555
556 let doc1 = document_type
557 .random_document(Some(42), platform_version)
558 .expect("expected random document");
559
560 let mut doc2 = doc1.clone();
561 doc2.set_created_at(Some(9_999_999));
563 doc2.set_updated_at(Some(8_888_888));
564
565 let result = doc1
566 .is_equal_ignoring_time_based_fields(&doc2, None, platform_version)
567 .expect("should succeed");
568 assert!(
569 result,
570 "same document with different timestamps should be equal ignoring time fields"
571 );
572 }
573
574 #[test]
579 fn get_raw_for_contract_dispatches_to_v0() {
580 let platform_version = PlatformVersion::latest();
581 let contract = json_document_to_contract(
582 "../rs-drive/tests/supporting_files/contract/dashpay/dashpay-contract.json",
583 false,
584 platform_version,
585 )
586 .expect("expected to get contract");
587
588 let document_type = contract
589 .document_type_for_name("profile")
590 .expect("expected to get profile document type");
591
592 let document = document_type
593 .random_document(Some(7), platform_version)
594 .expect("expected random document");
595
596 let raw_id = document
597 .get_raw_for_contract("$id", "profile", &contract, None, platform_version)
598 .expect("should succeed");
599 assert_eq!(raw_id, Some(document.id().to_vec()));
600 }
601
602 #[test]
607 fn document_hash_is_deterministic() {
608 let platform_version = PlatformVersion::latest();
609 let contract = json_document_to_contract(
610 "../rs-drive/tests/supporting_files/contract/dashpay/dashpay-contract.json",
611 false,
612 platform_version,
613 )
614 .expect("expected to get contract");
615
616 let document_type = contract
617 .document_type_for_name("profile")
618 .expect("expected to get profile document type");
619
620 let document = document_type
621 .random_document(Some(42), platform_version)
622 .expect("expected random document");
623
624 let hash1 = document
625 .hash(&contract, document_type, platform_version)
626 .expect("hash should succeed");
627 let hash2 = document
628 .hash(&contract, document_type, platform_version)
629 .expect("hash should succeed");
630 assert_eq!(hash1, hash2, "hash should be deterministic");
631 assert!(!hash1.is_empty(), "hash should not be empty");
632 }
633
634 #[test]
641 fn increment_revision_errors_on_overflow() {
642 let mut doc = Document::V0(DocumentV0 {
643 contract_version: None,
644 id: platform_value::Identifier::new([1u8; 32]),
645 owner_id: platform_value::Identifier::new([2u8; 32]),
646 properties: Default::default(),
647 revision: Some(crate::prelude::Revision::MAX),
648 created_at: None,
649 updated_at: None,
650 transferred_at: None,
651 created_at_block_height: None,
652 updated_at_block_height: None,
653 transferred_at_block_height: None,
654 created_at_core_block_height: None,
655 updated_at_core_block_height: None,
656 transferred_at_core_block_height: None,
657 creator_id: None,
658 moderated_at: None,
659 moderated_by: None,
660 });
661 let err = doc.increment_revision().expect_err("MAX + 1 must overflow");
662 match err {
663 ProtocolError::Overflow(_) => {}
664 other => panic!("expected ProtocolError::Overflow, got {:?}", other),
665 }
666 }
667
668 #[test]
673 fn from_document_v0_produces_v0_variant() {
674 let v0 = DocumentV0 {
675 contract_version: None,
676 id: platform_value::Identifier::new([1u8; 32]),
677 owner_id: platform_value::Identifier::new([2u8; 32]),
678 properties: Default::default(),
679 revision: Some(7),
680 created_at: None,
681 updated_at: None,
682 transferred_at: None,
683 created_at_block_height: None,
684 updated_at_block_height: None,
685 transferred_at_block_height: None,
686 created_at_core_block_height: None,
687 updated_at_core_block_height: None,
688 transferred_at_core_block_height: None,
689 creator_id: None,
690 moderated_at: None,
691 moderated_by: None,
692 };
693 let document: Document = v0.clone().into();
694 match document {
695 Document::V0(inner) => assert_eq!(inner, v0),
696 }
697 }
698
699 #[test]
705 fn document_display_has_version_prefix() {
706 let doc = Document::V0(DocumentV0 {
707 contract_version: None,
708 id: platform_value::Identifier::new([1u8; 32]),
709 owner_id: platform_value::Identifier::new([2u8; 32]),
710 properties: Default::default(),
711 revision: None,
712 created_at: None,
713 updated_at: None,
714 transferred_at: None,
715 created_at_block_height: None,
716 updated_at_block_height: None,
717 transferred_at_block_height: None,
718 created_at_core_block_height: None,
719 updated_at_core_block_height: None,
720 transferred_at_core_block_height: None,
721 creator_id: None,
722 moderated_at: None,
723 moderated_by: None,
724 });
725 let s = format!("{}", doc);
726 assert!(
727 s.starts_with("v0 : "),
728 "Display should prefix with version, got: {s}"
729 );
730 }
731
732 #[test]
738 fn get_raw_for_document_type_dispatch_path_returns_id() {
739 let platform_version = PlatformVersion::latest();
740 let contract = json_document_to_contract(
741 "../rs-drive/tests/supporting_files/contract/dashpay/dashpay-contract.json",
742 false,
743 platform_version,
744 )
745 .expect("expected contract");
746 let document_type = contract
747 .document_type_for_name("profile")
748 .expect("expected document type");
749
750 let document = document_type
751 .random_document(Some(11), platform_version)
752 .expect("expected random document");
753
754 let raw = document
755 .get_raw_for_document_type("$id", document_type, None, platform_version)
756 .expect("should succeed");
757 assert_eq!(raw, Some(document.id().to_vec()));
758 }
759}
760
761#[cfg(all(
762 test,
763 feature = "json-conversion",
764 feature = "value-conversion",
765 feature = "serde-conversion"
766))]
767mod json_convertible_tests {
768 use super::*;
769
770 use platform_value::{platform_value, Identifier};
771 use serde_json::json;
772 use std::collections::BTreeMap;
773
774 fn fixture() -> Document {
775 Document::V0(DocumentV0 {
776 contract_version: None,
777 id: Identifier::new([0xa1; 32]),
778 owner_id: Identifier::new([0xb2; 32]),
779 properties: BTreeMap::new(),
780 revision: Some(2),
781 created_at: Some(1_700_000_000_000),
782 updated_at: Some(1_700_000_001_000),
783 transferred_at: None,
784 created_at_block_height: Some(100),
785 updated_at_block_height: Some(101),
786 transferred_at_block_height: None,
787 created_at_core_block_height: Some(50),
788 updated_at_core_block_height: Some(51),
789 transferred_at_core_block_height: None,
790 creator_id: Some(Identifier::new([0xc3; 32])),
791 moderated_at: None,
792 moderated_by: None,
793 })
794 }
795
796 #[test]
797 fn json_round_trip_with_full_wire_shape() {
798 use crate::serialization::JsonConvertible;
799 let original = fixture();
800 let json = original.to_json().expect("to_json");
801 assert_eq!(
808 json,
809 json!({
810 "$formatVersion": "0",
811 "$id": Identifier::new([0xa1; 32]),
812 "$ownerId": Identifier::new([0xb2; 32]),
813 "$revision": 2,
814 "$createdAt": 1_700_000_000_000u64,
815 "$updatedAt": 1_700_000_001_000u64,
816 "$transferredAt": serde_json::Value::Null,
817 "$createdAtBlockHeight": 100,
818 "$updatedAtBlockHeight": 101,
819 "$transferredAtBlockHeight": serde_json::Value::Null,
820 "$createdAtCoreBlockHeight": 50,
821 "$updatedAtCoreBlockHeight": 51,
822 "$transferredAtCoreBlockHeight": serde_json::Value::Null,
823 "$creatorId": Identifier::new([0xc3; 32]),
824 })
825 );
826 let recovered = Document::from_json(json).expect("from_json");
827 assert_eq!(original, recovered);
828 }
829
830 #[test]
831 fn value_round_trip_with_full_wire_shape() {
832 use crate::serialization::ValueConvertible;
833 let original = fixture();
834 let value = original.to_object().expect("to_object");
835 assert_eq!(
838 value,
839 platform_value!({
840 "$formatVersion": "0",
841 "$id": Identifier::new([0xa1; 32]),
842 "$ownerId": Identifier::new([0xb2; 32]),
843 "$revision": 2u64,
844 "$createdAt": 1_700_000_000_000u64,
845 "$updatedAt": 1_700_000_001_000u64,
846 "$transferredAt": platform_value::Value::Null,
847 "$createdAtBlockHeight": 100u64,
848 "$updatedAtBlockHeight": 101u64,
849 "$transferredAtBlockHeight": platform_value::Value::Null,
850 "$createdAtCoreBlockHeight": 50u32,
851 "$updatedAtCoreBlockHeight": 51u32,
852 "$transferredAtCoreBlockHeight": platform_value::Value::Null,
853 "$creatorId": Identifier::new([0xc3; 32]),
854 })
855 );
856 let recovered = Document::from_object(value).expect("from_object");
857 assert_eq!(original, recovered);
858 }
859
860 #[test]
861 fn should_carry_the_moderation_stamp_through_json_and_value() {
862 let mut original = fixture();
863 original.set_moderated_at(Some(1_700_000_002_000));
864 original.set_moderated_by(Some(Identifier::new([0xd4; 32])));
865
866 let json = original.to_json().expect("to_json");
867 assert_eq!(json["$moderatedAt"], json!(1_700_000_002_000u64));
868 assert_eq!(json["$moderatedBy"], json!(Identifier::new([0xd4; 32])));
869 assert_eq!(Document::from_json(json).expect("from_json"), original);
870
871 let value = original.to_object().expect("to_object");
872 let map = value.to_btree_ref_string_map().expect("a map");
873 assert_eq!(
874 map.get("$moderatedAt"),
875 Some(&&platform_value::Value::U64(1_700_000_002_000))
876 );
877 assert_eq!(
878 map.get("$moderatedBy"),
879 Some(&&platform_value::Value::Identifier([0xd4; 32]))
880 );
881 assert_eq!(Document::from_object(value).expect("from_object"), original);
882 }
883}