Skip to main content

deser_validate/
report.rs

1//! Reporting all problems of an input.
2use std::fmt;
3use std::sync::{Arc, Mutex};
4
5use deser_core::de::DeserializeDriver;
6use deser_core::{Error, ErrorKind, State};
7use deser_path::{Path, PathLayer};
8
9use crate::Violation;
10
11/// A problem of the input.
12///
13/// This is a summary of an [`Error`] that can be cloned and stored: its
14/// kind, message, location and path and the [`Violation`] if a validator
15/// rejected the value.
16#[derive(Debug, Clone, PartialEq)]
17pub struct Issue {
18    kind: ErrorKind,
19    message: String,
20    path: Option<String>,
21    offset: Option<usize>,
22    line_column: Option<(usize, usize)>,
23    violation: Option<Violation>,
24}
25
26impl Issue {
27    /// Creates the issue of an error.
28    ///
29    /// For errors that hold multiple errors (see [`Error::errors`]), this
30    /// is the issue of the first one, see [`Report::from_error`] for all of
31    /// them.
32    pub fn from_error(err: &Error) -> Issue {
33        Issue {
34            kind: err.kind(),
35            message: err.message().to_string(),
36            path: err.attachment::<Path>().map(|path| path.to_string()),
37            offset: err.offset(),
38            line_column: err.line().zip(err.column()),
39            violation: err.attachment::<Violation>().cloned(),
40        }
41    }
42
43    /// Returns the kind of the error.
44    pub fn kind(&self) -> ErrorKind {
45        self.kind
46    }
47
48    /// Returns the message.
49    pub fn message(&self) -> &str {
50        &self.message
51    }
52
53    /// Returns the path of the value (for instance `servers[1].port`).
54    ///
55    /// The path of the root value is the empty string.
56    pub fn path(&self) -> Option<&str> {
57        self.path.as_deref()
58    }
59
60    /// Returns the byte offset in the input.
61    pub fn offset(&self) -> Option<usize> {
62        self.offset
63    }
64
65    /// Returns the line (1-based).
66    pub fn line(&self) -> Option<usize> {
67        self.line_column.map(|x| x.0)
68    }
69
70    /// Returns the column (1-based, in characters).
71    pub fn column(&self) -> Option<usize> {
72        self.line_column.map(|x| x.1)
73    }
74
75    /// Returns the violation if a validator rejected the value.
76    pub fn violation(&self) -> Option<&Violation> {
77        self.violation.as_ref()
78    }
79}
80
81impl fmt::Display for Issue {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        match self.path {
84            Some(ref path) if !path.is_empty() => write!(f, "{}: {}", path, self.message)?,
85            _ => f.write_str(&self.message)?,
86        }
87        match (self.line_column, self.offset) {
88            (Some((line, column)), _) => write!(f, " (at line {} column {})", line, column),
89            (None, Some(offset)) => write!(f, " (at offset {})", offset),
90            (None, None) => Ok(()),
91        }
92    }
93}
94
95/// All problems of an input.
96///
97/// A report is the result of a [`Validation`], it lists the issues in the
98/// order they appear in the input.  Its display output lists them one per
99/// line.
100#[derive(Debug, Clone, Default, PartialEq)]
101pub struct Report {
102    issues: Vec<Issue>,
103}
104
105impl Report {
106    /// Creates an empty report.
107    pub fn new() -> Report {
108        Report::default()
109    }
110
111    /// Creates the report of the errors an error holds.
112    pub fn from_error(err: &Error) -> Report {
113        Report {
114            issues: err.errors().map(Issue::from_error).collect(),
115        }
116    }
117
118    /// Returns `true` if there are no issues.
119    pub fn is_empty(&self) -> bool {
120        self.issues.is_empty()
121    }
122
123    /// Returns the number of issues.
124    pub fn len(&self) -> usize {
125        self.issues.len()
126    }
127
128    /// Returns the issues.
129    pub fn issues(&self) -> &[Issue] {
130        &self.issues
131    }
132
133    /// Iterates over the issues.
134    pub fn iter(&self) -> std::slice::Iter<'_, Issue> {
135        self.issues.iter()
136    }
137
138    /// Adds an issue.
139    pub fn push(&mut self, issue: Issue) {
140        self.issues.push(issue);
141    }
142
143    /// Resolves the offsets of the issues without lines and columns.
144    ///
145    /// The source is the input the offsets refer to.  Issues of values
146    /// that kept their errors (see [`Validated`](crate::Validated)) only
147    /// have lines and columns if the format provides the source (see
148    /// [`deser_location`]).  This resolves them otherwise.
149    pub fn resolve_positions(&mut self, source: &[u8]) {
150        for issue in self.issues.iter_mut() {
151            if let (Some(offset), None) = (issue.offset, issue.line_column) {
152                let pos = deser_core::Position::of(source, offset);
153                issue.line_column = Some((pos.line, pos.column));
154            }
155        }
156    }
157}
158
159impl<'a> IntoIterator for &'a Report {
160    type Item = &'a Issue;
161    type IntoIter = std::slice::Iter<'a, Issue>;
162
163    fn into_iter(self) -> Self::IntoIter {
164        self.issues.iter()
165    }
166}
167
168impl fmt::Display for Report {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        for (idx, issue) in self.issues.iter().enumerate() {
171            if idx > 0 {
172                writeln!(f)?;
173            }
174            fmt::Display::fmt(issue, f)?;
175        }
176        Ok(())
177    }
178}
179
180impl std::error::Error for Report {}
181
182/// Where the errors that values keep are reported to (in the state).
183#[derive(Debug, Default)]
184pub(crate) struct ReportHandle(Option<Arc<Mutex<Vec<Issue>>>>);
185
186impl ReportHandle {
187    /// Reports the errors of an error if a validation runs.
188    pub(crate) fn report(err: &Error, state: &State) {
189        if let Some(ReportHandle(Some(issues))) = state.get::<ReportHandle>() {
190            let mut issues = issues.lock().unwrap_or_else(|err| err.into_inner());
191            issues.extend(err.errors().map(Issue::from_error));
192        }
193    }
194}
195
196/// Finds all problems of an input.
197///
198/// A validation is set up on a deserialization (with the `setup` function
199/// that formats accept, see
200/// [`Deserializer::deserialize_with`](deser_core::de::Deserializer::deserialize_with)).
201/// It collects errors (see [`State::set_collect_errors`]) and tracks paths
202/// (see [`PathLayer`]).  Once the deserialization is done,
203/// [`finish`](Self::finish) returns the value together with a [`Report`]
204/// of all problems: the errors that failed the deserialization and the
205/// errors that [`Validated`](crate::Validated) values kept.
206///
207/// ```
208/// use deser::Deserialize;
209/// use deser_validate::{Email, Range, Validated, Validation};
210///
211/// #[derive(Deserialize)]
212/// struct Signup {
213///     email: Validated<String, Email>,
214///     age: Validated<u8, Range<13, 130>>,
215///     name: String,
216/// }
217///
218/// let input = r#"{"email": "nope", "age": 7}"#;
219/// let validation = Validation::new();
220/// let rv = deser_json::Deserializer::from_str(input)
221///     .deserialize_with::<Signup, _>(|driver| validation.setup(driver));
222/// let outcome = validation.finish(rv);
223/// assert!(outcome.value.is_none());
224/// assert_eq!(
225///     outcome.report.to_string(),
226///     "email: invalid value: must be an email address (at offset 10)\n\
227///      age: invalid value: must be between 13 and 130 (at offset 25)\n\
228///      missing field `name` (at line 1 column 27)"
229/// );
230/// ```
231#[derive(Debug, Default)]
232pub struct Validation {
233    issues: Arc<Mutex<Vec<Issue>>>,
234    max_errors: Option<usize>,
235}
236
237/// The result of a [`Validation`].
238#[derive(Debug)]
239pub struct Outcome<T> {
240    /// The value, if it could be deserialized.
241    ///
242    /// The value can hold invalid values (see
243    /// [`Validated`](crate::Validated)), the report lists them.
244    pub value: Option<T>,
245    /// The problems of the input.
246    pub report: Report,
247}
248
249impl<T> Outcome<T> {
250    /// Returns `true` if the input has no problems.
251    pub fn is_valid(&self) -> bool {
252        self.value.is_some() && self.report.is_empty()
253    }
254
255    /// Returns the value if the input has no problems, the report
256    /// otherwise.
257    pub fn into_result(self) -> Result<T, Report> {
258        match self.value {
259            Some(value) if self.report.is_empty() => Ok(value),
260            _ => Err(self.report),
261        }
262    }
263}
264
265impl Validation {
266    /// Creates a validation.
267    pub fn new() -> Validation {
268        Validation::default()
269    }
270
271    /// Limits the number of errors that are collected.
272    ///
273    /// See [`State::set_max_errors`].  This limits the errors that fail
274    /// the deserialization, not the errors that values keep.
275    pub fn set_max_errors(&mut self, max: usize) {
276        self.max_errors = Some(max);
277    }
278
279    /// Returns the limit of errors (see [`set_max_errors`](Self::set_max_errors)).
280    pub fn max_errors(&self) -> Option<usize> {
281        self.max_errors
282    }
283
284    /// Sets up a deserialization.
285    pub fn setup(&self, driver: &mut DeserializeDriver<'_, '_>) {
286        driver.push_layer(PathLayer::new());
287        let state = driver.state_mut();
288        state.set_collect_errors(true);
289        if let Some(max) = self.max_errors {
290            state.set_max_errors(max);
291        }
292        state.get_mut::<ReportHandle>().0 = Some(self.issues.clone());
293    }
294
295    /// Finishes the validation with the result of the deserialization.
296    pub fn finish<T>(&self, rv: Result<T, Error>) -> Outcome<T> {
297        let issues = {
298            let mut issues = self.issues.lock().unwrap_or_else(|err| err.into_inner());
299            std::mem::take(&mut *issues)
300        };
301        let mut report = Report { issues };
302        let value = match rv {
303            Ok(value) => Some(value),
304            Err(err) => {
305                report.issues.extend(err.errors().map(Issue::from_error));
306                None
307            }
308        };
309        // in the order of the input, issues without a location last
310        report
311            .issues
312            .sort_by_key(|issue| issue.offset.unwrap_or(usize::MAX));
313        Outcome { value, report }
314    }
315}