1use thiserror::Error;
2
3#[derive(Error, Debug)]
4pub enum AppError {
5 #[error("Invalid input: {0}")]
6 InvalidInput(String),
7
8 #[error("Config error: {0}")]
9 Config(String),
10
11 #[error("Not found: {0}")]
12 NotFound(String),
13
14 #[error("Ambiguous: {0}")]
15 Ambiguous(String),
16
17 #[error("IO error: {0}")]
18 Io(#[from] std::io::Error),
19
20 #[error("Database error: {0}")]
21 Db(#[from] rusqlite::Error),
22
23 #[error("Serde error: {0}")]
24 Serde(#[from] serde_json::Error),
25
26 #[error("Render error: {0}")]
27 Render(String),
28
29 #[error("{0}")]
30 Transient(String),
31
32 #[error("Rate limited: {0}")]
33 RateLimited(String),
34
35 #[error(transparent)]
36 Core(#[from] finance_core::error::CoreError),
37
38 #[error("{0}")]
39 Other(String),
40}
41
42impl AppError {
43 pub fn exit_code(&self) -> i32 {
44 match self {
45 Self::InvalidInput(_) | Self::Ambiguous(_) | Self::NotFound(_) => 3,
46 Self::Config(_) => 2,
47 Self::RateLimited(_) => 4,
48 _ => 1,
49 }
50 }
51
52 pub fn error_code(&self) -> &'static str {
53 match self {
54 Self::InvalidInput(_) => "invalid_input",
55 Self::Ambiguous(_) => "ambiguous",
56 Self::Config(_) => "config_error",
57 Self::NotFound(_) => "not_found",
58 Self::Io(_) => "io_error",
59 Self::Db(_) => "db_error",
60 Self::Serde(_) => "serde_error",
61 Self::Render(_) => "render_error",
62 Self::Transient(_) => "transient_error",
63 Self::RateLimited(_) => "rate_limited",
64 Self::Core(_) => "core_error",
65 Self::Other(_) => "other",
66 }
67 }
68
69 pub fn suggestion(&self) -> &'static str {
70 match self {
71 Self::InvalidInput(_) => "Check arguments with: contract --help",
72 Self::Ambiguous(_) => "Use the full slug or a more specific identifier",
73 Self::Config(_) => "Check config with: contract config show",
74 Self::NotFound(_) => "List available entities with: contract <kind> list",
75 Self::Io(_) => "Retry the command",
76 Self::Db(_) => "Check database integrity: contract doctor",
77 Self::Serde(_) => "Retry the command; if it persists, run: contract doctor",
78 Self::Render(_) => "Typst render failed — run: contract doctor",
79 Self::Transient(_) => "Retry the command",
80 Self::RateLimited(_) => "Wait a moment and retry",
81 Self::Core(_) => "Check shared accounting state with: contract doctor",
82 Self::Other(_) => "Retry the command; if it persists, run: contract doctor",
83 }
84 }
85}
86
87pub type Result<T> = std::result::Result<T, AppError>;