read_write_ext/
lib.rs

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