Skip to content

Commit

Permalink
0.95 - Use thiserror crate for error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
rnd-ash committed Jul 31, 2023
1 parent edca0c9 commit 122d38a
Show file tree
Hide file tree
Showing 8 changed files with 50 additions and 144 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ecu_diagnostics"
version = "0.94.0"
version = "0.95.0"
authors = ["Ashcon Mohseninia <[email protected]>"]
edition = "2021"
description = "A rust crate for ECU diagnostic servers and communication APIs"
Expand Down Expand Up @@ -35,6 +35,7 @@ libloading = { version = "0.7.3", optional = true }
log="0.4.16"
strum = "0.24"
strum_macros = "0.24"
thiserror="1.0.44"

[dev-dependencies]
env_logger = "0.10.0"
Expand Down
56 changes: 13 additions & 43 deletions src/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,53 +14,41 @@ use crate::hardware::HardwareError;
/// Communication channel result
pub type ChannelResult<T> = Result<T, ChannelError>;

#[derive(Debug, Clone)]
#[derive(Debug, Clone, thiserror::Error)]
/// Error produced by a communication channel
pub enum ChannelError {
/// Underlying IO Error with channel
IOError(Arc<std::io::Error>),
#[error("Device IO error")]
IOError(#[from] #[source] Arc<std::io::Error>),
/// Timeout when writing data to the channel
#[error("Write timeout")]
WriteTimeout,
/// Timeout when reading from the channel
#[error("Read timeout")]
ReadTimeout,
/// The channel's Rx buffer is empty. Only applies when read timeout is 0
#[error("No data in receive buffer")]
BufferEmpty,
/// The channels Tx buffer is full
#[error("Send buffer is full")]
BufferFull,
/// Unsupported channel request
#[error("Device unsupported request")]
UnsupportedRequest,
/// The interface is not open
#[error("Interface was not opened before request")]
InterfaceNotOpen,
/// Underlying API error with hardware
HardwareError(HardwareError),
/// Channel is not open, so cannot read/write data to it!
NotOpen,
#[error("Device hardware API error")]
HardwareError(#[from] #[source] HardwareError),
/// Channel not configured prior to opening
#[error("Channel configuration error")]
ConfigurationError,
/// Other Channel error
#[error("Unknown channel error: {0}")]
Other(String),
}

impl std::fmt::Display for ChannelError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ChannelError::IOError(e) => write!(f, "IO error: {e}"),
ChannelError::UnsupportedRequest => write!(f, "unsupported channel request"),
ChannelError::ReadTimeout => write!(f, "timeout reading from channel"),
ChannelError::WriteTimeout => write!(f, "timeout writing to channel"),
ChannelError::BufferFull => write!(f, "channel's Transmit buffer is full"),
ChannelError::BufferEmpty => write!(f, "channel's Receive buffer is empty"),
ChannelError::InterfaceNotOpen => write!(f, "channel's interface is not open"),
ChannelError::HardwareError(err) => write!(f, "Channel hardware error: {err}"),
ChannelError::NotOpen => write!(f, "Channel has not been opened"),
ChannelError::Other(e) => write!(f, "{e}"),
ChannelError::ConfigurationError => {
write!(f, "Channel opened prior to being configured")
}
}
}
}

impl<T> From<PoisonError<T>> for ChannelError {
fn from(err: PoisonError<T>) -> Self {
ChannelError::HardwareError(HardwareError::from(err))
Expand Down Expand Up @@ -112,24 +100,6 @@ impl<T> From<mpsc::SendError<T>> for ChannelError {
}
}

impl From<HardwareError> for ChannelError {
fn from(err: HardwareError) -> Self {
Self::HardwareError(err)
}
}

impl std::error::Error for ChannelError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
if let Self::IOError(io_err) = self {
Some(io_err)
} else if let Self::HardwareError(err) = self {
Some(err)
} else {
None
}
}
}

