pub mod config;
pub mod error;
use colored::*;
use std::fs;
use std::path::PathBuf;
use crate::config::{mask_config, ConfigFormat};
use crate::error::{MaskitError, Result};
pub struct MaskitProcessor {
input_path: PathBuf,
output_path: Option<PathBuf>,
keywords: Vec<String>,
}
impl MaskitProcessor {
pub fn new(input_path: PathBuf, output_path: Option<PathBuf>, keywords: Vec<String>) -> Self {
Self {
input_path,
output_path,
keywords,
}
}
fn generate_output_path(&self) -> Result<PathBuf> {
let file_name = self
.input_path
.file_name()
.and_then(|name| name.to_str())
.ok_or_else(|| MaskitError::InvalidPath("Invalid input file name".to_string()))?;
let extension = self
.input_path
.extension()
.and_then(|ext| ext.to_str())
.unwrap_or("");
let base_name = if !extension.is_empty() && file_name.ends_with(&format!(".{}", extension))
{
&file_name[..file_name.len() - extension.len() - 1]
} else {
file_name
};
let new_name = if extension.is_empty() {
format!("{}.maskit", base_name)
} else {
format!("{}.maskit.{}", base_name, extension)
};
Ok(PathBuf::from(new_name))
}
pub fn process(&self, silent: bool) -> Result<()> {
if !silent {
println!("{}", "🔒 Maskit - Configuration Masking Tool".cyan().bold());
}
if !self.input_path.exists() {
return Err(MaskitError::FileRead(format!(
"File not found: {}",
self.input_path.display()
)));
}
let format = ConfigFormat::from_path(&self.input_path)?;
if !silent {
println!("{} {}", "📄 Input:".green(), self.input_path.display());
println!("{} {:?}", "📋 Format:".green(), format);
}
let content = fs::read_to_string(&self.input_path)
.map_err(|e| MaskitError::FileRead(e.to_string()))?;
if !silent {
println!("{} Masking sensitive fields...", "🔍".yellow());
println!("{} {:?}", "📝 Keywords:".green(), self.keywords);
}
let masked_content = mask_config(&content, format, &self.keywords)?;
let output_path = match &self.output_path {
Some(path) => path.clone(),
None => self.generate_output_path()?,
};
fs::write(&output_path, masked_content)
.map_err(|e| MaskitError::FileWrite(e.to_string()))?;
if !silent {
println!("{} {}", "✅ Output:".green().bold(), output_path.display());
println!("{}", "🎉 Masking completed successfully!".green().bold());
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_generate_output_path() {
let processor =
MaskitProcessor::new(PathBuf::from("config.json"), None, vec!["key".to_string()]);
let output_path = processor.generate_output_path().unwrap();
assert_eq!(output_path.to_str().unwrap(), "config.maskit.json");
}
#[test]
fn test_generate_output_path_complex() {
let processor = MaskitProcessor::new(
PathBuf::from("/path/to/my.config.yaml"),
None,
vec!["key".to_string()],
);
let output_path = processor.generate_output_path().unwrap();
assert_eq!(output_path.to_str().unwrap(), "my.config.maskit.yaml");
}
#[test]
fn test_process_json_file() {
let temp_dir = TempDir::new().unwrap();
let input_path = temp_dir.path().join("test.json");
let json_content = r#"{
"api_key": "secret123",
"base_url": "http://example.com"
}"#;
fs::write(&input_path, json_content).unwrap();
let original_dir = std::env::current_dir().unwrap();
std::env::set_current_dir(&temp_dir).unwrap();
let processor = MaskitProcessor::new(input_path.clone(), None, vec!["key".to_string()]);
processor.process(false).unwrap();
let output_path = temp_dir.path().join("test.maskit.json");
assert!(output_path.exists());
let masked_content = fs::read_to_string(output_path).unwrap();
assert!(masked_content.contains("*****"));
assert!(masked_content.contains("http://example.com"));
std::env::set_current_dir(original_dir).unwrap();
}
}