Skip to main content

sheetkit_core/workbook/
open_options.rs

1/// Options for controlling how a workbook is opened and parsed.
2///
3/// All fields default to `None` (no limit / parse everything).
4/// Use the builder-style setter methods for convenience.
5#[derive(Debug, Clone, Default)]
6pub struct OpenOptions {
7    /// Maximum number of rows to read per sheet. Rows beyond this limit
8    /// are silently discarded during parsing.
9    pub sheet_rows: Option<u32>,
10
11    /// Only parse sheets whose names are in this list. Sheets not listed
12    /// are represented as empty worksheets (their XML is not parsed).
13    /// `None` means parse all sheets.
14    pub sheets: Option<Vec<String>>,
15
16    /// Maximum total decompressed size of all ZIP entries in bytes.
17    /// Exceeding this limit returns [`Error::ZipSizeExceeded`].
18    /// Default when `None`: no limit.
19    pub max_unzip_size: Option<u64>,
20
21    /// Maximum number of ZIP entries allowed.
22    /// Exceeding this limit returns [`Error::ZipEntryCountExceeded`].
23    /// Default when `None`: no limit.
24    pub max_zip_entries: Option<usize>,
25}
26
27impl OpenOptions {
28    /// Create a new `OpenOptions` with all defaults (no limits, parse everything).
29    pub fn new() -> Self {
30        Self::default()
31    }
32
33    /// Set the maximum number of rows to read per sheet.
34    pub fn sheet_rows(mut self, rows: u32) -> Self {
35        self.sheet_rows = Some(rows);
36        self
37    }
38
39    /// Only parse sheets whose names are in this list.
40    pub fn sheets(mut self, names: Vec<String>) -> Self {
41        self.sheets = Some(names);
42        self
43    }
44
45    /// Set the maximum total decompressed size in bytes.
46    pub fn max_unzip_size(mut self, size: u64) -> Self {
47        self.max_unzip_size = Some(size);
48        self
49    }
50
51    /// Set the maximum number of ZIP entries.
52    pub fn max_zip_entries(mut self, count: usize) -> Self {
53        self.max_zip_entries = Some(count);
54        self
55    }
56
57    /// Check whether a given sheet name should be parsed based on the `sheets` filter.
58    pub(crate) fn should_parse_sheet(&self, name: &str) -> bool {
59        match &self.sheets {
60            None => true,
61            Some(names) => names.iter().any(|n| n == name),
62        }
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn test_default_options() {
72        let opts = OpenOptions::default();
73        assert!(opts.sheet_rows.is_none());
74        assert!(opts.sheets.is_none());
75        assert!(opts.max_unzip_size.is_none());
76        assert!(opts.max_zip_entries.is_none());
77    }
78
79    #[test]
80    fn test_builder_methods() {
81        let opts = OpenOptions::new()
82            .sheet_rows(100)
83            .sheets(vec!["Sheet1".to_string()])
84            .max_unzip_size(1_000_000)
85            .max_zip_entries(500);
86        assert_eq!(opts.sheet_rows, Some(100));
87        assert_eq!(opts.sheets, Some(vec!["Sheet1".to_string()]));
88        assert_eq!(opts.max_unzip_size, Some(1_000_000));
89        assert_eq!(opts.max_zip_entries, Some(500));
90    }
91
92    #[test]
93    fn test_should_parse_sheet_no_filter() {
94        let opts = OpenOptions::default();
95        assert!(opts.should_parse_sheet("Sheet1"));
96        assert!(opts.should_parse_sheet("anything"));
97    }
98
99    #[test]
100    fn test_should_parse_sheet_with_filter() {
101        let opts = OpenOptions::new().sheets(vec!["Sales".to_string(), "Data".to_string()]);
102        assert!(opts.should_parse_sheet("Sales"));
103        assert!(opts.should_parse_sheet("Data"));
104        assert!(!opts.should_parse_sheet("Sheet1"));
105        assert!(!opts.should_parse_sheet("Other"));
106    }
107}