1use std::error::Error;
21use std::fmt;
22use std::fmt::{Debug, Display, Formatter};
23
24#[derive(thiserror::Error, Debug, PartialEq, Eq)]
26pub enum ErrorKind {
27 #[error("Failed to parse config")]
28 Config,
29
30 #[error("Failed to create session")]
31 Connect,
32
33 #[error("Message is invalid")]
34 InvalidMessage,
35
36 #[error("Server error")]
37 Server,
38
39 #[error("No broker available to send message")]
40 NoBrokerAvailable,
41
42 #[error("Client internal error")]
43 ClientInternal,
44
45 #[error("Client is not running")]
46 ClientIsNotRunning,
47
48 #[error("Failed to send message via channel")]
49 ChannelSend,
50
51 #[error("Failed to receive message via channel")]
52 ChannelReceive,
53
54 #[error("Unknown error")]
55 Unknown,
56}
57
58pub struct ClientError {
60 pub(crate) kind: ErrorKind,
61 pub(crate) message: String,
62 pub(crate) operation: &'static str,
63 pub(crate) context: Vec<(&'static str, String)>,
64 pub(crate) source: Option<anyhow::Error>,
65}
66
67impl Error for ClientError {}
68
69impl ClientError {
70 pub(crate) fn new(kind: ErrorKind, message: &str, operation: &'static str) -> Self {
71 Self {
72 kind,
73 message: message.to_string(),
74 operation,
75 context: Vec::new(),
76 source: None,
77 }
78 }
79
80 pub fn kind(&self) -> &ErrorKind {
82 &self.kind
83 }
84
85 pub fn message(&self) -> &str {
87 &self.message
88 }
89
90 pub fn operation(&self) -> &str {
92 self.operation
93 }
94
95 pub fn context(&self) -> &Vec<(&'static str, String)> {
97 &self.context
98 }
99
100 pub fn source(&self) -> Option<&anyhow::Error> {
102 self.source.as_ref()
103 }
104
105 pub(crate) fn with_operation(mut self, operation: &'static str) -> Self {
106 if !self.operation.is_empty() {
107 self.context.push(("called", self.operation.to_string()));
108 }
109
110 self.operation = operation;
111 self
112 }
113
114 pub(crate) fn with_context(mut self, key: &'static str, value: impl Into<String>) -> Self {
115 self.context.push((key, value.into()));
116 self
117 }
118
119 pub(crate) fn set_source(mut self, src: impl Into<anyhow::Error>) -> Self {
120 debug_assert!(self.source.is_none(), "the source error has been set");
121
122 self.source = Some(src.into());
123 self
124 }
125}
126
127impl Display for ClientError {
128 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
129 write!(f, "{} at {}", self.kind, self.operation)?;
130
131 if !self.context.is_empty() {
132 write!(f, ", context: {{ ")?;
133 write!(
134 f,
135 "{}",
136 self.context
137 .iter()
138 .map(|(k, v)| format!("{k}: {v}"))
139 .collect::<Vec<_>>()
140 .join(", ")
141 )?;
142 write!(f, " }}")?;
143 }
144
145 if !self.message.is_empty() {
146 write!(f, " => {}", self.message)?;
147 }
148
149 if let Some(source) = &self.source {
150 write!(f, ", source: {source}")?;
151 }
152
153 Ok(())
154 }
155}
156
157impl Debug for ClientError {
158 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
159 if f.alternate() {
161 let mut debug = f.debug_struct("Error");
162 debug.field("kind", &self.kind);
163 debug.field("message", &self.message);
164 debug.field("operation", &self.operation);
165 debug.field("context", &self.context);
166 debug.field("source", &self.source);
167 return debug.finish();
168 }
169
170 write!(f, "{} at {}", self.kind, self.operation)?;
171 if !self.message.is_empty() {
172 write!(f, " => {}", self.message)?;
173 }
174 writeln!(f)?;
175
176 if !self.context.is_empty() {
177 writeln!(f)?;
178 writeln!(f, "Context:")?;
179 for (k, v) in self.context.iter() {
180 writeln!(f, " {k}: {v}")?;
181 }
182 }
183 if let Some(source) = &self.source {
184 writeln!(f)?;
185 writeln!(f, "Source: {source:?}")?;
186 }
187
188 Ok(())
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn error_client_error() {
198 let err = ClientError::new(ErrorKind::Config, "fake_message", "error_client_error")
199 .with_operation("another_operation")
200 .with_context("context_key", "context_value")
201 .set_source(anyhow::anyhow!("fake_source_error"));
202 assert_eq!(
203 err.to_string(),
204 "Failed to parse config at another_operation, context: { called: error_client_error, context_key: context_value } => fake_message, source: fake_source_error"
205 );
206 assert_eq!(format!("{:?}", err), "Failed to parse config at another_operation => fake_message\n\nContext:\n called: error_client_error\n context_key: context_value\n\nSource: fake_source_error\n");
207 assert_eq!(format!("{:#?}", err), "Error {\n kind: Config,\n message: \"fake_message\",\n operation: \"another_operation\",\n context: [\n (\n \"called\",\n \"error_client_error\",\n ),\n (\n \"context_key\",\n \"context_value\",\n ),\n ],\n source: Some(\n \"fake_source_error\",\n ),\n}");
208 }
209}