diff --git a/util/src/controlflow.rs b/util/src/controlflow.rs index fa372b1..e00aa8e 100644 --- a/util/src/controlflow.rs +++ b/util/src/controlflow.rs @@ -2,6 +2,17 @@ #[macro_export] /// A simple for loop to repeat a $body a number of times +/// +/// # Examples +/// +/// ``` +/// use rosenpass_util::repeat; +/// let mut sum = 0; +/// repeat!(10, { +/// sum += 1; +/// }); +/// assert_eq!(sum, 10); +/// ``` macro_rules! repeat { ($times:expr, $body:expr) => { for _ in 0..($times) { @@ -12,6 +23,23 @@ macro_rules! repeat { #[macro_export] /// Return unless the condition $cond is true, with return value $val, if given. +/// +/// # Examples +/// +/// ``` +/// use rosenpass_util::return_unless; +/// fn test_fn() -> i32 { +/// return_unless!(true, 1); +/// 0 +/// } +/// assert_eq!(test_fn(), 0); + +/// fn test_fn2() -> i32 { +/// return_unless!(false, 1); +/// 0 +/// } +/// assert_eq!(test_fn2(), 1); +/// ``` macro_rules! return_unless { ($cond:expr) => { if !($cond) { @@ -27,6 +55,23 @@ macro_rules! return_unless { #[macro_export] /// Return if the condition $cond is true, with return value $val, if given. +/// +/// # Examples +/// +/// ``` +/// use rosenpass_util::return_if; +/// fn test_fn() -> i32 { +/// return_if!(true, 1); +/// 0 +/// } +/// assert_eq!(test_fn(), 1); + +/// fn test_fn2() -> i32 { +/// return_if!(false, 1); +/// 0 +/// } +/// assert_eq!(test_fn2(), 0); +/// ``` macro_rules! return_if { ($cond:expr) => { if $cond { @@ -42,6 +87,27 @@ macro_rules! return_if { #[macro_export] /// Break unless the condition is true, from the loop with label $val, if given. +/// +/// # Examples +/// +/// ``` +/// use rosenpass_util::break_if; +/// let mut sum = 0; +/// for i in 0..10 { +/// break_if!(i == 5); +/// sum += 1; +/// } +/// assert_eq!(sum, 5); + +/// let mut sum = 0; +/// 'one: for _ in 0..10 { +/// for j in 0..20 { +/// break_if!(j == 5, 'one); +/// sum += 1; +/// } +/// } +/// assert_eq!(sum, 5); +/// ``` macro_rules! break_if { ($cond:expr) => { if $cond { @@ -57,6 +123,25 @@ macro_rules! break_if { #[macro_export] /// Continue if the condition is true, in the loop with label $val, if given. +/// +/// # Examples +/// +/// ``` +/// use rosenpass_util::continue_if; +/// let mut sum = 0; +/// for i in 0..10 { +/// continue_if!(i == 5); +/// sum += 1; +/// } +/// assert_eq!(sum, 9); + +/// let mut sum = 0; +/// 'one: for i in 0..10 { +/// continue_if!(i == 5, 'one); +/// sum += 1; +/// } +/// assert_eq!(sum, 9); +/// ``` macro_rules! continue_if { ($cond:expr) => { if $cond { @@ -69,81 +154,3 @@ macro_rules! continue_if { } }; } - -#[cfg(test)] -mod tests { - #[test] - fn test_repeat() { - let mut sum = 0; - repeat!(10, { - sum += 1; - }); - assert_eq!(sum, 10); - } - - #[test] - fn test_return_unless() { - fn test_fn() -> i32 { - return_unless!(true, 1); - 0 - } - assert_eq!(test_fn(), 0); - - fn test_fn2() -> i32 { - return_unless!(false, 1); - 0 - } - assert_eq!(test_fn2(), 1); - } - - #[test] - fn test_return_if() { - fn test_fn() -> i32 { - return_if!(true, 1); - 0 - } - assert_eq!(test_fn(), 1); - - fn test_fn2() -> i32 { - return_if!(false, 1); - 0 - } - assert_eq!(test_fn2(), 0); - } - - #[test] - fn test_break_if() { - let mut sum = 0; - for i in 0..10 { - break_if!(i == 5); - sum += 1; - } - assert_eq!(sum, 5); - - let mut sum = 0; - 'one: for _ in 0..10 { - for j in 0..20 { - break_if!(j == 5, 'one); - sum += 1; - } - } - assert_eq!(sum, 5); - } - - #[test] - fn test_continue_if() { - let mut sum = 0; - for i in 0..10 { - continue_if!(i == 5); - sum += 1; - } - assert_eq!(sum, 9); - - let mut sum = 0; - 'one: for i in 0..10 { - continue_if!(i == 5, 'one); - sum += 1; - } - assert_eq!(sum, 9); - } -} diff --git a/util/src/fd.rs b/util/src/fd.rs index f3b9e06..34c219d 100644 --- a/util/src/fd.rs +++ b/util/src/fd.rs @@ -1,3 +1,5 @@ +//! Utilities for working with file descriptors + use anyhow::bail; use rustix::{ fd::{AsFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}, @@ -8,10 +10,37 @@ use crate::{mem::Forgetting, result::OkExt}; /// Prepare a file descriptor for use in Rust code. /// - /// Checks if the file descriptor is valid and duplicates it to a new file descriptor. /// The old file descriptor is masked to avoid potential use after free (on file descriptor) /// in case the given file descriptor is still used somewhere +/// +/// # Panic and safety +/// +/// Will panic if the given file descriptor is negative of or larger than +/// the file descriptor numbers permitted by the operating system. +/// +/// # Examples +/// +/// ``` +/// use std::io::Write; +/// use std::os::fd::{IntoRawFd, AsRawFd}; +/// use tempfile::tempdir; +/// use rosenpass_util::fd::{claim_fd, FdIo}; +/// +/// // Open a file and turn it into a raw file descriptor +/// let orig = tempfile::tempfile()?.into_raw_fd(); +/// +/// // Reclaim that file and ready it for reading +/// let mut claimed = FdIo(claim_fd(orig)?); +/// +/// // A different file descriptor is used +/// assert!(orig.as_raw_fd() != claimed.0.as_raw_fd()); +/// +/// // Write some data +/// claimed.write_all(b"Hello, World!")?; +/// +/// Ok::<(), std::io::Error>(()) +/// ``` pub fn claim_fd(fd: RawFd) -> rustix::io::Result { let new = clone_fd_cloexec(unsafe { BorrowedFd::borrow_raw(fd) })?; mask_fd(fd)?; @@ -22,7 +51,32 @@ pub fn claim_fd(fd: RawFd) -> rustix::io::Result { /// /// Checks if the file descriptor is valid. /// -/// Unlike [claim_fd], this will reuse the same file descriptor identifier instead of masking it. +/// Unlike [claim_fd], this will try to reuse the same file descriptor identifier instead of masking it. +/// +/// # Panic and safety +/// +/// Will panic if the given file descriptor is negative of or larger than +/// the file descriptor numbers permitted by the operating system. +/// +/// # Examples +/// +/// ``` +/// use std::io::Write; +/// use std::os::fd::IntoRawFd; +/// use tempfile::tempdir; +/// use rosenpass_util::fd::{claim_fd_inplace, FdIo}; +/// +/// // Open a file and turn it into a raw file descriptor +/// let fd = tempfile::tempfile()?.into_raw_fd(); +/// +/// // Reclaim that file and ready it for reading +/// let mut fd = FdIo(claim_fd_inplace(fd)?); +/// +/// // Write some data +/// fd.write_all(b"Hello, World!")?; +/// +/// Ok::<(), std::io::Error>(()) +/// ``` pub fn claim_fd_inplace(fd: RawFd) -> rustix::io::Result { let mut new = unsafe { OwnedFd::from_raw_fd(fd) }; let tmp = clone_fd_cloexec(&new)?; @@ -30,6 +84,13 @@ pub fn claim_fd_inplace(fd: RawFd) -> rustix::io::Result { Ok(new) } +/// Will close the given file descriptor and overwrite +/// it with a masking file descriptor (see [open_nullfd]) to prevent accidental reuse. +/// +/// # Panic and safety +/// +/// Will panic if the given file descriptor is negative of or larger than +/// the file descriptor numbers permitted by the operating system. pub fn mask_fd(fd: RawFd) -> rustix::io::Result<()> { // Safety: because the OwnedFd resulting from OwnedFd::from_raw_fd is wrapped in a Forgetting, // it never gets dropped, meaning that fd is never closed and thus outlives the OwnedFd @@ -37,11 +98,17 @@ pub fn mask_fd(fd: RawFd) -> rustix::io::Result<()> { clone_fd_to_cloexec(open_nullfd()?, &mut owned) } +/// Duplicate a file descriptor, setting the close on exec flag pub fn clone_fd_cloexec(fd: Fd) -> rustix::io::Result { - const MINFD: RawFd = 3; // Avoid stdin, stdout, and stderr + /// Avoid stdin, stdout, and stderr + const MINFD: RawFd = 3; fcntl_dupfd_cloexec(fd, MINFD) } +/// Duplicate a file descriptor, setting the close on exec flag. +/// +/// This is slightly different from [clone_fd_cloexec], as this function supports specifying an +/// explicit destination file descriptor. #[cfg(target_os = "linux")] pub fn clone_fd_to_cloexec(fd: Fd, new: &mut OwnedFd) -> rustix::io::Result<()> { use rustix::io::{dup3, DupFlags}; @@ -56,7 +123,21 @@ pub fn clone_fd_to_cloexec(fd: Fd, new: &mut OwnedFd) -> rustix::io::R } /// Open a "blocked" file descriptor. I.e. a file descriptor that is neither meant for reading nor -/// writing +/// writing. +/// +/// # Safety +/// +/// The behavior of the file descriptor when being written to or from is undefined. +/// +/// # Examples +/// +/// ``` +/// use std::{fs::File, io::Write, os::fd::IntoRawFd}; +/// use rustix::fd::FromRawFd; +/// use rosenpass_util::fd::open_nullfd; +/// +/// let nullfd = open_nullfd().unwrap(); +/// ``` pub fn open_nullfd() -> rustix::io::Result { use rustix::fs::{open, Mode, OFlags}; // TODO: Add tests showing that this will throw errors on use @@ -64,8 +145,24 @@ pub fn open_nullfd() -> rustix::io::Result { } /// Convert low level errors into std::io::Error +/// +/// # Examples +/// +/// ``` +/// use std::io::ErrorKind as EK; +/// use rustix::io::Errno; +/// use rosenpass_util::fd::IntoStdioErr; +/// +/// let e = Errno::INTR.into_stdio_err(); +/// assert!(matches!(e.kind(), EK::Interrupted)); +/// +/// let r : rustix::io::Result<()> = Err(Errno::INTR); +/// assert!(matches!(r, Err(e) if e.kind() == EK::Interrupted)); +/// ``` pub trait IntoStdioErr { + /// Target type produced (e.g. std::io:Error or std::io::Result depending on context type Target; + /// Convert low level errors to fn into_stdio_err(self) -> Self::Target; } @@ -86,6 +183,10 @@ impl IntoStdioErr for rustix::io::Result { } /// Read and write directly from a file descriptor +/// +/// # Examples +/// +/// See [claim_fd]. pub struct FdIo(pub Fd); impl std::io::Read for FdIo { @@ -104,7 +205,17 @@ impl std::io::Write for FdIo { } } +/// Helpers for accessing stat(2) information pub trait StatExt { + /// Check if the file is a socket + /// + /// # Examples + /// + /// ``` + /// use rosenpass_util::fd::StatExt; + /// assert!(rustix::fs::stat("/")?.is_socket() == false); + /// Ok::<(), rustix::io::Errno>(()) + /// ```` fn is_socket(&self) -> bool; } @@ -116,8 +227,21 @@ impl StatExt for rustix::fs::Stat { } } +/// Helpers for accessing stat(2) information on an open file descriptor pub trait TryStatExt { + /// Error type returned by operations type Error; + + /// Check if the file is a socket + /// + /// # Examples + /// + /// ``` + /// use rosenpass_util::fd::TryStatExt; + /// let fd = rustix::fs::open("/", rustix::fs::OFlags::empty(), rustix::fs::Mode::empty())?; + /// assert!(matches!(fd.is_socket(), Ok(false))); + /// Ok::<(), rustix::io::Errno>(()) + /// ```` fn is_socket(&self) -> Result; } @@ -132,13 +256,18 @@ where } } +/// Determine the type of socket a file descriptor represents pub trait GetSocketType { + /// Error type returned by operations in this trait type Error; + /// Look up the socket; see [rustix::net::sockopt::get_socket_type] fn socket_type(&self) -> Result; + /// Checks if the socket is a datagram socket fn is_datagram_socket(&self) -> Result { use rustix::net::SocketType; matches!(self.socket_type()?, SocketType::DGRAM).ok() } + /// Checks if the socket is a stream socket fn is_stream_socket(&self) -> Result { Ok(self.socket_type()? == rustix::net::SocketType::STREAM) } @@ -155,13 +284,18 @@ where } } +/// Distinguish different socket address familys; e.g. IP and unix sockets #[cfg(target_os = "linux")] pub trait GetSocketDomain { + /// Error type returned by operations in this trait type Error; + /// Retrieve the socket domain (address family) fn socket_domain(&self) -> Result; + /// Alias for [socket_domain] fn socket_address_family(&self) -> Result { self.socket_domain() } + /// Check if the underlying socket is a unix domain socket fn is_unix_socket(&self) -> Result { Ok(self.socket_domain()? == rustix::net::AddressFamily::UNIX) } @@ -179,10 +313,14 @@ where } } +/// Distinguish different types of unix sockets #[cfg(target_os = "linux")] pub trait GetUnixSocketType { + /// Error type returned by operations in this trait type Error; + /// Check if the socket is a unix stream socket fn is_unix_stream_socket(&self) -> Result; + /// Returns Ok(()) only if the underlying socket is a unix stream socket fn demand_unix_stream_socket(&self) -> anyhow::Result<()>; } @@ -210,14 +348,18 @@ where } #[cfg(target_os = "linux")] +/// Distinguish between different network socket protocols (e.g. tcp, udp) pub trait GetSocketProtocol { + /// Retrieve the socket protocol fn socket_protocol(&self) -> Result, rustix::io::Errno>; + /// Check if the socket is a udp socket fn is_udp_socket(&self) -> Result { self.socket_protocol()? .map(|p| p == rustix::net::ipproto::UDP) .unwrap_or(false) .ok() } + /// Return Ok(()) only if the socket is a udp socket fn demand_udp_socket(&self) -> anyhow::Result<()> { match self.socket_protocol() { Ok(Some(rustix::net::ipproto::UDP)) => Ok(()), @@ -243,58 +385,50 @@ where #[cfg(test)] mod tests { use super::*; - use std::fs::{read_to_string, File}; use std::io::{Read, Write}; - use std::os::fd::IntoRawFd; - use tempfile::tempdir; - - #[test] - fn test_claim_fd() { - let tmp_dir = tempdir().unwrap(); - let path = tmp_dir.path().join("test"); - let file = File::create(path.clone()).unwrap(); - let fd: RawFd = file.into_raw_fd(); - let owned_fd = claim_fd(fd).unwrap(); - let mut file = unsafe { File::from_raw_fd(owned_fd.into_raw_fd()) }; - file.write_all(b"Hello, World!").unwrap(); - - let message = read_to_string(path).unwrap(); - assert_eq!(message, "Hello, World!"); - } #[test] #[should_panic(expected = "fd != u32::MAX as RawFd")] fn test_claim_fd_invalid_neg() { - let fd: RawFd = -1; - let _ = claim_fd(fd); + let _ = claim_fd(-1); } #[test] #[should_panic(expected = "fd != u32::MAX as RawFd")] fn test_claim_fd_invalid_max() { - let fd: RawFd = i64::MAX as RawFd; - let _ = claim_fd(fd); + let _ = claim_fd(i64::MAX as RawFd); } #[test] - fn test_open_nullfd_write() { - let nullfd = open_nullfd().unwrap(); - let mut file = unsafe { File::from_raw_fd(nullfd.into_raw_fd()) }; - let res = file.write_all(b"Hello, World!"); - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - "Bad file descriptor (os error 9)" - ); + #[should_panic] + fn test_claim_fd_inplace_invalid_neg() { + let _ = claim_fd_inplace(-1); } #[test] - fn test_open_nullfd_read() { - let nullfd = open_nullfd().unwrap(); - let mut file = unsafe { File::from_raw_fd(nullfd.into_raw_fd()) }; - let mut buffer = [0; 10]; - let res = file.read_exact(&mut buffer); - assert!(res.is_err()); - assert_eq!(res.unwrap_err().to_string(), "failed to fill whole buffer"); + #[should_panic] + fn test_claim_fd_inplace_invalid_max() { + let _ = claim_fd_inplace(i64::MAX as RawFd); + } + + #[test] + #[should_panic] + fn test_mask_fd_invalid_neg() { + let _ = mask_fd(-1); + } + + #[test] + #[should_panic] + fn test_mask_fd_invalid_max() { + let _ = mask_fd(i64::MAX as RawFd); + } + + #[test] + fn test_open_nullfd() -> anyhow::Result<()> { + let mut file = FdIo(open_nullfd()?); + let mut buf = [0; 10]; + assert!(matches!(file.read(&mut buf), Ok(0) | Err(_))); + assert!(matches!(file.write(&buf), Err(_))); + Ok(()) } } diff --git a/util/src/file.rs b/util/src/file.rs index 3124cd7..86d0c01 100644 --- a/util/src/file.rs +++ b/util/src/file.rs @@ -1,15 +1,45 @@ +//! Helpers for working with files + use anyhow::ensure; use std::fs::File; use std::io::Read; use std::os::unix::fs::OpenOptionsExt; use std::{fs::OpenOptions, path::Path}; +/// Level of secrecy applied for a file pub enum Visibility { + /// The file might contain a public key Public, + /// The file might contain a secret key Secret, } -/// Open a file writable +/// Open a file writeably, truncating the file. +/// +/// Sensible default permissions are chosen based on the value of `visibility` +/// +/// # Examples +/// +/// ``` +/// use std::io::{Write, Read}; +/// use tempfile::tempdir; +/// use rosenpass_util::file::{fopen_r, fopen_w, Visibility}; +/// +/// const CONTENTS : &[u8] = b"Hello World"; +/// +/// let dir = tempdir()?; +/// let path = dir.path().join("secret_key"); +/// +/// let mut f = fopen_w(&path, Visibility::Secret)?; +/// f.write_all(CONTENTS)?; +/// +/// let mut f = fopen_r(&path)?; +/// let mut b = Vec::new(); +/// f.read_to_end(&mut b)?; +/// assert_eq!(CONTENTS, &b); +/// +/// Ok::<(), std::io::Error>(()) +/// ``` pub fn fopen_w>(path: P, visibility: Visibility) -> std::io::Result { let mut options = OpenOptions::new(); options.create(true).write(true).read(false).truncate(true); @@ -19,7 +49,12 @@ pub fn fopen_w>(path: P, visibility: Visibility) -> std::io::Resu }; options.open(path) } -/// Open a file readable + +/// Open a file readably +/// +/// # Examples +/// +/// See [fopen_w]. pub fn fopen_r>(path: P) -> std::io::Result { OpenOptions::new() .read(true) @@ -29,9 +64,47 @@ pub fn fopen_r>(path: P) -> std::io::Result { .open(path) } +/// Extension trait for [std::io::Read] adding [read_slice_to_end] pub trait ReadSliceToEnd { + /// Error type returned by functions in this trait type Error; + /// Read slice asserting that the length of the data to read is at most + /// as long as the buffer to read into + /// + /// Note that this *may* append data read to [buf] even if the function fails, + /// so the caller should make no assumptions about the contents of the buffer + /// after calling read_slice_to_end if the result is an error. + /// + /// # Examples + /// + /// ``` + /// use rosenpass_util::file::ReadSliceToEnd; + /// + /// const DATA : &[u8] = b"Hello World"; + /// + /// // It is OK if file and buffer are equally long + /// let mut buf = vec![b' '; 11]; + /// let res = Clone::clone(&DATA).read_slice_to_end(&mut buf[..DATA.len()]); + /// assert!(res.is_ok()); // Read is overlong + /// assert_eq!(buf, DATA); // Finally, data was successfully read + /// + /// // It is OK if the buffer is longer than the file + /// let mut buf = vec![b' '; 16]; + /// let res = Clone::clone(&DATA).read_slice_to_end(&mut buf); + /// assert!(matches!(res, Ok(11))); + /// assert_eq!(buf, b"Hello World "); // Data was still read to the buffer! + /// + /// // It is not OK if the buffer is shorter than the file + /// let mut buf = vec![b' '; 5]; + /// let res = Clone::clone(&DATA).read_slice_to_end(&mut buf); + /// assert!(res.is_err()); + /// + /// // THE BUFFER MAY STILL BE FILLED THOUGH, BUT THIS IS NOT GUARANTEED + /// assert_eq!(buf, b"Hello"); // Data was still read to the buffer! + /// + /// Ok::<(), std::io::Error>(()) + /// ``` fn read_slice_to_end(&mut self, buf: &mut [u8]) -> Result; } @@ -53,9 +126,50 @@ impl ReadSliceToEnd for R { } } +/// Extension trait for [std::io::Read] adding [read_exact_to_end] pub trait ReadExactToEnd { + /// Error type returned by functions in this trait type Error; + /// Read slice asserting that the length of the data to be read + /// and the buffer are exactly the same length. + /// + /// Note that this *may* append data read to [buf] even if the function fails, + /// so the caller should make no assumptions about the contents of the buffer + /// after calling read_exact_to_end if the result is an error. + /// + /// # Examples + /// + /// ``` + /// use rosenpass_util::file::ReadExactToEnd; + /// + /// const DATA : &[u8] = b"Hello World"; + /// + /// // It is OK if file and buffer are equally long + /// let mut buf = vec![b' '; 11]; + /// let res = Clone::clone(&DATA).read_exact_to_end(&mut buf[..DATA.len()]); + /// assert!(res.is_ok()); // Read is overlong + /// assert_eq!(buf, DATA); // Finally, data was successfully read + /// + /// // It is not OK if the buffer is longer than the file + /// let mut buf = vec![b' '; 16]; + /// let res = Clone::clone(&DATA).read_exact_to_end(&mut buf); + /// assert!(res.is_err()); + /// + /// // THE BUFFER MAY STILL BE FILLED THOUGH, BUT THIS IS NOT GUARANTEED + /// // The read implementation for &[u8] happens not to do this + /// assert_eq!(buf, b" "); // Data was still read to the buffer! + /// + /// // It is not OK if the buffer is shorter than the file + /// let mut buf = vec![b' '; 5]; + /// let res = Clone::clone(&DATA).read_exact_to_end(&mut buf); + /// assert!(res.is_err()); + /// + /// // THE BUFFER MAY STILL BE FILLED THOUGH, BUT THIS IS NOT GUARANTEED + /// assert_eq!(buf, b"Hello"); // Data was still read to the buffer! + /// + /// Ok::<(), std::io::Error>(()) + /// ``` fn read_exact_to_end(&mut self, buf: &mut [u8]) -> Result<(), Self::Error>; } @@ -70,51 +184,190 @@ impl ReadExactToEnd for R { } } +/// Load a value from a file pub trait LoadValue { + /// Error type returned type Error; + /// Load a value from a file + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// use std::io::Write; + /// use tempfile::tempdir; + /// use rosenpass_util::file::{fopen_r, fopen_w, LoadValue, ReadExactToEnd, StoreValue, Visibility}; + /// + /// #[derive(Debug, PartialEq, Eq)] + /// struct MyInt(pub u32); + /// + /// impl StoreValue for MyInt { + /// type Error = std::io::Error; + /// + /// fn store>(&self, path: P) -> Result<(), Self::Error> { + /// let mut f = fopen_w(path, Visibility::Public)?; + /// f.write_all(&self.0.to_le_bytes()) + /// } + /// } + /// + /// impl LoadValue for MyInt { + /// type Error = anyhow::Error; + /// + /// fn load>(path: P) -> Result + /// where + /// Self: Sized, + /// { + /// let mut b = [0u8; 4]; + /// fopen_r(path)?.read_exact_to_end(&mut b)?; + /// Ok(MyInt(u32::from_le_bytes(b))) + /// } + /// } + /// + /// let dir = tempdir()?; + /// let path = dir.path().join("my_int"); + /// + /// let orig = MyInt(17); + /// orig.store(&path)?; + /// + /// let copy = MyInt::load(&path)?; + /// assert_eq!(orig, copy); + /// + /// Ok::<(), anyhow::Error>(()) + /// ``` fn load>(path: P) -> Result where Self: Sized; } +/// Load a value from a file encoded as base64 pub trait LoadValueB64 { + /// Error type returned type Error; + /// Load a value from a file encoded as base64 + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// use tempfile::tempdir; + /// use rosenpass_util::b64::{b64_decode, b64_encode}; + /// use rosenpass_util::file::{ + /// fopen_r, fopen_w, LoadValueB64, ReadSliceToEnd, StoreValueB64, StoreValueB64Writer, + /// Visibility, + /// }; + /// + /// #[derive(Debug, PartialEq, Eq)] + /// struct MyInt(pub u32); + /// + /// impl StoreValueB64Writer for MyInt { + /// type Error = anyhow::Error; + /// + /// fn store_b64_writer( + /// &self, + /// mut writer: W, + /// ) -> Result<(), Self::Error> { + /// // Let me just point out while writing this example, + /// // that this API is currently, entirely shit in terms of + /// // how it deals with buffer lengths. + /// let mut buf = [0u8; F]; + /// let b64 = b64_encode(&self.0.to_le_bytes(), &mut buf)?; + /// writer.write_all(b64.as_bytes())?; + /// Ok(()) + /// } + /// } + /// + /// impl StoreValueB64 for MyInt { + /// type Error = anyhow::Error; + /// + /// fn store_b64>(&self, path: P) -> Result<(), Self::Error> + /// where + /// Self: Sized, + /// { + /// // The buffer length (first generic arg) is kind of an upper bound + /// self.store_b64_writer::(fopen_w(path, Visibility::Public)?) + /// } + /// } + /// + /// impl LoadValueB64 for MyInt { + /// type Error = anyhow::Error; + /// + /// fn load_b64>(path: P) -> Result + /// where + /// Self: Sized, + /// { + /// // The buffer length is kind of an upper bound + /// let mut b64_buf = [0u8; F]; + /// let b64_len = fopen_r(path)?.read_slice_to_end(&mut b64_buf)?; + /// let b64_dat = &b64_buf[..b64_len]; + /// + /// let mut buf = [0u8; 4]; + /// b64_decode(b64_dat, &mut buf)?; + /// Ok(MyInt(u32::from_le_bytes(buf))) + /// } + /// } + /// + /// let dir = tempdir()?; + /// let path = dir.path().join("my_int"); + /// + /// let orig = MyInt(17); + /// orig.store_b64::<10, _>(&path)?; + /// + /// let copy = MyInt::load_b64::<10, _>(&path)?; + /// assert_eq!(orig, copy); + /// + /// Ok::<(), anyhow::Error>(()) + /// ``` fn load_b64>(path: P) -> Result where Self: Sized; } +/// Store a value encoded as base64 in a file. pub trait StoreValueB64 { + /// Error type returned type Error; + /// Store a value encoded as base64 in a file. + /// + /// # Examples + /// + /// See [LoadValueB64::load_b64]. fn store_b64>(&self, path: P) -> Result<(), Self::Error> where Self: Sized; } +/// Store a value encoded as base64 to a writable stream pub trait StoreValueB64Writer { + /// Error type returned type Error; + /// Store a value encoded as base64 to a writable stream + /// + /// # Examples + /// + /// See [LoadValueB64::load_b64]. fn store_b64_writer( &self, writer: W, ) -> Result<(), Self::Error>; } +/// Store a value in a file pub trait StoreValue { + /// Error type returned type Error; + /// Store a value in a file + /// + /// # Examples + /// + /// See [LoadValue::load]. fn store>(&self, path: P) -> Result<(), Self::Error>; } -pub trait DisplayValueB64 { - type Error; - - fn display_b64<'o>(&self, output: &'o mut [u8]) -> Result<&'o str, Self::Error>; -} - #[cfg(test)] mod tests { use super::*; diff --git a/util/src/functional.rs b/util/src/functional.rs index 54ebe8c..fc2cb2b 100644 --- a/util/src/functional.rs +++ b/util/src/functional.rs @@ -1,87 +1,260 @@ -pub fn mutating(mut v: T, f: F) -> T +//! Syntax sugar & helpers for a functional programming style and method chains + +/// Mutate a value; mostly syntactic sugar +/// +/// # Examples +/// +/// ``` +/// use std::borrow::Borrow; +/// use rosenpass_util::functional::{mutating, MutatingExt, sideeffect, SideffectExt, ApplyExt}; +/// use rosenpass_util::mem::DiscardResultExt; +/// +/// // Say you have a function that takes a mutable reference +/// fn replace(slice: &mut [T], targ: T, by: T) { +/// for val in slice.iter_mut() { +/// if *val == targ { +/// *val = by; +/// } +/// } +/// } +/// +/// // Or you have some action that you want to perform as a side effect +/// fn count(accumulator: &mut usize, slice: &[T], targ: T) { +/// *accumulator += slice.iter() +/// .filter(|e| *e == &targ) +/// .count(); +/// } +/// +/// // Lets say, you also have a function that actually modifies the value +/// fn rot2(slice: [u8; N]) -> [u8; N] { +/// let it = slice.iter() +/// .cycle() +/// .skip(2) +/// .take(N); +/// +/// let mut ret = [0u8; N]; +/// for (no, elm) in it.enumerate() { +/// ret[no] = *elm; +/// } +/// +/// ret +/// } +/// +/// // Then these function are kind of clunky to use in an expression; +/// // it can be done, but the resulting code is a bit verbose +/// let mut accu = 0; +/// assert_eq!(b"llo_WorldHe", &{ +/// let mut buf = b"Hello World".to_owned(); +/// count(&mut accu, &buf, b'l'); +/// replace(&mut buf, b' ', b'_'); +/// rot2(buf) +/// }); +/// assert_eq!(accu, 3); +/// +/// // Instead you could use mutating for a slightly prettier syntax, +/// // but this makes only sense if you want to apply a single action +/// assert_eq!(b"Hello_World", +/// &mutating(b"Hello World".to_owned(), |buf| +/// replace(buf, b' ', b'_'))); +/// +/// // The same is the case for sideeffect() +/// assert_eq!(b"Hello World", +/// &sideeffect(b"Hello World".to_owned(), |buf| +/// count(&mut accu, buf, b'l'))); +/// assert_eq!(accu, 6); +/// +/// // Calling rot2 on its own is straightforward of course +/// assert_eq!(b"llo WorldHe", &rot2(b"Hello World".to_owned())); +/// +/// // These operations can be conveniently used in a method chain +/// // by using the extension traits. +/// // +/// // This is also quite handy if you just need to +/// // modify a value in a long method chain. +/// // +/// // Here apply() also comes in quite handy, because we can use it +/// // to modify the value itself (turning it into a reference). +/// assert_eq!(b"llo_WorldHe", +/// b"Hello World" +/// .to_owned() +/// .sideeffect(|buf| count(&mut accu, buf, b'l')) +/// .mutating(|buf| replace(buf, b' ', b'_')) +/// .apply(rot2) +/// .borrow() as &[u8]); +/// assert_eq!(accu, 9); +/// +/// // There is also the mutating_mut variant, which can operate on any mutable reference; +/// // this is mainly useful in a method chain if you are dealing with a mutable reference. +/// // +/// // This example is quite artificial though. +/// assert_eq!(b"llo_WorldHe", +/// b"hello world" +/// .to_owned() +/// .mutating(|buf| +/// // Can not use sideeffect_ref at the start, because it drops the mut reference +/// // status +/// buf.sideeffect_mut(|buf| count(&mut accu, buf, b'l')) +/// .mutating_mut(|buf| replace(buf, b' ', b'_')) +/// .mutating_mut(|buf| replace(buf, b'h', b'H')) +/// .mutating_mut(|buf| replace(buf, b'w', b'W')) +/// // Using rot2 is more complex now +/// .mutating_mut(|buf| { +/// *buf = rot2(*buf); +/// }) +/// // Can use sideeffect_ref at the end, because we no longer need +/// // the &mut reference +/// .sideeffect_ref(|buf| count(&mut accu, *buf, b'l')) +/// // And we can use apply to fix the return value – if we really want to go +/// // crazy and avoid using a {} block +/// .apply(|_| ()) +/// // [crate::mem::DiscardResult::discard_result] does the same job and it is more explicit. +/// .discard_result()) +/// .borrow() as &[u8]); +/// assert_eq!(accu, 15); +/// ``` +pub fn mutating(mut v: T, mut f: F) -> T where - F: Fn(&mut T), + F: FnMut(&mut T), { f(&mut v); v } +/// Mutating values on the fly in a method chain pub trait MutatingExt { + /// Mutating values on the fly in a method chain (owning) + /// + /// # Examples + /// + /// See [mutating]. fn mutating(self, f: F) -> Self where - F: Fn(&mut Self); + F: FnMut(&mut Self); + + /// Mutating values on the fly in a method chain (non-owning) + /// + /// # Examples + /// + /// See [mutating]. fn mutating_mut(&mut self, f: F) -> &mut Self where - F: Fn(&mut Self); + F: FnMut(&mut Self); } impl MutatingExt for T { fn mutating(self, f: F) -> Self where - F: Fn(&mut Self), + F: FnMut(&mut Self), { mutating(self, f) } - fn mutating_mut(&mut self, f: F) -> &mut Self + fn mutating_mut(&mut self, mut f: F) -> &mut Self where - F: Fn(&mut Self), + F: FnMut(&mut Self), { f(self); self } } -pub fn sideeffect(v: T, f: F) -> T +/// Apply a sideeffect using some value in an expression +/// +/// # Examples +/// +/// See [mutating]. +pub fn sideeffect(v: T, mut f: F) -> T where - F: Fn(&T), + F: FnMut(&T), { f(&v); v } +/// Apply sideeffect on the fly in a method chain pub trait SideffectExt { + /// Apply sideeffect on the fly in a method chain (owning) + /// + /// # Examples + /// + /// See [mutating]. fn sideeffect(self, f: F) -> Self where - F: Fn(&Self); + F: FnMut(&Self); + /// Apply sideeffect on the fly in a method chain (immutable ref) + /// + /// # Examples + /// + /// See [mutating]. fn sideeffect_ref(&self, f: F) -> &Self where - F: Fn(&Self); + F: FnMut(&Self); + /// Apply sideeffect on the fly in a method chain (mutable ref) + /// + /// # Examples + /// + /// See [mutating]. fn sideeffect_mut(&mut self, f: F) -> &mut Self where - F: Fn(&Self); + F: FnMut(&Self); } impl SideffectExt for T { fn sideeffect(self, f: F) -> Self where - F: Fn(&Self), + F: FnMut(&Self), { sideeffect(self, f) } - fn sideeffect_ref(&self, f: F) -> &Self + fn sideeffect_ref(&self, mut f: F) -> &Self where - F: Fn(&Self), + F: FnMut(&Self), { f(self); self } - fn sideeffect_mut(&mut self, f: F) -> &mut Self + fn sideeffect_mut(&mut self, mut f: F) -> &mut Self where - F: Fn(&Self), + F: FnMut(&Self), { f(self); self } } +/// Just run the function +/// +/// This is occasionally useful; in particular, you can +/// use it to control the meaning of the question mark operator. +/// +/// # Examples +/// +/// ``` +/// use rosenpass_util::functional::run; +/// +/// fn add_and_mul(a: Option, b: Option, c: anyhow::Result, d: anyhow::Result) -> u32 { +/// run(|| -> anyhow::Result { +/// let ab = run(|| Some(a? * b?)).unwrap_or(0); +/// Ok(ab + c? + d?) +/// }).unwrap() +/// } +/// +/// assert_eq!(98, add_and_mul(Some(10), Some(9), Ok(3), Ok(5))); +/// assert_eq!(8, add_and_mul(None, Some(15), Ok(3), Ok(5))); +/// ``` pub fn run R>(f: F) -> R { f() } +/// Apply a function to a value in a method chain pub trait ApplyExt: Sized { + /// Apply a function to a value in a method chain + /// + /// # Examples + /// + /// See [mutating]. fn apply(self, f: F) -> R where F: FnOnce(Self) -> R; diff --git a/util/src/io.rs b/util/src/io.rs index d5ee812..62c2e63 100644 --- a/util/src/io.rs +++ b/util/src/io.rs @@ -1,8 +1,262 @@ +//! Helpers for performing IO +//! +//! # IO Error handling helpers tutorial +//! +//! ``` +//! use std::io::ErrorKind as EK; +//! +//! // It can be a bit hard to use IO errors in match statements +//! +//! fn io_placeholder() -> std::io::Result<()> { +//! Ok(()) +//! } +//! +//! loop { +//! match io_placeholder() { +//! Ok(()) => break, +//! // All errors are unreachable; just here for demo purposes +//! Err(e) if e.kind() == EK::Interrupted => continue, +//! Err(e) if e.kind() == EK::WouldBlock => { +//! panic!("This particular function is not designed to be used in nonblocking code!"); +//! } +//! Err(e) => Err(e)?, +//! } +//! } +//! +//! // For this reason this module contains various helper functions to make +//! // matching on error kinds a bit less repetitive. [IoResultKindHintExt::io_err_kind_hint] +//! // provides the basic functionality for use mostly with std::io::Result +//! +//! use rosenpass_util::io::IoResultKindHintExt; +//! +//! loop { +//! match io_placeholder().io_err_kind_hint() { +//! Ok(()) => break, +//! // All errors are unreachable; just here for demo purposes +//! Err((_, EK::Interrupted)) => continue, +//! Err((_, EK::WouldBlock)) => { +//! // Unreachable, just here for explanation purposes +//! panic!("This particular function is not designed to be used in nonblocking code!"); +//! } +//! Err((e, _)) => Err(e)?, +//! } +//! } +//! +//! // The trait can be customized; firstly, you can use IoErrorKind +//! // for error types that can be fully represented as std::io::ErrorKind +//! +//! use rosenpass_util::io::IoErrorKind; +//! +//! #[derive(thiserror::Error, Debug, PartialEq, Eq)] +//! enum MyErrno { +//! #[error("Got interrupted")] +//! Interrupted, +//! #[error("In nonblocking mode")] +//! WouldBlock, +//! } +//! +//! impl IoErrorKind for MyErrno { +//! fn io_error_kind(&self) -> std::io::ErrorKind { +//! use MyErrno as ME; +//! match self { +//! ME::Interrupted => EK::Interrupted, +//! ME::WouldBlock => EK::WouldBlock, +//! } +//! } +//! } +//! +//! assert_eq!( +//! EK::Interrupted, +//! std::io::Error::new(EK::Interrupted, "artificially interrupted").io_error_kind() +//! ); +//! assert_eq!(EK::Interrupted, MyErrno::Interrupted.io_error_kind()); +//! assert_eq!(EK::WouldBlock, MyErrno::WouldBlock.io_error_kind()); +//! +//! // And when an error can not fully be represented as an std::io::ErrorKind, +//! // you can still use [TryIoErrorKind] +//! +//! use rosenpass_util::io::TryIoErrorKind; +//! +//! #[derive(thiserror::Error, Debug, PartialEq, Eq)] +//! enum MyErrnoOrBlue { +//! #[error("Got interrupted")] +//! Interrupted, +//! #[error("In nonblocking mode")] +//! WouldBlock, +//! #[error("I am feeling blue")] +//! FeelingBlue, +//! } +//! +//! impl TryIoErrorKind for MyErrnoOrBlue { +//! fn try_io_error_kind(&self) -> Option { +//! use MyErrnoOrBlue as ME; +//! match self { +//! ME::Interrupted => Some(EK::Interrupted), +//! ME::WouldBlock => Some(EK::WouldBlock), +//! ME::FeelingBlue => None, +//! } +//! } +//! } +//! +//! assert_eq!( +//! Some(EK::Interrupted), +//! MyErrnoOrBlue::Interrupted.try_io_error_kind() +//! ); +//! assert_eq!( +//! Some(EK::WouldBlock), +//! MyErrnoOrBlue::WouldBlock.try_io_error_kind() +//! ); +//! assert_eq!(None, MyErrnoOrBlue::FeelingBlue.try_io_error_kind()); +//! +//! // TryIoErrorKind is automatically implemented for all types that implement +//! // IoErrorKind +//! +//! assert_eq!( +//! Some(EK::Interrupted), +//! std::io::Error::new(EK::Interrupted, "artificially interrupted").try_io_error_kind() +//! ); +//! assert_eq!( +//! Some(EK::Interrupted), +//! MyErrno::Interrupted.try_io_error_kind() +//! ); +//! assert_eq!( +//! Some(EK::WouldBlock), +//! MyErrno::WouldBlock.try_io_error_kind() +//! ); +//! +//! // By implementing IoErrorKind, we can automatically make use of IoResultKindHintExt +//! // with our custom error type +//! +//! //use rosenpass_util::io::IoResultKindHintExt; +//! +//! assert_eq!( +//! Ok::<_, MyErrno>(42).io_err_kind_hint(), +//! Ok(42)); +//! assert!(matches!( +//! Err::<(), _>(std::io::Error::new(EK::Interrupted, "artificially interrupted")).io_err_kind_hint(), +//! Err((err, EK::Interrupted)) if format!("{err:?}") == "Custom { kind: Interrupted, error: \"artificially interrupted\" }")); +//! assert_eq!( +//! Err::<(), _>(MyErrno::Interrupted).io_err_kind_hint(), +//! Err((MyErrno::Interrupted, EK::Interrupted))); +//! +//! // Correspondingly, TryIoResultKindHintExt can be used for Results with Errors +//! // that implement TryIoErrorKind +//! +//! use crate::rosenpass_util::io::TryIoResultKindHintExt; +//! +//! assert_eq!( +//! Ok::<_, MyErrnoOrBlue>(42).try_io_err_kind_hint(), +//! Ok(42)); +//! assert_eq!( +//! Err::<(), _>(MyErrnoOrBlue::Interrupted).try_io_err_kind_hint(), +//! Err((MyErrnoOrBlue::Interrupted, Some(EK::Interrupted)))); +//! assert_eq!( +//! Err::<(), _>(MyErrnoOrBlue::FeelingBlue).try_io_err_kind_hint(), +//! Err((MyErrnoOrBlue::FeelingBlue, None))); +//! +//! // SubstituteForIoErrorKindExt serves as a helper to handle specific ErrorKinds +//! // using a method chaining style. It works on anything that implements TryIoErrorKind. +//! +//! use rosenpass_util::io::SubstituteForIoErrorKindExt; +//! +//! assert_eq!(Ok(42), +//! Err(MyErrnoOrBlue::Interrupted) +//! .substitute_for_ioerr_kind_with(EK::Interrupted, || 42)); +//! +//! assert_eq!(Err(MyErrnoOrBlue::WouldBlock), +//! Err(MyErrnoOrBlue::WouldBlock) +//! .substitute_for_ioerr_kind_with(EK::Interrupted, || 42)); +//! +//! // The other functions in SubstituteForIoErrorKindExt are mostly just wrappers, +//! // getting the same job done with minor convenience +//! +//! // Plain Ok() value instead of function +//! assert_eq!(Ok(42), +//! Err(MyErrnoOrBlue::Interrupted) +//! .substitute_for_ioerr_kind(EK::Interrupted, 42)); +//! assert_eq!(Err(MyErrnoOrBlue::WouldBlock), +//! Err(MyErrnoOrBlue::WouldBlock) +//! .substitute_for_ioerr_kind(EK::Interrupted, 42)); +//! +//! // For specific errors +//! assert_eq!(Ok(42), +//! Err(MyErrnoOrBlue::Interrupted) +//! .substitute_for_ioerr_interrupted_with(|| 42) +//! .substitute_for_ioerr_wouldblock_with(|| 23)); +//! assert_eq!(Ok(23), +//! Err(MyErrnoOrBlue::WouldBlock) +//! .substitute_for_ioerr_interrupted_with(|| 42) +//! .substitute_for_ioerr_wouldblock_with(|| 23)); +//! assert_eq!(Err(MyErrnoOrBlue::FeelingBlue), +//! Err(MyErrnoOrBlue::FeelingBlue) +//! .substitute_for_ioerr_interrupted_with(|| 42) +//! .substitute_for_ioerr_wouldblock_with(|| 23)); +//! +//! // And for specific errors without the function call +//! assert_eq!(Ok(42), +//! Err(MyErrnoOrBlue::Interrupted) +//! .substitute_for_ioerr_interrupted(42) +//! .substitute_for_ioerr_wouldblock(23)); +//! assert_eq!(Ok(23), +//! Err(MyErrnoOrBlue::WouldBlock) +//! .substitute_for_ioerr_interrupted(42) +//! .substitute_for_ioerr_wouldblock(23)); +//! assert_eq!(Err(MyErrnoOrBlue::FeelingBlue), +//! Err(MyErrnoOrBlue::FeelingBlue) +//! .substitute_for_ioerr_interrupted(42) +//! .substitute_for_ioerr_wouldblock(23)); +//! +//! // handle_interrupted automates the process of handling ErrorKind::Interrupted +//! // in cases where the action should simply be rerun; it can handle any error type +//! // that implements TryIoErrorKind. It lets other errors and Ok(_) pass through. +//! +//! use rosenpass_util::io::handle_interrupted; +//! +//! let mut ctr = 0u32; +//! let mut simulate_io = || -> Result { +//! let r = match ctr % 6 { +//! 1 => Ok(42), +//! 3 => Err(MyErrnoOrBlue::FeelingBlue), +//! 5 => Err(MyErrnoOrBlue::WouldBlock), +//! _ => Err(MyErrnoOrBlue::Interrupted), +//! }; +//! ctr += 1; +//! r +//! }; +//! +//! assert_eq!(Ok(Some(42)), handle_interrupted(&mut simulate_io)); +//! assert_eq!(Err(MyErrnoOrBlue::FeelingBlue), handle_interrupted(&mut simulate_io)); +//! assert_eq!(Err(MyErrnoOrBlue::WouldBlock), handle_interrupted(&mut simulate_io)); +//! // never returns None +//! +//! // nonblocking_handle_io_errors performs the same job, except that +//! // WouldBlock is substituted with Ok(None) +//! +//! use rosenpass_util::io::nonblocking_handle_io_errors; +//! +//! assert_eq!(Ok(Some(42)), nonblocking_handle_io_errors(&mut simulate_io)); +//! assert_eq!(Err(MyErrnoOrBlue::FeelingBlue), nonblocking_handle_io_errors(&mut simulate_io)); +//! assert_eq!(Ok(None), nonblocking_handle_io_errors(&mut simulate_io)); +//! +//! Ok::<_, anyhow::Error>(()) +//! ``` + use std::{borrow::Borrow, io}; use anyhow::ensure; +use zerocopy::AsBytes; +/// Generic trait for accessing [std::io::Error::kind] +/// +/// # Examples +/// +/// See [tutorial in the module](self). pub trait IoErrorKind { + /// Conversion to [std::io::Error::kind] + /// + /// # Examples + /// + /// See [tutorial in the module](self). fn io_error_kind(&self) -> io::ErrorKind; } @@ -12,7 +266,17 @@ impl> IoErrorKind for T { } } +/// Generic trait for accessing [std::io::Error::kind] where it may not be present +/// +/// # Examples +/// +/// See [tutorial in the module](self). pub trait TryIoErrorKind { + /// Conversion to [std::io::Error::kind] where it may not be present + /// + /// # Examples + /// + /// See [tutorial in the module](self). fn try_io_error_kind(&self) -> Option; } @@ -22,8 +286,19 @@ impl TryIoErrorKind for T { } } +/// Helper for accessing [std::io::Error::kind] in Results +/// +/// # Examples +/// +/// See [tutorial in the module](self). pub trait IoResultKindHintExt: Sized { + // Error trait including the ErrorKind hint type Error; + /// Helper for accessing [std::io::Error::kind] in Results + /// + /// # Examples + /// + /// See [tutorial in the module](self). fn io_err_kind_hint(self) -> Result; } @@ -37,8 +312,19 @@ impl IoResultKindHintExt for Result { } } +/// Helper for accessing [std::io::Error::kind] in Results where it may not be present +/// +/// # Examples +/// +/// See [tutorial in the module](self). pub trait TryIoResultKindHintExt: Sized { + // Error trait including the ErrorKind hint type Error; + /// Helper for accessing [std::io::Error::kind] in Results where it may not be present + /// + /// # Examples + /// + /// See [tutorial in the module](self). fn try_io_err_kind_hint(self) -> Result)>; } @@ -52,17 +338,41 @@ impl TryIoResultKindHintExt for Result { } } +/// Helper for working with IO results using a method chaining style +/// +/// # Examples +/// +/// See [tutorial in the module](self). pub trait SubstituteForIoErrorKindExt: Sized { + /// Error type produced by methods in this trait type Error; + + /// Substitute errors with a certain [std::io::ErrorKind] by a value produced by a function + /// + /// # Examples + /// + /// See [tutorial in the module](self). fn substitute_for_ioerr_kind_with T>( self, kind: io::ErrorKind, f: F, ) -> Result; + + /// Substitute errors with a certain [std::io::ErrorKind] by a value + /// + /// # Examples + /// + /// See [tutorial in the module](self). fn substitute_for_ioerr_kind(self, kind: io::ErrorKind, v: T) -> Result { self.substitute_for_ioerr_kind_with(kind, || v) } + /// Substitute errors with [std::io::ErrorKind] [std::io::ErrorKind::Interrupted] by a value + /// produced by a function + /// + /// # Examples + /// + /// See [tutorial in the module](self). fn substitute_for_ioerr_interrupted_with T>( self, f: F, @@ -70,10 +380,21 @@ pub trait SubstituteForIoErrorKindExt: Sized { self.substitute_for_ioerr_kind_with(io::ErrorKind::Interrupted, f) } + /// Substitute errors with [std::io::ErrorKind] [std::io::ErrorKind::Interrupted] by a value + /// + /// # Examples + /// + /// See [tutorial in the module](self). fn substitute_for_ioerr_interrupted(self, v: T) -> Result { self.substitute_for_ioerr_interrupted_with(|| v) } + /// Substitute errors with [std::io::ErrorKind] [std::io::ErrorKind::WouldBlock] by a value + /// produced by a function + /// + /// # Examples + /// + /// See [tutorial in the module](self). fn substitute_for_ioerr_wouldblock_with T>( self, f: F, @@ -81,6 +402,11 @@ pub trait SubstituteForIoErrorKindExt: Sized { self.substitute_for_ioerr_kind_with(io::ErrorKind::WouldBlock, f) } + /// Substitute errors with [std::io::ErrorKind] [std::io::ErrorKind::WouldBlock] by a value + /// + /// # Examples + /// + /// See [tutorial in the module](self). fn substitute_for_ioerr_wouldblock(self, v: T) -> Result { self.substitute_for_ioerr_wouldblock_with(|| v) } @@ -107,6 +433,10 @@ impl SubstituteForIoErrorKindExt for Result { /// - If there is no error (i.e. on `Ok(r)`), the function will return `Ok(Some(r))` /// - `Interrupted` is handled internally, by retrying the IO operation /// - Other errors are returned as is +/// +/// # Examples +/// +/// See [tutorial in the module](self). pub fn handle_interrupted(mut iofn: F) -> Result, E> where E: TryIoErrorKind, @@ -128,6 +458,10 @@ where /// - `Interrupted` is handled internally, by retrying the IO operation /// - `WouldBlock` is handled by returning `Ok(None)`, /// - Other errors are returned as is +/// +/// # Examples +/// +/// See [tutorial in the module](self). pub fn nonblocking_handle_io_errors(mut iofn: F) -> Result, E> where E: TryIoErrorKind, @@ -144,6 +478,7 @@ where } } +/// [std:io::Read] extension trait for call with [nonblocking_handle_io_errors] applied pub trait ReadNonblockingWithBoringErrorsHandledExt { /// Convenience wrapper using [nonblocking_handle_io_errors] with [std::io::Read] fn read_nonblocking_with_boring_errors_handled( @@ -161,7 +496,27 @@ impl ReadNonblockingWithBoringErrorsHandledExt for T { } } +/// Extension trait for [std::io::Read] providing the ability to read +/// a buffer exactly pub trait ReadExt { + /// Version of [std::io::Read::read_exact] that throws if there + /// is extra data in the stream to be read + /// + /// # Examples + /// + /// ``` + /// use rosenpass_util::io::ReadExt; + /// + /// let mut buf = [0u8; 4]; + /// + /// // Over or underlong buffer yields error + /// assert!(b"12345".as_slice().read_exact_til_end(&mut buf).is_err()); + /// assert!(b"123".as_slice().read_exact_til_end(&mut buf).is_err()); + /// + /// // Buffer of precisely the right length leads to successful read + /// assert!(b"1234".as_slice().read_exact_til_end(&mut buf).is_ok()); + /// assert_eq!(b"1234", &buf); + /// ``` fn read_exact_til_end(&mut self, buf: &mut [u8]) -> anyhow::Result<()>; }