use clap::{Parser, Subcommand};
use sure_client_rs::{Auth, CategoryId, SureClient};
use url::Url;
#[derive(Parser)]
#[command(name = "categories")]
#[command(about = "Manage categories via the Sure API", long_about = None)]
struct Cli {
#[arg(long, env = "SURE_TOKEN")]
token: String,
#[arg(long, env = "SURE_BASE_URL", default_value = "http://localhost:3000")]
base_url: Url,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
List {
#[arg(long)]
page: Option<u32>,
#[arg(long, alias = "per-page")]
per_page: Option<u32>,
#[arg(long)]
roots_only: bool,
#[arg(long)]
parent_id: Option<String>,
},
Get {
#[arg(long)]
id: String,
},
Create {
#[arg(long)]
name: String,
#[arg(long)]
color: String,
#[arg(long)]
icon: Option<String>,
#[arg(long)]
parent_id: Option<String>,
},
Update {
#[arg(long)]
id: String,
#[arg(long)]
name: Option<String>,
#[arg(long)]
color: Option<String>,
#[arg(long)]
icon: Option<String>,
#[arg(long)]
parent_id: Option<String>,
},
Delete {
#[arg(long)]
id: String,
},
}
fn validate_and_normalize_hex_code(code: &str) -> Option<String> {
let code = code.trim();
(code.len() == 7
&& code.starts_with('#')
&& code.chars().skip(1).all(|c| c.is_ascii_hexdigit()))
.then(|| code.to_uppercase())
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
let client = SureClient::new(
reqwest::Client::new(),
Auth::api_key(cli.token),
cli.base_url,
);
match cli.command {
Commands::List {
page,
per_page,
roots_only,
parent_id,
} => {
let parent_id = if let Some(id_str) = &parent_id {
Some(
CategoryId::parse(id_str)
.map_err(|e| anyhow::anyhow!("Invalid parent category ID: {}", e))?,
)
} else {
None
};
let response = client
.get_categories()
.maybe_page(page)
.maybe_per_page(per_page)
.roots_only(roots_only)
.maybe_parent_id(parent_id.as_ref())
.call()
.await?;
println!(
"Categories (Page {} of {}):",
response.pagination.page, response.pagination.total_pages
);
println!();
for category in response.items.categories {
println!("ID: {}", category.id);
println!("Name: {}", category.name);
println!("Color: {}", category.color);
println!("Icon: {}", category.icon);
if let Some(parent) = category.parent {
println!("Parent: {} ({})", parent.name, parent.id);
}
if category.subcategories_count > 0 {
println!("Subcategories: {}", category.subcategories_count);
}
println!();
}
println!("Total: {} categories", response.pagination.total_count);
}
Commands::Get { id } => {
let category_id = CategoryId::parse(&id)
.map_err(|e| anyhow::anyhow!("Invalid category ID: {}", e))?;
let category = client.get_category(&category_id).await?;
println!("Category Details:");
println!();
println!("ID: {}", category.id);
println!("Name: {}", category.name);
println!("Color: {}", category.color);
println!("Icon: {}", category.icon);
if let Some(parent) = category.parent {
println!("Parent: {} ({})", parent.name, parent.id);
}
if category.subcategories_count > 0 {
println!("Subcategories: {}", category.subcategories_count);
}
}
Commands::Create {
name,
color,
icon,
parent_id,
} => {
let parent_id = if let Some(id_str) = &parent_id {
Some(
CategoryId::parse(id_str)
.map_err(|e| anyhow::anyhow!("Invalid parent category ID: {}", e))?,
)
} else {
None
};
if validate_and_normalize_hex_code(&color).is_none() {
return Err(anyhow::anyhow!(
"Invalid color code: {}. Must be in hex format like '#FF5733'.",
color
));
}
let category = client
.create_category()
.name(name)
.color(color)
.maybe_lucide_icon(icon)
.maybe_parent_id(parent_id)
.call()
.await?;
println!("✓ Category created successfully!");
println!();
println!("ID: {}", category.id);
println!("Name: {}", category.name);
println!("Color: {}", category.color);
println!("Icon: {}", category.icon);
if let Some(parent) = category.parent {
println!("Parent: {} ({})", parent.name, parent.id);
}
}
Commands::Update {
id,
name,
color,
icon,
parent_id,
} => {
let category_id = CategoryId::parse(&id)
.map_err(|e| anyhow::anyhow!("Invalid category ID: {}", e))?;
let parent_id = if let Some(id_str) = &parent_id {
Some(
CategoryId::parse(id_str)
.map_err(|e| anyhow::anyhow!("Invalid parent category ID: {}", e))?,
)
} else {
None
};
let category = client
.update_category()
.id(&category_id)
.maybe_name(name)
.maybe_color(color)
.maybe_lucide_icon(icon)
.maybe_parent_id(parent_id)
.call()
.await?;
println!("✓ Category updated successfully!");
println!();
println!("ID: {}", category.id);
println!("Name: {}", category.name);
println!("Color: {}", category.color);
println!("Icon: {}", category.icon);
if let Some(parent) = category.parent {
println!("Parent: {} ({})", parent.name, parent.id);
}
}
Commands::Delete { id } => {
let category_id = CategoryId::parse(&id)
.map_err(|e| anyhow::anyhow!("Invalid category ID: {}", e))?;
let response = client.delete_category(&category_id).await?;
println!("✓ {}", response.message);
}
}
Ok(())
}