use anyhow::Result;
use clap::Subcommand;
use colored::Colorize;
use serde::Serialize;
use crate::commands::tracked::tracked_op;
use crate::output::OutputFormat;
use raps_dm::DataManagementClient;
#[derive(Debug, Subcommand)]
pub enum ItemCommands {
Info {
project_id: String,
item_id: String,
},
Versions {
project_id: String,
item_id: String,
},
#[command(name = "create-from-oss")]
CreateFromOss {
project_id: String,
folder_id: String,
#[arg(short, long)]
name: String,
#[arg(long)]
object_id: String,
},
Delete {
project_id: String,
item_id: String,
},
Rename {
project_id: String,
item_id: String,
#[arg(short, long)]
name: String,
},
}
impl ItemCommands {
pub async fn execute(
self,
client: &DataManagementClient,
output_format: OutputFormat,
) -> Result<()> {
match self {
ItemCommands::Info {
project_id,
item_id,
} => item_info(client, &project_id, &item_id, output_format).await,
ItemCommands::Versions {
project_id,
item_id,
} => list_versions(client, &project_id, &item_id, output_format).await,
ItemCommands::CreateFromOss {
project_id,
folder_id,
name,
object_id,
} => {
create_from_oss(
client,
&project_id,
&folder_id,
&name,
&object_id,
output_format,
)
.await
}
ItemCommands::Delete {
project_id,
item_id,
} => delete_item(client, &project_id, &item_id, output_format).await,
ItemCommands::Rename {
project_id,
item_id,
name,
} => rename_item(client, &project_id, &item_id, &name, output_format).await,
}
}
}
#[derive(Serialize)]
struct ItemInfoOutput {
id: String,
name: String,
item_type: String,
create_time: Option<String>,
modified_time: Option<String>,
extension_type: Option<String>,
extension_version: Option<String>,
}
async fn item_info(
client: &DataManagementClient,
project_id: &str,
item_id: &str,
output_format: OutputFormat,
) -> Result<()> {
let item = tracked_op("Fetching item details", output_format, || {
client.get_item(project_id, item_id)
})
.await?;
let extension_type = item
.attributes
.extension
.as_ref()
.and_then(|e| e.extension_type.clone());
let extension_version = item
.attributes
.extension
.as_ref()
.and_then(|e| e.version.clone());
let output = ItemInfoOutput {
id: item.id.clone(),
name: item.attributes.display_name.clone(),
item_type: item.item_type.clone(),
create_time: item.attributes.create_time.clone(),
modified_time: item.attributes.last_modified_time.clone(),
extension_type,
extension_version,
};
match output_format {
OutputFormat::Table => {
println!("\n{}", "Item Details".bold());
println!("{}", "-".repeat(60));
println!(" {} {}", "Name:".bold(), output.name.cyan());
println!(" {} {}", "ID:".bold(), output.id);
println!(" {} {}", "Type:".bold(), output.item_type);
if let Some(ref create_time) = output.create_time {
println!(" {} {}", "Created:".bold(), create_time);
}
if let Some(ref modified_time) = output.modified_time {
println!(" {} {}", "Modified:".bold(), modified_time);
}
if let Some(ref ext_type) = output.extension_type {
println!(" {} {}", "Extension:".bold(), ext_type);
}
if let Some(version) = output.extension_version {
println!(" {} {}", "Ext Version:".bold(), version);
}
println!("{}", "-".repeat(60));
println!(
"\n{}",
"Use 'raps item versions' to see version history".dimmed()
);
}
_ => {
output_format.write(&output)?;
}
}
Ok(())
}
#[derive(Serialize)]
struct VersionOutput {
version_number: Option<i32>,
name: String,
size: Option<u64>,
size_human: Option<String>,
create_time: Option<String>,
}
async fn list_versions(
client: &DataManagementClient,
project_id: &str,
item_id: &str,
output_format: OutputFormat,
) -> Result<()> {
let versions = tracked_op("Fetching item versions", output_format, || {
client.get_item_versions(project_id, item_id)
})
.await?;
let version_outputs: Vec<VersionOutput> = versions
.iter()
.map(|v| {
let name = v
.attributes
.display_name
.as_ref()
.or(Some(&v.attributes.name))
.cloned()
.unwrap_or_default();
VersionOutput {
version_number: v.attributes.version_number,
name,
size: v.attributes.storage_size.map(|s| s as u64),
size_human: v.attributes.storage_size.map(|s| format_size(s as u64)),
create_time: v.attributes.create_time.clone(),
}
})
.collect();
if version_outputs.is_empty() {
match output_format {
OutputFormat::Table => println!("{}", "No versions found.".yellow()),
_ => {
output_format.write(&Vec::<VersionOutput>::new())?;
}
}
return Ok(());
}
match output_format {
OutputFormat::Table => {
println!("\n{}", "Item Versions:".bold());
println!("{}", "-".repeat(80));
println!(
"{:<6} {:<40} {:>12} {}",
"Ver".bold(),
"Name".bold(),
"Size".bold(),
"Created".bold()
);
println!("{}", "-".repeat(80));
for version in &version_outputs {
let ver_num = version
.version_number
.map(|n| n.to_string())
.unwrap_or_else(|| "-".to_string());
let name = truncate_str(&version.name, 40);
let size = version.size_human.as_deref().unwrap_or("-");
let created = version.create_time.as_deref().unwrap_or("-");
println!(
"{:<6} {:<40} {:>12} {}",
ver_num.cyan(),
name,
size,
created.dimmed()
);
}
println!("{}", "-".repeat(80));
}
_ => {
output_format.write(&version_outputs)?;
}
}
Ok(())
}
fn format_size(bytes: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024;
if bytes >= GB {
format!("{:.2} GB", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.2} MB", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.2} KB", bytes as f64 / KB as f64)
} else {
format!("{} B", bytes)
}
}
fn truncate_str(s: &str, max_len: usize) -> String {
if s.len() <= max_len {
s.to_string()
} else {
format!("{}...", &s[..max_len - 3])
}
}
#[derive(Serialize)]
struct CreateFromOssOutput {
success: bool,
item_id: String,
name: String,
message: String,
}
async fn create_from_oss(
client: &DataManagementClient,
project_id: &str,
folder_id: &str,
name: &str,
object_id: &str,
output_format: OutputFormat,
) -> Result<()> {
if output_format.supports_colors() {
println!("{}", "Creating item from OSS object...".dimmed());
println!(" {} {}", "Project:".bold(), project_id);
println!(" {} {}", "Folder:".bold(), folder_id);
println!(" {} {}", "Name:".bold(), name.cyan());
println!(" {} {}", "Object ID:".bold(), object_id.dimmed());
}
let item = client
.create_item_from_storage(project_id, folder_id, name, object_id)
.await?;
let output = CreateFromOssOutput {
success: true,
item_id: item.id.clone(),
name: item.attributes.display_name.clone(),
message: format!("Item '{}' created successfully from OSS object", name),
};
match output_format {
OutputFormat::Table => {
println!("\n{} {}", "✓".green().bold(), output.message);
println!(" {} {}", "Item ID:".bold(), output.item_id);
println!(" {} {}", "Name:".bold(), output.name.cyan());
}
_ => {
output_format.write(&output)?;
}
}
Ok(())
}
async fn delete_item(
client: &DataManagementClient,
project_id: &str,
item_id: &str,
output_format: OutputFormat,
) -> Result<()> {
if output_format.supports_colors() {
println!("{}", "Deleting item...".dimmed());
}
client.delete_item(project_id, item_id).await?;
match output_format {
OutputFormat::Table => {
println!("\n{} Item deleted successfully!", "✓".green().bold());
println!(" {} {}", "Item ID:".bold(), item_id.cyan());
}
_ => {
output_format.write(&serde_json::json!({
"id": item_id,
"deleted": true
}))?;
}
}
Ok(())
}
#[derive(Serialize)]
struct RenameItemOutput {
id: String,
name: String,
renamed: bool,
}
async fn rename_item(
client: &DataManagementClient,
project_id: &str,
item_id: &str,
new_name: &str,
output_format: OutputFormat,
) -> Result<()> {
if output_format.supports_colors() {
println!("{}", "Renaming item...".dimmed());
}
let item = client.rename_item(project_id, item_id, new_name).await?;
let output = RenameItemOutput {
id: item.id.clone(),
name: item.attributes.display_name.clone(),
renamed: true,
};
match output_format {
OutputFormat::Table => {
println!("\n{} Item renamed successfully!", "✓".green().bold());
println!(" {} {}", "Item ID:".bold(), output.id.cyan());
println!(" {} {}", "Name:".bold(), output.name.cyan());
}
_ => {
output_format.write(&output)?;
}
}
Ok(())
}