1use serde::{Deserialize, Serialize};
11
12use crate::error::{Error, Result};
13use crate::hash::Hash;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum ObjectType {
18 Blob,
19 Tree,
20 Changeset,
22 Intent,
24}
25
26impl ObjectType {
27 pub fn code(self) -> u8 {
29 match self {
30 ObjectType::Blob => 1,
31 ObjectType::Tree => 2,
32 ObjectType::Changeset => 3,
33 ObjectType::Intent => 4,
34 }
35 }
36
37 pub fn from_code(code: u8) -> Result<Self> {
39 match code {
40 1 => Ok(ObjectType::Blob),
41 2 => Ok(ObjectType::Tree),
42 3 => Ok(ObjectType::Changeset),
43 4 => Ok(ObjectType::Intent),
44 other => Err(Error::UnknownObjectType(other)),
45 }
46 }
47}
48
49pub trait Object: Sized {
51 const TYPE: ObjectType;
53
54 fn encode_body(&self) -> Result<Vec<u8>>;
56
57 fn decode_body(bytes: &[u8]) -> Result<Self>;
59
60 fn id(&self) -> Result<Hash> {
62 Ok(Hash::of(&self.encode_body()?))
63 }
64}
65
66#[derive(Debug, Clone, PartialEq, Eq)]
68pub struct Blob {
69 pub content: Vec<u8>,
70}
71
72impl Blob {
73 pub fn new(content: impl Into<Vec<u8>>) -> Self {
74 Self {
75 content: content.into(),
76 }
77 }
78}
79
80impl Object for Blob {
81 const TYPE: ObjectType = ObjectType::Blob;
82
83 fn encode_body(&self) -> Result<Vec<u8>> {
84 Ok(self.content.clone())
85 }
86
87 fn decode_body(bytes: &[u8]) -> Result<Self> {
88 Ok(Blob {
89 content: bytes.to_vec(),
90 })
91 }
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
96pub enum EntryKind {
97 File,
98 Directory,
99 Symlink,
100}
101
102#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
104pub struct TreeEntry {
105 pub name: String,
106 pub kind: EntryKind,
107 pub hash: Hash,
108 pub mode: u32,
109 pub size: u64,
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, Default)]
114pub struct Tree {
115 pub entries: Vec<TreeEntry>,
116}
117
118impl Tree {
119 pub fn new(entries: Vec<TreeEntry>) -> Self {
120 Self { entries }
121 }
122
123 fn canonical_entries(&self) -> Vec<TreeEntry> {
125 let mut e = self.entries.clone();
126 e.sort_by(|a, b| a.name.cmp(&b.name));
127 e
128 }
129}
130
131impl Object for Tree {
132 const TYPE: ObjectType = ObjectType::Tree;
133
134 fn encode_body(&self) -> Result<Vec<u8>> {
135 rmp_serde::to_vec(&self.canonical_entries()).map_err(|e| Error::Serialize(e.to_string()))
136 }
137
138 fn decode_body(bytes: &[u8]) -> Result<Self> {
139 let entries: Vec<TreeEntry> =
140 rmp_serde::from_slice(bytes).map_err(|e| Error::Deserialize(e.to_string()))?;
141 Ok(Tree { entries })
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn blob_id_is_blake3_of_content() {
151 let b = Blob::new(b"hello".to_vec());
152 assert_eq!(b.id().unwrap(), Hash::of(b"hello"));
153 }
154
155 #[test]
156 fn blob_body_roundtrips() {
157 let b = Blob::new(b"some bytes".to_vec());
158 let body = b.encode_body().unwrap();
159 assert_eq!(Blob::decode_body(&body).unwrap(), b);
160 }
161
162 #[test]
163 fn tree_id_is_order_independent() {
164 let e1 = TreeEntry {
165 name: "a.txt".into(),
166 kind: EntryKind::File,
167 hash: Hash::of(b"a"),
168 mode: 0o644,
169 size: 1,
170 };
171 let e2 = TreeEntry {
172 name: "b.txt".into(),
173 kind: EntryKind::File,
174 hash: Hash::of(b"b"),
175 mode: 0o644,
176 size: 1,
177 };
178 let t1 = Tree::new(vec![e1.clone(), e2.clone()]);
179 let t2 = Tree::new(vec![e2, e1]);
180 assert_eq!(t1.id().unwrap(), t2.id().unwrap());
181 }
182
183 #[test]
184 fn tree_body_roundtrips() {
185 let t = Tree::new(vec![TreeEntry {
186 name: "src".into(),
187 kind: EntryKind::Directory,
188 hash: Hash::of(b"tree"),
189 mode: 0o755,
190 size: 0,
191 }]);
192 let body = t.encode_body().unwrap();
193 assert_eq!(Tree::decode_body(&body).unwrap().entries.len(), 1);
194 }
195
196 #[test]
197 fn object_type_codes_roundtrip() {
198 for ty in [ObjectType::Blob, ObjectType::Tree] {
199 assert_eq!(ObjectType::from_code(ty.code()).unwrap(), ty);
200 }
201 assert!(ObjectType::from_code(99).is_err());
202 }
203}