Skip to content

Commit 0186035

Browse files
Add EncoderStringWriter
1 parent 4509575 commit 0186035

File tree

5 files changed

+179
-40
lines changed

5 files changed

+179
-40
lines changed

RELEASE-NOTES.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1+
# Next
2+
3+
- Config methods are const
4+
- Added `EncoderStringWriter` to allow encoding directly to a String
5+
- `EncoderWriter` now owns its delegate writer rather than keeping a reference to it (though refs still work)
6+
- As a consequence, it is now possible to extract the delegate writer from an `EncoderWriter` via `finish()`
7+
18
# 0.12.2
29

3-
Add `BinHex` alphabet
10+
- Add `BinHex` alphabet
411

512
# 0.12.1
613

src/write/encoder.rs

Lines changed: 60 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -25,27 +25,24 @@ const MIN_ENCODE_CHUNK_SIZE: usize = 3;
2525
/// use std::io::Write;
2626
///
2727
/// // use a vec as the simplest possible `Write` -- in real code this is probably a file, etc.
28-
/// let mut wrapped_writer = Vec::new();
29-
/// {
30-
/// let mut enc = base64::write::EncoderWriter::new(
31-
/// &mut wrapped_writer, base64::STANDARD);
28+
/// let mut enc = base64::write::EncoderWriter::new(Vec::new(), base64::STANDARD);
3229
///
33-
/// // handle errors as you normally would
34-
/// enc.write_all(b"asdf").unwrap();
35-
/// // could leave this out to be called by Drop, if you don't care
36-
/// // about handling errors
37-
/// enc.finish().unwrap();
30+
/// // handle errors as you normally would
31+
/// enc.write_all(b"asdf").unwrap();
3832
///
39-
/// }
33+
/// // could leave this out to be called by Drop, if you don't care
34+
/// // about handling errors or getting the delegate writer back
35+
/// let delegate = enc.finish().unwrap();
4036
///
4137
/// // base64 was written to the writer
42-
/// assert_eq!(b"YXNkZg==", &wrapped_writer[..]);
38+
/// assert_eq!(b"YXNkZg==", &delegate[..]);
4339
///
4440
/// ```
4541
///
4642
/// # Panics
4743
///
48-
/// Calling `write()` after `finish()` is invalid and will panic.
44+
/// Calling `write()` (or related methods) or `finish()` after `finish()` has completed without
45+
/// error is invalid and will panic.
4946
///
5047
/// # Errors
5148
///
@@ -56,10 +53,12 @@ const MIN_ENCODE_CHUNK_SIZE: usize = 3;
5653
///
5754
/// It has some minor performance loss compared to encoding slices (a couple percent).
5855
/// It does not do any heap allocation.
59-
pub struct EncoderWriter<'a, W: 'a + Write> {
56+
pub struct EncoderWriter<W: Write> {
6057
config: Config,
61-
/// Where encoded data is written to
62-
w: &'a mut W,
58+
/// Where encoded data is written to. It's an Option as it's None immediately before Drop is
59+
/// called so that finish() can return the underlying writer. None implies that finish() has
60+
/// been called successfully.
61+
delegate: Option<W>,
6362
/// Holds a partial chunk, if any, after the last `write()`, so that we may then fill the chunk
6463
/// with the next `write()`, encode it, then proceed with the rest of the input normally.
6564
extra_input: [u8; MIN_ENCODE_CHUNK_SIZE],
@@ -70,13 +69,11 @@ pub struct EncoderWriter<'a, W: 'a + Write> {
7069
output: [u8; BUF_SIZE],
7170
/// How much of `output` is occupied with encoded data that couldn't be written last time
7271
output_occupied_len: usize,
73-
/// True iff padding / partial last chunk has been written.
74-
finished: bool,
7572
/// panic safety: don't write again in destructor if writer panicked while we were writing to it
7673
panicked: bool,
7774
}
7875

79-
impl<'a, W: Write> fmt::Debug for EncoderWriter<'a, W> {
76+
impl<W: Write> fmt::Debug for EncoderWriter<W> {
8077
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
8178
write!(
8279
f,
@@ -89,38 +86,58 @@ impl<'a, W: Write> fmt::Debug for EncoderWriter<'a, W> {
8986
}
9087
}
9188

92-
impl<'a, W: Write> EncoderWriter<'a, W> {
89+
impl<W: Write> EncoderWriter<W> {
9390
/// Create a new encoder that will write to the provided delegate writer `w`.
94-
pub fn new(w: &'a mut W, config: Config) -> EncoderWriter<'a, W> {
91+
pub fn new(w: W, config: Config) -> EncoderWriter<W> {
9592
EncoderWriter {
9693
config,
97-
w,
94+
delegate: Some(w),
9895
extra_input: [0u8; MIN_ENCODE_CHUNK_SIZE],
9996
extra_input_occupied_len: 0,
10097
output: [0u8; BUF_SIZE],
10198
output_occupied_len: 0,
102-
finished: false,
10399
panicked: false,
104100
}
105101
}
106102

107103
/// Encode all remaining buffered data and write it, including any trailing incomplete input
108104
/// triples and associated padding.
109105
///
110-
/// Once this succeeds, no further writes can be performed, as that would produce invalid
111-
/// base64.
106+
/// Once this succeeds, no further writes or calls to this method are allowed.
112107
///
113-
/// This may write to the delegate writer multiple times if the delegate writer does not accept all input provided
114-
/// to its `write` each invocation.
108+
/// This may write to the delegate writer multiple times if the delegate writer does not accept
109+
/// all input provided to its `write` each invocation.
110+
///
111+
/// If you don't care about error handling, it is not necessary to call this function, as the
112+
/// equivalent finalization is done by the Drop impl.
113+
///
114+
/// Returns the writer that this was constructed around.
115115
///
116116
/// # Errors
117117
///
118-
/// The first error that is not of [`ErrorKind::Interrupted`] will be returned.
119-
pub fn finish(&mut self) -> Result<()> {
120-
if self.finished {
121-
return Ok(());
118+
/// The first error that is not of `ErrorKind::Interrupted` will be returned.
119+
pub fn finish(&mut self) -> Result<W> {
120+
// If we could consume self in finish(), we wouldn't have to worry about this case, but
121+
// finish() is retryable in the face of I/O errors, so we can't consume here.
122+
if self.delegate.is_none() {
123+
panic!("Encoder has already had finish() called")
122124
};
123125

126+
self.write_final_leftovers()?;
127+
128+
let writer = self.delegate.take().expect("Writer must be present");
129+
130+
Ok(writer)
131+
}
132+
133+
/// Write any remaining buffered data to the delegate writer.
134+
fn write_final_leftovers(&mut self) -> Result<()> {
135+
if self.delegate.is_none() {
136+
// finish() has already successfully called this, and we are now in drop() with a None
137+
// writer, so just no-op
138+
return Ok(());
139+
}
140+
124141
self.write_all_encoded_output()?;
125142

126143
if self.extra_input_occupied_len > 0 {
@@ -138,7 +155,6 @@ impl<'a, W: Write> EncoderWriter<'a, W> {
138155
self.extra_input_occupied_len = 0;
139156
}
140157

141-
self.finished = true;
142158
Ok(())
143159
}
144160

@@ -152,7 +168,11 @@ impl<'a, W: Write> EncoderWriter<'a, W> {
152168
/// that no write took place.
153169
fn write_to_delegate(&mut self, current_output_len: usize) -> Result<()> {
154170
self.panicked = true;
155-
let res = self.w.write(&self.output[..current_output_len]);
171+
let res = self
172+
.delegate
173+
.as_mut()
174+
.expect("Writer must be present")
175+
.write(&self.output[..current_output_len]);
156176
self.panicked = false;
157177

158178
res.map(|consumed| {
@@ -197,7 +217,7 @@ impl<'a, W: Write> EncoderWriter<'a, W> {
197217
}
198218
}
199219

200-
impl<'a, W: Write> Write for EncoderWriter<'a, W> {
220+
impl<W: Write> Write for EncoderWriter<W> {
201221
/// Encode input and then write to the delegate writer.
202222
///
203223
/// Under non-error circumstances, this returns `Ok` with the value being the number of bytes
@@ -215,7 +235,7 @@ impl<'a, W: Write> Write for EncoderWriter<'a, W> {
215235
///
216236
/// Any errors emitted by the delegate writer are returned.
217237
fn write(&mut self, input: &[u8]) -> Result<usize> {
218-
if self.finished {
238+
if self.delegate.is_none() {
219239
panic!("Cannot write more after calling finish()");
220240
}
221241

@@ -341,15 +361,18 @@ impl<'a, W: Write> Write for EncoderWriter<'a, W> {
341361
/// incomplete chunks of input or write padding.
342362
fn flush(&mut self) -> Result<()> {
343363
self.write_all_encoded_output()?;
344-
self.w.flush()
364+
self.delegate
365+
.as_mut()
366+
.expect("Writer must be present")
367+
.flush()
345368
}
346369
}
347370

348-
impl<'a, W: Write> Drop for EncoderWriter<'a, W> {
371+
impl<W: Write> Drop for EncoderWriter<W> {
349372
fn drop(&mut self) {
350373
if !self.panicked {
351374
// like `BufWriter`, ignore errors during drop
352-
let _ = self.finish();
375+
let _ = self.write_final_leftovers();
353376
}
354377
}
355378
}

src/write/encoder_string_writer.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
use crate::Config;
2+
use std::io;
3+
use std::io::Write;
4+
use super::encoder::EncoderWriter;
5+
6+
/// A `Write` implementation that base64-encodes data using the provided config and accumulates the
7+
/// resulting base64 in memory, which is then exposed as a String via `finish()`.
8+
///
9+
/// # Examples
10+
///
11+
/// ```
12+
/// use std::io::Write;
13+
///
14+
/// let mut enc = base64::write::EncoderStringWriter::new(base64::STANDARD);
15+
///
16+
/// enc.write_all(b"asdf").unwrap();
17+
///
18+
/// // get the resulting String
19+
/// let b64_string = enc.finish().unwrap();
20+
///
21+
/// assert_eq!("YXNkZg==", &b64_string);
22+
/// ```
23+
///
24+
/// # Panics
25+
///
26+
/// Calling `write()` (or related methods) or `finish()` after `finish()` has completed without
27+
/// error is invalid and will panic.
28+
///
29+
/// # Performance
30+
///
31+
/// B64-encoded data is buffered in the heap since the point is to collect it in a String.
32+
pub struct EncoderStringWriter {
33+
encoder: EncoderWriter<Vec<u8>>,
34+
}
35+
36+
impl EncoderStringWriter {
37+
/// Create a new EncoderStringWriter that will encode with the provided config.
38+
pub fn new(config: Config) -> EncoderStringWriter {
39+
EncoderStringWriter { encoder: EncoderWriter::new(Vec::new(), config) }
40+
}
41+
42+
/// Encode all remaining buffered data, including any trailing incomplete input triples and
43+
/// associated padding.
44+
///
45+
/// Once this succeeds, no further writes or calls to this method are allowed.
46+
///
47+
/// Returns the base64-encoded form of the accumulated written data.
48+
///
49+
/// # Errors
50+
///
51+
/// The first error that is not of `ErrorKind::Interrupted` will be returned.
52+
pub fn finish(&mut self) -> io::Result<String> {
53+
let buf = self.encoder.finish()?;
54+
55+
let str = String::from_utf8(buf).expect("Base64 should always be valid UTF-8");
56+
Ok(str)
57+
}
58+
}
59+
60+
impl<'a> Write for EncoderStringWriter {
61+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
62+
self.encoder.write(buf)
63+
}
64+
65+
fn flush(&mut self) -> io::Result<()> {
66+
self.encoder.flush()
67+
}
68+
}
69+
70+
#[cfg(test)]
71+
mod tests {
72+
use crate::encode_config_buf;
73+
use crate::tests::random_config;
74+
use rand::Rng;
75+
use std::io::Write;
76+
use crate::write::encoder_string_writer::EncoderStringWriter;
77+
78+
#[test]
79+
fn every_possible_split_of_input() {
80+
let mut rng = rand::thread_rng();
81+
let mut orig_data = Vec::<u8>::new();
82+
let mut normal_encoded = String::new();
83+
84+
let size = 5_000;
85+
86+
for i in 0..size {
87+
orig_data.clear();
88+
normal_encoded.clear();
89+
90+
for _ in 0..size {
91+
orig_data.push(rng.gen());
92+
}
93+
94+
let config = random_config(&mut rng);
95+
encode_config_buf(&orig_data, config, &mut normal_encoded);
96+
97+
let mut stream_encoder = EncoderStringWriter::new(config);
98+
// Write the first i bytes, then the rest
99+
stream_encoder.write_all(&orig_data[0..i]).unwrap();
100+
stream_encoder.write_all(&orig_data[i..]).unwrap();
101+
102+
let stream_encoded = stream_encoder.finish().unwrap();
103+
104+
assert_eq!(normal_encoded, stream_encoded);
105+
}
106+
}
107+
}

src/write/encoder_tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ fn writes_that_only_write_part_of_input_and_sometimes_interrupt_produce_correct_
436436
}
437437
}
438438

439-
stream_encoder.finish().unwrap();
439+
let _ = stream_encoder.finish().unwrap();
440440

441441
assert_eq!(orig_len, bytes_consumed);
442442
}
@@ -500,7 +500,7 @@ fn do_encode_random_config_matches_normal_encode(max_input_len: usize) {
500500
bytes_consumed += input_len;
501501
}
502502

503-
stream_encoder.finish().unwrap();
503+
let _ = stream_encoder.finish().unwrap();
504504

505505
assert_eq!(orig_len, bytes_consumed);
506506
}

src/write/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
//! Implementations of `io::Write` to transparently handle base64.
22
mod encoder;
3+
mod encoder_string_writer;
34
pub use self::encoder::EncoderWriter;
5+
pub use self::encoder_string_writer::EncoderStringWriter;
46

57
#[cfg(test)]
68
mod encoder_tests;

0 commit comments

Comments
 (0)