1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
//! [![crates.io version](https://img.shields.io/crates/v/read-write-ext.svg)](https://crates.io/crates/read-write-ext)
//! [![license: Apache 2.0](https://gitlab.com/leonhard-llc/fixed-buffer-rs/-/raw/main/license-apache-2.0.svg)](http://www.apache.org/licenses/LICENSE-2.0)
//! [![unsafe forbidden](https://gitlab.com/leonhard-llc/fixed-buffer-rs/-/raw/main/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)
//! [![pipeline status](https://gitlab.com/leonhard-llc/fixed-buffer-rs/badges/main/pipeline.svg)](https://gitlab.com/leonhard-llc/fixed-buffer-rs/-/pipelines)
//!
//! `ReadWriteExt` trait with `chain_after` and `take_rw` for `std::io::Read + Write` structs.
//!
//! # Features
//! - `forbid(unsafe_code)`
//! - Depends only on `std`.
//! - Good test coverage (99%)
//! - Like [`std::io::Read::chain`](https://doc.rust-lang.org/std/io/trait.Read.html#method.chain)
//! and [`std::io::Read::take`](https://doc.rust-lang.org/std/io/trait.Read.html#method.take)
//! but also passes through writes.
//! - Useful with `Read + Write` objects like
//! [`std::net::TcpStream`](https://doc.rust-lang.org/stable/std/net/struct.TcpStream.html)
//! and [`rustls::Stream`](https://docs.rs/rustls/latest/rustls/struct.Stream.html).
//!
//! # Changelog
//! - v0.1.1 - `take_rw` to take `u64` like `std::io::Read::take`.
//! - v0.1.0 - Initial release. Moved code from `fixed-buffer`.
//!
//! # TO DO
//!
//! # Release Process
//! 1. Edit `Cargo.toml` and bump version number.
//! 1. Run `../release.sh`
#![forbid(unsafe_code)]
/// A wrapper for a pair of structs, R and RW.
///
/// Implements `std::io::Read`. Reads from `R` until it is empty, then reads from `RW`.
///
/// Implements `std::io::Write`. Passes all writes through to `RW`.
///
/// This is like [`std::io::Chain`](https://doc.rust-lang.org/std/io/struct.Chain.html)
/// that also passes through writes.
pub struct ReadWriteChain<R: std::io::Read, RW: std::io::Read + std::io::Write> {
reader: Option<R>,
read_writer: RW,
}
impl<R: std::io::Read, RW: std::io::Read + std::io::Write> ReadWriteChain<R, RW> {
/// See [`ReadWriteChain`](struct.ReadWriteChain.html).
pub fn new(reader: R, read_writer: RW) -> ReadWriteChain<R, RW> {
Self {
reader: Some(reader),
read_writer,
}
}
}
impl<R: std::io::Read, RW: std::io::Read + std::io::Write> std::io::Read for ReadWriteChain<R, RW> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
if let Some(ref mut reader) = self.reader {
match reader.read(buf) {
Ok(0) => {
// EOF
self.reader = None;
}
Ok(num_read) => return Ok(num_read),
Err(e) => return Err(e),
}
}
self.read_writer.read(buf)
}
}
impl<R: std::io::Read, RW: std::io::Read + std::io::Write> std::io::Write
for ReadWriteChain<R, RW>
{
fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
self.read_writer.write(buf)
}
fn flush(&mut self) -> Result<(), std::io::Error> {
self.read_writer.flush()
}
}
/// Wraps a `std::io::Read + std::io::Write` struct.
/// Passes through reads and writes to the struct.
/// Limits the number of bytes that can be read.
///
/// This is like [`std::io::Take`](https://doc.rust-lang.org/std/io/struct.Take.html)
/// that also passes through writes.
pub struct ReadWriteTake<RW: std::io::Read + std::io::Write> {
read_writer: RW,
remaining_bytes: u64,
}
impl<RW: std::io::Read + std::io::Write> ReadWriteTake<RW> {
/// See [`ReadWriteTake`](struct.ReadWriteTake.html).
pub fn new(read_writer: RW, len: u64) -> ReadWriteTake<RW> {
Self {
read_writer,
remaining_bytes: len,
}
}
}
impl<RW: std::io::Read + std::io::Write> std::io::Read for ReadWriteTake<RW> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
if self.remaining_bytes == 0 {
return Ok(0);
}
let num_to_read =
usize::try_from(self.remaining_bytes.min(buf.len() as u64)).unwrap_or(usize::MAX);
let dest = &mut buf[0..num_to_read];
match self.read_writer.read(dest) {
Ok(num_read) => {
self.remaining_bytes -= num_read as u64;
Ok(num_read)
}
Err(e) => Err(e),
}
}
}
impl<RW: std::io::Read + std::io::Write> std::io::Write for ReadWriteTake<RW> {
fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
self.read_writer.write(buf)
}
fn flush(&mut self) -> Result<(), std::io::Error> {
self.read_writer.flush()
}
}
pub trait ReadWriteExt: std::io::Read + std::io::Write {
/// Returns a struct that implements `std::io::Read` and `std::io::Write`.
///
/// It reads from `reader` until it is empty, then reads from `self`.
///
/// It passes all writes through to `self`.
///
/// This is like [`std::io::Read::chain`](https://doc.rust-lang.org/std/io/trait.Read.html#method.chain)
/// that also passes through writes.
fn chain_after<R: std::io::Read>(&mut self, reader: R) -> ReadWriteChain<R, &mut Self> {
ReadWriteChain::new(reader, self)
}
/// Wraps a struct that implements `std::io::Read` and `std::io::Write`.
///
/// The returned struct passes through reads and writes to the struct.
///
/// It limits the number of bytes that can be read.
///
/// This is like [`std::io::Read::take`](https://doc.rust-lang.org/std/io/trait.Read.html#method.take)
/// that also passes through writes.
fn take_rw(&mut self, len: u64) -> ReadWriteTake<&mut Self> {
ReadWriteTake::new(self, len)
}
}
impl<RW: std::io::Read + std::io::Write + ?Sized> ReadWriteExt for RW {}