oct 0.26.0

Octonary transcodings.
Documentation
// Copyright 2024-2025 Gabriel Bjørnager Jensen.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, you
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.

//! The [`Error`] error type.

mod test;

mod raw;

use crate::io::{ErrorKind, SimpleError};

use core::fmt::{self, Display, Formatter};
use core::marker::PhantomData;

#[cfg(feature = "alloc")]
use core::mem::forget;

#[cfg(feature = "alloc")]
use alloc::boxed::Box;

#[cfg(feature = "alloc")]
use alloc::collections::TryReserveError;

#[cfg(feature = "alloc")]
use alloc::ffi::NulError;

#[cfg(feature = "std")]
use std::fs::TryLockError;

use raw::Raw;

/// An input/output error.
#[derive(Debug)]
pub struct Error {
	/// The raw, packed error.
	raw: Raw,

	/// Phantom, mutable, dynamic reference for the sake
	/// of auto traits.
	data: PhantomData<&'static mut (dyn core::error::Error + Send + Sync)>,
}

impl Error {
	/// Constructs a new input/output error.
	#[cfg(feature = "alloc")]
	#[must_use]
	pub fn new<E: Into<Box<dyn core::error::Error + Send + Sync>>>(kind: ErrorKind, source: E) -> Self {
		let custom = Box::new(CustomError {
			kind,
			source: source.into(),
		});

		let raw = Raw::new_custom(custom);
		Self { raw, data: PhantomData }
	}

	/// Constructs a new input/output error.
	///
	/// This constructor is equivalent to [`new`] with
	/// the [`Other`] error kind.
	///
	/// [`new`]: Self::new
	/// [`Other`]: ErrorKind::Other
	#[cfg(feature = "alloc")]
	#[inline]
	#[must_use]
	pub fn other<E: Into<Box<dyn core::error::Error + Send + Sync>>>(source: E) -> Self {
		Self::new(ErrorKind::Other, source)
	}

	/// Constructs a new input/output error from an
	/// operation system error.
	#[cfg(feature = "std")]
	#[inline]
	#[must_use]
	pub fn from_raw_os_error(raw: i32) -> Self {
		let raw = Raw::new_os(raw);
		Self { raw, data: PhantomData }
	}

	/// Retrieves the last operating system error that
	/// occured, if any.
	#[cfg(feature = "std")]
	#[inline]
	#[must_use]
	pub fn last_os_error() -> Self {
		let Some(raw) = std::io::Error::last_os_error().raw_os_error() else {
			unreachable!("`last_os_error` must yield an operating system error");
		};

		Self::from_raw_os_error(raw)
	}

	/// Gets a constant reference to the inner error.
	#[must_use]
	pub fn get_ref(&self) -> Option<&(dyn core::error::Error + Send + Sync + 'static)> {
		match self.raw.data() {
			#[cfg(feature = "alloc")]
			ErrorData::Custom(p) => {
				let r = unsafe { &*p };
				Some(&*r.source)
			},

			_ => None,
		}
	}

	/// Gets a mutable reference to the inner error.
	#[must_use]
	pub fn get_mut(&mut self) -> Option<&mut (dyn core::error::Error + Send + Sync + 'static)> {
		match self.raw.data() {
			#[cfg(feature = "alloc")]
			ErrorData::Custom(p) => {
				let r = unsafe { &mut *p };
				Some(&mut *r.source)
			},
			_ => None,
		}
	}

	/// Retrieves the kind of error.
	#[must_use]
	pub fn kind(&self) -> ErrorKind {
		match self.raw.data() {
			ErrorData::Simple(message) => {
				message.kind()
			}

			ErrorData::Inline(kind) => {
				kind
			}

			#[cfg(feature = "alloc")]
			ErrorData::Custom(p) => {
				let r = unsafe { &*p };
				r.kind
			}

			#[cfg(feature = "std")]
			ErrorData::Os(raw) => {
				let e = std::io::Error::from_raw_os_error(raw);
				e.kind().into()
			}
		}
	}

	/// Tests if the error is an interrupt.
	#[inline]
	#[must_use]
	pub(crate) fn is_interrupted(&self) -> bool {
		matches!(self.kind(), ErrorKind::Interrupted)
	}

	/// Retrieves the raw, equivalent, operating system
	/// error.
	///
	/// If the error doesn't have an associated
	/// operating system error, this method will return
	/// [`None`].
	#[cfg(feature = "std")]
	#[must_use]
	pub fn raw_os_error(&self) -> Option<i32> {
		match self.raw.data() {
			ErrorData::Os(raw) => Some(raw),

			_ => None,
		}
	}

