#[cfg(feature = "alloc")]
use alloc::collections::TryReserveError;
#[cfg(feature = "unstable_ascii_char")]
use core::ascii;
#[cfg(feature = "utf8")]
pub use simdutf8::compat::Utf8Error as SimdUtf8Error;
use core::fmt::{Display, Formatter, Result as FmtResult};
#[cfg(feature = "utf8")]
use core::num::NonZeroU8;
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
#[cfg(feature = "std")]
Io(std::io::Error),
#[cfg(feature = "unstable_ascii_char")]
Ascii(AsciiError),
#[cfg(feature = "utf8")]
Utf8(Utf8Error),
#[cfg(feature = "alloc")]
Allocation(TryReserveError),
Overflow {
remaining: usize
},
End {
required_count: usize
},
NoEnd,
InsufficientBuffer {
spare_capacity: usize,
required_count: usize
},
}
impl Error {
#[allow(clippy::missing_panics_doc)]
#[inline]
#[cfg(feature = "unstable_ascii_char")]
pub const fn invalid_ascii(invalid_byte: u8, valid_up_to: usize, consumed_count: usize) -> Self {
assert!(consumed_count >= valid_up_to, "at least `valid_up_to` bytes must be consumed");
Self::Ascii(AsciiError { invalid_byte, valid_up_to, consumed_count })
}
#[inline]
pub const fn overflow(remaining: usize) -> Self {
Self::Overflow { remaining }
}
#[inline]
pub const fn end(required_count: usize) -> Self {
Self::End { required_count }
}
#[inline]
pub const fn insufficient_buffer(spare_capacity: usize, required_count: usize) -> Self {
Self::InsufficientBuffer { spare_capacity, required_count }
}
}
#[allow(clippy::std_instead_of_core, reason = "Error trait in core is not yet stable")]
#[cfg(feature = "std")]
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io(error) => Some(error),
#[cfg(feature = "unstable_ascii_char")]
Self::Ascii(_) => None,
#[cfg(feature = "utf8")]
Self::Utf8(error) => error.source(),
#[cfg(feature = "alloc")]
Self::Allocation(error) => Some(error),
Self::Overflow { .. } |
Self::End { .. } |
Self::NoEnd |
Self::InsufficientBuffer { .. } => None,
}
}
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
#[cfg(feature = "std")]
Self::Io(error) => Display::fmt(error, f),
#[cfg(feature = "unstable_ascii_char")]
Self::Ascii(error) => Display::fmt(error, f),
#[cfg(feature = "utf8")]
Self::Utf8(error) => Display::fmt(error, f),
#[cfg(feature = "alloc")]
Self::Allocation(error) => Display::fmt(error, f),
Self::Overflow { remaining } => write!(f, "sink overflowed with {remaining} bytes remaining to write"),
Self::End { required_count } => write!(f, "premature end-of-stream when reading {required_count} bytes"),
Self::NoEnd => write!(f, "cannot read to end of infinite source"),
Self::InsufficientBuffer {
spare_capacity, required_count
} => write!(f, "insufficient buffer capacity ({spare_capacity}) to read {required_count} bytes"),
}
}
}
#[cfg(feature = "std")]
impl From<std::io::Error> for Error {
#[inline]
fn from(value: std::io::Error) -> Self {
Self::Io(value)
}
}
#[cfg(feature = "utf8")]
impl From<SimdUtf8Error> for Error {
#[inline]
fn from(value: SimdUtf8Error) -> Self {
Self::Utf8(value.into())
}
}
#[cfg(feature = "utf8")]
impl From<Utf8Error> for Error {
#[inline]
fn from(value: Utf8Error) -> Self {
Self::Utf8(value)
}
}
#[cfg(feature = "unstable_ascii_char")]
impl From<AsciiError> for Error {
#[inline]
fn from(value: AsciiError) -> Self {
Self::Ascii(value)
}
}
#[cfg(feature = "alloc")]
impl From<TryReserveError> for Error {
#[inline]
fn from(value: TryReserveError) -> Self {
Self::Allocation(value)
}
}
#[cfg(feature = "utf8")]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Utf8Error {
offset: usize,
inner: SimdUtf8Error,
}
#[allow(clippy::exhaustive_enums)]
#[cfg(feature = "utf8")]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Utf8ErrorKind {
IncompleteChar,
InvalidBytes(NonZeroU8),
}
#[cfg(feature = "utf8")]
impl Utf8Error {
#[inline]
#[must_use]
pub const fn offset(&self) -> usize { self.offset }
#[inline]
#[must_use]
pub fn valid_up_to(&self) -> usize {
self.offset + self.inner.valid_up_to()
}
#[inline]
#[must_use]
pub fn error_len(&self) -> Option<usize> {
self.inner.error_len()
}
#[inline]
#[must_use]
pub const fn last_error(&self) -> SimdUtf8Error { self.inner }
#[inline]
#[must_use]
pub fn error_kind(&self) -> Utf8ErrorKind {
match self.inner.error_len() {
Some(len) => Utf8ErrorKind::InvalidBytes(
unsafe {
NonZeroU8::new_unchecked(len as u8)
}
),
None => Utf8ErrorKind::IncompleteChar
}
}
#[must_use]
pub unsafe fn valid_slice_unchecked<'a>(&self, bytes: &'a [u8]) -> &'a str {
core::str::from_utf8_unchecked(bytes.get_unchecked(..self.valid_up_to()))
}
pub unsafe fn split_valid<'a>(&self, bytes: &'a [u8]) -> (&'a str, &'a [u8]) {
let (valid, invalid) = bytes.split_at_unchecked(self.valid_up_to());
(core::str::from_utf8_unchecked(valid), invalid)
}
pub unsafe fn split_valid_mut<'a>(&self, bytes: &'a mut [u8]) -> (&'a mut str, &'a mut [u8]) {
let (valid, invalid) = bytes.split_at_mut_unchecked(self.valid_up_to());
(core::str::from_utf8_unchecked_mut(valid), invalid)
}
}
#[cfg(feature = "utf8")]
impl Utf8Error {
#[cfg(any(feature = "unstable_specialization", feature = "alloc"))]
pub(crate) fn set_offset(&mut self, offset: usize) {
self.offset += offset;
}
#[cfg(feature = "unstable_specialization")]
pub(crate) fn with_offset(mut self, offset: usize) -> Self {
self.set_offset(offset);
self
}
}
#[allow(clippy::std_instead_of_core, reason = "Error trait in core is not yet stable")]
#[cfg(all(feature = "std", feature = "utf8"))]
impl std::error::Error for Utf8Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.inner)
}
}
#[cfg(feature = "utf8")]
impl Display for Utf8Error {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let valid_up_to = self.valid_up_to();
match self.error_len() {
Some(len) => write!(f, "invalid UTF-8 sequence of {len} bytes from index {valid_up_to}"),
None => write!(f, "incomplete UTF-8 byte sequence from index {valid_up_to}")
}
}
}
#[cfg(feature = "utf8")]
impl From<SimdUtf8Error> for Utf8Error {
#[inline]
fn from(inner: SimdUtf8Error) -> Self {
Self { offset: 0, inner }
}
}
#[cfg(feature = "unstable_ascii_char")]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct AsciiError {
pub invalid_byte: u8,
pub valid_up_to: usize,
pub consumed_count: usize,
}
#[cfg(feature = "unstable_ascii_char")]
impl AsciiError {
#[inline]
#[must_use]
pub const fn invalid_byte(&self) -> u8 { self.invalid_byte }
#[inline]
#[must_use]
pub const fn valid_up_to(&self) -> usize { self.valid_up_to }
#[inline]
#[must_use]
pub const fn consumed_count(&self) -> usize { self.consumed_count }
#[inline]
#[must_use]
pub const fn unchecked_count(&self) -> usize { self.consumed_count.saturating_sub(1 + self.valid_up_to) }
#[must_use]
pub fn valid_slice<'a>(&self, bytes: &'a [u8]) -> &'a [ascii::Char] {
assert!(bytes.len() >= self.valid_up_to, "the slice must contain at least `valid_up_to` bytes");
assert!(bytes[..self.valid_up_to].is_ascii(), "the valid slice must be ASCII");
unsafe {
self.valid_slice_unchecked(bytes)
}
}
#[must_use]
pub unsafe fn valid_slice_unchecked<'a>(&self, bytes: &'a [u8]) -> &'a [ascii::Char] {
bytes.get_unchecked(..self.valid_up_to).as_ascii_unchecked()
}
#[must_use]
pub fn split_valid<'a>(&self, bytes: &'a [u8]) -> (&'a [ascii::Char], &'a [u8]) {
assert!(self.consumed_count >= self.valid_up_to, "at least `valid_up_to` bytes must be consumed");
assert!(bytes.len() >= self.consumed_count, "the slice length must be longer than the consumed count");
assert!(bytes[..self.valid_up_to].is_ascii(), "the valid slice must be ASCII");
unsafe {
self.split_valid_unchecked(bytes)
}
}
#[must_use]
pub unsafe fn split_valid_unchecked<'a>(&self, bytes: &'a [u8]) -> (&'a [ascii::Char], &'a [u8]) {
(self.valid_slice_unchecked(bytes),
bytes.get_unchecked(self.valid_up_to..self.consumed_count))
}
}
#[allow(clippy::std_instead_of_core, reason = "Error trait in core is not yet stable")]
#[cfg(all(feature = "std", feature = "unstable_ascii_char"))]
impl std::error::Error for AsciiError { }
#[cfg(feature = "unstable_ascii_char")]
impl Display for AsciiError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let Self { invalid_byte, valid_up_to, .. } = self;
write!(f, "non-ASCII byte {invalid_byte:#X} at index {valid_up_to}")
}
}