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