bonsaidb_core/document/
revision.rs1use std::fmt::{Debug, Display, Write};
2
3use serde::{Deserialize, Serialize};
4use sha2::{Digest, Sha256};
5
6#[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Hash)]
8pub struct Revision {
9 pub id: u32,
11
12 pub sha256: [u8; 32],
14}
15
16impl Revision {
17 #[must_use]
19 pub fn new(contents: &[u8]) -> Self {
20 Self::with_id(0, contents)
21 }
22
23 #[must_use]
25 pub fn with_id(id: u32, contents: &[u8]) -> Self {
26 Self {
27 id,
28 sha256: digest(contents),
29 }
30 }
31
32 #[must_use]
38 pub fn next_revision(&self, new_contents: &[u8]) -> Option<Self> {
39 let sha256 = digest(new_contents);
40 if sha256 == self.sha256 {
41 None
42 } else {
43 Some(Self {
44 id: self
45 .id
46 .checked_add(1)
47 .expect("need to implement revision id wrapping or increase revision id size"),
48 sha256,
49 })
50 }
51 }
52}
53
54impl Debug for Revision {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 write!(f, "Revision({self})")
57 }
58}
59
60impl Display for Revision {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 <u32 as Display>::fmt(&self.id, f)?;
63 f.write_char('-')?;
64 for byte in self.sha256 {
65 f.write_fmt(format_args!("{byte:02x}"))?;
66 }
67 Ok(())
68 }
69}
70
71fn digest(payload: &[u8]) -> [u8; 32] {
72 let mut hasher = Sha256::default();
73 hasher.update(payload);
74 hasher.finalize().into()
75}
76
77#[test]
78fn revision_tests() {
79 let original_contents = b"one";
80 let first_revision = Revision::new(original_contents);
81 let original_digest =
82 hex_literal::hex!("7692c3ad3540bb803c020b3aee66cd8887123234ea0c6e7143c0add73ff431ed");
83 assert_eq!(
84 first_revision,
85 Revision {
86 id: 0,
87 sha256: original_digest
88 }
89 );
90 assert!(first_revision.next_revision(original_contents).is_none());
91
92 let updated_contents = b"two";
93 let next_revision = first_revision
94 .next_revision(updated_contents)
95 .expect("new contents should create a new revision");
96 assert_eq!(
97 next_revision,
98 Revision {
99 id: 1,
100 sha256: hex_literal::hex!(
101 "3fc4ccfe745870e2c0d99f71f30ff0656c8dedd41cc1d7d3d376b0dbe685e2f3"
102 )
103 }
104 );
105 assert!(next_revision.next_revision(updated_contents).is_none());
106
107 assert_eq!(
108 next_revision.next_revision(original_contents),
109 Some(Revision {
110 id: 2,
111 sha256: original_digest
112 })
113 );
114}
115
116#[test]
117fn revision_display_test() {
118 let original_contents = b"one";
119 let first_revision = Revision::new(original_contents);
120 assert_eq!(
121 first_revision.to_string(),
122 "0-7692c3ad3540bb803c020b3aee66cd8887123234ea0c6e7143c0add73ff431ed"
123 );
124 assert_eq!(
125 format!("{first_revision:?}"),
126 "Revision(0-7692c3ad3540bb803c020b3aee66cd8887123234ea0c6e7143c0add73ff431ed)"
127 );
128}