Skip to content

Commit c93043e

Browse files
committed
Add ReaderWithContext
1 parent d016039 commit c93043e

5 files changed

Lines changed: 214 additions & 100 deletions

File tree

src/io/mod.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,3 +306,72 @@ where
306306
Self::writer_with_context(self, format!("{msg}: '{path}'", path = file.as_ref().display()))
307307
}
308308
}
309+
310+
/// A wrapper around a reader of type `R` such that error context is added to
311+
/// any failed reads.
312+
pub struct ReaderWithContext<R> {
313+
/// The inner reader.
314+
reader: R,
315+
/// The context to add to any failed reads.
316+
description: String,
317+
}
318+
319+
impl<R> Read for ReaderWithContext<R>
320+
where
321+
R: Read,
322+
{
323+
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
324+
Ok(self.reader.read(buf).with_context(&self.description)?)
325+
}
326+
327+
fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result<usize> {
328+
Ok(self.reader.read_vectored(bufs).with_context(&self.description)?)
329+
}
330+
331+
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> std::io::Result<usize> {
332+
Ok(self.reader.read_to_end(buf).with_context(&self.description)?)
333+
}
334+
335+
fn read_to_string(&mut self, buf: &mut String) -> std::io::Result<usize> {
336+
Ok(self.reader.read_to_string(buf).with_context(&self.description)?)
337+
}
338+
339+
fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> {
340+
Ok(self.reader.read_exact(buf).with_context(&self.description)?)
341+
}
342+
}
343+
344+
/// An extension trait for [`Read`] allowing additional context to be added to
345+
/// each failed read (via [`ReaderWithContext`]).
346+
pub trait ReaderWithErrorContext: Sized {
347+
/// Wraps any errors that get produced during reading in an
348+
/// [`ErrorWithContext`] with the given description.
349+
///
350+
/// The `description` field may be anything implementing `Into<String>`.
351+
/// Passing an owned `String` avoids an extra allocation.
352+
///
353+
/// [`ErrorWithContext`]: zoe::data::err::ErrorWithContext
354+
fn reader_with_context(self, description: impl Into<String>) -> ReaderWithContext<Self>;
355+
356+
/// Convenience function for adding file context to any produced errors.
357+
///
358+
/// The context will be formatted as `msg: file`. The `msg` field may be
359+
/// anything implementing [`Display`].
360+
fn reader_with_file_context(self, msg: impl Display, file: impl AsRef<Path>) -> ReaderWithContext<Self>;
361+
}
362+
363+
impl<R> ReaderWithErrorContext for R
364+
where
365+
R: Read,
366+
{
367+
fn reader_with_context(self, description: impl Into<String>) -> ReaderWithContext<Self> {
368+
ReaderWithContext {
369+
reader: self,
370+
description: description.into(),
371+
}
372+
}
373+
374+
fn reader_with_file_context(self, msg: impl Display, file: impl AsRef<Path>) -> ReaderWithContext<Self> {
375+
Self::reader_with_context(self, format!("{msg}: '{path}'", path = file.as_ref().display()))
376+
}
377+
}

src/io/open_options/context.rs

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
use crate::io::{
2-
IterWithContext, IterWithErrorContext, PairedWriters, RecordReaders, RecordWriters, WriterWithContext,
3-
WriterWithErrorContext,
2+
IterWithContext, IterWithErrorContext, PairedWriters, ReaderWithContext, ReaderWithErrorContext, RecordReaders,
3+
RecordWriters, WriterWithContext, WriterWithErrorContext,
4+
};
5+
use std::{
6+
error::Error,
7+
fmt::Display,
8+
io::{Read, Write},
9+
path::Path,
410
};
5-
use std::{error::Error, fmt::Display, io::Write, path::Path};
611
use zoe::data::err::{ErrorWithContext, WithErrorContext};
712

813
/// An enum to represent the possible reader types for [`InputOptions`], for the
@@ -231,7 +236,7 @@ impl InputContext<'_> {
231236
}
232237

233238
/// Wrap the fallible iterators contained in a [`RecordReaders`] so that
234-
/// failed reads have context added.
239+
/// items that are errors have context added.
235240
pub fn add_paired_iter_context<I>(&self, iters: RecordReaders<I>) -> RecordReaders<IterWithContext<I>>
236241
where
237242
I: IterWithErrorContext, {
@@ -242,6 +247,31 @@ impl InputContext<'_> {
242247
.map(|iter| InputContext::add_iter_context(iter, self.reader2, self.input2)),
243248
}
244249
}
250+
251+
/// Adds context to a reader, so that failed reads have context.
252+
///
253+
/// `reader` and `input` should be corresponding fields in an
254+
/// [`InputContext`] struct. The context will include the path if available
255+
/// and the record type.
256+
pub fn add_reader_context<R>(reader: R, input: InputType) -> ReaderWithContext<R>
257+
where
258+
R: Read, {
259+
match input {
260+
InputType::File(path) => reader.reader_with_file_context("Failed to read from file", path),
261+
InputType::Stdin => reader.reader_with_context("Failed to read from stdin"),
262+
}
263+
}
264+
265+
/// Wrap the readers contained in a [`RecordReaders`] so that failed reads
266+
/// have context added.
267+
pub fn add_paired_reader_context<R>(&self, readers: RecordReaders<R>) -> RecordReaders<ReaderWithContext<R>>
268+
where
269+
R: Read, {
270+
RecordReaders {
271+
reader1: InputContext::add_reader_context(readers.reader1, self.input1),
272+
reader2: readers.reader2.map(|r| InputContext::add_reader_context(r, self.input2)),
273+
}
274+
}
245275
}
246276

247277
/// The complete context for [`OutputOptions`] necessary for displaying any

0 commit comments

Comments
 (0)