1use serde::Serialize;
2use serde_json::json;
3
4#[derive(Debug)]
5pub struct AppError {
6 exit_code: i32,
7 code: Box<str>,
8 message: Box<str>,
9 details: Option<Box<serde_json::Value>>,
10}
11
12#[derive(Debug, Serialize)]
13pub struct JsonError<'a> {
14 pub code: &'a str,
15 pub message: &'a str,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub details: Option<&'a serde_json::Value>,
18}
19
20impl AppError {
21 pub fn usage(message: impl Into<String>) -> Self {
22 Self {
23 exit_code: 64,
24 code: "invalid-arguments".into(),
25 message: message.into().into_boxed_str(),
26 details: None,
27 }
28 }
29
30 pub fn data(message: impl Into<String>) -> Self {
31 Self {
32 exit_code: 65,
33 code: "invalid-input".into(),
34 message: message.into().into_boxed_str(),
35 details: None,
36 }
37 }
38
39 pub fn runtime(message: impl Into<String>) -> Self {
40 Self {
41 exit_code: 1,
42 code: "runtime-failure".into(),
43 message: message.into().into_boxed_str(),
44 details: None,
45 }
46 }
47
48 pub fn db(err: rusqlite::Error) -> Self {
49 Self::runtime(format!("database error: {err}"))
50 }
51
52 pub fn db_open(err: impl std::fmt::Display) -> Self {
53 Self::runtime(format!("database open failed: {err}")).with_code("db-open-failed")
54 }
55
56 pub fn db_query(err: rusqlite::Error) -> Self {
57 Self::runtime(format!("database query failed: {err}")).with_code("db-query-failed")
58 }
59
60 pub fn db_write(err: rusqlite::Error) -> Self {
61 Self::runtime(format!("database write failed: {err}")).with_code("db-write-failed")
62 }
63
64 pub fn invalid_cursor(cursor: &str) -> Self {
65 Self::usage("cursor is invalid for current database state")
66 .with_code("invalid-cursor")
67 .with_details(json!({ "cursor": cursor }))
68 }
69
70 pub fn invalid_apply_payload(message: impl Into<String>, path: Option<&str>) -> Self {
71 let mut err = Self::data(message).with_code("invalid-apply-payload");
72 if let Some(path) = path {
73 err = err.with_details(json!({ "path": path }));
74 }
75 err
76 }
77
78 pub fn with_code(mut self, code: impl Into<String>) -> Self {
79 self.code = code.into().into_boxed_str();
80 self
81 }
82
83 pub fn with_details(mut self, details: serde_json::Value) -> Self {
84 self.details = Some(Box::new(details));
85 self
86 }
87
88 pub fn exit_code(&self) -> i32 {
89 self.exit_code
90 }
91
92 pub fn code(&self) -> &str {
93 self.code.as_ref()
94 }
95
96 pub fn message(&self) -> &str {
97 self.message.as_ref()
98 }
99
100 pub fn json_error(&self) -> JsonError<'_> {
101 JsonError {
102 code: self.code(),
103 message: self.message(),
104 details: self.details.as_deref(),
105 }
106 }
107}
108
109impl From<anyhow::Error> for AppError {
110 fn from(value: anyhow::Error) -> Self {
111 Self::runtime(value.to_string())
112 }
113}