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