1mod accessors;
7#[cfg(feature = "document-cbor-conversion")]
8pub(super) mod cbor_conversion;
9#[cfg(feature = "value-conversion")]
10mod platform_value_conversion;
11pub mod serialize;
12
13use chrono::DateTime;
14use std::collections::BTreeMap;
15use std::fmt;
16
17use platform_value::Value;
18
19use crate::document::document_methods::{
20 DocumentGetRawForContractV0, DocumentGetRawForDocumentTypeV0, DocumentHashV0Method,
21 DocumentIsEqualIgnoringTimestampsV0,
22};
23
24use crate::identity::TimestampMillis;
25use crate::prelude::Revision;
26use crate::prelude::{BlockHeight, CoreBlockHeight, Identifier};
27#[cfg(feature = "json-conversion")]
28use crate::serialization::json_safe_fields;
29
30#[cfg_attr(feature = "json-conversion", json_safe_fields)]
32#[derive(Clone, Debug, PartialEq, Default)]
33#[cfg_attr(
34 feature = "serde-conversion",
35 derive(serde::Serialize, serde::Deserialize)
36)]
37pub struct DocumentV0 {
38 #[cfg_attr(feature = "serde-conversion", serde(rename = "$id"))]
40 pub id: Identifier,
41 #[cfg_attr(feature = "serde-conversion", serde(rename = "$ownerId"))]
43 pub owner_id: Identifier,
44 #[cfg_attr(feature = "serde-conversion", serde(flatten))]
46 pub properties: BTreeMap<String, Value>,
47 #[cfg_attr(feature = "serde-conversion", serde(rename = "$revision", default))]
49 pub revision: Option<Revision>,
50 #[cfg_attr(feature = "serde-conversion", serde(rename = "$createdAt", default))]
52 pub created_at: Option<TimestampMillis>,
53 #[cfg_attr(feature = "serde-conversion", serde(rename = "$updatedAt", default))]
55 pub updated_at: Option<TimestampMillis>,
56 #[cfg_attr(
58 feature = "serde-conversion",
59 serde(rename = "$transferredAt", default)
60 )]
61 pub transferred_at: Option<TimestampMillis>,
62 #[cfg_attr(
64 feature = "serde-conversion",
65 serde(rename = "$createdAtBlockHeight", default)
66 )]
67 pub created_at_block_height: Option<BlockHeight>,
68 #[cfg_attr(
70 feature = "serde-conversion",
71 serde(rename = "$updatedAtBlockHeight", default)
72 )]
73 pub updated_at_block_height: Option<BlockHeight>,
74 #[cfg_attr(
76 feature = "serde-conversion",
77 serde(rename = "$transferredAtBlockHeight", default)
78 )]
79 pub transferred_at_block_height: Option<BlockHeight>,
80 #[cfg_attr(
82 feature = "serde-conversion",
83 serde(rename = "$createdAtCoreBlockHeight", default)
84 )]
85 pub created_at_core_block_height: Option<CoreBlockHeight>,
86 #[cfg_attr(
88 feature = "serde-conversion",
89 serde(rename = "$updatedAtCoreBlockHeight", default)
90 )]
91 pub updated_at_core_block_height: Option<CoreBlockHeight>,
92 #[cfg_attr(
94 feature = "serde-conversion",
95 serde(rename = "$transferredAtCoreBlockHeight", default)
96 )]
97 pub transferred_at_core_block_height: Option<CoreBlockHeight>,
98 #[cfg_attr(feature = "serde-conversion", serde(rename = "$creatorId", default))]
100 pub creator_id: Option<Identifier>,
101}
102
103impl DocumentGetRawForContractV0 for DocumentV0 {
104 }
106
107impl DocumentIsEqualIgnoringTimestampsV0 for DocumentV0 {
108 }
110
111impl DocumentGetRawForDocumentTypeV0 for DocumentV0 {
112 }
114
115impl DocumentHashV0Method for DocumentV0 {
116 }
118
119impl fmt::Display for DocumentV0 {
120 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
121 write!(f, "id:{} ", self.id)?;
122 write!(f, "owner_id:{} ", self.owner_id)?;
123 if let Some(created_at) = self.created_at {
124 let datetime = DateTime::from_timestamp_millis(created_at as i64).unwrap_or_default();
125 write!(f, "created_at:{} ", datetime.format("%Y-%m-%d %H:%M:%S"))?;
126 }
127 if let Some(updated_at) = self.updated_at {
128 let datetime = DateTime::from_timestamp_millis(updated_at as i64).unwrap_or_default();
129 write!(f, "updated_at:{} ", datetime.format("%Y-%m-%d %H:%M:%S"))?;
130 }
131 if let Some(transferred_at) = self.transferred_at {
132 let datetime =
133 DateTime::from_timestamp_millis(transferred_at as i64).unwrap_or_default();
134 write!(
135 f,
136 "transferred_at:{} ",
137 datetime.format("%Y-%m-%d %H:%M:%S")
138 )?;
139 }
140
141 if let Some(created_at_block_height) = self.created_at_block_height {
142 write!(f, "created_at_block_height:{} ", created_at_block_height)?;
143 }
144 if let Some(updated_at_block_height) = self.updated_at_block_height {
145 write!(f, "updated_at_block_height:{} ", updated_at_block_height)?;
146 }
147 if let Some(transferred_at_block_height) = self.transferred_at_block_height {
148 write!(
149 f,
150 "transferred_at_block_height:{} ",
151 transferred_at_block_height
152 )?;
153 }
154 if let Some(created_at_core_block_height) = self.created_at_core_block_height {
155 write!(
156 f,
157 "created_at_core_block_height:{} ",
158 created_at_core_block_height
159 )?;
160 }
161 if let Some(updated_at_core_block_height) = self.updated_at_core_block_height {
162 write!(
163 f,
164 "updated_at_core_block_height:{} ",
165 updated_at_core_block_height
166 )?;
167 }
168 if let Some(transferred_at_core_block_height) = self.transferred_at_core_block_height {
169 write!(
170 f,
171 "transferred_at_core_block_height:{} ",
172 transferred_at_core_block_height
173 )?;
174 }
175
176 if let Some(creator_id) = self.creator_id {
177 write!(f, "creator_id:{} ", creator_id)?;
178 }
179
180 if self.properties.is_empty() {
181 write!(f, "no properties")?;
182 } else {
183 for (key, value) in self.properties.iter() {
184 write!(f, "{}:{} ", key, value)?
185 }
186 }
187 Ok(())
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194 use crate::data_contract::accessors::v0::DataContractV0Getters;
195 use crate::document::{DocumentV0Getters, DocumentV0Setters};
196 use platform_value::Identifier;
197
198 fn minimal_doc() -> DocumentV0 {
199 DocumentV0 {
200 id: Identifier::new([1u8; 32]),
201 owner_id: Identifier::new([2u8; 32]),
202 properties: BTreeMap::new(),
203 revision: None,
204 created_at: None,
205 updated_at: None,
206 transferred_at: None,
207 created_at_block_height: None,
208 updated_at_block_height: None,
209 transferred_at_block_height: None,
210 created_at_core_block_height: None,
211 updated_at_core_block_height: None,
212 transferred_at_core_block_height: None,
213 creator_id: None,
214 }
215 }
216
217 #[test]
222 fn display_minimal_document_has_no_properties_marker() {
223 let doc = minimal_doc();
224 let s = format!("{}", doc);
225 assert!(s.contains("id:"), "should contain id");
226 assert!(s.contains("owner_id:"), "should contain owner_id");
227 assert!(
228 s.contains("no properties"),
229 "empty properties should render as 'no properties', got: {s}"
230 );
231 }
232
233 #[test]
234 fn display_with_properties_formats_key_value_pairs() {
235 let mut doc = minimal_doc();
236 doc.properties
237 .insert("name".to_string(), Value::Text("Bob".to_string()));
238 let s = format!("{}", doc);
239 assert!(!s.contains("no properties"));
240 assert!(s.contains("name:"), "should contain property key");
241 }
242
243 #[test]
244 fn display_formats_all_optional_timestamp_fields() {
245 let mut doc = minimal_doc();
246 doc.created_at = Some(1_700_000_000_000);
248 doc.updated_at = Some(1_700_000_100_000);
249 doc.transferred_at = Some(1_700_000_200_000);
250 doc.created_at_block_height = Some(10);
251 doc.updated_at_block_height = Some(20);
252 doc.transferred_at_block_height = Some(30);
253 doc.created_at_core_block_height = Some(1);
254 doc.updated_at_core_block_height = Some(2);
255 doc.transferred_at_core_block_height = Some(3);
256 doc.creator_id = Some(Identifier::new([9u8; 32]));
257
258 let s = format!("{}", doc);
259 assert!(s.contains("created_at:"), "missing created_at: {s}");
261 assert!(s.contains("updated_at:"), "missing updated_at: {s}");
262 assert!(s.contains("transferred_at:"), "missing transferred_at: {s}");
263 assert!(
264 s.contains("created_at_block_height:10"),
265 "missing created_at_block_height: {s}"
266 );
267 assert!(
268 s.contains("updated_at_block_height:20"),
269 "missing updated_at_block_height: {s}"
270 );
271 assert!(
272 s.contains("transferred_at_block_height:30"),
273 "missing transferred_at_block_height: {s}"
274 );
275 assert!(
276 s.contains("created_at_core_block_height:1"),
277 "missing created_at_core_block_height: {s}"
278 );
279 assert!(
280 s.contains("updated_at_core_block_height:2"),
281 "missing updated_at_core_block_height: {s}"
282 );
283 assert!(
284 s.contains("transferred_at_core_block_height:3"),
285 "missing transferred_at_core_block_height: {s}"
286 );
287 assert!(s.contains("creator_id:"), "missing creator_id: {s}");
288 }
289
290 #[test]
291 fn display_invalid_timestamp_uses_default_formatter() {
292 let mut doc = minimal_doc();
295 doc.created_at = Some(i64::MAX as u64);
300 let s = format!("{}", doc);
301 assert!(s.contains("created_at:"));
303 }
304
305 #[test]
310 fn bump_revision_increments_when_some() {
311 let mut doc = minimal_doc();
312 doc.set_revision(Some(5));
313 doc.bump_revision();
314 assert_eq!(doc.revision(), Some(6));
315 }
316
317 #[test]
318 fn bump_revision_is_noop_when_none() {
319 let mut doc = minimal_doc();
320 assert_eq!(doc.revision(), None);
321 doc.bump_revision();
322 assert_eq!(doc.revision(), None);
324 }
325
326 #[test]
327 fn bump_revision_saturates_at_max() {
328 let mut doc = minimal_doc();
329 doc.set_revision(Some(Revision::MAX));
330 doc.bump_revision();
331 assert_eq!(doc.revision(), Some(Revision::MAX));
333 }
334
335 #[test]
340 fn default_document_has_zero_identifiers_and_none_fields() {
341 let doc = DocumentV0::default();
342 assert_eq!(doc.id, Identifier::new([0u8; 32]));
343 assert_eq!(doc.owner_id, Identifier::new([0u8; 32]));
344 assert!(doc.properties.is_empty());
345 assert_eq!(doc.revision, None);
346 assert_eq!(doc.created_at, None);
347 assert_eq!(doc.updated_at, None);
348 assert_eq!(doc.transferred_at, None);
349 assert_eq!(doc.creator_id, None);
350 }
351
352 #[test]
357 fn documents_with_different_creator_id_are_not_equal() {
358 let a = minimal_doc();
359 let mut b = minimal_doc();
360 b.creator_id = Some(Identifier::new([7u8; 32]));
361 assert_ne!(a, b);
362 }
363
364 #[test]
365 fn documents_with_equal_fields_are_equal() {
366 let a = minimal_doc();
367 let b = minimal_doc();
368 assert_eq!(a, b);
369 }
370
371 #[test]
372 fn clone_produces_equal_document() {
373 let mut doc = minimal_doc();
374 doc.properties.insert("k".to_string(), Value::U64(42));
375 doc.revision = Some(3);
376 let cloned = doc.clone();
377 assert_eq!(doc, cloned);
378 }
379
380 #[test]
385 fn display_writes_properties_in_btreemap_sorted_order() {
386 let mut doc = minimal_doc();
391 doc.properties
392 .insert("zebra".to_string(), Value::Text("z".into()));
393 doc.properties
394 .insert("apple".to_string(), Value::Text("a".into()));
395 doc.properties
396 .insert("mango".to_string(), Value::Text("m".into()));
397
398 let s = format!("{}", doc);
399 let apple_idx = s.find("apple:").expect("apple missing");
400 let mango_idx = s.find("mango:").expect("mango missing");
401 let zebra_idx = s.find("zebra:").expect("zebra missing");
402 assert!(
403 apple_idx < mango_idx && mango_idx < zebra_idx,
404 "properties should appear in sorted (BTreeMap) order: {s}"
405 );
406 }
407
408 #[test]
409 fn display_mixes_system_fields_and_user_properties() {
410 let mut doc = minimal_doc();
415 doc.revision = Some(42);
416 doc.created_at_block_height = Some(7);
417 doc.properties
418 .insert("greeting".to_string(), Value::Text("hi".into()));
419
420 let s = format!("{}", doc);
421 assert!(s.contains("created_at_block_height:7"));
422 assert!(s.contains("greeting:"));
423 assert!(!s.contains("revision"));
426 }
427
428 #[test]
435 fn hash_v0_produces_deterministic_output_for_identical_documents() {
436 use crate::document::document_methods::DocumentHashV0Method;
437 use crate::document::serialization_traits::DocumentPlatformConversionMethodsV0;
438 use crate::tests::json_document::json_document_to_contract;
439 use platform_version::version::PlatformVersion;
440
441 let platform_version = PlatformVersion::first();
444 let contract = json_document_to_contract(
445 "../rs-drive/tests/supporting_files/contract/family/family-contract.json",
446 false,
447 platform_version,
448 )
449 .expect("expected to load family contract");
450 let doc_type = contract
451 .document_type_for_name("person")
452 .expect("expected person type");
453
454 use crate::data_contract::document_type::random_document::CreateRandomDocument;
456 let document = doc_type
457 .random_document(Some(7), platform_version)
458 .expect("random document");
459 let doc_v0 = match &document {
460 crate::document::Document::V0(d) => d.clone(),
461 };
462
463 let h1 = doc_v0
465 .hash_v0(&contract, doc_type, platform_version)
466 .expect("hash succeeds");
467 let h2 = doc_v0
468 .hash_v0(&contract, doc_type, platform_version)
469 .expect("hash succeeds");
470 assert_eq!(h1, h2);
471 assert_eq!(h1.len(), 32);
473
474 let serialized = doc_v0
477 .serialize(doc_type, &contract, platform_version)
478 .expect("serialize");
479 assert_ne!(h1, serialized);
480 }
481
482 #[test]
483 fn hash_v0_differs_between_different_documents() {
484 use crate::document::document_methods::DocumentHashV0Method;
485 use crate::tests::json_document::json_document_to_contract;
486 use platform_version::version::PlatformVersion;
487
488 let platform_version = PlatformVersion::first();
489 let contract = json_document_to_contract(
490 "../rs-drive/tests/supporting_files/contract/family/family-contract.json",
491 false,
492 platform_version,
493 )
494 .expect("family contract");
495 let doc_type = contract
496 .document_type_for_name("person")
497 .expect("person type");
498
499 use crate::data_contract::document_type::random_document::CreateRandomDocument;
500 let crate::document::Document::V0(doc_a) = doc_type
501 .random_document(Some(1), platform_version)
502 .expect("random a");
503 let crate::document::Document::V0(doc_b) = doc_type
504 .random_document(Some(2), platform_version)
505 .expect("random b");
506
507 let h_a = doc_a
508 .hash_v0(&contract, doc_type, platform_version)
509 .expect("hash a");
510 let h_b = doc_b
511 .hash_v0(&contract, doc_type, platform_version)
512 .expect("hash b");
513 assert_ne!(h_a, h_b);
514 }
515
516 #[test]
522 fn not_equal_when_revision_differs() {
523 let a = minimal_doc();
524 let mut b = minimal_doc();
525 b.revision = Some(1);
526 assert_ne!(a, b);
527 }
528
529 #[test]
530 fn not_equal_when_each_timestamp_differs() {
531 let a = minimal_doc();
532
533 let mut b = minimal_doc();
534 b.created_at = Some(1);
535 assert_ne!(a, b);
536
537 let mut b = minimal_doc();
538 b.updated_at = Some(2);
539 assert_ne!(a, b);
540
541 let mut b = minimal_doc();
542 b.transferred_at = Some(3);
543 assert_ne!(a, b);
544
545 let mut b = minimal_doc();
546 b.created_at_block_height = Some(4);
547 assert_ne!(a, b);
548
549 let mut b = minimal_doc();
550 b.updated_at_block_height = Some(5);
551 assert_ne!(a, b);
552
553 let mut b = minimal_doc();
554 b.transferred_at_block_height = Some(6);
555 assert_ne!(a, b);
556
557 let mut b = minimal_doc();
558 b.created_at_core_block_height = Some(7);
559 assert_ne!(a, b);
560
561 let mut b = minimal_doc();
562 b.updated_at_core_block_height = Some(8);
563 assert_ne!(a, b);
564
565 let mut b = minimal_doc();
566 b.transferred_at_core_block_height = Some(9);
567 assert_ne!(a, b);
568 }
569
570 #[test]
571 fn not_equal_when_properties_differ() {
572 let a = minimal_doc();
573 let mut b = minimal_doc();
574 b.properties.insert("foo".to_string(), Value::U64(1));
575 assert_ne!(a, b);
576 }
577
578 #[test]
579 fn not_equal_when_id_differs() {
580 let a = minimal_doc();
581 let mut b = minimal_doc();
582 b.id = Identifier::new([99u8; 32]);
583 assert_ne!(a, b);
584 }
585
586 #[test]
587 fn not_equal_when_owner_id_differs() {
588 let a = minimal_doc();
589 let mut b = minimal_doc();
590 b.owner_id = Identifier::new([98u8; 32]);
591 assert_ne!(a, b);
592 }
593
594 #[test]
600 fn bump_revision_from_zero_increments_to_one() {
601 let mut doc = minimal_doc();
602 doc.set_revision(Some(0));
603 doc.bump_revision();
604 assert_eq!(doc.revision(), Some(1));
605 }
606
607 #[test]
608 fn bump_revision_from_max_minus_one_reaches_max_then_saturates() {
609 let mut doc = minimal_doc();
610 doc.set_revision(Some(Revision::MAX - 1));
611 doc.bump_revision();
612 assert_eq!(doc.revision(), Some(Revision::MAX));
613 doc.bump_revision();
614 assert_eq!(doc.revision(), Some(Revision::MAX));
615 doc.bump_revision();
617 assert_eq!(doc.revision(), Some(Revision::MAX));
618 }
619
620 #[test]
626 fn setters_round_trip_every_field() {
627 use crate::document::{DocumentV0Getters, DocumentV0Setters};
628 let mut doc = DocumentV0::default();
629 doc.set_id(Identifier::new([1u8; 32]));
630 doc.set_owner_id(Identifier::new([2u8; 32]));
631 let mut props = BTreeMap::new();
632 props.insert("a".to_string(), Value::U64(99));
633 doc.set_properties(props.clone());
634 doc.set_revision(Some(4));
635 doc.set_created_at(Some(10));
636 doc.set_updated_at(Some(20));
637 doc.set_transferred_at(Some(30));
638 doc.set_created_at_block_height(Some(100));
639 doc.set_updated_at_block_height(Some(200));
640 doc.set_transferred_at_block_height(Some(300));
641 doc.set_created_at_core_block_height(Some(1));
642 doc.set_updated_at_core_block_height(Some(2));
643 doc.set_transferred_at_core_block_height(Some(3));
644 doc.set_creator_id(Some(Identifier::new([9u8; 32])));
645
646 assert_eq!(doc.id(), Identifier::new([1u8; 32]));
647 assert_eq!(doc.owner_id(), Identifier::new([2u8; 32]));
648 assert_eq!(doc.properties(), &props);
649 assert_eq!(doc.revision(), Some(4));
650 assert_eq!(doc.created_at(), Some(10));
651 assert_eq!(doc.updated_at(), Some(20));
652 assert_eq!(doc.transferred_at(), Some(30));
653 assert_eq!(doc.created_at_block_height(), Some(100));
654 assert_eq!(doc.updated_at_block_height(), Some(200));
655 assert_eq!(doc.transferred_at_block_height(), Some(300));
656 assert_eq!(doc.created_at_core_block_height(), Some(1));
657 assert_eq!(doc.updated_at_core_block_height(), Some(2));
658 assert_eq!(doc.transferred_at_core_block_height(), Some(3));
659 assert_eq!(doc.creator_id(), Some(Identifier::new([9u8; 32])));
660
661 assert_eq!(doc.id_ref(), &Identifier::new([1u8; 32]));
664 assert_eq!(doc.owner_id_ref(), &Identifier::new([2u8; 32]));
665 assert_eq!(doc.clone().properties_consumed(), props);
666 }
667
668 #[test]
674 fn properties_mut_allows_inserting_new_key() {
675 use crate::document::DocumentV0Getters;
676 let mut doc = minimal_doc();
677 doc.properties_mut().insert("k".into(), Value::U64(7));
678 assert_eq!(doc.properties().get("k"), Some(&Value::U64(7)));
679 }
680
681 #[test]
688 fn debug_format_contains_field_names() {
689 let doc = minimal_doc();
690 let dbg = format!("{:?}", doc);
691 assert!(dbg.contains("DocumentV0"), "expected struct name in Debug");
692 assert!(dbg.contains("id"));
693 assert!(dbg.contains("owner_id"));
694 }
695
696 #[test]
703 fn display_with_only_creator_id_and_no_timestamps() {
704 let mut doc = minimal_doc();
705 doc.creator_id = Some(Identifier::new([7u8; 32]));
706 let s = format!("{}", doc);
707 assert!(s.contains("creator_id:"));
708 assert!(!s.contains("created_at:"));
710 assert!(!s.contains("updated_at:"));
711 assert!(!s.contains("transferred_at:"));
712 assert!(s.contains("no properties"));
714 }
715}