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::ObjectHash;
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: ObjectHash,
32 pub target_object_id: ObjectHash,
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: ObjectHash, content: String) -> Self {
62 let id = ObjectHash::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(ObjectHash::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: ObjectHash) {
105 self.target_object_id = new_target;
106 }
107
108 pub fn from_bytes_with_target(
121 data: &[u8],
122 hash: ObjectHash,
123 target_object_id: ObjectHash,
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>, ObjectHash), 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: ObjectHash) -> 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: ObjectHash::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 crate::hash::{HashKind, ObjectHash, set_hash_kind_for_test};
197 use std::str::FromStr;
198
199 #[test]
200 fn test_note_creation_and_serialization() {
201 let _guard = set_hash_kind_for_test(HashKind::Sha1);
202 let target_id = ObjectHash::from_str("1234567890abcdef1234567890abcdef12345678").unwrap();
203 let content = "This commit needs review".to_string();
204 let note = Note::new(target_id, content.clone());
205
206 assert_eq!(note.target_object_id, target_id);
207 assert_eq!(note.content, content);
208 assert_ne!(note.id, ObjectHash::default());
209 assert_eq!(note.get_type(), ObjectType::Blob);
210
211 let data = note.to_data().unwrap();
213 assert_eq!(data, content.as_bytes());
214 assert_eq!(note.get_size(), content.len());
215 }
216 #[test]
217 fn test_note_creation_and_serialization_sha256() {
218 let _guard = set_hash_kind_for_test(HashKind::Sha256);
219 let target_id = ObjectHash::from_str(
220 "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
221 )
222 .unwrap();
223 let content = "This commit needs review".to_string();
224 let note = Note::new(target_id, content.clone());
225
226 assert_eq!(note.target_object_id, target_id);
227 assert_eq!(note.content, content);
228 assert_ne!(note.id, ObjectHash::default());
229 assert_eq!(note.get_type(), ObjectType::Blob);
230
231 let data = note.to_data().unwrap();
233 assert_eq!(data, content.as_bytes());
234 assert_eq!(note.get_size(), content.len());
235 }
236
237 #[test]
238 fn test_note_deserialization() {
239 let _guard = set_hash_kind_for_test(HashKind::Sha1);
240 let content = "Deserialization test content";
241 let hash = ObjectHash::from_str("fedcba0987654321fedcba0987654321fedcba09").unwrap();
242 let target_id = ObjectHash::from_str("abcdef1234567890abcdef1234567890abcdef12").unwrap();
243
244 let note = Note::from_bytes(content.as_bytes(), hash).unwrap();
246 assert_eq!(note.content, content);
247 assert_eq!(note.id, hash);
248 assert_eq!(note.target_object_id, ObjectHash::default());
249
250 let note_with_target =
252 Note::from_bytes_with_target(content.as_bytes(), hash, target_id).unwrap();
253 assert_eq!(note_with_target.content, content);
254 assert_eq!(note_with_target.id, hash);
255 assert_eq!(note_with_target.target_object_id, target_id);
256 }
257 #[test]
258 fn test_note_deserialization_sha256() {
259 let _guard = set_hash_kind_for_test(HashKind::Sha256);
260 let content = "Deserialization test content";
261 let hash = ObjectHash::from_str(
262 "fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321",
263 )
264 .unwrap();
265 let target_id = ObjectHash::from_str(
266 "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
267 )
268 .unwrap();
269
270 let note = Note::from_bytes(content.as_bytes(), hash).unwrap();
272 assert_eq!(note.content, content);
273 assert_eq!(note.id, hash);
274 assert_eq!(note.target_object_id, ObjectHash::default());
275
276 let note_with_target =
278 Note::from_bytes_with_target(content.as_bytes(), hash, target_id).unwrap();
279 assert_eq!(note_with_target.content, content);
280 assert_eq!(note_with_target.id, hash);
281 assert_eq!(note_with_target.target_object_id, target_id);
282 }
283
284 #[test]
285 fn test_note_with_target_methods() {
286 let _guard = set_hash_kind_for_test(HashKind::Sha1);
287 let target_id = ObjectHash::from_str("1234567890abcdef1234567890abcdef12345678").unwrap();
288 let content = "Test note with target methods";
289 let note = Note::new(target_id, content.to_string());
290
291 let (data, returned_target) = note.to_data_with_target().unwrap();
293 assert_eq!(data, content.as_bytes());
294 assert_eq!(returned_target, target_id);
295
296 let restored_note = Note::from_bytes_with_target(&data, note.id, target_id).unwrap();
298 assert_eq!(restored_note, note);
299 assert_eq!(restored_note.target_object_id, target_id);
300 assert_eq!(restored_note.content, content);
301 }
302 #[test]
303 fn test_note_with_target_methods_sha256() {
304 let _guard = set_hash_kind_for_test(HashKind::Sha256);
305 let target_id = ObjectHash::from_str(
306 "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
307 )
308 .unwrap();
309 let content = "Test note with target methods";
310 let note = Note::new(target_id, content.to_string());
311
312 let (data, returned_target) = note.to_data_with_target().unwrap();
314 assert_eq!(data, content.as_bytes());
315 assert_eq!(returned_target, target_id);
316
317 let restored_note = Note::from_bytes_with_target(&data, note.id, target_id).unwrap();
319 assert_eq!(restored_note, note);
320 assert_eq!(restored_note.target_object_id, target_id);
321 assert_eq!(restored_note.content, content);
322 }
323
324 #[test]
325 fn test_note_error_handling() {
326 let _guard = set_hash_kind_for_test(HashKind::Sha1);
327 let invalid_utf8 = vec![0xFF, 0xFE, 0xFD];
329 let hash = ObjectHash::from_str("3333333333333333333333333333333333333333").unwrap();
330 let target = ObjectHash::from_str("4444444444444444444444444444444444444444").unwrap();
331 let result = Note::from_bytes(&invalid_utf8, hash);
332 assert!(result.is_err());
333
334 let result_with_target = Note::from_bytes_with_target(&invalid_utf8, hash, target);
335 assert!(result_with_target.is_err());
336 }
337 #[test]
338 fn test_note_error_handling_sha256() {
339 let _guard = set_hash_kind_for_test(HashKind::Sha256);
340 let invalid_utf8 = vec![0xFF, 0xFE, 0xFD];
342 let hash = ObjectHash::from_str(
343 "3333333333333333333333333333333333333333333333333333333333333333",
344 )
345 .unwrap();
346 let target = ObjectHash::from_str(
347 "4444444444444444444444444444444444444444444444444444444444444444",
348 )
349 .unwrap();
350 let result = Note::from_bytes(&invalid_utf8, hash);
351 assert!(result.is_err());
352
353 let result_with_target = Note::from_bytes_with_target(&invalid_utf8, hash, target);
354 assert!(result_with_target.is_err());
355 }
356 #[test]
357 fn test_note_demo_functionality() {
358 let _guard = set_hash_kind_for_test(HashKind::Sha1);
359 println!("\n🚀 Git Note Object Demo - Best Practices");
362 println!("==========================================");
363
364 let commit_id = ObjectHash::from_str("a1b2c3d4e5f6789012345678901234567890abcd").unwrap();
365
366 println!("\n1️⃣ Creating a new Note object:");
367 let note = Note::new(
368 commit_id,
369 "Code review: LGTM! Great implementation.".to_string(),
370 );
371 println!(" Target Commit: {}", note.target_object_id);
372 println!(" Note ID: {}", note.id);
373 println!(" Content: {}", note.content);
374 println!(" Size: {} bytes", note.get_size());
375
376 println!("\n2️⃣ Serializing Note with target association:");
377 let (serialized_data, target_id) = note.to_data_with_target().unwrap();
378 println!(" Serialized size: {} bytes", serialized_data.len());
379 println!(" Target object ID: {}", target_id);
380 println!(
381 " Git object format: blob {}\\0<content>",
382 note.content.len()
383 );
384 println!(
385 " Raw data preview: {:?}...",
386 &serialized_data[..std::cmp::min(30, serialized_data.len())]
387 );
388
389 println!("\n3️⃣ Basic deserialization (ObjectTrait):");
390 let basic_note = Note::from_bytes(&serialized_data, note.id).unwrap();
391 println!(" Successfully deserialized!");
392 println!(
393 " Target Commit: {} (default - target managed externally)",
394 basic_note.target_object_id
395 );
396 println!(" Content: {}", basic_note.content);
397 println!(" Content matches: {}", note.content == basic_note.content);
398
399 println!("\n4️⃣ Best practice deserialization (with target):");
400 let complete_note =
401 Note::from_bytes_with_target(&serialized_data, note.id, target_id).unwrap();
402 println!(" Successfully deserialized with target!");
403 println!(" Target Commit: {}", complete_note.target_object_id);
404 println!(" Content: {}", complete_note.content);
405 println!(" Complete objects are equal: {}", note == complete_note);
406
407 assert_eq!(note, complete_note);
409 assert_eq!(target_id, commit_id);
410 }
411 #[test]
412 fn test_note_demo_functionality_sha256() {
413 let _guard = set_hash_kind_for_test(HashKind::Sha256);
414 println!("\n🚀 Git Note Object Demo - Best Practices");
417 println!("==========================================");
418
419 let commit_id = ObjectHash::from_str(
420 "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
421 )
422 .unwrap();
423
424 println!("\n1️⃣ Creating a new Note object:");
425 let note = Note::new(
426 commit_id,
427 "Code review: LGTM! Great implementation.".to_string(),
428 );
429 println!(" Target Commit: {}", note.target_object_id);
430 println!(" Note ID: {}", note.id);
431 println!(" Content: {}", note.content);
432 println!(" Size: {} bytes", note.get_size());
433
434 println!("\n2️⃣ Serializing Note with target association:");
435 let (serialized_data, target_id) = note.to_data_with_target().unwrap();
436 println!(" Serialized size: {} bytes", serialized_data.len());
437 println!(" Target object ID: {}", target_id);
438 println!(
439 " Git object format: blob {}\\0<content>",
440 note.content.len()
441 );
442 println!(
443 " Raw data preview: {:?}...",
444 &serialized_data[..std::cmp::min(30, serialized_data.len())]
445 );
446
447 println!("\n3️⃣ Basic deserialization (ObjectTrait):");
448 let basic_note = Note::from_bytes(&serialized_data, note.id).unwrap();
449 println!(" Successfully deserialized!");
450 println!(
451 " Target Commit: {} (default - target managed externally)",
452 basic_note.target_object_id
453 );
454 println!(" Content: {}", basic_note.content);
455 println!(" Content matches: {}", note.content == basic_note.content);
456
457 println!("\n4️⃣ Best practice deserialization (with target):");
458 let complete_note =
459 Note::from_bytes_with_target(&serialized_data, note.id, target_id).unwrap();
460 println!(" Successfully deserialized with target!");
461 println!(" Target Commit: {}", complete_note.target_object_id);
462 println!(" Content: {}", complete_note.content);
463 println!(" Complete objects are equal: {}", note == complete_note);
464
465 assert_eq!(note, complete_note);
467 assert_eq!(target_id, commit_id);
468 }
469}