1use crate::object::{PdfDict, PdfObject};
8
9#[derive(Debug, Clone)]
15pub enum Operation {
16 AddObject { obj_num: u32 },
18 ModifyObject { obj_num: u32, old_value: PdfObject },
20 DeleteObject { obj_num: u32, old_value: PdfObject },
22 Batch {
24 ops: Vec<Operation>,
25 description: String,
26 },
27}
28
29#[derive(Debug)]
35pub struct Journal {
36 undo_stack: Vec<Operation>,
38 redo_stack: Vec<Operation>,
40 recording: bool,
42}
43
44impl Journal {
45 pub fn new() -> Self {
47 Self {
48 undo_stack: Vec::new(),
49 redo_stack: Vec::new(),
50 recording: true,
51 }
52 }
53
54 pub fn start_recording(&mut self) {
56 self.recording = true;
57 }
58
59 pub fn stop_recording(&mut self) {
61 self.recording = false;
62 }
63
64 pub fn is_recording(&self) -> bool {
66 self.recording
67 }
68
69 pub fn record(&mut self, op: Operation) {
71 if !self.recording {
72 return;
73 }
74 self.redo_stack.clear();
75 self.undo_stack.push(op);
76 }
77
78 pub fn can_undo(&self) -> bool {
80 !self.undo_stack.is_empty()
81 }
82
83 pub fn can_redo(&self) -> bool {
85 !self.redo_stack.is_empty()
86 }
87
88 pub fn undo(&mut self) -> Option<Operation> {
91 let op = self.undo_stack.pop()?;
92 self.redo_stack.push(op.clone());
93 Some(op)
94 }
95
96 pub fn redo(&mut self) -> Option<Operation> {
98 let op = self.redo_stack.pop()?;
99 self.undo_stack.push(op.clone());
100 Some(op)
101 }
102
103 pub fn inverse(op: &Operation) -> Operation {
105 match op {
106 Operation::AddObject { obj_num } => Operation::DeleteObject {
107 obj_num: *obj_num,
108 old_value: PdfObject::Null,
109 },
110 Operation::ModifyObject { obj_num, old_value } => Operation::ModifyObject {
111 obj_num: *obj_num,
112 old_value: old_value.clone(),
113 },
114 Operation::DeleteObject { obj_num, old_value: _ } => Operation::AddObject {
115 obj_num: *obj_num,
116 },
117 Operation::Batch { ops, description } => Operation::Batch {
118 ops: ops.iter().rev().map(Journal::inverse).collect(),
119 description: format!("Undo: {}", description),
120 },
121 }
122 }
123
124 pub fn undo_count(&self) -> usize {
126 self.undo_stack.len()
127 }
128
129 pub fn redo_count(&self) -> usize {
131 self.redo_stack.len()
132 }
133
134 pub fn clear(&mut self) {
136 self.undo_stack.clear();
137 self.redo_stack.clear();
138 }
139
140 pub fn begin_batch(&mut self, description: &str) -> BatchBuilder<'_> {
142 BatchBuilder {
143 journal: self,
144 ops: Vec::new(),
145 description: description.to_string(),
146 }
147 }
148
149 pub fn to_bytes(&self) -> Vec<u8> {
159 let mut buf = Vec::new();
160 buf.extend_from_slice(b"JRNL");
162 buf.extend_from_slice(&1u32.to_le_bytes());
164 buf.extend_from_slice(&(self.undo_stack.len() as u32).to_le_bytes());
166 for op in &self.undo_stack {
167 serialize_operation(&mut buf, op);
168 }
169 buf.extend_from_slice(&(self.redo_stack.len() as u32).to_le_bytes());
171 for op in &self.redo_stack {
172 serialize_operation(&mut buf, op);
173 }
174 buf
175 }
176
177 pub fn from_bytes(data: &[u8]) -> Option<Self> {
179 let mut cursor = Cursor::new(data);
180 let magic = cursor.read_bytes(4)?;
182 if magic != b"JRNL" {
183 return None;
184 }
185 let version = cursor.read_u32()?;
187 if version != 1 {
188 return None;
189 }
190 let undo_count = cursor.read_u32()? as usize;
192 let mut undo_stack = Vec::with_capacity(undo_count);
193 for _ in 0..undo_count {
194 undo_stack.push(deserialize_operation(&mut cursor)?);
195 }
196 let redo_count = cursor.read_u32()? as usize;
198 let mut redo_stack = Vec::with_capacity(redo_count);
199 for _ in 0..redo_count {
200 redo_stack.push(deserialize_operation(&mut cursor)?);
201 }
202 Some(Self {
203 undo_stack,
204 redo_stack,
205 recording: true,
206 })
207 }
208}
209
210impl Default for Journal {
211 fn default() -> Self {
212 Self::new()
213 }
214}
215
216pub struct BatchBuilder<'a> {
222 journal: &'a mut Journal,
223 ops: Vec<Operation>,
224 description: String,
225}
226
227impl<'a> BatchBuilder<'a> {
228 pub fn record(&mut self, op: Operation) {
230 self.ops.push(op);
231 }
232
233 pub fn commit(self) {
235 if self.ops.is_empty() {
236 return;
237 }
238 let batch = Operation::Batch {
239 ops: self.ops,
240 description: self.description,
241 };
242 self.journal.record(batch);
243 }
244
245 pub fn cancel(self) {
247 }
249}
250
251const TAG_ADD: u8 = 1;
257const TAG_MODIFY: u8 = 2;
258const TAG_DELETE: u8 = 3;
259const TAG_BATCH: u8 = 4;
260
261const OBJ_NULL: u8 = 0;
263const OBJ_BOOL: u8 = 1;
264const OBJ_INTEGER: u8 = 2;
265const OBJ_REAL: u8 = 3;
266const OBJ_NAME: u8 = 4;
267const OBJ_STRING: u8 = 5;
268const OBJ_ARRAY: u8 = 6;
269const OBJ_DICT: u8 = 7;
270const OBJ_STREAM: u8 = 8;
271const OBJ_REFERENCE: u8 = 9;
272
273fn serialize_operation(buf: &mut Vec<u8>, op: &Operation) {
274 match op {
275 Operation::AddObject { obj_num } => {
276 buf.push(TAG_ADD);
277 buf.extend_from_slice(&obj_num.to_le_bytes());
278 }
279 Operation::ModifyObject { obj_num, old_value } => {
280 buf.push(TAG_MODIFY);
281 buf.extend_from_slice(&obj_num.to_le_bytes());
282 serialize_pdf_object(buf, old_value);
283 }
284 Operation::DeleteObject { obj_num, old_value } => {
285 buf.push(TAG_DELETE);
286 buf.extend_from_slice(&obj_num.to_le_bytes());
287 serialize_pdf_object(buf, old_value);
288 }
289 Operation::Batch { ops, description } => {
290 buf.push(TAG_BATCH);
291 let desc_bytes = description.as_bytes();
292 buf.extend_from_slice(&(desc_bytes.len() as u32).to_le_bytes());
293 buf.extend_from_slice(desc_bytes);
294 buf.extend_from_slice(&(ops.len() as u32).to_le_bytes());
295 for op in ops {
296 serialize_operation(buf, op);
297 }
298 }
299 }
300}
301
302fn serialize_pdf_object(buf: &mut Vec<u8>, obj: &PdfObject) {
303 match obj {
304 PdfObject::Null => buf.push(OBJ_NULL),
305 PdfObject::Bool(v) => {
306 buf.push(OBJ_BOOL);
307 buf.push(if *v { 1 } else { 0 });
308 }
309 PdfObject::Integer(v) => {
310 buf.push(OBJ_INTEGER);
311 buf.extend_from_slice(&v.to_le_bytes());
312 }
313 PdfObject::Real(v) => {
314 buf.push(OBJ_REAL);
315 buf.extend_from_slice(&v.to_le_bytes());
316 }
317 PdfObject::Name(v) => {
318 buf.push(OBJ_NAME);
319 buf.extend_from_slice(&(v.len() as u32).to_le_bytes());
320 buf.extend_from_slice(v);
321 }
322 PdfObject::String(v) => {
323 buf.push(OBJ_STRING);
324 buf.extend_from_slice(&(v.len() as u32).to_le_bytes());
325 buf.extend_from_slice(v);
326 }
327 PdfObject::Array(items) => {
328 buf.push(OBJ_ARRAY);
329 buf.extend_from_slice(&(items.len() as u32).to_le_bytes());
330 for item in items {
331 serialize_pdf_object(buf, item);
332 }
333 }
334 PdfObject::Dict(dict) => {
335 buf.push(OBJ_DICT);
336 serialize_pdf_dict(buf, dict);
337 }
338 PdfObject::Stream { dict, data } => {
339 buf.push(OBJ_STREAM);
340 serialize_pdf_dict(buf, dict);
341 buf.extend_from_slice(&(data.len() as u32).to_le_bytes());
342 buf.extend_from_slice(data);
343 }
344 PdfObject::Reference(r) => {
345 buf.push(OBJ_REFERENCE);
346 buf.extend_from_slice(&r.obj_num.to_le_bytes());
347 buf.extend_from_slice(&r.gen_num.to_le_bytes());
348 }
349 }
350}
351
352fn serialize_pdf_dict(buf: &mut Vec<u8>, dict: &PdfDict) {
353 let entries: Vec<_> = dict.iter().collect();
354 buf.extend_from_slice(&(entries.len() as u32).to_le_bytes());
355 for (key, value) in entries {
356 buf.extend_from_slice(&(key.len() as u32).to_le_bytes());
357 buf.extend_from_slice(key);
358 serialize_pdf_object(buf, value);
359 }
360}
361
362struct Cursor<'a> {
368 data: &'a [u8],
369 pos: usize,
370}
371
372impl<'a> Cursor<'a> {
373 fn new(data: &'a [u8]) -> Self {
374 Self { data, pos: 0 }
375 }
376
377 fn read_bytes(&mut self, n: usize) -> Option<&'a [u8]> {
378 if self.pos + n > self.data.len() {
379 return None;
380 }
381 let slice = &self.data[self.pos..self.pos + n];
382 self.pos += n;
383 Some(slice)
384 }
385
386 fn read_u8(&mut self) -> Option<u8> {
387 let b = self.read_bytes(1)?;
388 Some(b[0])
389 }
390
391 fn read_u16(&mut self) -> Option<u16> {
392 let b = self.read_bytes(2)?;
393 Some(u16::from_le_bytes([b[0], b[1]]))
394 }
395
396 fn read_u32(&mut self) -> Option<u32> {
397 let b = self.read_bytes(4)?;
398 Some(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
399 }
400
401 fn read_i64(&mut self) -> Option<i64> {
402 let b = self.read_bytes(8)?;
403 Some(i64::from_le_bytes([
404 b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
405 ]))
406 }
407
408 fn read_f64(&mut self) -> Option<f64> {
409 let b = self.read_bytes(8)?;
410 Some(f64::from_le_bytes([
411 b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
412 ]))
413 }
414}
415
416fn deserialize_operation(cursor: &mut Cursor<'_>) -> Option<Operation> {
417 let tag = cursor.read_u8()?;
418 match tag {
419 TAG_ADD => {
420 let obj_num = cursor.read_u32()?;
421 Some(Operation::AddObject { obj_num })
422 }
423 TAG_MODIFY => {
424 let obj_num = cursor.read_u32()?;
425 let old_value = deserialize_pdf_object(cursor)?;
426 Some(Operation::ModifyObject { obj_num, old_value })
427 }
428 TAG_DELETE => {
429 let obj_num = cursor.read_u32()?;
430 let old_value = deserialize_pdf_object(cursor)?;
431 Some(Operation::DeleteObject { obj_num, old_value })
432 }
433 TAG_BATCH => {
434 let desc_len = cursor.read_u32()? as usize;
435 let desc_bytes = cursor.read_bytes(desc_len)?;
436 let description = std::str::from_utf8(desc_bytes).ok()?.to_string();
437 let count = cursor.read_u32()? as usize;
438 let mut ops = Vec::with_capacity(count);
439 for _ in 0..count {
440 ops.push(deserialize_operation(cursor)?);
441 }
442 Some(Operation::Batch { ops, description })
443 }
444 _ => None,
445 }
446}
447
448fn deserialize_pdf_object(cursor: &mut Cursor<'_>) -> Option<PdfObject> {
449 let tag = cursor.read_u8()?;
450 match tag {
451 OBJ_NULL => Some(PdfObject::Null),
452 OBJ_BOOL => {
453 let v = cursor.read_u8()?;
454 Some(PdfObject::Bool(v != 0))
455 }
456 OBJ_INTEGER => {
457 let v = cursor.read_i64()?;
458 Some(PdfObject::Integer(v))
459 }
460 OBJ_REAL => {
461 let v = cursor.read_f64()?;
462 Some(PdfObject::Real(v))
463 }
464 OBJ_NAME => {
465 let len = cursor.read_u32()? as usize;
466 let bytes = cursor.read_bytes(len)?;
467 Some(PdfObject::Name(bytes.to_vec()))
468 }
469 OBJ_STRING => {
470 let len = cursor.read_u32()? as usize;
471 let bytes = cursor.read_bytes(len)?;
472 Some(PdfObject::String(bytes.to_vec()))
473 }
474 OBJ_ARRAY => {
475 let count = cursor.read_u32()? as usize;
476 let mut items = Vec::with_capacity(count);
477 for _ in 0..count {
478 items.push(deserialize_pdf_object(cursor)?);
479 }
480 Some(PdfObject::Array(items))
481 }
482 OBJ_DICT => {
483 let dict = deserialize_pdf_dict(cursor)?;
484 Some(PdfObject::Dict(dict))
485 }
486 OBJ_STREAM => {
487 let dict = deserialize_pdf_dict(cursor)?;
488 let data_len = cursor.read_u32()? as usize;
489 let data = cursor.read_bytes(data_len)?.to_vec();
490 Some(PdfObject::Stream { dict, data })
491 }
492 OBJ_REFERENCE => {
493 let obj_num = cursor.read_u32()?;
494 let gen_num = cursor.read_u16()?;
495 Some(PdfObject::Reference(crate::object::IndirectRef {
496 obj_num,
497 gen_num,
498 }))
499 }
500 _ => None,
501 }
502}
503
504fn deserialize_pdf_dict(cursor: &mut Cursor<'_>) -> Option<PdfDict> {
505 let count = cursor.read_u32()? as usize;
506 let mut dict = PdfDict::new();
507 for _ in 0..count {
508 let key_len = cursor.read_u32()? as usize;
509 let key = cursor.read_bytes(key_len)?.to_vec();
510 let value = deserialize_pdf_object(cursor)?;
511 dict.insert(key, value);
512 }
513 Some(dict)
514}
515
516#[cfg(test)]
521mod tests {
522 use super::*;
523 use crate::object::{IndirectRef, PdfDict, PdfObject};
524
525 fn sample_object() -> PdfObject {
526 PdfObject::Integer(42)
527 }
528
529 #[test]
530 fn record_and_undo_single() {
531 let mut j = Journal::new();
532 j.record(Operation::AddObject { obj_num: 1 });
533 assert!(j.can_undo());
534 assert_eq!(j.undo_count(), 1);
535
536 let op = j.undo().unwrap();
537 assert!(!j.can_undo());
538 assert!(j.can_redo());
539 match op {
540 Operation::AddObject { obj_num } => assert_eq!(obj_num, 1),
541 _ => panic!("expected AddObject"),
542 }
543 }
544
545 #[test]
546 fn record_and_redo_after_undo() {
547 let mut j = Journal::new();
548 j.record(Operation::ModifyObject {
549 obj_num: 5,
550 old_value: sample_object(),
551 });
552 j.undo();
553 assert!(j.can_redo());
554 assert_eq!(j.redo_count(), 1);
555
556 let op = j.redo().unwrap();
557 assert!(j.can_undo());
558 assert!(!j.can_redo());
559 match op {
560 Operation::ModifyObject { obj_num, .. } => assert_eq!(obj_num, 5),
561 _ => panic!("expected ModifyObject"),
562 }
563 }
564
565 #[test]
566 fn recording_disabled_no_ops() {
567 let mut j = Journal::new();
568 j.stop_recording();
569 assert!(!j.is_recording());
570 j.record(Operation::AddObject { obj_num: 1 });
571 assert!(!j.can_undo());
572 assert_eq!(j.undo_count(), 0);
573 }
574
575 #[test]
576 fn redo_cleared_on_new_record() {
577 let mut j = Journal::new();
578 j.record(Operation::AddObject { obj_num: 1 });
579 j.record(Operation::AddObject { obj_num: 2 });
580 j.undo(); assert!(j.can_redo());
582
583 j.record(Operation::AddObject { obj_num: 3 });
585 assert!(!j.can_redo());
586 assert_eq!(j.redo_count(), 0);
587 }
588
589 #[test]
590 fn batch_operations() {
591 let mut j = Journal::new();
592 {
593 let mut batch = j.begin_batch("add two objects");
594 batch.record(Operation::AddObject { obj_num: 10 });
595 batch.record(Operation::AddObject { obj_num: 11 });
596 batch.commit();
597 }
598 assert_eq!(j.undo_count(), 1);
599 let op = j.undo().unwrap();
600 match op {
601 Operation::Batch { ops, description } => {
602 assert_eq!(ops.len(), 2);
603 assert_eq!(description, "add two objects");
604 }
605 _ => panic!("expected Batch"),
606 }
607 }
608
609 #[test]
610 fn batch_cancel() {
611 let mut j = Journal::new();
612 {
613 let mut batch = j.begin_batch("will cancel");
614 batch.record(Operation::AddObject { obj_num: 99 });
615 batch.cancel();
616 }
617 assert!(!j.can_undo());
618 assert_eq!(j.undo_count(), 0);
619 }
620
621 #[test]
622 fn inverse_add() {
623 let op = Operation::AddObject { obj_num: 7 };
624 let inv = Journal::inverse(&op);
625 match inv {
626 Operation::DeleteObject { obj_num, .. } => assert_eq!(obj_num, 7),
627 _ => panic!("expected DeleteObject"),
628 }
629 }
630
631 #[test]
632 fn inverse_modify() {
633 let op = Operation::ModifyObject {
634 obj_num: 3,
635 old_value: PdfObject::Bool(true),
636 };
637 let inv = Journal::inverse(&op);
638 match inv {
639 Operation::ModifyObject { obj_num, old_value } => {
640 assert_eq!(obj_num, 3);
641 assert_eq!(old_value, PdfObject::Bool(true));
642 }
643 _ => panic!("expected ModifyObject"),
644 }
645 }
646
647 #[test]
648 fn inverse_delete() {
649 let op = Operation::DeleteObject {
650 obj_num: 4,
651 old_value: PdfObject::Integer(100),
652 };
653 let inv = Journal::inverse(&op);
654 match inv {
655 Operation::AddObject { obj_num } => assert_eq!(obj_num, 4),
656 _ => panic!("expected AddObject"),
657 }
658 }
659
660 #[test]
661 fn inverse_batch() {
662 let op = Operation::Batch {
663 ops: vec![
664 Operation::AddObject { obj_num: 1 },
665 Operation::DeleteObject {
666 obj_num: 2,
667 old_value: PdfObject::Null,
668 },
669 ],
670 description: "test batch".to_string(),
671 };
672 let inv = Journal::inverse(&op);
673 match inv {
674 Operation::Batch { ops, description } => {
675 assert_eq!(description, "Undo: test batch");
676 assert_eq!(ops.len(), 2);
678 match &ops[0] {
679 Operation::AddObject { obj_num } => assert_eq!(*obj_num, 2),
680 _ => panic!("expected AddObject as inverse of DeleteObject"),
681 }
682 match &ops[1] {
683 Operation::DeleteObject { obj_num, .. } => assert_eq!(*obj_num, 1),
684 _ => panic!("expected DeleteObject as inverse of AddObject"),
685 }
686 }
687 _ => panic!("expected Batch"),
688 }
689 }
690
691 #[test]
692 fn multiple_undo_redo_cycles() {
693 let mut j = Journal::new();
694 j.record(Operation::AddObject { obj_num: 1 });
695 j.record(Operation::AddObject { obj_num: 2 });
696 j.record(Operation::AddObject { obj_num: 3 });
697 assert_eq!(j.undo_count(), 3);
698
699 j.undo();
701 j.undo();
702 j.undo();
703 assert_eq!(j.undo_count(), 0);
704 assert_eq!(j.redo_count(), 3);
705
706 j.redo();
708 j.redo();
709 j.redo();
710 assert_eq!(j.undo_count(), 3);
711 assert_eq!(j.redo_count(), 0);
712
713 j.undo();
715 j.undo();
716 assert_eq!(j.undo_count(), 1);
717 assert_eq!(j.redo_count(), 2);
718 j.redo();
719 assert_eq!(j.undo_count(), 2);
720 assert_eq!(j.redo_count(), 1);
721 }
722
723 #[test]
724 fn serialization_roundtrip() {
725 let mut j = Journal::new();
726 j.record(Operation::AddObject { obj_num: 1 });
727 j.record(Operation::ModifyObject {
728 obj_num: 2,
729 old_value: PdfObject::Name(b"Type".to_vec()),
730 });
731 j.record(Operation::DeleteObject {
732 obj_num: 3,
733 old_value: PdfObject::Array(vec![
734 PdfObject::Integer(10),
735 PdfObject::Real(3.14),
736 PdfObject::String(b"hello".to_vec()),
737 ]),
738 });
739 {
741 let mut batch = j.begin_batch("batch op");
742 batch.record(Operation::AddObject { obj_num: 100 });
743 batch.commit();
744 }
745 j.undo();
747
748 let bytes = j.to_bytes();
749 let j2 = Journal::from_bytes(&bytes).expect("deserialization failed");
750
751 assert_eq!(j2.undo_count(), j.undo_count());
752 assert_eq!(j2.redo_count(), j.redo_count());
753
754 let bytes2 = j2.to_bytes();
756 assert_eq!(bytes, bytes2);
757 }
758
759 #[test]
760 fn serialization_roundtrip_complex_objects() {
761 let mut dict = PdfDict::new();
762 dict.insert(b"Key".to_vec(), PdfObject::Bool(false));
763 dict.insert(b"Other".to_vec(), PdfObject::Null);
764
765 let mut j = Journal::new();
766 j.record(Operation::ModifyObject {
767 obj_num: 50,
768 old_value: PdfObject::Dict(dict),
769 });
770 j.record(Operation::DeleteObject {
771 obj_num: 51,
772 old_value: PdfObject::Stream {
773 dict: PdfDict::new(),
774 data: vec![0xDE, 0xAD, 0xBE, 0xEF],
775 },
776 });
777 j.record(Operation::ModifyObject {
778 obj_num: 52,
779 old_value: PdfObject::Reference(IndirectRef {
780 obj_num: 99,
781 gen_num: 2,
782 }),
783 });
784
785 let bytes = j.to_bytes();
786 let j2 = Journal::from_bytes(&bytes).expect("deserialization failed");
787 assert_eq!(j2.undo_count(), 3);
788 assert_eq!(j2.to_bytes(), bytes);
789 }
790
791 #[test]
792 fn empty_journal_serialization() {
793 let j = Journal::new();
794 let bytes = j.to_bytes();
795 assert_eq!(bytes.len(), 16);
797 assert_eq!(&bytes[0..4], b"JRNL");
798
799 let j2 = Journal::from_bytes(&bytes).expect("deserialization failed");
800 assert_eq!(j2.undo_count(), 0);
801 assert_eq!(j2.redo_count(), 0);
802 assert!(j2.is_recording());
803 }
804
805 #[test]
806 fn can_undo_can_redo_states() {
807 let mut j = Journal::new();
808 assert!(!j.can_undo());
809 assert!(!j.can_redo());
810
811 j.record(Operation::AddObject { obj_num: 1 });
812 assert!(j.can_undo());
813 assert!(!j.can_redo());
814
815 j.undo();
816 assert!(!j.can_undo());
817 assert!(j.can_redo());
818
819 j.redo();
820 assert!(j.can_undo());
821 assert!(!j.can_redo());
822 }
823
824 #[test]
825 fn clear_history() {
826 let mut j = Journal::new();
827 j.record(Operation::AddObject { obj_num: 1 });
828 j.record(Operation::AddObject { obj_num: 2 });
829 j.undo();
830 assert!(j.can_undo());
831 assert!(j.can_redo());
832
833 j.clear();
834 assert!(!j.can_undo());
835 assert!(!j.can_redo());
836 assert_eq!(j.undo_count(), 0);
837 assert_eq!(j.redo_count(), 0);
838 }
839
840 #[test]
841 fn undo_on_empty_returns_none() {
842 let mut j = Journal::new();
843 assert!(j.undo().is_none());
844 }
845
846 #[test]
847 fn redo_on_empty_returns_none() {
848 let mut j = Journal::new();
849 assert!(j.redo().is_none());
850 }
851
852 #[test]
853 fn from_bytes_invalid_magic() {
854 assert!(Journal::from_bytes(b"XXXX").is_none());
855 }
856
857 #[test]
858 fn from_bytes_truncated() {
859 assert!(Journal::from_bytes(b"JRN").is_none());
860 assert!(Journal::from_bytes(b"").is_none());
861 }
862}