Skip to main content

commit_wizard/engine/error/
mod.rs

1mod code;
2mod kind;
3
4pub use code::ErrorCode;
5pub use kind::ErrorKind;
6
7use serde::Serialize;
8use std::fmt;
9use thiserror::Error;
10
11#[derive(Debug, Clone, Serialize)]
12pub struct ErrorContextEntry {
13    pub key: String,
14    pub value: String,
15}
16
17#[derive(Debug, Clone, Serialize)]
18pub struct ErrorDetails {
19    pub code: String,
20    pub kind: String,
21    pub message: String,
22    #[serde(default, skip_serializing_if = "Vec::is_empty")]
23    pub context: Vec<ErrorContextEntry>,
24}
25
26impl ErrorDetails {
27    pub fn context_from_vec_string(&mut self, vecs: Vec<(String, String)>) {
28        self.context = vecs
29            .into_iter()
30            .map(|(k, v)| ErrorContextEntry { key: k, value: v })
31            .collect();
32    }
33
34    pub fn context_as_map(&self) -> std::collections::BTreeMap<String, String> {
35        self.context
36            .iter()
37            .map(|entry| (entry.key.clone(), entry.value.clone()))
38            .collect()
39    }
40}
41
42#[derive(Debug, Error)]
43pub struct Error {
44    pub code: ErrorCode,
45    pub kind: ErrorKind,
46    pub message: String,
47    pub context: Vec<(String, String)>,
48}
49
50impl fmt::Display for Error {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        write!(f, "{} ({}): {}", self.message, self.code, self.message)?;
53        for (k, v) in &self.context {
54            write!(f, "\n  {k}: {v}")?;
55        }
56        Ok(())
57    }
58}
59
60impl Error {
61    pub fn from_code(code: ErrorCode) -> Self {
62        Self {
63            kind: code.kind(),
64            message: code.message().to_string(),
65            code,
66            context: Vec::new(),
67        }
68    }
69
70    pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
71        Self {
72            kind: code.kind(),
73            code,
74            message: message.into(),
75            context: Vec::new(),
76        }
77    }
78
79    pub fn with_context(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
80        self.context.push((key.into(), value.into()));
81        self
82    }
83
84    pub fn with_context_str(
85        mut self,
86        key: impl Into<String>,
87        value: impl std::fmt::Display,
88    ) -> Self {
89        self.context.push((key.into(), value.to_string()));
90        self
91    }
92
93    pub fn exit_code(&self) -> i32 {
94        self.code.exit_code()
95    }
96
97    pub fn details(&self) -> ErrorDetails {
98        ErrorDetails {
99            code: self.code.id(),
100            kind: format!("{:?}", self.kind).to_lowercase(),
101            message: self.message.clone(),
102            context: self
103                .context
104                .iter()
105                .map(|(key, value)| ErrorContextEntry {
106                    key: key.clone(),
107                    value: value.clone(),
108                })
109                .collect(),
110        }
111    }
112}
113
114impl ErrorCode {
115    pub fn error(self) -> Error {
116        Error::from_code(self)
117    }
118}
119
120impl From<std::io::Error> for Error {
121    fn from(err: std::io::Error) -> Self {
122        ErrorCode::IoFailure
123            .error()
124            .with_context("error", err.to_string())
125    }
126}
127
128impl From<toml::de::Error> for Error {
129    fn from(err: toml::de::Error) -> Self {
130        ErrorCode::ConfigInvalid
131            .error()
132            .with_context("error", err.to_string())
133    }
134}
135
136impl From<serde_json::Error> for Error {
137    fn from(err: serde_json::Error) -> Self {
138        ErrorCode::SerializationFailure
139            .error()
140            .with_context("error", err.to_string())
141    }
142}
143
144pub type Result<T> = std::result::Result<T, Error>;