Skip to main content

exarch_core/
archive.rs

1//! Archive types and builders.
2
3use std::path::Path;
4use std::path::PathBuf;
5
6use crate::ExtractionReport;
7use crate::Result;
8use crate::SecurityConfig;
9
10/// Represents an archive file with associated metadata.
11#[derive(Debug)]
12pub struct Archive {
13    path: PathBuf,
14    config: SecurityConfig,
15}
16
17impl Archive {
18    /// Creates a new `Archive` from a file path.
19    ///
20    /// This constructor is infallible. I/O errors surface later when
21    /// [`Archive::extract`] is called.
22    ///
23    /// # Examples
24    ///
25    /// ```no_run
26    /// use exarch_core::Archive;
27    ///
28    /// let archive = Archive::open("archive.tar.gz");
29    /// ```
30    #[must_use]
31    pub fn open<P: AsRef<Path>>(path: P) -> Self {
32        let path = path.as_ref().to_path_buf();
33        Self {
34            path,
35            config: SecurityConfig::default(),
36        }
37    }
38
39    /// Returns the path to the archive file.
40    #[must_use]
41    pub fn path(&self) -> &Path {
42        &self.path
43    }
44
45    /// Returns a reference to the security configuration.
46    #[must_use]
47    pub fn config(&self) -> &SecurityConfig {
48        &self.config
49    }
50
51    /// Extracts the archive to the specified directory.
52    ///
53    /// # Errors
54    ///
55    /// Returns an error if extraction fails or security checks are violated.
56    pub fn extract<P: AsRef<Path>>(&self, output_dir: P) -> Result<ExtractionReport> {
57        crate::api::extract_archive(&self.path, output_dir.as_ref(), &self.config)
58    }
59}
60
61/// Builder for configuring archive extraction.
62///
63/// # Examples
64///
65/// ```no_run
66/// use exarch_core::ArchiveBuilder;
67/// use exarch_core::SecurityConfig;
68///
69/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
70/// let report = ArchiveBuilder::new()
71///     .archive("archive.tar.gz")
72///     .output_dir("/tmp/output")
73///     .config(SecurityConfig::permissive())
74///     .extract()?;
75/// # Ok(())
76/// # }
77/// ```
78#[derive(Debug, Default)]
79pub struct ArchiveBuilder {
80    archive_path: Option<PathBuf>,
81    output_dir: Option<PathBuf>,
82    config: Option<SecurityConfig>,
83}
84
85impl ArchiveBuilder {
86    /// Creates a new `ArchiveBuilder`.
87    #[must_use]
88    pub fn new() -> Self {
89        Self::default()
90    }
91
92    /// Sets the archive file path.
93    #[must_use]
94    pub fn archive<P: AsRef<Path>>(mut self, path: P) -> Self {
95        self.archive_path = Some(path.as_ref().to_path_buf());
96        self
97    }
98
99    /// Sets the output directory.
100    #[must_use]
101    pub fn output_dir<P: AsRef<Path>>(mut self, path: P) -> Self {
102        self.output_dir = Some(path.as_ref().to_path_buf());
103        self
104    }
105
106    /// Sets the security configuration.
107    #[must_use]
108    pub fn config(mut self, config: SecurityConfig) -> Self {
109        self.config = Some(config);
110        self
111    }
112
113    /// Executes the extraction with the configured settings.
114    ///
115    /// # Errors
116    ///
117    /// Returns an error if `archive_path` or `output_dir` are not set,
118    /// or if extraction fails.
119    pub fn extract(self) -> Result<ExtractionReport> {
120        let archive_path =
121            self.archive_path
122                .ok_or_else(|| crate::ExtractionError::InvalidConfiguration {
123                    reason: "archive path not set".to_string(),
124                })?;
125
126        let output_dir =
127            self.output_dir
128                .ok_or_else(|| crate::ExtractionError::InvalidConfiguration {
129                    reason: "output directory not set".to_string(),
130                })?;
131
132        let config = self.config.unwrap_or_default();
133
134        crate::api::extract_archive(archive_path, output_dir, &config)
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn test_archive_builder() {
144        let builder = ArchiveBuilder::new()
145            .archive("test.tar")
146            .output_dir("/tmp/test");
147
148        assert!(builder.archive_path.is_some());
149        assert!(builder.output_dir.is_some());
150    }
151
152    #[test]
153    fn test_archive_builder_missing_path() {
154        let builder = ArchiveBuilder::new().output_dir("/tmp/test");
155        let result = builder.extract();
156        assert!(matches!(
157            result,
158            Err(crate::ExtractionError::InvalidConfiguration { .. })
159        ));
160    }
161
162    #[test]
163    fn test_archive_builder_missing_output() {
164        let builder = ArchiveBuilder::new().archive("test.tar");
165        let result = builder.extract();
166        assert!(matches!(
167            result,
168            Err(crate::ExtractionError::InvalidConfiguration { .. })
169        ));
170    }
171}