/// A payload channel is a way for a device to have a bi-directional communication
/// link with a specific ECU
pub trait PayloadChannel: Send + Sync {
Expand Down
41 changes: 8 additions & 33 deletions src/hardware/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,11 @@ pub trait HardwareScanner<T: Hardware> {
fn open_device_by_name(&self, name: &str) -> HardwareResult<T>;
}

#[derive(Clone, Debug)]
#[derive(Clone, Debug, thiserror::Error)]
/// Represents error that can be returned by Hardware API
pub enum HardwareError {
/// Low level device driver error
#[error("Device library API error. Code {code}, Description: '{desc}'")]
APIError {
/// API Error code
code: u32,
Expand All @@ -100,17 +101,22 @@ pub enum HardwareError {
},
/// Indicates that a conflicting channel type was opened on a device which does not
/// support multiple channels of the same underlying network to be open at once.
#[error("Channel type conflicts with an already open channel")]
ConflictingChannel,
/// Indicates a channel type is not supported by the API
#[error("Channel type not supported on this hardware")]
ChannelNotSupported,
/// Hardware not found
#[error("Device not found")]
DeviceNotFound,
/// Function called on device that has not yet been opened
#[error("Device was not opened")]
DeviceNotOpen,

/// Lib loading error
#[cfg(feature = "passthru")]
LibLoadError(Arc<libloading::Error>),
#[error("Device API library load error")]
LibLoadError(#[from] #[source] Arc<libloading::Error>),
}

#[cfg(feature = "passthru")]
Expand All @@ -120,37 +126,6 @@ impl From<libloading::Error> for HardwareError {
}
}

impl std::fmt::Display for HardwareError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self {
HardwareError::APIError { code, desc } => write!(
f,
"Hardware API Error. Code {code}, Description: {desc}"
),
HardwareError::ConflictingChannel => {
write!(f, "Conflicting communication channel already open")
}
HardwareError::ChannelNotSupported => {
write!(f, "Channel type is not supported by hardware")
}
HardwareError::DeviceNotFound => write!(f, "Device not found"),
HardwareError::DeviceNotOpen => write!(f, "Hardware device not open"),
#[cfg(feature = "passthru")]
HardwareError::LibLoadError(e) => write!(f, "LibLoading error: {e}"),
}
}
}

impl std::error::Error for HardwareError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self {
#[cfg(feature = "passthru")]
HardwareError::LibLoadError(l) => Some(l.as_ref()),
_ => None,
}
}
}

