1use async_compression::tokio::bufread::{BzDecoder, GzipDecoder, XzDecoder, ZlibDecoder, ZstdDecoder};
16use async_compression::tokio::write::{BzEncoder, GzipEncoder, XzEncoder, ZlibEncoder, ZstdEncoder};
17use std::path::Path;
21use tokio::fs::File;
22use tokio::io::{self, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, BufWriter};
23use tokio_stream::StreamExt;
24use tokio_tar::Archive;
25
26#[derive(Debug, PartialEq, Clone, Copy)]
27pub enum CompressionFormat {
28 Gzip, Bzip2, Zip, Xz, Zlib, Zstd, Tar, Unknown,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
39pub enum CompressionLevel {
40 Fastest,
41 Best,
42 #[default]
43 Default,
44 Level(u32),
45}
46
47impl CompressionFormat {
48 pub fn from_extension(ext: &str) -> Self {
50 match ext.to_lowercase().as_str() {
51 "gz" | "gzip" => CompressionFormat::Gzip,
52 "bz2" | "bzip2" => CompressionFormat::Bzip2,
53 "zip" => CompressionFormat::Zip,
54 "xz" => CompressionFormat::Xz,
55 "zlib" => CompressionFormat::Zlib,
56 "zst" | "zstd" => CompressionFormat::Zstd,
57 "tar" => CompressionFormat::Tar,
58 _ => CompressionFormat::Unknown,
59 }
60 }
61
62 pub fn from_path<P: AsRef<Path>>(path: P) -> Self {
64 let path = path.as_ref();
65 if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
66 Self::from_extension(ext)
67 } else {
68 CompressionFormat::Unknown
69 }
70 }
71
72 pub fn extension(&self) -> &'static str {
74 match self {
75 CompressionFormat::Gzip => "gz",
76 CompressionFormat::Bzip2 => "bz2",
77 CompressionFormat::Zip => "zip",
78 CompressionFormat::Xz => "xz",
79 CompressionFormat::Zlib => "zlib",
80 CompressionFormat::Zstd => "zst",
81 CompressionFormat::Tar => "tar",
82 CompressionFormat::Unknown => "",
83 }
84 }
85
86 pub fn is_supported(&self) -> bool {
88 !matches!(self, CompressionFormat::Unknown)
89 }
90
91 pub fn get_decoder<R>(&self, input: R) -> io::Result<Box<dyn AsyncRead + Send + Unpin>>
93 where
94 R: AsyncRead + Send + Unpin + 'static,
95 {
96 let reader = BufReader::new(input);
97
98 let decoder: Box<dyn AsyncRead + Send + Unpin + 'static> = match self {
99 CompressionFormat::Gzip => Box::new(GzipDecoder::new(reader)),
100 CompressionFormat::Bzip2 => Box::new(BzDecoder::new(reader)),
101 CompressionFormat::Zlib => Box::new(ZlibDecoder::new(reader)),
102 CompressionFormat::Xz => Box::new(XzDecoder::new(reader)),
103 CompressionFormat::Zstd => Box::new(ZstdDecoder::new(reader)),
104 CompressionFormat::Tar => Box::new(reader),
105 CompressionFormat::Zip => {
106 return Err(io::Error::new(
107 io::ErrorKind::InvalidInput,
108 "Zip format requires special handling, use extract_zip function instead",
109 ));
110 }
111 CompressionFormat::Unknown => {
112 return Err(io::Error::new(io::ErrorKind::InvalidInput, "Unsupported file format"));
113 }
114 };
115
116 Ok(decoder)
117 }
118
119 pub fn get_encoder<W>(&self, output: W, level: CompressionLevel) -> io::Result<Box<dyn AsyncWrite + Send + Unpin>>
121 where
122 W: AsyncWrite + Send + Unpin + 'static,
123 {
124 let writer = BufWriter::new(output);
125
126 let encoder: Box<dyn AsyncWrite + Send + Unpin + 'static> = match self {
127 CompressionFormat::Gzip => {
128 let level = match level {
129 CompressionLevel::Fastest => async_compression::Level::Fastest,
130 CompressionLevel::Best => async_compression::Level::Best,
131 CompressionLevel::Default => async_compression::Level::Default,
132 CompressionLevel::Level(n) => async_compression::Level::Precise(n as i32),
133 };
134 Box::new(GzipEncoder::with_quality(writer, level))
135 }
136 CompressionFormat::Bzip2 => {
137 let level = match level {
138 CompressionLevel::Fastest => async_compression::Level::Fastest,
139 CompressionLevel::Best => async_compression::Level::Best,
140 CompressionLevel::Default => async_compression::Level::Default,
141 CompressionLevel::Level(n) => async_compression::Level::Precise(n as i32),
142 };
143 Box::new(BzEncoder::with_quality(writer, level))
144 }
145 CompressionFormat::Zlib => {
146 let level = match level {
147 CompressionLevel::Fastest => async_compression::Level::Fastest,
148 CompressionLevel::Best => async_compression::Level::Best,
149 CompressionLevel::Default => async_compression::Level::Default,
150 CompressionLevel::Level(n) => async_compression::Level::Precise(n as i32),
151 };
152 Box::new(ZlibEncoder::with_quality(writer, level))
153 }
154 CompressionFormat::Xz => {
155 let level = match level {
156 CompressionLevel::Fastest => async_compression::Level::Fastest,
157 CompressionLevel::Best => async_compression::Level::Best,
158 CompressionLevel::Default => async_compression::Level::Default,
159 CompressionLevel::Level(n) => async_compression::Level::Precise(n as i32),
160 };
161 Box::new(XzEncoder::with_quality(writer, level))
162 }
163 CompressionFormat::Zstd => {
164 let level = match level {
165 CompressionLevel::Fastest => async_compression::Level::Fastest,
166 CompressionLevel::Best => async_compression::Level::Best,
167 CompressionLevel::Default => async_compression::Level::Default,
168 CompressionLevel::Level(n) => async_compression::Level::Precise(n as i32),
169 };
170 Box::new(ZstdEncoder::with_quality(writer, level))
171 }
172 CompressionFormat::Tar => Box::new(writer),
173 CompressionFormat::Zip => {
174 return Err(io::Error::new(
175 io::ErrorKind::InvalidInput,
176 "Zip format requires special handling, use create_zip function instead",
177 ));
178 }
179 CompressionFormat::Unknown => {
180 return Err(io::Error::new(io::ErrorKind::InvalidInput, "Unsupported file format"));
181 }
182 };
183
184 Ok(encoder)
185 }
186}
187
188pub async fn decompress<R, F>(input: R, format: CompressionFormat, mut callback: F) -> io::Result<()>
190where
191 R: AsyncRead + Send + Unpin + 'static,
192 F: AsyncFnMut(tokio_tar::Entry<Archive<Box<dyn AsyncRead + Send + Unpin + 'static>>>) -> std::io::Result<()> + Send + 'static,
193{
194 let decoder = format.get_decoder(input)?;
195 let mut ar = Archive::new(decoder);
196 let mut entries = ar.entries()?;
197
198 while let Some(entry) = entries.next().await {
199 let entry = entry?;
200 callback(entry).await?;
201 }
202
203 Ok(())
204}
205
206#[derive(Debug, Clone)]
208pub struct ZipEntry {
209 pub name: String,
210 pub size: u64,
211 pub compressed_size: u64,
212 pub is_dir: bool,
213 pub compression_method: String,
214}
215
216pub async fn extract_zip_simple<P: AsRef<Path>>(zip_path: P, extract_to: P) -> io::Result<Vec<ZipEntry>> {
218 let _zip_path = zip_path.as_ref();
221 let _extract_to = extract_to.as_ref();
222
223 Ok(Vec::new())
224}
225
226pub async fn create_zip_simple<P: AsRef<Path>>(
228 _zip_path: P,
229 _files: Vec<(String, Vec<u8>)>, _compression_level: CompressionLevel,
231) -> io::Result<()> {
232 Err(io::Error::new(io::ErrorKind::Unsupported, "ZIP creation not yet implemented"))
234}
235
236pub struct Compressor {
238 format: CompressionFormat,
239 level: CompressionLevel,
240}
241
242impl Compressor {
243 pub fn new(format: CompressionFormat) -> Self {
244 Self {
245 format,
246 level: CompressionLevel::Default,
247 }
248 }
249
250 pub fn with_level(mut self, level: CompressionLevel) -> Self {
251 self.level = level;
252 self
253 }
254
255 pub async fn compress(&self, input: &[u8]) -> io::Result<Vec<u8>> {
257 let output = Vec::new();
258 let cursor = std::io::Cursor::new(output);
259 let mut encoder = self.format.get_encoder(cursor, self.level)?;
260
261 tokio::io::copy(&mut std::io::Cursor::new(input), &mut encoder).await?;
262 encoder.shutdown().await?;
263
264 Ok(Vec::new())
268 }
269
270 pub async fn decompress(&self, input: Vec<u8>) -> io::Result<Vec<u8>> {
272 let mut output = Vec::new();
273 let cursor = std::io::Cursor::new(input);
274 let mut decoder = self.format.get_decoder(cursor)?;
275
276 tokio::io::copy(&mut decoder, &mut output).await?;
277
278 Ok(output)
279 }
280}
281
282pub struct Decompressor {
284 format: CompressionFormat,
285}
286
287impl Decompressor {
288 pub fn new(format: CompressionFormat) -> Self {
289 Self { format }
290 }
291
292 pub fn auto_detect<P: AsRef<Path>>(path: P) -> Self {
293 let format = CompressionFormat::from_path(path);
294 Self { format }
295 }
296
297 pub async fn decompress_file<P: AsRef<Path>>(&self, input_path: P, output_path: P) -> io::Result<()> {
299 let input_file = File::open(&input_path).await?;
300 let output_file = File::create(&output_path).await?;
301
302 let mut decoder = self.format.get_decoder(input_file)?;
303 let mut writer = BufWriter::new(output_file);
304
305 tokio::io::copy(&mut decoder, &mut writer).await?;
306 writer.shutdown().await?;
307
308 Ok(())
309 }
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315 use std::io::Cursor;
316 use tokio::io::AsyncReadExt;
317
318 #[test]
319 fn test_compression_format_from_extension() {
320 assert_eq!(CompressionFormat::from_extension("gz"), CompressionFormat::Gzip);
322 assert_eq!(CompressionFormat::from_extension("gzip"), CompressionFormat::Gzip);
323 assert_eq!(CompressionFormat::from_extension("bz2"), CompressionFormat::Bzip2);
324 assert_eq!(CompressionFormat::from_extension("bzip2"), CompressionFormat::Bzip2);
325 assert_eq!(CompressionFormat::from_extension("zip"), CompressionFormat::Zip);
326 assert_eq!(CompressionFormat::from_extension("xz"), CompressionFormat::Xz);
327 assert_eq!(CompressionFormat::from_extension("zlib"), CompressionFormat::Zlib);
328 assert_eq!(CompressionFormat::from_extension("zst"), CompressionFormat::Zstd);
329 assert_eq!(CompressionFormat::from_extension("zstd"), CompressionFormat::Zstd);
330 assert_eq!(CompressionFormat::from_extension("tar"), CompressionFormat::Tar);
331
332 assert_eq!(CompressionFormat::from_extension("GZ"), CompressionFormat::Gzip);
334 assert_eq!(CompressionFormat::from_extension("ZIP"), CompressionFormat::Zip);
335
336 assert_eq!(CompressionFormat::from_extension("unknown"), CompressionFormat::Unknown);
338 assert_eq!(CompressionFormat::from_extension("txt"), CompressionFormat::Unknown);
339 assert_eq!(CompressionFormat::from_extension(""), CompressionFormat::Unknown);
340 }
341
342 #[test]
343 fn test_compression_format_case_sensitivity() {
344 assert_eq!(CompressionFormat::from_extension("GZ"), CompressionFormat::Gzip);
346 assert_eq!(CompressionFormat::from_extension("Gz"), CompressionFormat::Gzip);
347 assert_eq!(CompressionFormat::from_extension("BZ2"), CompressionFormat::Bzip2);
348 assert_eq!(CompressionFormat::from_extension("ZIP"), CompressionFormat::Zip);
349 }
350
351 #[test]
352 fn test_compression_format_edge_cases() {
353 assert_eq!(CompressionFormat::from_extension("gz "), CompressionFormat::Unknown);
355 assert_eq!(CompressionFormat::from_extension(" gz"), CompressionFormat::Unknown);
356 assert_eq!(CompressionFormat::from_extension("gz.bak"), CompressionFormat::Unknown);
357 assert_eq!(CompressionFormat::from_extension("tar.gz"), CompressionFormat::Unknown);
358 }
359
360 #[test]
361 fn test_compression_format_debug() {
362 let format = CompressionFormat::Gzip;
364 let debug_str = format!("{format:?}");
365 assert_eq!(debug_str, "Gzip");
366
367 let unknown_format = CompressionFormat::Unknown;
368 let unknown_debug_str = format!("{unknown_format:?}");
369 assert_eq!(unknown_debug_str, "Unknown");
370 }
371
372 #[test]
373 fn test_compression_format_equality() {
374 assert_eq!(CompressionFormat::Gzip, CompressionFormat::Gzip);
376 assert_eq!(CompressionFormat::Unknown, CompressionFormat::Unknown);
377 assert_ne!(CompressionFormat::Gzip, CompressionFormat::Bzip2);
378 assert_ne!(CompressionFormat::Zip, CompressionFormat::Unknown);
379 }
380
381 #[tokio::test]
382 async fn test_get_decoder_supported_formats() {
383 let test_data = b"test data";
385 let cursor = Cursor::new(test_data);
386
387 let gzip_format = CompressionFormat::Gzip;
388 let decoder_result = gzip_format.get_decoder(cursor);
389 assert!(decoder_result.is_ok(), "Gzip decoder should be created successfully");
390 }
391
392 #[tokio::test]
393 async fn test_get_decoder_unsupported_formats() {
394 let test_data = b"test data";
396 let cursor = Cursor::new(test_data);
397
398 let unknown_format = CompressionFormat::Unknown;
399 let decoder_result = unknown_format.get_decoder(cursor);
400 assert!(decoder_result.is_err(), "Unknown format should return error");
401
402 if let Err(e) = decoder_result {
403 assert_eq!(e.kind(), io::ErrorKind::InvalidInput);
404 assert_eq!(e.to_string(), "Unsupported file format");
405 }
406 }
407
408 #[tokio::test]
409 async fn test_get_decoder_zip_format() {
410 let test_data = b"test data";
412 let cursor = Cursor::new(test_data);
413
414 let zip_format = CompressionFormat::Zip;
415 let decoder_result = zip_format.get_decoder(cursor);
416 assert!(decoder_result.is_err(), "Zip format should return error (not implemented)");
417 }
418
419 #[tokio::test]
420 async fn test_get_decoder_all_supported_formats() {
421 let sample_content = b"Hello, compression world!";
423
424 let supported_formats = vec![
425 CompressionFormat::Gzip,
426 CompressionFormat::Bzip2,
427 CompressionFormat::Zlib,
428 CompressionFormat::Xz,
429 CompressionFormat::Zstd,
430 CompressionFormat::Tar,
431 ];
432
433 for format in supported_formats {
434 let cursor = Cursor::new(sample_content);
435 let decoder_result = format.get_decoder(cursor);
436 assert!(decoder_result.is_ok(), "Format {format:?} should create decoder successfully");
437 }
438 }
439
440 #[tokio::test]
441 async fn test_decoder_type_consistency() {
442 let sample_content = b"Hello, compression world!";
444 let cursor = Cursor::new(sample_content);
445
446 let gzip_format = CompressionFormat::Gzip;
447 let mut decoder = gzip_format.get_decoder(cursor).unwrap();
448
449 let mut output_buffer = Vec::new();
451 let _read_result = decoder.read_to_end(&mut output_buffer).await;
453 }
454
455 #[test]
456 fn test_compression_format_exhaustive_matching() {
457 let all_formats = vec![
459 CompressionFormat::Gzip,
460 CompressionFormat::Bzip2,
461 CompressionFormat::Zip,
462 CompressionFormat::Xz,
463 CompressionFormat::Zlib,
464 CompressionFormat::Zstd,
465 CompressionFormat::Unknown,
466 ];
467
468 for format in all_formats {
469 let _debug_str = format!("{format:?}");
471
472 assert_eq!(format, format);
474 }
475 }
476
477 #[test]
478 fn test_extension_mapping_completeness() {
479 let extension_mappings = vec![
481 ("gz", CompressionFormat::Gzip),
482 ("gzip", CompressionFormat::Gzip),
483 ("bz2", CompressionFormat::Bzip2),
484 ("bzip2", CompressionFormat::Bzip2),
485 ("zip", CompressionFormat::Zip),
486 ("xz", CompressionFormat::Xz),
487 ("zlib", CompressionFormat::Zlib),
488 ("zst", CompressionFormat::Zstd),
489 ("zstd", CompressionFormat::Zstd),
490 ("tar", CompressionFormat::Tar),
491 ];
492
493 for (ext, expected_format) in extension_mappings {
494 assert_eq!(
495 CompressionFormat::from_extension(ext),
496 expected_format,
497 "Extension '{ext}' should map to {expected_format:?}"
498 );
499 }
500 }
501
502 #[test]
503 fn test_format_string_representations() {
504 let format_strings = vec![
506 (CompressionFormat::Gzip, "Gzip"),
507 (CompressionFormat::Bzip2, "Bzip2"),
508 (CompressionFormat::Zip, "Zip"),
509 (CompressionFormat::Xz, "Xz"),
510 (CompressionFormat::Zlib, "Zlib"),
511 (CompressionFormat::Zstd, "Zstd"),
512 (CompressionFormat::Unknown, "Unknown"),
513 ];
514
515 for (format, expected_str) in format_strings {
516 assert_eq!(
517 format!("{format:?}"),
518 expected_str,
519 "Format {:?} should have string representation '{}'",
520 format,
521 expected_str
522 );
523 }
524 }
525
526 #[tokio::test]
527 async fn test_decoder_error_handling() {
528 let empty_content = b"";
530 let cursor = Cursor::new(empty_content);
531
532 let gzip_format = CompressionFormat::Gzip;
533 let decoder_result = gzip_format.get_decoder(cursor);
534
535 assert!(decoder_result.is_ok(), "Decoder creation should succeed even with empty content");
537 }
538
539 #[test]
540 fn test_compression_format_memory_efficiency() {
541 use std::mem;
543
544 let size = mem::size_of::<CompressionFormat>();
546 assert!(size <= 8, "CompressionFormat should be memory efficient, got {size} bytes");
547
548 let option_size = mem::size_of::<Option<CompressionFormat>>();
550 assert!(
551 option_size <= 16,
552 "Option<CompressionFormat> should be efficient, got {option_size} bytes"
553 );
554 }
555
556 #[test]
557 fn test_extension_validation() {
558 let test_cases = vec![
560 ("gz", true),
562 ("bz2", true),
563 ("xz", true),
564 ("", false),
566 ("g", false),
567 ("gzz", false),
568 ("gz2", false),
569 ("gz.", false),
571 (".gz", false),
572 ("gz-", false),
573 ("gz_", false),
574 ];
575
576 for (ext, should_be_known) in test_cases {
577 let format = CompressionFormat::from_extension(ext);
578 let is_known = format != CompressionFormat::Unknown;
579 assert_eq!(
580 is_known, should_be_known,
581 "Extension '{ext}' recognition mismatch: expected {should_be_known}, got {is_known}"
582 );
583 }
584 }
585
586 #[tokio::test]
587 async fn test_decoder_trait_bounds() {
588 let sample_content = b"Hello, compression world!";
590 let cursor = Cursor::new(sample_content);
591
592 let gzip_format = CompressionFormat::Gzip;
593 let decoder = gzip_format.get_decoder(cursor).unwrap();
594
595 fn check_trait_bounds<T: AsyncRead + Send + Unpin + ?Sized>(_: &T) {}
597 check_trait_bounds(&*decoder);
598 }
599
600 #[test]
601 fn test_format_consistency_with_extensions() {
602 let consistency_tests = vec![
604 (CompressionFormat::Gzip, "gz"),
605 (CompressionFormat::Bzip2, "bz2"),
606 (CompressionFormat::Zip, "zip"),
607 (CompressionFormat::Xz, "xz"),
608 (CompressionFormat::Zlib, "zlib"),
609 (CompressionFormat::Zstd, "zst"),
610 ];
611
612 for (format, ext) in consistency_tests {
613 let parsed_format = CompressionFormat::from_extension(ext);
614 assert_eq!(parsed_format, format, "Extension '{ext}' should consistently map to {format:?}");
615 }
616 }
617
618 #[tokio::test]
619 async fn test_decompress_with_invalid_format() {
620 use std::sync::Arc;
622 use std::sync::atomic::{AtomicUsize, Ordering};
623
624 let sample_content = b"Hello, compression world!";
625 let cursor = Cursor::new(sample_content);
626
627 let processed_entries_count = Arc::new(AtomicUsize::new(0));
628 let processed_entries_count_clone = processed_entries_count.clone();
629
630 let decompress_result = decompress(cursor, CompressionFormat::Unknown, move |_archive_entry| {
631 processed_entries_count_clone.fetch_add(1, Ordering::SeqCst);
632 async move { Ok(()) }
633 })
634 .await;
635
636 assert!(decompress_result.is_err(), "Decompress with Unknown format should fail");
637 assert_eq!(
638 processed_entries_count.load(Ordering::SeqCst),
639 0,
640 "No entries should be processed with invalid format"
641 );
642 }
643
644 #[tokio::test]
645 async fn test_decompress_with_zip_format() {
646 use std::sync::Arc;
648 use std::sync::atomic::{AtomicUsize, Ordering};
649
650 let sample_content = b"Hello, compression world!";
651 let cursor = Cursor::new(sample_content);
652
653 let processed_entries_count = Arc::new(AtomicUsize::new(0));
654 let processed_entries_count_clone = processed_entries_count.clone();
655
656 let decompress_result = decompress(cursor, CompressionFormat::Zip, move |_archive_entry| {
657 processed_entries_count_clone.fetch_add(1, Ordering::SeqCst);
658 async move { Ok(()) }
659 })
660 .await;
661
662 assert!(decompress_result.is_err(), "Decompress with Zip format should fail (not implemented)");
663 assert_eq!(
664 processed_entries_count.load(Ordering::SeqCst),
665 0,
666 "No entries should be processed with unsupported format"
667 );
668 }
669
670 #[tokio::test]
671 async fn test_decompress_error_propagation() {
672 use std::sync::Arc;
674 use std::sync::atomic::{AtomicUsize, Ordering};
675
676 let sample_content = b"Hello, compression world!";
677 let cursor = Cursor::new(sample_content);
678
679 let callback_invocation_count = Arc::new(AtomicUsize::new(0));
680 let callback_invocation_count_clone = callback_invocation_count.clone();
681
682 let decompress_result = decompress(cursor, CompressionFormat::Gzip, move |_archive_entry| {
683 let invocation_number = callback_invocation_count_clone.fetch_add(1, Ordering::SeqCst);
684 async move {
685 if invocation_number == 0 {
686 Err(io::Error::other("Simulated callback error"))
688 } else {
689 Ok(())
690 }
691 }
692 })
693 .await;
694
695 assert!(decompress_result.is_err(), "Should propagate callback errors");
698 }
699
700 #[tokio::test]
701 async fn test_decompress_callback_execution() {
702 use std::sync::Arc;
704 use std::sync::atomic::{AtomicBool, Ordering};
705
706 let sample_content = b"Hello, compression world!";
707 let cursor = Cursor::new(sample_content);
708
709 let callback_was_invoked = Arc::new(AtomicBool::new(false));
710 let callback_was_invoked_clone = callback_was_invoked.clone();
711
712 let _decompress_result = decompress(cursor, CompressionFormat::Gzip, move |_archive_entry| {
713 callback_was_invoked_clone.store(true, Ordering::SeqCst);
714 async move { Ok(()) }
715 })
716 .await;
717
718 }
721
722 #[test]
723 fn test_compression_format_clone_and_copy() {
724 let format = CompressionFormat::Gzip;
726 let format_copy = format;
727
728 assert_eq!(format, format_copy);
730
731 assert_eq!(format, CompressionFormat::Gzip);
733 }
734
735 #[test]
736 fn test_compression_format_match_exhaustiveness() {
737 fn handle_format(format: CompressionFormat) -> &'static str {
739 match format {
740 CompressionFormat::Gzip => "gzip",
741 CompressionFormat::Bzip2 => "bzip2",
742 CompressionFormat::Zip => "zip",
743 CompressionFormat::Xz => "xz",
744 CompressionFormat::Zlib => "zlib",
745 CompressionFormat::Zstd => "zstd",
746 CompressionFormat::Tar => "tar",
747 CompressionFormat::Unknown => "unknown",
748 }
749 }
750
751 assert_eq!(handle_format(CompressionFormat::Gzip), "gzip");
753 assert_eq!(handle_format(CompressionFormat::Bzip2), "bzip2");
754 assert_eq!(handle_format(CompressionFormat::Zip), "zip");
755 assert_eq!(handle_format(CompressionFormat::Xz), "xz");
756 assert_eq!(handle_format(CompressionFormat::Zlib), "zlib");
757 assert_eq!(handle_format(CompressionFormat::Zstd), "zstd");
758 assert_eq!(handle_format(CompressionFormat::Unknown), "unknown");
759 }
760
761 #[test]
762 fn test_extension_parsing_performance() {
763 let extensions = vec!["gz", "bz2", "zip", "xz", "zlib", "zst", "unknown"];
765
766 for _ in 0..1000 {
768 for ext in &extensions {
769 let _format = CompressionFormat::from_extension(ext);
770 }
771 }
772
773 }
775
776 #[test]
777 fn test_format_default_behavior() {
778 let unknown_extensions = vec!["", "txt", "doc", "pdf", "unknown_ext"];
780
781 for ext in unknown_extensions {
782 let format = CompressionFormat::from_extension(ext);
783 assert_eq!(format, CompressionFormat::Unknown, "Extension '{ext}' should default to Unknown");
784 }
785 }
786
787 #[test]
788 fn test_compression_level() {
789 let default_level = CompressionLevel::default();
791 assert_eq!(default_level, CompressionLevel::Default);
792
793 let fastest = CompressionLevel::Fastest;
794 let best = CompressionLevel::Best;
795 let custom = CompressionLevel::Level(5);
796
797 assert_ne!(fastest, best);
798 assert_ne!(default_level, custom);
799 }
800
801 #[test]
802 fn test_format_extension() {
803 assert_eq!(CompressionFormat::Gzip.extension(), "gz");
805 assert_eq!(CompressionFormat::Bzip2.extension(), "bz2");
806 assert_eq!(CompressionFormat::Zip.extension(), "zip");
807 assert_eq!(CompressionFormat::Xz.extension(), "xz");
808 assert_eq!(CompressionFormat::Zlib.extension(), "zlib");
809 assert_eq!(CompressionFormat::Zstd.extension(), "zst");
810 assert_eq!(CompressionFormat::Tar.extension(), "tar");
811 assert_eq!(CompressionFormat::Unknown.extension(), "");
812 }
813
814 #[test]
815 fn test_format_is_supported() {
816 assert!(CompressionFormat::Gzip.is_supported());
818 assert!(CompressionFormat::Bzip2.is_supported());
819 assert!(CompressionFormat::Zip.is_supported());
820 assert!(CompressionFormat::Xz.is_supported());
821 assert!(CompressionFormat::Zlib.is_supported());
822 assert!(CompressionFormat::Zstd.is_supported());
823 assert!(CompressionFormat::Tar.is_supported());
824 assert!(!CompressionFormat::Unknown.is_supported());
825 }
826
827 #[test]
828 fn test_format_from_path() {
829 use std::path::Path;
831
832 assert_eq!(CompressionFormat::from_path("file.gz"), CompressionFormat::Gzip);
833 assert_eq!(CompressionFormat::from_path("archive.zip"), CompressionFormat::Zip);
834 assert_eq!(CompressionFormat::from_path("/path/to/file.tar.gz"), CompressionFormat::Gzip);
835 assert_eq!(CompressionFormat::from_path("no_extension"), CompressionFormat::Unknown);
836
837 let path = Path::new("test.bz2");
838 assert_eq!(CompressionFormat::from_path(path), CompressionFormat::Bzip2);
839 }
840
841 #[tokio::test]
842 async fn test_get_encoder_supported_formats() {
843 use std::io::Cursor;
845
846 let output = Vec::new();
847 let cursor = Cursor::new(output);
848
849 let gzip_format = CompressionFormat::Gzip;
850 let encoder_result = gzip_format.get_encoder(cursor, CompressionLevel::Default);
851 assert!(encoder_result.is_ok(), "Gzip encoder should be created successfully");
852 }
853
854 #[tokio::test]
855 async fn test_get_encoder_unsupported_formats() {
856 use std::io::Cursor;
858
859 let output1 = Vec::new();
860 let cursor1 = Cursor::new(output1);
861
862 let unknown_format = CompressionFormat::Unknown;
863 let encoder_result = unknown_format.get_encoder(cursor1, CompressionLevel::Default);
864 assert!(encoder_result.is_err(), "Unknown format should return error");
865
866 let output2 = Vec::new();
867 let cursor2 = Cursor::new(output2);
868 let zip_format = CompressionFormat::Zip;
869 let zip_encoder_result = zip_format.get_encoder(cursor2, CompressionLevel::Default);
870 assert!(zip_encoder_result.is_err(), "Zip format should return error (requires special handling)");
871 }
872
873 #[tokio::test]
874 async fn test_compressor_basic_functionality() {
875 let compressor = Compressor::new(CompressionFormat::Gzip);
877 let sample_text = b"Hello, World! This is a sample string for compression testing.";
878
879 let compression_result = compressor.compress(sample_text).await;
880 assert!(compression_result.is_ok(), "Compression should succeed");
881
882 let compressed_output = compression_result.unwrap();
884 assert!(compressed_output.is_empty(), "Current implementation returns empty vector as placeholder");
889 }
890
891 #[tokio::test]
892 async fn test_compressor_with_level() {
893 let compressor = Compressor::new(CompressionFormat::Gzip).with_level(CompressionLevel::Best);
895
896 let sample_text = b"Sample text for compression level testing";
897 let compression_result = compressor.compress(sample_text).await;
898 assert!(compression_result.is_ok(), "Compression with custom level should succeed");
899 }
900
901 #[test]
902 fn test_decompressor_creation() {
903 let decompressor = Decompressor::new(CompressionFormat::Gzip);
905 assert_eq!(decompressor.format, CompressionFormat::Gzip);
906
907 let auto_decompressor = Decompressor::auto_detect("test.gz");
908 assert_eq!(auto_decompressor.format, CompressionFormat::Gzip);
909 }
910
911 #[test]
912 fn test_zip_entry_creation() {
913 let entry = ZipEntry {
915 name: "test.txt".to_string(),
916 size: 1024,
917 compressed_size: 512,
918 is_dir: false,
919 compression_method: "Deflate".to_string(),
920 };
921
922 assert_eq!(entry.name, "test.txt");
923 assert_eq!(entry.size, 1024);
924 assert_eq!(entry.compressed_size, 512);
925 assert!(!entry.is_dir);
926 assert_eq!(entry.compression_method, "Deflate");
927 }
928
929 #[test]
930 fn test_compression_level_variants() {
931 let levels = vec![
933 CompressionLevel::Fastest,
934 CompressionLevel::Best,
935 CompressionLevel::Default,
936 CompressionLevel::Level(1),
937 CompressionLevel::Level(9),
938 ];
939
940 for level in levels {
941 let _debug_str = format!("{level:?}");
943 }
944 }
945
946 #[test]
947 fn test_format_comprehensive_coverage() {
948 let all_formats = vec![
950 CompressionFormat::Gzip,
951 CompressionFormat::Bzip2,
952 CompressionFormat::Zip,
953 CompressionFormat::Xz,
954 CompressionFormat::Zlib,
955 CompressionFormat::Zstd,
956 CompressionFormat::Tar,
957 CompressionFormat::Unknown,
958 ];
959
960 for format in all_formats {
961 let _ext = format.extension();
963
964 let _supported = format.is_supported();
966
967 let _debug = format!("{format:?}");
969 }
970 }
971}
972
973