diff --git a/embedded-io-async/src/impls/slice_mut.rs b/embedded-io-async/src/impls/slice_mut.rs index bd64d1320..b3610e32f 100644 --- a/embedded-io-async/src/impls/slice_mut.rs +++ b/embedded-io-async/src/impls/slice_mut.rs @@ -1,5 +1,5 @@ use core::mem; -use embedded_io::SliceWriteError; +use embedded_io::ErrorKind; use crate::Write; @@ -17,7 +17,7 @@ impl Write for &mut [u8] { async fn write(&mut self, buf: &[u8]) -> Result { let amt = core::cmp::min(buf.len(), self.len()); if !buf.is_empty() && amt == 0 { - return Err(SliceWriteError::Full); + return Err(ErrorKind::StorageFull); } let (a, b) = mem::take(self).split_at_mut(amt); a.copy_from_slice(&buf[..amt]); diff --git a/embedded-io-async/src/lib.rs b/embedded-io-async/src/lib.rs index eef50ebcd..ff413d28e 100644 --- a/embedded-io-async/src/lib.rs +++ b/embedded-io-async/src/lib.rs @@ -9,9 +9,7 @@ extern crate alloc; mod impls; -pub use embedded_io::{ - Error, ErrorKind, ErrorType, ReadExactError, ReadReady, SeekFrom, WriteReady, -}; +pub use embedded_io::{Error, ErrorKind, ErrorType, ReadReady, SeekFrom, WriteReady}; /// Async reader. /// @@ -54,6 +52,15 @@ pub trait Read: ErrorType { /// the hardware with e.g. DMA. /// /// Implementations should document whether they're actually side-effect-free on cancel or not. + /// + /// # Errors + /// + /// If this function encounters any form of I/O or other error, an error + /// variant will be returned. If an error is returned then it must be + /// guaranteed that no bytes were read. + /// + /// An error of the [`ErrorKind::Interrupted`] kind is non-fatal and the read + /// operation should be retried if there is nothing else to do. async fn read(&mut self, buf: &mut [u8]) -> Result; /// Read the exact number of bytes required to fill `buf`. @@ -63,18 +70,31 @@ pub trait Read: ErrorType { /// /// This function is not side-effect-free on cancel (AKA "cancel-safe"), i.e. if you cancel (drop) a returned /// future that hasn't completed yet, some bytes might have already been read, which will get lost. - async fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), ReadExactError> { + //// + /// /// # Errors + /// + /// If this function encounters an "end of file" before completely filling + /// the buffer, it returns an error of the kind [`ErrorKind::UnexpectedEof`]. + /// The contents of `buf` are unspecified in this case. + /// + /// If any other read error is encountered then this function immediately + /// returns. The contents of `buf` are unspecified in this case. + /// + /// If this function returns an error, it is unspecified how many bytes it + /// has read, but it will never read more than would be necessary to + /// completely fill the buffer. + async fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), Self::Error> { while !buf.is_empty() { match self.read(buf).await { Ok(0) => break, Ok(n) => buf = &mut buf[n..], - Err(e) => return Err(ReadExactError::Other(e)), + Err(e) => return Err(e), } } if buf.is_empty() { Ok(()) } else { - Err(ReadExactError::UnexpectedEof) + Err(ErrorKind::UnexpectedEof.into()) } } } diff --git a/embedded-io/src/impls/slice_mut.rs b/embedded-io/src/impls/slice_mut.rs index cee991ea6..8f1722108 100644 --- a/embedded-io/src/impls/slice_mut.rs +++ b/embedded-io/src/impls/slice_mut.rs @@ -1,16 +1,8 @@ -use crate::{Error, ErrorKind, ErrorType, SliceWriteError, Write, WriteReady}; +use crate::{ErrorKind, ErrorType, SliceWriteError, Write, WriteReady}; use core::mem; -impl Error for SliceWriteError { - fn kind(&self) -> ErrorKind { - match self { - SliceWriteError::Full => ErrorKind::WriteZero, - } - } -} - impl ErrorType for &mut [u8] { - type Error = SliceWriteError; + type Error = ErrorKind; } impl core::fmt::Display for SliceWriteError { @@ -34,7 +26,7 @@ impl Write for &mut [u8] { fn write(&mut self, buf: &[u8]) -> Result { let amt = core::cmp::min(buf.len(), self.len()); if !buf.is_empty() && amt == 0 { - return Err(SliceWriteError::Full); + return Err(ErrorKind::StorageFull); } let (a, b) = mem::take(self).split_at_mut(amt); a.copy_from_slice(&buf[..amt]); diff --git a/embedded-io/src/impls/slice_ref.rs b/embedded-io/src/impls/slice_ref.rs index 0270aca27..d4372f04a 100644 --- a/embedded-io/src/impls/slice_ref.rs +++ b/embedded-io/src/impls/slice_ref.rs @@ -1,7 +1,7 @@ -use crate::{BufRead, ErrorType, Read, ReadReady}; +use crate::{BufRead, ErrorKind, ErrorType, Read, ReadReady}; impl ErrorType for &[u8] { - type Error = core::convert::Infallible; + type Error = ErrorKind; } /// Read is implemented for `&[u8]` by copying from the slice. diff --git a/embedded-io/src/impls/vec.rs b/embedded-io/src/impls/vec.rs index 3b279c564..5bea4e8b5 100644 --- a/embedded-io/src/impls/vec.rs +++ b/embedded-io/src/impls/vec.rs @@ -1,9 +1,9 @@ -use crate::{ErrorType, Write}; +use crate::{ErrorKind, ErrorType, Write}; use alloc::vec::Vec; #[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] impl ErrorType for Vec { - type Error = core::convert::Infallible; + type Error = ErrorKind; } #[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] diff --git a/embedded-io/src/lib.rs b/embedded-io/src/lib.rs index 9e2e528a9..d6e1c99dc 100644 --- a/embedded-io/src/lib.rs +++ b/embedded-io/src/lib.rs @@ -99,6 +99,10 @@ pub enum ErrorKind { /// /// [`InvalidInput`]: ErrorKind::InvalidInput InvalidData, + /// The underlying storage (typically, a filesystem) is full. + /// + /// This does not include out of quota errors. + StorageFull, /// The I/O operation's timeout expired, causing it to be canceled. TimedOut, /// This operation was interrupted. @@ -109,6 +113,13 @@ pub enum ErrorKind { /// /// This means that the operation can never succeed. Unsupported, + /// An error returned when an operation could not be completed because an + /// "end of file" was reached prematurely. + /// + /// This typically means that an operation could only succeed if it read a + /// particular number of bytes but only a smaller number of bytes could be + /// read. + UnexpectedEof, /// An operation could not be completed, because it failed /// to allocate enough memory. OutOfMemory, @@ -133,9 +144,11 @@ impl From for std::io::ErrorKind { ErrorKind::AlreadyExists => std::io::ErrorKind::AlreadyExists, ErrorKind::InvalidInput => std::io::ErrorKind::InvalidInput, ErrorKind::InvalidData => std::io::ErrorKind::InvalidData, + ErrorKind::StorageFull => std::io::ErrorKind::StorageFull, ErrorKind::TimedOut => std::io::ErrorKind::TimedOut, ErrorKind::Interrupted => std::io::ErrorKind::Interrupted, ErrorKind::Unsupported => std::io::ErrorKind::Unsupported, + ErrorKind::UnexpectedEof => std::io::ErrorKind::UnexpectedEof, ErrorKind::OutOfMemory => std::io::ErrorKind::OutOfMemory, _ => std::io::ErrorKind::Other, } @@ -159,9 +172,11 @@ impl From for ErrorKind { std::io::ErrorKind::AlreadyExists => ErrorKind::AlreadyExists, std::io::ErrorKind::InvalidInput => ErrorKind::InvalidInput, std::io::ErrorKind::InvalidData => ErrorKind::InvalidData, + std::io::ErrorKind::StorageFull => ErrorKind::StorageFull, std::io::ErrorKind::TimedOut => ErrorKind::TimedOut, std::io::ErrorKind::Interrupted => ErrorKind::Interrupted, std::io::ErrorKind::Unsupported => ErrorKind::Unsupported, + std::io::ErrorKind::UnexpectedEof => ErrorKind::UnexpectedEof, std::io::ErrorKind::OutOfMemory => ErrorKind::OutOfMemory, _ => ErrorKind::Other, } @@ -172,17 +187,11 @@ impl From for ErrorKind { /// /// This trait allows generic code to do limited inspecting of errors, /// to react differently to different kinds. -pub trait Error: core::error::Error { +pub trait Error: core::error::Error + From { /// Get the kind of this error. fn kind(&self) -> ErrorKind; } -impl Error for core::convert::Infallible { - fn kind(&self) -> ErrorKind { - match *self {} - } -} - impl Error for ErrorKind { fn kind(&self) -> ErrorKind { *self @@ -205,6 +214,14 @@ impl Error for std::io::Error { } } +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl From for std::io::Error { + fn from(value: ErrorKind) -> Self { + std::io::ErrorKind::from(value).into() + } +} + /// Base trait for all IO traits, defining the error type. /// /// All IO operations of all traits return the error defined in this trait. @@ -223,44 +240,6 @@ impl ErrorType for &mut T { type Error = T::Error; } -/// Error returned by [`Read::read_exact`] -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ReadExactError { - /// An EOF error was encountered before reading the exact amount of requested bytes. - UnexpectedEof, - /// Error returned by the inner Read. - Other(E), -} - -impl From for ReadExactError { - fn from(err: E) -> Self { - Self::Other(err) - } -} - -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] -impl From> for std::io::Error { - fn from(err: ReadExactError) -> Self { - match err { - ReadExactError::UnexpectedEof => std::io::Error::new( - std::io::ErrorKind::UnexpectedEof, - "UnexpectedEof".to_owned(), - ), - ReadExactError::Other(e) => std::io::Error::new(e.kind(), format!("{e:?}")), - } - } -} - -impl fmt::Display for ReadExactError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self:?}") - } -} - -impl core::error::Error for ReadExactError {} - /// Errors that could be returned by `Write` on `&mut [u8]`. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -326,6 +305,15 @@ pub trait Read: ErrorType { /// /// If `buf.len() == 0`, `read` returns without blocking, with either `Ok(0)` or an error. /// The `Ok(0)` doesn't indicate EOF, unlike when called with a non-empty buffer. + /// + /// # Errors + /// + /// If this function encounters any form of I/O or other error, an error + /// variant will be returned. If an error is returned then it must be + /// guaranteed that no bytes were read. + /// + /// An error of the [`ErrorKind::Interrupted`] kind is non-fatal and the read + /// operation should be retried if there is nothing else to do. fn read(&mut self, buf: &mut [u8]) -> Result; /// Read the exact number of bytes required to fill `buf`. @@ -336,18 +324,31 @@ pub trait Read: ErrorType { /// If you are using [`ReadReady`] to avoid blocking, you should not use this function. /// `ReadReady::read_ready()` returning true only guarantees the first call to `read()` will /// not block, so this function may still block in subsequent calls. - fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), ReadExactError> { + //// + /// /// # Errors + /// + /// If this function encounters an "end of file" before completely filling + /// the buffer, it returns an error of the kind [`ErrorKind::UnexpectedEof`]. + /// The contents of `buf` are unspecified in this case. + /// + /// If any other read error is encountered then this function immediately + /// returns. The contents of `buf` are unspecified in this case. + /// + /// If this function returns an error, it is unspecified how many bytes it + /// has read, but it will never read more than would be necessary to + /// completely fill the buffer. + fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), Self::Error> { while !buf.is_empty() { match self.read(buf) { Ok(0) => break, Ok(n) => buf = &mut buf[n..], - Err(e) => return Err(ReadExactError::Other(e)), + Err(e) => return Err(e), } } if buf.is_empty() { Ok(()) } else { - Err(ReadExactError::UnexpectedEof) + Err(ErrorKind::UnexpectedEof.into()) } } }