Skip to main content

memo_cli/
errors.rs

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}