use std::fmt::{self, Display, Formatter, Write};
use std::{convert::TryInto, error::Error};
use itertools::{Itertools, Position::*};
use nom::error::{ErrorKind, FromExternalError, ParseError};
use pretty_lint::{Position, PrettyLint, Span};
pub(crate) trait WithTagError<I> {
fn from_tag(input: I, tag: &'static str) -> Self;
}
impl<I> WithTagError<I> for () {
fn from_tag(_: I, _: &'static str) -> Self {}
}
#[derive(Debug, Clone, Copy)]
pub enum Expected {
Eof,
Char(char),
Tag(&'static str),
Number,
}
impl Display for Expected {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Expected::Eof => write!(f, "EoF"),
Expected::Number => write!(f, "a number"),
Expected::Char(c) => write!(f, "{:?}", c),
Expected::Tag(tag) => write!(f, "{:?}", tag),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct ExpectedAt {
pub index: usize,
pub line: u32,
pub column: u32,
pub expected: Expected,
}
#[derive(Debug, Clone)]
pub(crate) struct ExpectedContext<'a> {
input_tail: &'a str,
expected: Expected,
}
impl<'a> ExpectedContext<'a> {
pub fn extract_context(self, input: &'a str) -> ExpectedAt {
let offset = input
.len()
.checked_sub(self.input_tail.len())
.expect("input size was smaller than the tail size");
let prefix = &input[..offset];
let line_number = prefix.chars().filter(|&c| c == '\n').count() + 1;
let last_line_start = prefix
.char_indices()
.rev()
.find(|&(_, c)| c == '\n')
.map(|(index, _)| index + 1)
.unwrap_or(0);
let column_number = (offset - last_line_start) + 1;
ExpectedAt {
line: line_number
.try_into()
.expect("More than 4 billion lines of input"),
column: column_number
.try_into()
.expect("More than 4 billion columns of input"),
index: offset,
expected: self.expected,
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct PasswordRulesErrorContext<'a> {
expectations: Vec<ExpectedContext<'a>>,
}
impl<'a> PasswordRulesErrorContext<'a> {
pub fn extract_context(self, input: &'a str) -> PasswordRulesError {
let mut expectations: Vec<ExpectedAt> = self
.expectations
.into_iter()
.map(|exp| exp.extract_context(input))
.collect();
expectations.sort_unstable_by_key(|exp| exp.index);
PasswordRulesError { expectations }
}
}
impl<'a> ParseError<&'a str> for PasswordRulesErrorContext<'a> {
fn from_error_kind(input: &'a str, kind: ErrorKind) -> Self {
match kind {
ErrorKind::Eof => Self {
expectations: vec![ExpectedContext {
input_tail: input,
expected: Expected::Eof,
}],
},
ErrorKind::Digit => Self {
expectations: vec![ExpectedContext {
input_tail: input,
expected: Expected::Number,
}],
},
_ => Self {
expectations: vec![],
},
}
}
fn append(input: &'a str, kind: nom::error::ErrorKind, other: Self) -> Self {
Self::from_error_kind(input, kind).or(other)
}
fn or(mut self, other: Self) -> Self {
self.expectations.extend(other.expectations);
self
}
fn from_char(input: &'a str, c: char) -> Self {
Self {
expectations: vec![ExpectedContext {
input_tail: input,
expected: Expected::Char(c),
}],
}
}
}
impl<'a> WithTagError<&'a str> for PasswordRulesErrorContext<'a> {
fn from_tag(input: &'a str, tag: &'static str) -> Self {
Self {
expectations: vec![ExpectedContext {
input_tail: input,
expected: Expected::Tag(tag),
}],
}
}
}
impl<'a> FromExternalError<&'a str, std::num::ParseIntError> for PasswordRulesErrorContext<'a> {
fn from_external_error(input: &'a str, kind: ErrorKind, _: std::num::ParseIntError) -> Self {
Self::from_error_kind(input, kind)
}
}
#[derive(Debug, Clone)]
pub struct PasswordRulesError {
pub expectations: Vec<ExpectedAt>,
}
impl PasswordRulesError {
pub(crate) fn empty() -> Self {
Self {
expectations: vec![],
}
}
pub fn to_string_pretty(&self, s: &str) -> Result<String, fmt::Error> {
let lint_base = PrettyLint::error(s).with_message("parsing failed");
Ok(match self.expectations.as_slice() {
[] => lint_base.with_inline_message("unknown error").to_string(),
[exp] => lint_base
.with_inline_message(&format!("expected {}", exp.expected))
.at(Span {
start: Position {
line: exp.line as usize,
col: exp.column as usize,
},
end: Position {
line: exp.line as usize,
col: exp.column as usize,
},
})
.to_string(),
expectations => {
let groups = expectations.iter().group_by(|exp| (exp.line, exp.column));
let mut lint_string = String::new();
groups.into_iter().try_for_each(|((line, column), group)| {
let mut inline_message = String::from("expected one of ");
group
.with_position()
.try_for_each(|positioned_exp| match positioned_exp {
Only(exp) => write!(inline_message, "{}", exp.expected),
First(exp) | Middle(exp) => {
write!(inline_message, "{}, ", exp.expected)
}
Last(exp) => write!(inline_message, "or {}", exp.expected),
})?;
let lint = PrettyLint::error(s)
.with_message("parsing failed")
.with_inline_message(&inline_message)
.at(Span {
start: Position {
line: line as usize,
col: column as usize,
},
end: Position {
line: line as usize,
col: column as usize,
},
});
write!(lint_string, "{}", lint)
})?;
lint_string
}
})
}
}
impl Display for PasswordRulesError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self.expectations.as_slice() {
[] => write!(f, "unknown error"),
[exp] => write!(
f,
"expected {} at {}:{}",
exp.expected, exp.line, exp.column
),
expectations => {
writeln!(f, "expected one of:")?;
let groups = expectations.iter().group_by(|exp| (exp.line, exp.column));
groups.into_iter().try_for_each(|((line, column), group)| {
write!(f, " ")?;
group
.with_position()
.try_for_each(|positioned_exp| match positioned_exp {
Only(exp) => write!(f, "{}", exp.expected),
First(exp) | Middle(exp) => write!(f, "{}, ", exp.expected),
Last(exp) => write!(f, "or {}", exp.expected),
})?;
writeln!(f, " at {}:{}", line, column)
})
}
}
}
}
impl Error for PasswordRulesError {}