auths_cli/commands/
error_lookup.rs1use clap::{Parser, Subcommand};
2
3use crate::config::CliConfig;
4use crate::errors::registry;
5
6#[derive(Parser, Debug, Clone)]
14#[command(
15 about = "Look up an error code or list all known codes",
16 after_help = "Examples:
17 auths error list # List all known error codes
18 auths error show AUTHS-E3001 # Show details for a specific code
19 auths error AUTHS-E3001 # Short form (same as show)
20
21Related:
22 auths doctor — Run health checks to diagnose issues
23 auths status — Check your identity and device status"
24)]
25pub struct ErrorLookupCommand {
26 #[command(subcommand)]
27 pub subcommand: Option<ErrorSubcommand>,
28
29 pub code: Option<String>,
31
32 #[arg(long)]
34 pub list: bool,
35}
36
37#[derive(Subcommand, Debug, Clone)]
38pub enum ErrorSubcommand {
39 List,
41 Show { code: String },
43}
44
45impl ErrorLookupCommand {
46 pub fn execute(&self, ctx: &CliConfig) -> anyhow::Result<()> {
47 if let Some(ref subcommand) = self.subcommand {
49 match subcommand {
50 ErrorSubcommand::List => return list_codes(ctx),
51 ErrorSubcommand::Show { code } => return explain_code(code, ctx),
52 }
53 }
54
55 if self.list {
57 return list_codes(ctx);
58 }
59
60 match &self.code {
61 Some(code) => explain_code(code, ctx),
62 None => {
63 list_codes(ctx)
65 }
66 }
67 }
68}
69
70pub fn normalize_error_code(code: &str) -> String {
84 let upper = code.trim().to_uppercase();
85 let without_prefix = upper.strip_prefix("AUTHS-").unwrap_or(&upper);
86 let digits = without_prefix.strip_prefix('E').unwrap_or(without_prefix);
87 if !digits.is_empty() && digits.bytes().all(|b| b.is_ascii_digit()) {
88 format!("AUTHS-E{digits}")
89 } else {
90 upper
91 }
92}
93
94fn explain_code(code: &str, ctx: &CliConfig) -> anyhow::Result<()> {
95 let normalized = normalize_error_code(code);
96
97 if ctx.is_json() {
98 match registry::explain(&normalized) {
99 Some(text) => {
100 let json = serde_json::json!({
101 "code": normalized,
102 "explanation": text,
103 });
104 println!("{}", serde_json::to_string_pretty(&json)?);
105 }
106 None => {
107 let json = serde_json::json!({
108 "error": { "message": format!("Unknown error code: {normalized}") }
109 });
110 println!("{}", serde_json::to_string_pretty(&json)?);
111 }
112 }
113 } else {
114 match registry::explain(&normalized) {
115 Some(text) => println!("{text}"),
116 None => {
117 eprintln!("Unknown error code: {normalized}");
118 eprintln!();
119 if normalized == "LIST" {
121 eprintln!("Did you mean: auths error list");
122 } else {
123 eprintln!("Run `auths error list` to see all known codes.");
124 }
125 std::process::exit(1);
126 }
127 }
128 }
129 Ok(())
130}
131
132fn list_codes(ctx: &CliConfig) -> anyhow::Result<()> {
133 let codes = registry::all_codes();
134
135 if ctx.is_json() {
136 let json = serde_json::json!({ "codes": codes });
137 println!("{}", serde_json::to_string_pretty(&json)?);
138 } else {
139 for code in codes {
140 println!("{code}");
141 }
142 eprintln!("\n{} error codes registered", codes.len());
143 eprintln!("Run `auths error show CODE` to see details for a specific code.");
144 }
145 Ok(())
146}
147
148#[cfg(test)]
149mod tests {
150 use super::normalize_error_code;
151 use crate::errors::registry;
152
153 #[test]
154 fn bare_and_prefixed_codes_resolve_the_same() {
155 assert_eq!(normalize_error_code("E4203"), "AUTHS-E4203");
158 assert_eq!(normalize_error_code("4203"), "AUTHS-E4203");
159 assert_eq!(normalize_error_code("auths-e4203"), "AUTHS-E4203");
160 assert_eq!(normalize_error_code("AUTHS-E4203"), "AUTHS-E4203");
161 assert_eq!(
162 registry::explain(&normalize_error_code("E4203")),
163 registry::explain("AUTHS-E4203")
164 );
165 assert!(registry::explain(&normalize_error_code("E4203")).is_some());
166 }
167
168 #[test]
169 fn non_numeric_input_passes_through_uppercased() {
170 assert_eq!(normalize_error_code("list"), "LIST");
173 }
174}