read_write_ext/lib.rs
1//! [](https://crates.io/crates/read-write-ext)
2//! [](http://www.apache.org/licenses/LICENSE-2.0)
3//! [](https://github.com/rust-secure-code/safety-dance/)
4//! [](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 {}