git_internal/internal/object/
note.rs1use std::fmt::Display;
14
15use bincode::{Decode, Encode};
16use serde::{Deserialize, Serialize};
17
18use crate::errors::GitError;
19use crate::hash::SHA1;
20use crate::internal::object::ObjectTrait;
21use crate::internal::object::ObjectType;
22
23#[derive(Eq, Debug, Clone, Serialize, Deserialize, Decode, Encode)]
29pub struct Note {
30 pub id: SHA1,
32 pub target_object_id: SHA1,
34 pub content: String,
36}
37
38impl PartialEq for Note {
39 fn eq(&self, other: &Self) -> bool {
41 self.id == other.id
42 }
43}
44
45impl Display for Note {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 writeln!(f, "Note for object: {}", self.target_object_id)?;
48 writeln!(f, "Content: {}", self.content)
49 }
50}
51
52impl Note {
53 pub fn new(target_object_id: SHA1, content: String) -> Self {
62 let id = SHA1::from_type_and_data(ObjectType::Blob, content.as_bytes());
65
66 Self {
67 id,
68 target_object_id,
69 content,
70 }
71 }
72
73 pub fn from_content(content: &str) -> Self {
84 Self::new(SHA1::default(), content.to_string())
85 }
86
87 pub fn content_size(&self) -> usize {
89 self.content.len()
90 }
91
92 pub fn is_empty(&self) -> bool {
94 self.content.is_empty()
95 }
96
97 pub fn set_target(&mut self, new_target: SHA1) {
105 self.target_object_id = new_target;
106 }
107
108 pub fn from_bytes_with_target(
121 data: &[u8],
122 hash: SHA1,
123 target_object_id: SHA1,
124 ) -> Result<Self, GitError> {
125 let content = String::from_utf8(data.to_vec())
126 .map_err(|e| GitError::InvalidNoteObject(format!("Invalid UTF-8 content: {}", e)))?;
127
128 Ok(Note {
129 id: hash,
130 target_object_id,
131 content,
132 })
133 }
134
135 pub fn to_data_with_target(&self) -> Result<(Vec<u8>, SHA1), GitError> {
143 let data = self.to_data()?;
144 Ok((data, self.target_object_id))
145 }
146}
147
148impl ObjectTrait for Note {
149 fn from_bytes(data: &[u8], hash: SHA1) -> Result<Self, GitError>
158 where
159 Self: Sized,
160 {
161 let content = String::from_utf8(data.to_vec())
163 .map_err(|e| GitError::InvalidNoteObject(format!("Invalid UTF-8 content: {}", e)))?;
164
165 Ok(Note {
166 id: hash,
167 target_object_id: SHA1::default(), content,
169 })
170 }
171
172 fn get_type(&self) -> ObjectType {
176 ObjectType::Blob
177 }
178
179 fn get_size(&self) -> usize {
181 self.content.len()
182 }
183
184 fn to_data(&self) -> Result<Vec<u8>, GitError> {
189 Ok(self.content.as_bytes().to_vec())
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196 use std::str::FromStr;
197
198 #[test]
199 fn test_note_creation_and_serialization() {
200 let target_id = SHA1::from_str("1234567890abcdef1234567890abcdef12345678").unwrap();
201 let content = "This commit needs review".to_string();
202 let note = Note::new(target_id, content.clone());
203
204 assert_eq!(note.target_object_id, target_id);
205 assert_eq!(note.content, content);
206 assert_ne!(note.id, SHA1::default());
207 assert_eq!(note.get_type(), ObjectType::Blob);
208
209 let data = note.to_data().unwrap();
211 assert_eq!(data, content.as_bytes());
212 assert_eq!(note.get_size(), content.len());
213 }
214
215 #[test]
216 fn test_note_deserialization() {
217 let content = "Deserialization test content";
218 let hash = SHA1::from_str("fedcba0987654321fedcba0987654321fedcba09").unwrap();
219 let target_id = SHA1::from_str("abcdef1234567890abcdef1234567890abcdef12").unwrap();
220
221 let note = Note::from_bytes(content.as_bytes(), hash).unwrap();
223 assert_eq!(note.content, content);
224 assert_eq!(note.id, hash);
225 assert_eq!(note.target_object_id, SHA1::default());
226
227 let note_with_target =
229 Note::from_bytes_with_target(content.as_bytes(), hash, target_id).unwrap();
230 assert_eq!(note_with_target.content, content);
231 assert_eq!(note_with_target.id, hash);
232 assert_eq!(note_with_target.target_object_id, target_id);
233 }
234
235 #[test]
236 fn test_note_with_target_methods() {
237 let target_id = SHA1::from_str("1234567890abcdef1234567890abcdef12345678").unwrap();
238 let content = "Test note with target methods";
239 let note = Note::new(target_id, content.to_string());
240
241 let (data, returned_target) = note.to_data_with_target().unwrap();
243 assert_eq!(data, content.as_bytes());
244 assert_eq!(returned_target, target_id);
245
246 let restored_note = Note::from_bytes_with_target(&data, note.id, target_id).unwrap();
248 assert_eq!(restored_note, note);
249 assert_eq!(restored_note.target_object_id, target_id);
250 assert_eq!(restored_note.content, content);
251 }
252
253 #[test]
254 fn test_note_error_handling() {
255 let invalid_utf8 = vec![0xFF, 0xFE, 0xFD];
257 let hash = SHA1::from_str("3333333333333333333333333333333333333333").unwrap();
258 let target = SHA1::from_str("4444444444444444444444444444444444444444").unwrap();
259
260 let result = Note::from_bytes(&invalid_utf8, hash);
261 assert!(result.is_err());
262
263 let result_with_target = Note::from_bytes_with_target(&invalid_utf8, hash, target);
264 assert!(result_with_target.is_err());
265 }
266
267 #[test]
268 fn test_note_demo_functionality() {
269 println!("\n🚀 Git Note Object Demo - Best Practices");
272 println!("==========================================");
273
274 let commit_id = SHA1::from_str("a1b2c3d4e5f6789012345678901234567890abcd").unwrap();
275
276 println!("\n1️⃣ Creating a new Note object:");
277 let note = Note::new(
278 commit_id,
279 "Code review: LGTM! Great implementation.".to_string(),
280 );
281 println!(" Target Commit: {}", note.target_object_id);
282 println!(" Note ID: {}", note.id);
283 println!(" Content: {}", note.content);
284 println!(" Size: {} bytes", note.get_size());
285
286 println!("\n2️⃣ Serializing Note with target association:");
287 let (serialized_data, target_id) = note.to_data_with_target().unwrap();
288 println!(" Serialized size: {} bytes", serialized_data.len());
289 println!(" Target object ID: {}", target_id);
290 println!(
291 " Git object format: blob {}\\0<content>",
292 note.content.len()
293 );
294 println!(
295 " Raw data preview: {:?}...",
296 &serialized_data[..std::cmp::min(30, serialized_data.len())]
297 );
298
299 println!("\n3️⃣ Basic deserialization (ObjectTrait):");
300 let basic_note = Note::from_bytes(&serialized_data, note.id).unwrap();
301 println!(" Successfully deserialized!");
302 println!(
303 " Target Commit: {} (default - target managed externally)",
304 basic_note.target_object_id
305 );
306 println!(" Content: {}", basic_note.content);
307 println!(" Content matches: {}", note.content == basic_note.content);
308
309 println!("\n4️⃣ Best practice deserialization (with target):");
310 let complete_note =
311 Note::from_bytes_with_target(&serialized_data, note.id, target_id).unwrap();
312 println!(" Successfully deserialized with target!");
313 println!(" Target Commit: {}", complete_note.target_object_id);
314 println!(" Content: {}", complete_note.content);
315 println!(" Complete objects are equal: {}", note == complete_note);
316
317 assert_eq!(note, complete_note);
319 assert_eq!(target_id, commit_id);
320 }
321}