/// Contains details about what communication protocols
/// are supported by the physical hardware
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
Expand Down
4 changes: 2 additions & 2 deletions src/hardware/passthru/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ pub struct PassthruCanChannel {
impl PassthruCanChannel {
fn get_channel_id(&self) -> ChannelResult<u32> {
match self.channel_id {
None => Err(ChannelError::NotOpen),
None => Err(ChannelError::InterfaceNotOpen),
Some(x) => Ok(x),
}
}
Expand Down Expand Up @@ -648,7 +648,7 @@ pub struct PassthruIsoTpChannel {
impl PassthruIsoTpChannel {
fn get_channel_id(&self) -> ChannelResult<u32> {
match self.channel_id {
None => Err(ChannelError::NotOpen),
None => Err(ChannelError::InterfaceNotOpen),
Some(x) => Ok(x),
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/hardware/passthru/sw_isotp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ impl PtCombiChannel {
if iso_tp_cfg.is_none() || iso_tp_filter.is_none() {
tx_isotp_send_res.send(Err(ChannelError::ConfigurationError))
} else if channel.is_none() {
tx_isotp_send_res.send(Err(ChannelError::NotOpen))
tx_isotp_send_res.send(Err(ChannelError::InterfaceNotOpen))
} else {
let cfg = iso_tp_cfg.unwrap();
let can = channel.as_mut().unwrap();
Expand Down
4 changes: 1 addition & 3 deletions src/kwp2000/read_data_by_identifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@ impl DynamicDiagSession {
}
let ident_response = ((res[1] as u16) << 8) | (res[2] as u16);
if ident_response != identifier {
return Err(DiagError::MismatchedResponse(format!(
"Expected identifier 0x{identifier:04X}, got identifier 0x{ident_response:04X}"
)));
return Err(DiagError::MismatchedIdentResponse{ want: identifier, received: ident_response });
}
res.drain(0..3);
Ok(res)
Expand Down
5 changes: 1 addition & 4 deletions src/kwp2000/read_data_by_local_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,10 +252,7 @@ impl DynamicDiagSession {
return Err(DiagError::InvalidResponseLength);
}
if res[1] != local_identifier {
return Err(DiagError::MismatchedResponse(format!(
"Expected local identifier 0x{:02X}, got local identifier 0x{:02X}",
local_identifier, res[1]
)));
return Err(DiagError::MismatchedIdentResponse{ want: local_identifier as _, received: res[1] as _ });
}
res.drain(0..2);
Ok(res)
Expand Down
79 changes: 22 additions & 57 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,87 +77,52 @@ pub use automotive_diag::ByteWrapper::*;
/// Diagnostic server result
pub type DiagServerResult<T> = Result<T, DiagError>;

#[derive(Clone, Debug)]
#[derive(Clone, Debug, thiserror::Error)]
/// Diagnostic server error
pub enum DiagError {
/// The Diagnostic server does not support the request
#[error("Diagnostic server does not support the request")]
NotSupported,
/// Diagnostic error code from the ECU itself
#[error("ECU Negative response. Error 0x{:02X?}, definition: {:?}", code, def)]
ECUError {
/// Raw Negative response code from ECU
code: u8,
/// Negative response code definition according to protocol
def: Option<String>,
},
/// Response empty
#[error("ECU did not respond to the request")]
EmptyResponse,
/// ECU Responded but send a message that wasn't a reply for the sent message
#[error("ECU response is out of order")]
WrongMessage,
/// Diagnostic server terminated!?
#[error("Diagnostic server was terminated before the request")]
ServerNotRunning,
/// ECU Responded with a message, but the length was incorrect
#[error("ECU response size was not the correct length")]
InvalidResponseLength,
/// A parameter given to the function is invalid. Check the function's documentation
/// for more information
#[error("Diagnostic function parameter invalid")]
ParameterInvalid,
/// Error with underlying communication channel
ChannelError(ChannelError),
/// Denotes a TODO action (Non-implemented function stub)
/// This will be removed in Version 1
NotImplemented(String),
#[error("Diagnostic server hardware channel error")]
ChannelError(#[from] #[source] ChannelError),
/// Device hardware error
HardwareError(Arc<HardwareError>),
/// ECU Param ID did not match the request, but the Service ID was correct
MismatchedResponse(String),
}

impl std::fmt::Display for DiagError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self {
DiagError::NotSupported => write!(f, "request not supported"),
DiagError::ECUError { code, def } => {
if let Some(d) = def {
write!(f, "ECU error 0x{code:02X} ({d})")
} else {
write!(f, "ECU error 0x{code:02X}")
}
}
DiagError::EmptyResponse => write!(f, "ECU provided an empty response"),
DiagError::WrongMessage => write!(f, "ECU response message did not match request"),
DiagError::ServerNotRunning => write!(f, "diagnostic server not running"),
DiagError::ParameterInvalid => write!(f, "a parameter provided was invalid"),
DiagError::InvalidResponseLength => {
write!(f, "ECU response message was of invalid length")
}
DiagError::ChannelError(err) => write!(f, "underlying channel error: {err}"),
DiagError::NotImplemented(s) => {
write!(f, "server encountered an unimplemented function: {s}")
}
DiagError::HardwareError(e) => write!(f, "Hardware error: {e}"),
DiagError::MismatchedResponse(e) => write!(f, "Param mismatched response: {e}"),
}
}
}

impl std::error::Error for DiagError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self {
DiagError::ChannelError(e) => Some(e),
DiagError::HardwareError(e) => Some(e.as_ref()),
_ => None,
}
}
}

impl From<ChannelError> for DiagError {
fn from(x: ChannelError) -> Self {
Self::ChannelError(x)
}
}

impl From<HardwareError> for DiagError {
fn from(x: HardwareError) -> Self {
Self::HardwareError(Arc::new(x))
#[error("Diagnostic server hardware error")]
HardwareError(#[from] #[source] Arc<HardwareError>),
/// Feauture is not iumplemented yet
#[error("Diagnostic server feature is unimplemented: '{0}'")]
NotImplemented(String),
/// Mismatched PID response ID
#[error("Requested Ident 0x{:04X?}, but received ident 0x{:04X?}", want, received)]
MismatchedIdentResponse {
/// Requested PID
want: u16,
/// Received PID from ECU
received: u16
}
}

Expand Down

0 comments on commit 122d38a

Please sign in to comment.