use crate::cli::CliResult;
use crate::PackageAction;
use std::path::PathBuf;
pub async fn run(action: PackageAction) -> CliResult<()> {
match action {
PackageAction::Import {
file,
models_root,
dry_run,
details,
force,
} => import(file, models_root, dry_run, details, force).await,
PackageAction::Export {
input,
output,
version,
} => export(input, output, version).await,
}
}
async fn import(
file: PathBuf,
models_root: PathBuf,
dry_run: bool,
details: bool,
force: bool,
) -> CliResult<()> {
println!("Importing namespace package...");
println!(" Package: {}", file.display());
println!(" Target Directory: {}", models_root.display());
if dry_run {
println!(" Mode: DRY RUN (no files will be written)");
if details {
println!(" Details: ENABLED");
}
} else if force {
println!(" Force: ENABLED (existing files will be overwritten)");
}
println!();
if !file.exists() {
return Err(format!("Package file not found: {}", file.display()).into());
}
if !file.extension().is_some_and(|ext| ext == "zip") {
return Err(format!(
"Invalid package format: {}\nExpected .zip file",
file.display()
)
.into());
}
let result = oxirs_samm::package::import_package(&file, &models_root, dry_run, force)
.await
.map_err(|e| format!("Failed to import package: {}", e))?;
println!("✓ Import analysis complete\n");
println!("Package Structure:");
println!(" Namespaces: {}", result.namespaces.len());
println!(" Total Models: {}", result.total_models);
println!();
println!("Models by Namespace:");
for (namespace, models) in &result.namespaces {
println!(" {} ({})", namespace, models.len());
for model in models {
let status = if model.exists && !force {
"SKIP (exists)"
} else if model.exists && force {
"OVERWRITE"
} else {
"NEW"
};
println!(" - {} [{}]", model.name, status);
if details && dry_run {
println!(" Path: {}", model.path.display());
println!(" Version: {}", model.version);
}
}
println!();
}
if dry_run {
println!("ℹ No files were written (--dry-run mode)");
println!("Run without --dry-run to perform the actual import.");
} else {
println!(
"✓ Successfully imported {} model(s) to {}",
result.total_models,
models_root.display()
);
if result.skipped > 0 {
println!(
" ⚠ Skipped {} existing file(s) (use --force to overwrite)",
result.skipped
);
}
}
Ok(())
}
async fn export(input: String, output: PathBuf, version: Option<String>) -> CliResult<()> {
println!("Exporting namespace package...");
let is_urn = input.starts_with("urn:");
if is_urn {
println!(" Source: URN ({})", input);
if let Some(ref ver) = version {
println!(" Version Filter: {}", ver);
}
} else {
println!(" Source: File ({})", input);
}
println!(" Output: {}", output.display());
println!();
if !is_urn {
let input_path = PathBuf::from(&input);
if !input_path.exists() {
return Err(format!("Aspect Model file not found: {}", input).into());
}
if !input_path.extension().is_some_and(|ext| ext == "ttl") {
return Err(
format!("Invalid Aspect Model format: {}\nExpected .ttl file", input).into(),
);
}
}
if output.exists() {
return Err(format!(
"Output file already exists: {}\nPlease remove it first or choose a different path",
output.display()
)
.into());
}
let result = if is_urn {
oxirs_samm::package::export_from_urn(&input, &output, version.as_deref())
.await
.map_err(|e| format!("Failed to export package: {}", e))?
} else {
oxirs_samm::package::export_from_file(&input, &output)
.await
.map_err(|e| format!("Failed to export package: {}", e))?
};
println!("✓ Export complete\n");
println!("Package Contents:");
println!(" Namespace: {}", result.namespace);
println!(" Version: {}", result.version);
println!(" Models: {}", result.models.len());
println!();
println!("Exported Models:");
for model in &result.models {
println!(" - {}", model);
}
println!();
println!("✓ Successfully exported to {}", output.display());
println!(" Package size: {} KB", result.size_bytes / 1024);
Ok(())
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
#[test]
fn test_import_validates_zip_extension() {
let file = PathBuf::from("test.txt");
assert!(!file.extension().is_some_and(|ext| ext == "zip"));
}
#[test]
fn test_export_validates_ttl_extension() {
let file = PathBuf::from("test.ttl");
assert!(file.extension().is_some_and(|ext| ext == "ttl"));
}
#[test]
fn test_is_urn_detection() {
assert!("urn:samm:org.example:1.0.0".starts_with("urn:"));
assert!(!"AspectModel.ttl".starts_with("urn:"));
}
}