exarch_core/creation/
config.rs1use crate::ArchiveError;
4use crate::Result;
5use crate::formats::detect::ArchiveType;
6use std::path::PathBuf;
7
8#[derive(Debug, Clone)]
30pub struct CreationConfig {
31 pub follow_symlinks: bool,
38
39 pub include_hidden: bool,
43
44 pub max_file_size: Option<u64>,
51
52 pub exclude_patterns: Vec<String>,
58
59 pub strip_prefix: Option<PathBuf>,
66
67 pub compression_level: Option<u8>,
76
77 pub preserve_permissions: bool,
81
82 pub format: Option<ArchiveType>,
88}
89
90impl Default for CreationConfig {
91 fn default() -> Self {
103 Self {
104 follow_symlinks: false,
105 include_hidden: false,
106 max_file_size: None,
107 exclude_patterns: vec![
108 ".git".to_string(),
109 ".DS_Store".to_string(),
110 "*.tmp".to_string(),
111 ],
112 strip_prefix: None,
113 compression_level: Some(6),
114 preserve_permissions: true,
115 format: None,
116 }
117 }
118}
119
120impl CreationConfig {
121 #[must_use]
123 pub fn new() -> Self {
124 Self::default()
125 }
126
127 #[must_use]
129 pub fn with_follow_symlinks(mut self, follow: bool) -> Self {
130 self.follow_symlinks = follow;
131 self
132 }
133
134 #[must_use]
136 pub fn with_include_hidden(mut self, include: bool) -> Self {
137 self.include_hidden = include;
138 self
139 }
140
141 #[must_use]
143 pub fn with_max_file_size(mut self, max_size: Option<u64>) -> Self {
144 self.max_file_size = max_size;
145 self
146 }
147
148 #[must_use]
150 pub fn with_exclude_patterns(mut self, patterns: Vec<String>) -> Self {
151 self.exclude_patterns = patterns;
152 self
153 }
154
155 #[must_use]
157 pub fn with_strip_prefix(mut self, prefix: Option<PathBuf>) -> Self {
158 self.strip_prefix = prefix;
159 self
160 }
161
162 pub fn with_compression_level(mut self, level: u8) -> Result<Self> {
169 if !(1..=9).contains(&level) {
170 return Err(ArchiveError::InvalidCompressionLevel { level });
171 }
172 self.compression_level = Some(level);
173 Ok(self)
174 }
175
176 #[must_use]
178 pub fn with_preserve_permissions(mut self, preserve: bool) -> Self {
179 self.preserve_permissions = preserve;
180 self
181 }
182
183 #[must_use]
185 pub fn with_format(mut self, format: Option<ArchiveType>) -> Self {
186 self.format = format;
187 self
188 }
189
190 pub fn validate(&self) -> Result<()> {
197 if let Some(level) = self.compression_level
198 && !(1..=9).contains(&level)
199 {
200 return Err(ArchiveError::InvalidCompressionLevel { level });
201 }
202 Ok(())
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn test_creation_config_default() {
212 let config = CreationConfig::default();
213 assert!(!config.follow_symlinks);
214 assert!(!config.include_hidden);
215 assert_eq!(config.max_file_size, None);
216 assert_eq!(config.exclude_patterns.len(), 3);
217 assert!(config.exclude_patterns.contains(&".git".to_string()));
218 assert!(config.exclude_patterns.contains(&".DS_Store".to_string()));
219 assert!(config.exclude_patterns.contains(&"*.tmp".to_string()));
220 assert_eq!(config.strip_prefix, None);
221 assert_eq!(config.compression_level, Some(6));
222 assert!(config.preserve_permissions);
223 assert_eq!(config.format, None);
224 }
225
226 #[test]
227 #[allow(clippy::unwrap_used)]
228 fn test_creation_config_builder() {
229 let config = CreationConfig::default()
230 .with_follow_symlinks(true)
231 .with_include_hidden(true)
232 .with_max_file_size(Some(1024 * 1024))
233 .with_exclude_patterns(vec!["*.log".to_string()])
234 .with_strip_prefix(Some(PathBuf::from("/base")))
235 .with_compression_level(9)
236 .unwrap()
237 .with_preserve_permissions(false)
238 .with_format(Some(ArchiveType::TarGz));
239
240 assert!(config.follow_symlinks);
241 assert!(config.include_hidden);
242 assert_eq!(config.max_file_size, Some(1024 * 1024));
243 assert_eq!(config.exclude_patterns, vec!["*.log".to_string()]);
244 assert_eq!(config.strip_prefix, Some(PathBuf::from("/base")));
245 assert_eq!(config.compression_level, Some(9));
246 assert!(!config.preserve_permissions);
247 assert_eq!(config.format, Some(ArchiveType::TarGz));
248 }
249
250 #[test]
251 #[allow(clippy::unwrap_used)]
252 fn test_creation_config_validate_valid() {
253 let config = CreationConfig::default();
254 assert!(config.validate().is_ok());
255
256 let config = CreationConfig::default().with_compression_level(1).unwrap();
257 assert!(config.validate().is_ok());
258
259 let config = CreationConfig::default().with_compression_level(9).unwrap();
260 assert!(config.validate().is_ok());
261
262 let config = CreationConfig {
263 compression_level: None,
264 ..Default::default()
265 };
266 assert!(config.validate().is_ok());
267 }
268
269 #[test]
270 #[allow(clippy::unwrap_used)]
271 fn test_creation_config_validate_invalid() {
272 let config = CreationConfig {
273 compression_level: Some(0),
274 ..Default::default()
275 };
276 let result = config.validate();
277 assert!(result.is_err());
278 assert!(matches!(
279 result.unwrap_err(),
280 ArchiveError::InvalidCompressionLevel { level: 0 }
281 ));
282
283 let config = CreationConfig {
284 compression_level: Some(10),
285 ..Default::default()
286 };
287 let result = config.validate();
288 assert!(result.is_err());
289 assert!(matches!(
290 result.unwrap_err(),
291 ArchiveError::InvalidCompressionLevel { level: 10 }
292 ));
293 }
294
295 #[test]
296 fn test_creation_config_builder_invalid_compression() {
297 assert!(matches!(
298 CreationConfig::default().with_compression_level(0),
299 Err(ArchiveError::InvalidCompressionLevel { level: 0 })
300 ));
301 assert!(matches!(
302 CreationConfig::default().with_compression_level(10),
303 Err(ArchiveError::InvalidCompressionLevel { level: 10 })
304 ));
305 }
306
307 #[test]
308 fn test_creation_config_new() {
309 let config = CreationConfig::new();
310 assert_eq!(config.compression_level, Some(6));
311 assert!(config.preserve_permissions);
312 }
313
314 #[test]
315 fn test_creation_config_secure_defaults() {
316 let config = CreationConfig::default();
317
318 assert!(
320 !config.follow_symlinks,
321 "should not follow symlinks by default (security)"
322 );
323
324 assert!(
326 !config.include_hidden,
327 "should not include hidden files by default"
328 );
329
330 assert!(
332 config.exclude_patterns.contains(&".git".to_string()),
333 "should exclude .git by default"
334 );
335 }
336}