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