use std::fmt;
use std::sync::{Arc, Mutex};
use deser_core::de::DeserializeDriver;
use deser_core::{Error, ErrorKind, State};
use deser_path::{Path, PathLayer};
use crate::Violation;
#[derive(Debug, Clone, PartialEq)]
pub struct Issue {
kind: ErrorKind,
message: String,
path: Option<String>,
offset: Option<usize>,
line_column: Option<(usize, usize)>,
violation: Option<Violation>,
}
impl Issue {
pub fn from_error(err: &Error) -> Issue {
Issue {
kind: err.kind(),
message: err.message().to_string(),
path: err.attachment::<Path>().map(|path| path.to_string()),
offset: err.offset(),
line_column: err.line().zip(err.column()),
violation: err.attachment::<Violation>().cloned(),
}
}
pub fn kind(&self) -> ErrorKind {
self.kind
}
pub fn message(&self) -> &str {
&self.message
}
pub fn path(&self) -> Option<&str> {
self.path.as_deref()
}
pub fn offset(&self) -> Option<usize> {
self.offset
}
pub fn line(&self) -> Option<usize> {
self.line_column.map(|x| x.0)
}
pub fn column(&self) -> Option<usize> {
self.line_column.map(|x| x.1)
}
pub fn violation(&self) -> Option<&Violation> {
self.violation.as_ref()
}
}
impl fmt::Display for Issue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.path {
Some(ref path) if !path.is_empty() => write!(f, "{}: {}", path, self.message)?,
_ => f.write_str(&self.message)?,
}
match (self.line_column, self.offset) {
(Some((line, column)), _) => write!(f, " (at line {} column {})", line, column),
(None, Some(offset)) => write!(f, " (at offset {})", offset),
(None, None) => Ok(()),
}
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Report {
issues: Vec<Issue>,
}
impl Report {
pub fn new() -> Report {
Report::default()
}
pub fn from_error(err: &Error) -> Report {
Report {
issues: err.errors().map(Issue::from_error).collect(),
}
}
pub fn is_empty(&self) -> bool {
self.issues.is_empty()
}
pub fn len(&self) -> usize {
self.issues.len()
}
pub fn issues(&self) -> &[Issue] {
&self.issues
}
pub fn iter(&self) -> std::slice::Iter<'_, Issue> {
self.issues.iter()
}
pub fn push(&mut self, issue: Issue) {
self.issues.push(issue);
}
pub fn resolve_positions(&mut self, source: &[u8]) {
for issue in self.issues.iter_mut() {
if let (Some(offset), None) = (issue.offset, issue.line_column) {
let pos = deser_core::Position::of(source, offset);
issue.line_column = Some((pos.line, pos.column));
}
}
}
}
impl<'a> IntoIterator for &'a Report {
type Item = &'a Issue;
type IntoIter = std::slice::Iter<'a, Issue>;
fn into_iter(self) -> Self::IntoIter {
self.issues.iter()
}
}
impl fmt::Display for Report {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (idx, issue) in self.issues.iter().enumerate() {
if idx > 0 {
writeln!(f)?;
}
fmt::Display::fmt(issue, f)?;
}
Ok(())
}
}
impl std::error::Error for Report {}
#[derive(Debug, Default)]
pub(crate) struct ReportHandle(Option<Arc<Mutex<Vec<Issue>>>>);
impl ReportHandle {
pub(crate) fn report(err: &Error, state: &State) {
if let Some(ReportHandle(Some(issues))) = state.get::<ReportHandle>() {
let mut issues = issues.lock().unwrap_or_else(|err| err.into_inner());
issues.extend(err.errors().map(Issue::from_error));
}
}
}
#[derive(Debug, Default)]
pub struct Validation {
issues: Arc<Mutex<Vec<Issue>>>,
max_errors: Option<usize>,
}
#[derive(Debug)]
pub struct Outcome<T> {
pub value: Option<T>,
pub report: Report,
}
impl<T> Outcome<T> {
pub fn is_valid(&self) -> bool {
self.value.is_some() && self.report.is_empty()
}
pub fn into_result(self) -> Result<T, Report> {
match self.value {
Some(value) if self.report.is_empty() => Ok(value),
_ => Err(self.report),
}
}
}
impl Validation {
pub fn new() -> Validation {
Validation::default()
}
pub fn max_errors(mut self, max: usize) -> Validation {
self.max_errors = Some(max);
self
}
pub fn setup(&self, driver: &mut DeserializeDriver<'_, '_>) {
driver.push_layer(PathLayer::new());
let state = driver.state_mut();
state.set_collect_errors(true);
if let Some(max) = self.max_errors {
state.set_max_errors(max);
}
state.get_mut::<ReportHandle>().0 = Some(self.issues.clone());
}
pub fn finish<T>(&self, rv: Result<T, Error>) -> Outcome<T> {
let issues = {
let mut issues = self.issues.lock().unwrap_or_else(|err| err.into_inner());
std::mem::take(&mut *issues)
};
let mut report = Report { issues };
let value = match rv {
Ok(value) => Some(value),
Err(err) => {
report.issues.extend(err.errors().map(Issue::from_error));
None
}
};
report
.issues
.sort_by_key(|issue| issue.offset.unwrap_or(usize::MAX));
Outcome { value, report }
}
}