rust/src/error.rs (168 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //! Error data model of RocketMQ rust client. use std::error::Error; use std::fmt; use std::fmt::{Debug, Display, Formatter}; /// Error type using by [`ClientError`]. #[derive(thiserror::Error, Debug, PartialEq, Eq)] pub enum ErrorKind { #[error("Failed to parse config")] Config, #[error("Failed to create session")] Connect, #[error("Message is invalid")] InvalidMessage, #[error("Server error")] Server, #[error("No broker available to send message")] NoBrokerAvailable, #[error("Client internal error")] ClientInternal, #[error("Client is not running")] ClientIsNotRunning, #[error("Failed to send message via channel")] ChannelSend, #[error("Failed to receive message via channel")] ChannelReceive, #[error("Unknown error")] Unknown, } /// Error returned by producer or consumer. pub struct ClientError { pub(crate) kind: ErrorKind, pub(crate) message: String, pub(crate) operation: &'static str, pub(crate) context: Vec<(&'static str, String)>, pub(crate) source: Option<anyhow::Error>, } impl Error for ClientError {} impl ClientError { pub(crate) fn new(kind: ErrorKind, message: &str, operation: &'static str) -> Self { Self { kind, message: message.to_string(), operation, context: Vec::new(), source: None, } } /// Error type pub fn kind(&self) -> &ErrorKind { &self.kind } /// Error message pub fn message(&self) -> &str { &self.message } /// Name of operation that produced this error pub fn operation(&self) -> &str { self.operation } /// Error context, formatted in key-value pairs pub fn context(&self) -> &Vec<(&'static str, String)> { &self.context } /// Source error pub fn source(&self) -> Option<&anyhow::Error> { self.source.as_ref() } pub(crate) fn with_operation(mut self, operation: &'static str) -> Self { if !self.operation.is_empty() { self.context.push(("called", self.operation.to_string())); } self.operation = operation; self } pub(crate) fn with_context(mut self, key: &'static str, value: impl Into<String>) -> Self { self.context.push((key, value.into())); self } pub(crate) fn set_source(mut self, src: impl Into<anyhow::Error>) -> Self { debug_assert!(self.source.is_none(), "the source error has been set"); self.source = Some(src.into()); self } } impl Display for ClientError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{} at {}", self.kind, self.operation)?; if !self.context.is_empty() { write!(f, ", context: {{ ")?; write!( f, "{}", self.context .iter() .map(|(k, v)| format!("{k}: {v}")) .collect::<Vec<_>>() .join(", ") )?; write!(f, " }}")?; } if !self.message.is_empty() { write!(f, " => {}", self.message)?; } if let Some(source) = &self.source { write!(f, ", source: {source}")?; } Ok(()) } } impl Debug for ClientError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { // If alternate has been specified, we will print like Debug. if f.alternate() { let mut debug = f.debug_struct("Error"); debug.field("kind", &self.kind); debug.field("message", &self.message); debug.field("operation", &self.operation); debug.field("context", &self.context); debug.field("source", &self.source); return debug.finish(); } write!(f, "{} at {}", self.kind, self.operation)?; if !self.message.is_empty() { write!(f, " => {}", self.message)?; } writeln!(f)?; if !self.context.is_empty() { writeln!(f)?; writeln!(f, "Context:")?; for (k, v) in self.context.iter() { writeln!(f, " {k}: {v}")?; } } if let Some(source) = &self.source { writeln!(f)?; writeln!(f, "Source: {source:?}")?; } Ok(()) } } #[cfg(test)] mod tests { use super::*; #[test] fn error_client_error() { let err = ClientError::new(ErrorKind::Config, "fake_message", "error_client_error") .with_operation("another_operation") .with_context("context_key", "context_value") .set_source(anyhow::anyhow!("fake_source_error")); assert_eq!( err.to_string(), "Failed to parse config at another_operation, context: { called: error_client_error, context_key: context_value } => fake_message, source: fake_source_error" ); 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"); 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}"); } }