Skip to main content

s_zip/
lib.rs

1//! # s-zip: High-Performance Streaming ZIP Library
2//!
3//! `s-zip` is a lightweight, high-performance ZIP library focused on streaming operations
4//! with minimal memory footprint. Perfect for working with large ZIP files without loading
5//! everything into memory.
6//!
7//! ## Features
8//!
9//! - **Streaming Read**: Read ZIP entries on-the-fly without loading entire archive
10//! - **Streaming Write**: Write ZIP files with on-the-fly compression, no temp files
11//! - **Low Memory**: Constant memory usage regardless of ZIP file size
12//! - **Fast**: Optimized for performance with minimal allocations
13//! - **Simple API**: Easy to use, intuitive interface
14//!
15//! ## Quick Start
16//!
17//! ### Reading a ZIP file
18//!
19//! ```no_run
20//! use s_zip::StreamingZipReader;
21//!
22//! let mut reader = StreamingZipReader::open("archive.zip")?;
23//!
24//! // List all entries
25//! for entry in reader.entries() {
26//!     println!("{}: {} bytes", entry.name, entry.uncompressed_size);
27//! }
28//!
29//! // Read a specific file
30//! let data = reader.read_entry_by_name("file.txt")?;
31//! # Ok::<(), s_zip::SZipError>(())
32//! ```
33//!
34//! ### Writing a ZIP file
35//!
36//! ```no_run
37//! use s_zip::StreamingZipWriter;
38//!
39//! let mut writer = StreamingZipWriter::new("output.zip")?;
40//!
41//! writer.start_entry("file1.txt")?;
42//! writer.write_data(b"Hello, World!")?;
43//!
44//! writer.start_entry("file2.txt")?;
45//! writer.write_data(b"Another file")?;
46//!
47//! writer.finish()?;
48//! # Ok::<(), s_zip::SZipError>(())
49//! ```
50//!
51//! ### Using arbitrary writers (in-memory, network, etc.)
52//!
53//! ```no_run
54//! use s_zip::StreamingZipWriter;
55//! use std::io::Cursor;
56//!
57//! // Write ZIP to in-memory buffer
58//! let buffer = Vec::new();
59//! let cursor = Cursor::new(buffer);
60//! let mut writer = StreamingZipWriter::from_writer(cursor)?;
61//!
62//! writer.start_entry("data.txt")?;
63//! writer.write_data(b"In-memory ZIP content")?;
64//!
65//! // finish() returns the writer, allowing you to extract the data
66//! let cursor = writer.finish()?;
67//! let zip_bytes = cursor.into_inner();
68//!
69//! println!("Created ZIP with {} bytes", zip_bytes.len());
70//! # Ok::<(), s_zip::SZipError>(())
71//! ```
72
73pub mod error;
74pub mod reader;
75pub mod writer;
76
77#[cfg(feature = "encryption")]
78pub mod encryption;
79
80#[cfg(feature = "async")]
81pub mod async_writer;
82
83#[cfg(feature = "async")]
84pub mod async_reader;
85
86#[cfg(feature = "async")]
87pub mod parallel;
88
89#[cfg(any(feature = "cloud-s3", feature = "cloud-gcs"))]
90pub mod cloud;
91
92pub use error::{Result, SZipError};
93pub use reader::{StreamingZipReader, ZipEntry};
94pub use writer::{CompressionMethod, StreamingZipWriter};
95
96#[cfg(feature = "encryption")]
97pub use encryption::AesStrength;
98
99#[cfg(feature = "async")]
100pub use async_writer::AsyncStreamingZipWriter;
101
102#[cfg(feature = "async")]
103pub use async_reader::{AsyncStreamingZipReader, GenericAsyncZipReader};
104
105#[cfg(feature = "async")]
106pub use parallel::{ParallelConfig, ParallelEntry};
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use std::io::Cursor;
112
113    #[test]
114    fn test_basic_write_read_roundtrip() {
115        // Create ZIP in memory
116        let buffer = Vec::new();
117        let cursor = Cursor::new(buffer);
118        let mut writer = StreamingZipWriter::from_writer(cursor).unwrap();
119
120        // Add first file
121        writer.start_entry("test1.txt").unwrap();
122        writer.write_data(b"Hello, World!").unwrap();
123
124        // Add second file
125        writer.start_entry("test2.txt").unwrap();
126        writer.write_data(b"Testing s-zip library").unwrap();
127
128        // Finish and get ZIP bytes
129        let cursor = writer.finish().unwrap();
130        let zip_bytes = cursor.into_inner();
131
132        // Verify ZIP was created
133        assert!(!zip_bytes.is_empty(), "ZIP should not be empty");
134
135        // Verify ZIP has correct signature
136        assert_eq!(
137            &zip_bytes[0..4],
138            b"PK\x03\x04",
139            "Should start with ZIP signature"
140        );
141    }
142
143    #[test]
144    fn test_compression_method_to_zip_method() {
145        assert_eq!(CompressionMethod::Stored.to_zip_method(), 0);
146        assert_eq!(CompressionMethod::Deflate.to_zip_method(), 8);
147
148        #[cfg(feature = "zstd-support")]
149        assert_eq!(CompressionMethod::Zstd.to_zip_method(), 93);
150    }
151
152    #[test]
153    fn test_empty_entry_name() {
154        let buffer = Vec::new();
155        let cursor = Cursor::new(buffer);
156        let mut writer = StreamingZipWriter::from_writer(cursor).unwrap();
157
158        // Try to create entry with empty name - should succeed (valid in ZIP spec)
159        assert!(writer.start_entry("").is_ok());
160    }
161
162    #[test]
163    fn test_multiple_small_entries() {
164        let buffer = Vec::new();
165        let cursor = Cursor::new(buffer);
166        let mut writer = StreamingZipWriter::from_writer(cursor).unwrap();
167
168        // Add 10 small files
169        for i in 0..10 {
170            let entry_name = format!("file_{}.txt", i);
171            let entry_data = format!("Content of file {}", i);
172
173            writer.start_entry(&entry_name).unwrap();
174            writer.write_data(entry_data.as_bytes()).unwrap();
175        }
176
177        let cursor = writer.finish().unwrap();
178        let zip_bytes = cursor.into_inner();
179
180        // Verify ZIP was created and has reasonable size
181        assert!(zip_bytes.len() > 100, "ZIP with 10 files should be larger");
182    }
183
184    #[test]
185    fn test_error_display() {
186        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
187        let err = SZipError::from(io_err);
188        assert!(format!("{}", err).contains("I/O error"));
189
190        let invalid_err = SZipError::InvalidFormat("bad format".to_string());
191        assert!(format!("{}", invalid_err).contains("Invalid ZIP format"));
192
193        let not_found_err = SZipError::EntryNotFound("missing.txt".to_string());
194        assert!(format!("{}", not_found_err).contains("Entry not found"));
195    }
196
197    #[cfg(feature = "encryption")]
198    #[test]
199    fn test_aes_strength() {
200        assert_eq!(AesStrength::Aes256.salt_size(), 16);
201        assert_eq!(AesStrength::Aes256.key_size(), 32);
202        assert_eq!(AesStrength::Aes256.to_winzip_code(), 0x03);
203    }
204}