use crate::ExtractionError;
use crate::Result;
use crate::formats::detect::ArchiveType;
use std::path::PathBuf;
#[derive(Debug, Clone)]
pub struct CreationConfig {
pub follow_symlinks: bool,
pub include_hidden: bool,
pub max_file_size: Option<u64>,
pub exclude_patterns: Vec<String>,
pub strip_prefix: Option<PathBuf>,
pub compression_level: Option<u8>,
pub preserve_permissions: bool,
pub format: Option<ArchiveType>,
}
impl Default for CreationConfig {
fn default() -> Self {
Self {
follow_symlinks: false,
include_hidden: false,
max_file_size: None,
exclude_patterns: vec![
".git".to_string(),
".DS_Store".to_string(),
"*.tmp".to_string(),
],
strip_prefix: None,
compression_level: Some(6),
preserve_permissions: true,
format: None,
}
}
}
impl CreationConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_follow_symlinks(mut self, follow: bool) -> Self {
self.follow_symlinks = follow;
self
}
#[must_use]
pub fn with_include_hidden(mut self, include: bool) -> Self {
self.include_hidden = include;
self
}
#[must_use]
pub fn with_max_file_size(mut self, max_size: Option<u64>) -> Self {
self.max_file_size = max_size;
self
}
#[must_use]
pub fn with_exclude_patterns(mut self, patterns: Vec<String>) -> Self {
self.exclude_patterns = patterns;
self
}
#[must_use]
pub fn with_strip_prefix(mut self, prefix: Option<PathBuf>) -> Self {
self.strip_prefix = prefix;
self
}
#[must_use]
pub fn with_compression_level(mut self, level: u8) -> Self {
assert!((1..=9).contains(&level), "compression level must be 1-9");
self.compression_level = Some(level);
self
}
#[must_use]
pub fn with_preserve_permissions(mut self, preserve: bool) -> Self {
self.preserve_permissions = preserve;
self
}
#[must_use]
pub fn with_format(mut self, format: Option<ArchiveType>) -> Self {
self.format = format;
self
}
pub fn validate(&self) -> Result<()> {
if let Some(level) = self.compression_level
&& !(1..=9).contains(&level)
{
return Err(ExtractionError::InvalidCompressionLevel { level });
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_creation_config_default() {
let config = CreationConfig::default();
assert!(!config.follow_symlinks);
assert!(!config.include_hidden);
assert_eq!(config.max_file_size, None);
assert_eq!(config.exclude_patterns.len(), 3);
assert!(config.exclude_patterns.contains(&".git".to_string()));
assert!(config.exclude_patterns.contains(&".DS_Store".to_string()));
assert!(config.exclude_patterns.contains(&"*.tmp".to_string()));
assert_eq!(config.strip_prefix, None);
assert_eq!(config.compression_level, Some(6));
assert!(config.preserve_permissions);
assert_eq!(config.format, None);
}
#[test]
fn test_creation_config_builder() {
let config = CreationConfig::default()
.with_follow_symlinks(true)
.with_include_hidden(true)
.with_max_file_size(Some(1024 * 1024))
.with_exclude_patterns(vec!["*.log".to_string()])
.with_strip_prefix(Some(PathBuf::from("/base")))
.with_compression_level(9)
.with_preserve_permissions(false)
.with_format(Some(ArchiveType::TarGz));
assert!(config.follow_symlinks);
assert!(config.include_hidden);
assert_eq!(config.max_file_size, Some(1024 * 1024));
assert_eq!(config.exclude_patterns, vec!["*.log".to_string()]);
assert_eq!(config.strip_prefix, Some(PathBuf::from("/base")));
assert_eq!(config.compression_level, Some(9));
assert!(!config.preserve_permissions);
assert_eq!(config.format, Some(ArchiveType::TarGz));
}
#[test]
fn test_creation_config_validate_valid() {
let config = CreationConfig::default();
assert!(config.validate().is_ok());
let config = CreationConfig::default().with_compression_level(1);
assert!(config.validate().is_ok());
let config = CreationConfig::default().with_compression_level(9);
assert!(config.validate().is_ok());
let config = CreationConfig {
compression_level: None,
..Default::default()
};
assert!(config.validate().is_ok());
}
#[test]
#[allow(clippy::unwrap_used)]
fn test_creation_config_validate_invalid() {
let config = CreationConfig {
compression_level: Some(0),
..Default::default()
};
let result = config.validate();
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
ExtractionError::InvalidCompressionLevel { level: 0 }
));
let config = CreationConfig {
compression_level: Some(10),
..Default::default()
};
let result = config.validate();
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
ExtractionError::InvalidCompressionLevel { level: 10 }
));
}
#[test]
#[should_panic(expected = "compression level must be 1-9")]
fn test_creation_config_builder_invalid_compression() {
let _config = CreationConfig::default().with_compression_level(0);
}
#[test]
fn test_creation_config_new() {
let config = CreationConfig::new();
assert_eq!(config.compression_level, Some(6));
assert!(config.preserve_permissions);
}
#[test]
fn test_creation_config_secure_defaults() {
let config = CreationConfig::default();
assert!(
!config.follow_symlinks,
"should not follow symlinks by default (security)"
);
assert!(
!config.include_hidden,
"should not include hidden files by default"
);
assert!(
config.exclude_patterns.contains(&".git".to_string()),
"should exclude .git by default"
);
}
}