use alloc::boxed::Box;
use alloc::string::String;
use core::fmt;
pub type Result<T> = core::result::Result<T, Error>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ErrorKind {
AlreadyExists,
BrokenPipe,
CrossesDevices,
Interrupted,
InvalidData,
InvalidInput,
NotFound,
Other,
PermissionDenied,
UnexpectedEof,
Unsupported,
WriteZero,
}
impl ErrorKind {
fn as_str(self) -> &'static str {
match self {
Self::AlreadyExists => "entity already exists",
Self::BrokenPipe => "broken pipe",
Self::CrossesDevices => "cross-device link or rename",
Self::Interrupted => "operation interrupted",
Self::InvalidData => "invalid data",
Self::InvalidInput => "invalid input parameter",
Self::NotFound => "entity not found",
Self::Other => "other error",
Self::PermissionDenied => "permission denied",
Self::UnexpectedEof => "unexpected end of file",
Self::Unsupported => "unsupported",
Self::WriteZero => "write returned 0 bytes",
}
}
}
pub struct Error {
kind: ErrorKind,
message: Option<Box<str>>,
}
impl Error {
pub fn new<M: Into<String>>(kind: ErrorKind, message: M) -> Self {
Self {
kind,
message: Some(message.into().into_boxed_str()),
}
}
#[must_use]
pub const fn from_kind(kind: ErrorKind) -> Self {
Self {
kind,
message: None,
}
}
#[must_use]
pub const fn kind(&self) -> ErrorKind {
self.kind
}
}
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Self {
Self::from_kind(kind)
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut dbg = f.debug_struct("Error");
dbg.field("kind", &self.kind);
if let Some(msg) = &self.message {
dbg.field("message", msg);
}
dbg.finish()
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.message {
Some(msg) => write!(f, "{}: {msg}", self.kind.as_str()),
None => f.write_str(self.kind.as_str()),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
#[cfg(feature = "std")]
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
let std_kind = err.kind();
let (kind, kind_is_mapped) = match std_kind {
std::io::ErrorKind::AlreadyExists => (ErrorKind::AlreadyExists, true),
std::io::ErrorKind::BrokenPipe => (ErrorKind::BrokenPipe, true),
std::io::ErrorKind::CrossesDevices => (ErrorKind::CrossesDevices, true),
std::io::ErrorKind::Interrupted => (ErrorKind::Interrupted, true),
std::io::ErrorKind::InvalidData => (ErrorKind::InvalidData, true),
std::io::ErrorKind::InvalidInput => (ErrorKind::InvalidInput, true),
std::io::ErrorKind::NotFound => (ErrorKind::NotFound, true),
std::io::ErrorKind::PermissionDenied => (ErrorKind::PermissionDenied, true),
std::io::ErrorKind::UnexpectedEof => (ErrorKind::UnexpectedEof, true),
std::io::ErrorKind::Unsupported => (ErrorKind::Unsupported, true),
std::io::ErrorKind::WriteZero => (ErrorKind::WriteZero, true),
std::io::ErrorKind::Other => (ErrorKind::Other, true),
_ => (ErrorKind::Other, false),
};
if err.raw_os_error().is_some() || err.get_ref().is_some() {
Self::new(kind, alloc::format!("{err}"))
} else if kind_is_mapped {
Self::from_kind(kind)
} else {
Self::new(kind, alloc::format!("{err}"))
}
}
}
#[cfg(feature = "std")]
impl From<Error> for std::io::Error {
fn from(err: Error) -> Self {
let kind = match err.kind {
ErrorKind::AlreadyExists => std::io::ErrorKind::AlreadyExists,
ErrorKind::BrokenPipe => std::io::ErrorKind::BrokenPipe,
ErrorKind::CrossesDevices => std::io::ErrorKind::CrossesDevices,
ErrorKind::Interrupted => std::io::ErrorKind::Interrupted,
ErrorKind::InvalidData => std::io::ErrorKind::InvalidData,
ErrorKind::InvalidInput => std::io::ErrorKind::InvalidInput,
ErrorKind::NotFound => std::io::ErrorKind::NotFound,
ErrorKind::Other => std::io::ErrorKind::Other,
ErrorKind::PermissionDenied => std::io::ErrorKind::PermissionDenied,
ErrorKind::UnexpectedEof => std::io::ErrorKind::UnexpectedEof,
ErrorKind::Unsupported => std::io::ErrorKind::Unsupported,
ErrorKind::WriteZero => std::io::ErrorKind::WriteZero,
};
match err.message {
Some(msg) => Self::new(kind, msg.into_string()),
None => Self::from(kind),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SeekFrom {
Start(u64),
End(i64),
Current(i64),
}
#[cfg(feature = "std")]
impl From<SeekFrom> for std::io::SeekFrom {
fn from(s: SeekFrom) -> Self {
match s {
SeekFrom::Start(n) => Self::Start(n),
SeekFrom::End(n) => Self::End(n),
SeekFrom::Current(n) => Self::Current(n),
}
}
}
#[cfg(feature = "std")]
impl From<std::io::SeekFrom> for SeekFrom {
fn from(s: std::io::SeekFrom) -> Self {
match s {
std::io::SeekFrom::Start(n) => Self::Start(n),
std::io::SeekFrom::End(n) => Self::End(n),
std::io::SeekFrom::Current(n) => Self::Current(n),
}
}
}
#[cfg(feature = "std")]
pub trait Read: std::io::Read {}
#[cfg(feature = "std")]
impl<R: std::io::Read + ?Sized> Read for R {}
#[cfg(feature = "std")]
pub trait Write: std::io::Write {}
#[cfg(feature = "std")]
impl<W: std::io::Write + ?Sized> Write for W {}
#[cfg(feature = "std")]
pub trait Seek: std::io::Seek {}
#[cfg(feature = "std")]
impl<S: std::io::Seek + ?Sized> Seek for S {}
#[cfg(not(feature = "std"))]
pub trait Read {
fn read(&mut self, buf: &mut [u8]) -> Result<usize>;
fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<()> {
while !buf.is_empty() {
match self.read(buf) {
Ok(0) => break,
Ok(n) => {
let (_, rest) = buf.split_at_mut(n);
buf = rest;
}
Err(e) if e.kind() == ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
if buf.is_empty() {
Ok(())
} else {
Err(Error::new(
ErrorKind::UnexpectedEof,
"failed to fill whole buffer",
))
}
}
}
#[cfg(not(feature = "std"))]
pub trait Write {
fn write(&mut self, buf: &[u8]) -> Result<usize>;
fn flush(&mut self) -> Result<()>;
fn write_all(&mut self, mut buf: &[u8]) -> Result<()> {
while !buf.is_empty() {
match self.write(buf) {
Ok(0) => {
return Err(Error::new(
ErrorKind::WriteZero,
"failed to write whole buffer",
));
}
Ok(n) => {
let (_, rest) = buf.split_at(n);
buf = rest;
}
Err(e) if e.kind() == ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
Ok(())
}
}
#[cfg(not(feature = "std"))]
pub trait Seek {
fn seek(&mut self, pos: SeekFrom) -> Result<u64>;
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "std")]
fn assert_read_alias_bound<R: Read>(_: &R) {}
#[cfg(feature = "std")]
fn assert_write_alias_bound<W: Write>(_: &W) {}
#[test]
fn error_kind_strings_are_distinct() {
let all = [
ErrorKind::AlreadyExists,
ErrorKind::BrokenPipe,
ErrorKind::CrossesDevices,
ErrorKind::Interrupted,
ErrorKind::InvalidData,
ErrorKind::InvalidInput,
ErrorKind::NotFound,
ErrorKind::Other,
ErrorKind::PermissionDenied,
ErrorKind::UnexpectedEof,
ErrorKind::Unsupported,
ErrorKind::WriteZero,
];
for (i, a) in all.iter().enumerate() {
for b in all.iter().skip(i + 1) {
assert_ne!(
a.as_str(),
b.as_str(),
"duplicate description for {a:?} vs {b:?}",
);
}
}
}
#[test]
fn error_carries_kind_and_optional_message() {
let e = Error::from_kind(ErrorKind::NotFound);
assert_eq!(e.kind(), ErrorKind::NotFound);
assert_eq!(alloc::format!("{e}"), "entity not found");
let e = Error::new(ErrorKind::InvalidData, "bad magic");
assert_eq!(e.kind(), ErrorKind::InvalidData);
assert_eq!(alloc::format!("{e}"), "invalid data: bad magic");
}
#[test]
fn error_kind_from_kind_is_const_friendly() {
const _E: Error = Error::from_kind(ErrorKind::Interrupted);
}
#[cfg(feature = "std")]
#[test]
fn from_std_io_error_preserves_kind_and_message() {
let std_err = std::io::Error::new(std::io::ErrorKind::WriteZero, "ran out");
let crate_err: Error = std_err.into();
assert_eq!(crate_err.kind(), ErrorKind::WriteZero);
let rendered = alloc::format!("{crate_err}");
assert!(
rendered.contains("ran out"),
"expected std message to survive in {rendered:?}",
);
}
#[cfg(feature = "std")]
#[test]
fn from_std_io_error_maps_unknown_to_other() {
let std_err = std::io::Error::from(std::io::ErrorKind::OutOfMemory);
let crate_err: Error = std_err.into();
assert_eq!(crate_err.kind(), ErrorKind::Other);
}
#[cfg(feature = "std")]
#[test]
fn round_trip_through_std_io_error_preserves_writezero() {
let original = Error::new(ErrorKind::WriteZero, "short write");
let as_std: std::io::Error = original.into();
assert_eq!(as_std.kind(), std::io::ErrorKind::WriteZero);
let back: Error = as_std.into();
assert_eq!(back.kind(), ErrorKind::WriteZero);
}
#[cfg(feature = "std")]
#[test]
fn kind_only_other_std_error_skips_message_attachment() {
let std_err = std::io::Error::from(std::io::ErrorKind::Other);
let ours: Error = std_err.into();
assert_eq!(ours.kind(), ErrorKind::Other);
let rendered = alloc::format!("{ours}");
assert!(
!rendered.contains(':'),
"kind-only Other must not attach a message, got: {rendered:?}"
);
}
#[cfg(feature = "std")]
#[test]
fn seek_from_round_trips_through_std() {
for case in [SeekFrom::Start(42), SeekFrom::End(-7), SeekFrom::Current(0)] {
let std_form: std::io::SeekFrom = case.into();
let back: SeekFrom = std_form.into();
assert_eq!(case, back);
}
}
#[cfg(feature = "std")]
#[test]
fn read_exact_via_blanket_impl_on_slice() -> std::io::Result<()> {
let mut src: &[u8] = b"\x01\x02\x03\x04";
assert_read_alias_bound(&src);
let mut buf = [0u8; 4];
<&[u8] as std::io::Read>::read_exact(&mut src, &mut buf)?;
assert_eq!(buf, [1, 2, 3, 4]);
Ok(())
}
#[cfg(feature = "std")]
#[test]
fn write_all_via_blanket_impl_on_vec() -> std::io::Result<()> {
let mut sink: Vec<u8> = Vec::new();
assert_write_alias_bound(&sink);
<Vec<u8> as std::io::Write>::write_all(&mut sink, b"hello")?;
assert_eq!(sink, b"hello");
Ok(())
}
}