commit_wizard/engine/error/
mod.rs1mod 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>;