use std::fmt;
use std::io;
#[derive(Debug)]
pub enum TryLockError {
Error(io::Error),
WouldBlock,
}
impl fmt::Display for TryLockError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Error(err) => err.fmt(f),
Self::WouldBlock => "try_lock failed because the operation would block".fmt(f),
}
}
}
impl std::error::Error for TryLockError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Error(err) => Some(err),
Self::WouldBlock => None,
}
}
}
impl From<TryLockError> for io::Error {
fn from(err: TryLockError) -> Self {
match err {
TryLockError::Error(err) => err,
TryLockError::WouldBlock => io::Error::from(io::ErrorKind::WouldBlock),
}
}
}
impl From<io::Error> for TryLockError {
fn from(err: io::Error) -> Self {
if err.kind() == io::ErrorKind::WouldBlock {
TryLockError::WouldBlock
} else {
TryLockError::Error(err)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error as _;
use std::io;
#[test]
fn from_io_error_would_block_collapses_to_would_block() {
let err: TryLockError = io::Error::from(io::ErrorKind::WouldBlock).into();
assert!(matches!(err, TryLockError::WouldBlock));
}
#[test]
fn from_io_error_other_preserves_inner() {
let err: TryLockError = io::Error::from(io::ErrorKind::PermissionDenied).into();
match err {
TryLockError::Error(inner) => {
assert_eq!(inner.kind(), io::ErrorKind::PermissionDenied);
}
other => panic!("expected TryLockError::Error, got {other:?}"),
}
}
#[test]
fn into_io_error_would_block() {
let err: io::Error = TryLockError::WouldBlock.into();
assert_eq!(err.kind(), io::ErrorKind::WouldBlock);
}
#[test]
fn into_io_error_passes_inner_through() {
let inner = io::Error::from(io::ErrorKind::NotFound);
let err: io::Error = TryLockError::Error(inner).into();
assert_eq!(err.kind(), io::ErrorKind::NotFound);
}
#[test]
fn round_trip_preserves_error_kind() {
for kind in [
io::ErrorKind::WouldBlock,
io::ErrorKind::PermissionDenied,
io::ErrorKind::NotFound,
io::ErrorKind::Interrupted,
] {
let original = io::Error::from(kind);
let converted: io::Error = TryLockError::from(original).into();
assert_eq!(
converted.kind(),
kind,
"round-trip changed kind for {kind:?}"
);
}
}
#[test]
fn display_is_non_empty() {
assert!(!TryLockError::WouldBlock.to_string().is_empty());
let inner = io::Error::from(io::ErrorKind::NotFound);
assert!(!TryLockError::Error(inner).to_string().is_empty());
}
#[test]
fn source_exposes_inner_only_for_error_variant() {
assert!(TryLockError::WouldBlock.source().is_none());
let inner = io::Error::from(io::ErrorKind::NotFound);
let err = TryLockError::Error(inner);
let src = err.source().expect("Error variant must expose source");
assert!(
src.downcast_ref::<io::Error>().is_some(),
"source must be an io::Error"
);
}
}