	/// Deconstructs the error into the internal error.
	#[cfg(feature = "alloc")]
	#[must_use]
	pub fn into_inner(self) -> Option<Box<dyn core::error::Error + Send + Sync>> {
		match self.raw.data() {
			ErrorData::Custom(p) => {
				// Do not run our destructor.
				forget(self);

				let boxed = unsafe { Box::from_raw(p) };
				Some(boxed.source)
			}

			_ => None,
		}
	}
}

impl Display for Error {
	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
		match self.raw.data() {
			ErrorData::Simple(e) => {
				Display::fmt(e, f)?;
			}

			ErrorData::Inline(kind) => {
				Display::fmt(&kind, f)?;
			}

			#[cfg(feature = "alloc")]
			ErrorData::Custom(p) => {
				let r = unsafe { &*p };

				write!(f, "{}: {}", r.kind, r.source)?;
			}

			#[cfg(feature = "std")]
			ErrorData::Os(raw) => {
				let e = std::io::Error::from_raw_os_error(raw);
				Display::fmt(&e, f)?;
			}
		}

		Ok(())
	}
}

impl Drop for Error {
	fn drop(&mut self) {
		match self.raw.data() {
			#[cfg(feature = "alloc")]
			ErrorData::Custom(p) => {
				// SAFETY: We guarantee that the pointer is valid.
				// Also, there will be no double drop as the box
				// doesn't really exist (it is only created here).
				let _ = unsafe { Box::from_raw(p) };
			}

			_ => {}
		}
	}
}

impl core::error::Error for Error {
	#[allow(deprecated)]
	fn cause(&self) -> Option<&dyn core::error::Error> {
		match self.raw.data() {
			#[cfg(feature = "alloc")]
			ErrorData::Custom(p) => {
				let r = unsafe { &*p };
				r.source.cause()
			}

			_ => None,
		}
	}

	fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
		match self.raw.data() {
			#[cfg(feature = "alloc")]
			ErrorData::Custom(p) => {
				let r = unsafe { &*p };
				r.source.source()
			}

			_ => None,
		}
	}
}

#[cfg(feature = "std")]
impl From<std::io::Error> for Error {
	fn from(value: std::io::Error) -> Self {
		if let Some(raw) = value.raw_os_error() {
			return Self::from_raw_os_error(raw);
		};

		let kind = ErrorKind::from(value.kind());

		if let Some(source) = value.into_inner() {
			return Self::new(kind, source);
		};

		kind.into()
	}
}

impl From<ErrorKind> for Error {
	#[inline]
	fn from(value: ErrorKind) -> Self {
		let raw = Raw::new_inline(value);
		Self { raw, data: PhantomData }
	}
}

#[cfg(feature = "alloc")]
impl From<NulError> for Error {
	#[cfg_attr(not(feature = "std"), expect(unused))]
	#[inline]
	fn from(_value: NulError) -> Self {
		let e = const { &SimpleError::new(ErrorKind::InvalidData, "nul octet found in c-like string") };
		e.into()
	}
}

impl From<&'static SimpleError> for Error {
	#[inline]
	fn from(value: &'static SimpleError) -> Self {
		let raw = Raw::new_simple(value);
		Self { raw, data: PhantomData }
	}
}

#[cfg(feature = "std")]
impl From<TryLockError> for Error {
	#[inline]
	fn from(value: TryLockError) -> Self {
		match value {
			TryLockError::Error(e) => e.into(),

			TryLockError::WouldBlock => ErrorKind::WouldBlock.into(),
		}
	}
}

#[cfg(feature = "alloc")]
impl From<TryReserveError> for Error {
	#[inline]
	fn from(_value: TryReserveError) -> Self {
		let e = const { &SimpleError::new(ErrorKind::OutOfMemory, "unable to reserve in collection") };
		e.into()
	}
}

/// Error data.
///
/// This enumeration wraps the decoded fields of an
/// [`Raw`] object. The generic denotes the custom
/// error type, which should be some pointer to
/// [`CustomError`].
#[derive(Debug)]
pub(super) enum ErrorData {
	/// A simple error.
	Simple(&'static SimpleError),

	/// An inlined error.
	Inline(ErrorKind),

	/// A custom error.
	#[cfg(feature = "alloc")]
	Custom(*mut CustomError),

	/// An operating system error.
	#[cfg(feature = "std")]
	Os(i32),
}

// NOTE: Alignement ensures some padding bits in
// addresses.
/// A custom error.
#[cfg(feature = "alloc")]
#[repr(align(4))]
#[derive(Debug)]
pub(super) struct CustomError {
	/// The error kind.
	pub kind: ErrorKind,

	/// The source error.
	pub source: Box<dyn core::error::Error + Send + Sync>,
}