exarch_core/creation/
config.rs1use crate::ExtractionError;
4use crate::Result;
5use crate::formats::detect::ArchiveType;
6use std::path::PathBuf;
7
8#[derive(Debug, Clone)]
27pub struct CreationConfig {
28 pub follow_symlinks: bool,
35
36 pub include_hidden: bool,
40
41 pub max_file_size: Option<u64>,
48
49 pub exclude_patterns: Vec<String>,
55
56 pub strip_prefix: Option<PathBuf>,
63
64 pub compression_level: Option<u8>,
73
74 pub preserve_permissions: bool,
78
79 pub format: Option<ArchiveType>,
85}
86
87impl Default for CreationConfig {
88 fn default() -> Self {
100 Self {
101 follow_symlinks: false,
102 include_hidden: false,
103 max_file_size: None,
104 exclude_patterns: vec![
105 ".git".to_string(),
106 ".DS_Store".to_string(),
107 "*.tmp".to_string(),
108 ],
109 strip_prefix: None,
110 compression_level: Some(6),
111 preserve_permissions: true,
112 format: None,
113 }
114 }
115}
116
117impl CreationConfig {
118 #[must_use]
120 pub fn new() -> Self {
121 Self::default()
122 }
123
124 #[must_use]
126 pub fn with_follow_symlinks(mut self, follow: bool) -> Self {
127 self.follow_symlinks = follow;
128 self
129 }
130
131 #[must_use]
133 pub fn with_include_hidden(mut self, include: bool) -> Self {
134 self.include_hidden = include;
135 self
136 }
137
138 #[must_use]
140 pub fn with_max_file_size(mut self, max_size: Option<u64>) -> Self {
141 self.max_file_size = max_size;
142 self
143 }
144
145 #[must_use]
147 pub fn with_exclude_patterns(mut self, patterns: Vec<String>) -> Self {
148 self.exclude_patterns = patterns;
149 self
150 }
151
152 #[must_use]
154 pub fn with_strip_prefix(mut self, prefix: Option<PathBuf>) -> Self {
155 self.strip_prefix = prefix;
156 self
157 }
158
159 #[must_use]
166 pub fn with_compression_level(mut self, level: u8) -> Self {
167 assert!((1..=9).contains(&level), "compression level must be 1-9");
168 self.compression_level = Some(level);
169 self
170 }
171
172 #[must_use]
174 pub fn with_preserve_permissions(mut self, preserve: bool) -> Self {
175 self.preserve_permissions = preserve;
176 self
177 }
178
179 #[must_use]
181 pub fn with_format(mut self, format: Option<ArchiveType>) -> Self {
182 self.format = format;
183 self
184 }
185
186 pub fn validate(&self) -> Result<()> {
193 if let Some(level) = self.compression_level
194 && !(1..=9).contains(&level)
195 {
196 return Err(ExtractionError::InvalidCompressionLevel { level });
197 }
198 Ok(())
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 #[test]
207 fn test_creation_config_default() {
208 let config = CreationConfig::default();
209 assert!(!config.follow_symlinks);
210 assert!(!config.include_hidden);
211 assert_eq!(config.max_file_size, None);
212 assert_eq!(config.exclude_patterns.len(), 3);
213 assert!(config.exclude_patterns.contains(&".git".to_string()));
214 assert!(config.exclude_patterns.contains(&".DS_Store".to_string()));
215 assert!(config.exclude_patterns.contains(&"*.tmp".to_string()));
216 assert_eq!(config.strip_prefix, None);
217 assert_eq!(config.compression_level, Some(6));
218 assert!(config.preserve_permissions);
219 assert_eq!(config.format, None);
220 }
221
222 #[test]
223 fn test_creation_config_builder() {
224 let config = CreationConfig::default()
225 .with_follow_symlinks(true)
226 .with_include_hidden(true)
227 .with_max_file_size(Some(1024 * 1024))
228 .with_exclude_patterns(vec!["*.log".to_string()])
229 .with_strip_prefix(Some(PathBuf::from("/base")))
230 .with_compression_level(9)
231 .with_preserve_permissions(false)
232 .with_format(Some(ArchiveType::TarGz));
233
234 assert!(config.follow_symlinks);
235 assert!(config.include_hidden);
236 assert_eq!(config.max_file_size, Some(1024 * 1024));
237 assert_eq!(config.exclude_patterns, vec!["*.log".to_string()]);
238 assert_eq!(config.strip_prefix, Some(PathBuf::from("/base")));
239 assert_eq!(config.compression_level, Some(9));
240 assert!(!config.preserve_permissions);
241 assert_eq!(config.format, Some(ArchiveType::TarGz));
242 }
243
244 #[test]
245 fn test_creation_config_validate_valid() {
246 let config = CreationConfig::default();
247 assert!(config.validate().is_ok());
248
249 let config = CreationConfig::default().with_compression_level(1);
250 assert!(config.validate().is_ok());
251
252 let config = CreationConfig::default().with_compression_level(9);
253 assert!(config.validate().is_ok());
254
255 let config = CreationConfig {
256 compression_level: None,
257 ..Default::default()
258 };
259 assert!(config.validate().is_ok());
260 }
261
262 #[test]
263 #[allow(clippy::unwrap_used)]
264 fn test_creation_config_validate_invalid() {
265 let config = CreationConfig {
266 compression_level: Some(0),
267 ..Default::default()
268 };
269 let result = config.validate();
270 assert!(result.is_err());
271 assert!(matches!(
272 result.unwrap_err(),
273 ExtractionError::InvalidCompressionLevel { level: 0 }
274 ));
275
276 let config = CreationConfig {
277 compression_level: Some(10),
278 ..Default::default()
279 };
280 let result = config.validate();
281 assert!(result.is_err());
282 assert!(matches!(
283 result.unwrap_err(),
284 ExtractionError::InvalidCompressionLevel { level: 10 }
285 ));
286 }
287
288 #[test]
289 #[should_panic(expected = "compression level must be 1-9")]
290 fn test_creation_config_builder_invalid_compression() {
291 let _config = CreationConfig::default().with_compression_level(0);
292 }
293
294 #[test]
295 fn test_creation_config_new() {
296 let config = CreationConfig::new();
297 assert_eq!(config.compression_level, Some(6));
298 assert!(config.preserve_permissions);
299 }
300
301 #[test]
302 fn test_creation_config_secure_defaults() {
303 let config = CreationConfig::default();
304
305 assert!(
307 !config.follow_symlinks,
308 "should not follow symlinks by default (security)"
309 );
310
311 assert!(
313 !config.include_hidden,
314 "should not include hidden files by default"
315 );
316
317 assert!(
319 config.exclude_patterns.contains(&".git".to_string()),
320 "should exclude .git by default"
321 );
322 }
323}