analyse_json/json/ndjson/errors/
collection.rs

1use std::cell::RefCell;
2use std::error::Error;
3use std::fmt::Display;
4use std::iter::Enumerate;
5use std::rc::Rc;
6use std::sync::{Arc, Mutex};
7use std::{fmt, io};
8
9use owo_colors::{OwoColorize, Stream};
10
11use super::NDJSONError;
12
13/// Holds linked position information for errors encountered while processing
14#[derive(Debug)]
15pub struct IndexedNDJSONError {
16    pub location: String,
17    pub error: NDJSONError,
18}
19
20impl IndexedNDJSONError {
21    pub(crate) fn new(location: String, error: NDJSONError) -> Self {
22        Self { location, error }
23    }
24}
25
26impl fmt::Display for IndexedNDJSONError {
27    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
28        write!(f, "Line {}: {}", self.location, self.error)?;
29        if let Some(source) = self.error.source() {
30            write!(f, "; {source}")?;
31        }
32        Ok(())
33    }
34}
35
36/// Threadsafe storage for errors enounter by parallel processing.
37/// Counterpart to [`Errors`]
38#[derive(Debug)]
39pub struct ErrorsPar<E> {
40    pub container: Arc<Mutex<Vec<E>>>,
41}
42
43impl<E> ErrorsPar<E> {
44    pub fn new(container: Arc<Mutex<Vec<E>>>) -> Self {
45        Self { container }
46    }
47
48    pub fn new_ref(&self) -> Self {
49        Self {
50            container: Arc::clone(&self.container),
51        }
52    }
53
54    pub fn push(&self, value: E) {
55        self.container.lock().expect("not poisoned").push(value)
56    }
57}
58
59impl<E> Default for ErrorsPar<E> {
60    fn default() -> Self {
61        Self::new(Arc::new(Mutex::new(vec![])))
62    }
63}
64
65impl<E: Display> fmt::Display for ErrorsPar<E> {
66    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
67        for i in self.container.lock().unwrap().as_slice() {
68            writeln!(f, "{i}")?;
69        }
70        Ok(())
71    }
72}
73
74// TODO: Consider blend with ErrorsContainer Trait?
75pub trait NDJSONProcessingErrors {
76    fn eprint(&self) {}
77}
78
79impl<E: Display> NDJSONProcessingErrors for ErrorsPar<E> {
80    fn eprint(&self) {
81        let stream = Stream::Stdout;
82        if !self.container.lock().unwrap().is_empty() {
83            eprintln!("{}", self.if_supports_color(stream, |text| text.red()));
84        }
85    }
86}
87
88// TODO: Create ErrorContainer Trait?
89/// Storage for errors enounter by processing
90/// Counterpart to [`ErrorsPar`]
91#[derive(Debug)]
92pub struct Errors<E> {
93    pub container: Rc<RefCell<Vec<E>>>,
94}
95
96impl<E> Errors<E> {
97    pub fn new(container: Rc<RefCell<Vec<E>>>) -> Self {
98        Self { container }
99    }
100
101    pub fn new_ref(&self) -> Self {
102        Self {
103            container: Rc::clone(&self.container),
104        }
105    }
106
107    pub fn push(&self, value: E) {
108        self.container.borrow_mut().push(value)
109    }
110}
111
112impl<E: Display> NDJSONProcessingErrors for Errors<E> {
113    fn eprint(&self) {
114        let stream = Stream::Stdout;
115        if !self.container.borrow().is_empty() {
116            eprintln!("{}", self.if_supports_color(stream, |text| text.red()));
117        }
118    }
119}
120
121impl<E> Default for Errors<E> {
122    fn default() -> Self {
123        Self::new(Rc::new(RefCell::new(vec![])))
124    }
125}
126
127impl<E: Display> fmt::Display for Errors<E> {
128    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
129        for i in self.container.borrow().as_slice() {
130            writeln!(f, "{i}")?;
131        }
132        Ok(())
133    }
134}
135
136/// Iterator that skips, but keeps track of, `Err`s while processing
137pub struct ErrFiltered<I, E> {
138    iter: I,
139    errors: Errors<E>,
140}
141
142impl<U, E, T, I: Iterator<Item = (U, Result<T, W>)>, W> ErrFiltered<I, E> {
143    pub fn new(iter: I, errors: Errors<E>) -> Self {
144        Self { iter, errors }
145    }
146}
147
148impl<U: Display, T, I: Iterator<Item = (U, Result<T, impl Into<NDJSONError>>)>> Iterator
149    for ErrFiltered<I, IndexedNDJSONError>
150{
151    type Item = (U, T);
152    fn next(&mut self) -> Option<Self::Item> {
153        loop {
154            let (id, next_item) = self.iter.next()?;
155            match next_item {
156                Ok(item) => break Some((id, item)),
157                Err(e) => {
158                    let error: NDJSONError = e.into();
159                    self.errors
160                        .push(IndexedNDJSONError::new(format!("{id}"), error));
161                }
162            }
163        }
164    }
165}
166
167pub trait IntoErrFiltered<U, E, T, W>: Iterator<Item = (U, Result<T, W>)> + Sized {
168    fn to_err_filtered(self, errors: Errors<E>) -> ErrFiltered<Self, E> {
169        ErrFiltered::new(self, errors)
170    }
171}
172
173impl<U, E, T, I: Iterator<Item = (U, Result<T, W>)>, W> IntoErrFiltered<U, E, T, W> for I {}
174
175/// Iterator that enumerates all items and skips, but keeps track of, `Err`s while processing
176pub struct EnumeratedErrFiltered<I, E> {
177    iter: Enumerate<I>,
178    errors: Errors<E>,
179}
180
181impl<E, T, I: Iterator<Item = Result<T, W>>, W> EnumeratedErrFiltered<I, E> {
182    pub fn new(iter: I, errors: Errors<E>) -> Self {
183        Self {
184            iter: iter.enumerate(),
185            errors,
186        }
187    }
188}
189
190impl<T, I> Iterator for EnumeratedErrFiltered<I, IndexedNDJSONError>
191where
192    I: Iterator<Item = Result<T, io::Error>>,
193{
194    type Item = (usize, T);
195    fn next(&mut self) -> Option<Self::Item> {
196        loop {
197            let (i, next_item) = self.iter.next()?;
198            let i = i + 1; // count lines from 1
199            match next_item {
200                Ok(item) => break Some((i, item)),
201                Err(e) => {
202                    self.errors.push(IndexedNDJSONError::new(
203                        i.to_string(),
204                        NDJSONError::IOError(e),
205                    ));
206                }
207            }
208        }
209    }
210}
211
212pub trait IntoEnumeratedErrFiltered<E, T, W>: Iterator<Item = Result<T, W>> + Sized {
213    fn to_enumerated_err_filtered(self, errors: Errors<E>) -> EnumeratedErrFiltered<Self, E> {
214        EnumeratedErrFiltered::new(self, errors)
215    }
216}
217
218impl<E, T, I: Iterator<Item = Result<T, W>>, W> IntoEnumeratedErrFiltered<E, T, W> for I {}