use clap::{Parser, Subcommand};
use crate::config::CliConfig;
use crate::errors::registry;
#[derive(Parser, Debug, Clone)]
#[command(
about = "Look up an error code or list all known codes",
after_help = "Examples:
auths error list # List all known error codes
auths error show AUTHS-E3001 # Show details for a specific code
auths error AUTHS-E3001 # Short form (same as show)
Related:
auths doctor — Run health checks to diagnose issues
auths status — Check your identity and device status"
)]
pub struct ErrorLookupCommand {
#[command(subcommand)]
pub subcommand: Option<ErrorSubcommand>,
pub code: Option<String>,
#[arg(long)]
pub list: bool,
}
#[derive(Subcommand, Debug, Clone)]
pub enum ErrorSubcommand {
List,
Show { code: String },
}
impl ErrorLookupCommand {
pub fn execute(&self, ctx: &CliConfig) -> anyhow::Result<()> {
if let Some(ref subcommand) = self.subcommand {
match subcommand {
ErrorSubcommand::List => return list_codes(ctx),
ErrorSubcommand::Show { code } => return explain_code(code, ctx),
}
}
if self.list {
return list_codes(ctx);
}
match &self.code {
Some(code) => explain_code(code, ctx),
None => {
list_codes(ctx)
}
}
}
}
pub fn normalize_error_code(code: &str) -> String {
let upper = code.trim().to_uppercase();
let without_prefix = upper.strip_prefix("AUTHS-").unwrap_or(&upper);
let digits = without_prefix.strip_prefix('E').unwrap_or(without_prefix);
if !digits.is_empty() && digits.bytes().all(|b| b.is_ascii_digit()) {
format!("AUTHS-E{digits}")
} else {
upper
}
}
fn explain_code(code: &str, ctx: &CliConfig) -> anyhow::Result<()> {
let normalized = normalize_error_code(code);
if ctx.is_json() {
match registry::explain(&normalized) {
Some(text) => {
let json = serde_json::json!({
"code": normalized,
"explanation": text,
});
println!("{}", serde_json::to_string_pretty(&json)?);
}
None => {
let json = serde_json::json!({
"error": { "message": format!("Unknown error code: {normalized}") }
});
println!("{}", serde_json::to_string_pretty(&json)?);
}
}
} else {
match registry::explain(&normalized) {
Some(text) => println!("{text}"),
None => {
eprintln!("Unknown error code: {normalized}");
eprintln!();
if normalized == "LIST" {
eprintln!("Did you mean: auths error list");
} else {
eprintln!("Run `auths error list` to see all known codes.");
}
std::process::exit(1);
}
}
}
Ok(())
}
fn list_codes(ctx: &CliConfig) -> anyhow::Result<()> {
let codes = registry::all_codes();
if ctx.is_json() {
let json = serde_json::json!({ "codes": codes });
println!("{}", serde_json::to_string_pretty(&json)?);
} else {
for code in codes {
println!("{code}");
}
eprintln!("\n{} error codes registered", codes.len());
eprintln!("Run `auths error show CODE` to see details for a specific code.");
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::normalize_error_code;
use crate::errors::registry;
#[test]
fn bare_and_prefixed_codes_resolve_the_same() {
assert_eq!(normalize_error_code("E4203"), "AUTHS-E4203");
assert_eq!(normalize_error_code("4203"), "AUTHS-E4203");
assert_eq!(normalize_error_code("auths-e4203"), "AUTHS-E4203");
assert_eq!(normalize_error_code("AUTHS-E4203"), "AUTHS-E4203");
assert_eq!(
registry::explain(&normalize_error_code("E4203")),
registry::explain("AUTHS-E4203")
);
assert!(registry::explain(&normalize_error_code("E4203")).is_some());
}
#[test]
fn non_numeric_input_passes_through_uppercased() {
assert_eq!(normalize_error_code("list"), "LIST");
}
}