use crate::lazy::text::buffer::TextBuffer;
use crate::position::Position;
use crate::result::{DecodingError, IncompleteError};
use crate::{IonError, IonResult};
use std::borrow::Cow;
use std::fmt::Debug;
use winnow::error::{AddContext, ErrMode, ErrorKind, Needed, ParserError};
use winnow::stream::Stream;
use winnow::PResult;
pub(crate) type IonParseResult<'a, O> = PResult<O, IonParseError<'a>>;
pub(crate) type IonMatchResult<'a> = IonParseResult<'a, TextBuffer<'a>>;
#[derive(Debug, Clone)]
pub struct IonParseError<'data> {
input: TextBuffer<'data>,
context: &'static str,
kind: IonParseErrorKind,
}
impl<'data> IonParseError<'data> {
pub(crate) fn context(mut self, context: &'static str) -> Self {
self.context = context;
self
}
pub(crate) fn cut_err(self) -> ErrMode<Self> {
ErrMode::Cut(self)
}
pub(crate) fn cut<T>(self) -> IonParseResult<'data, T> {
Err(ErrMode::Cut(self))
}
pub(crate) fn backtrack<T>(self) -> IonParseResult<'data, T> {
Err(ErrMode::Backtrack(self))
}
}
const DEFAULT_CONTEXT: &str = "reading Ion data";
impl<'data> TextBuffer<'data> {
#[inline(never)]
pub(crate) fn incomplete_err_mode(
&self,
context: &'static str,
) -> ErrMode<IonParseError<'data>> {
if self.is_final_data() {
self.invalid("stream is incomplete; ran out of data")
.context(context)
.cut_err()
} else {
ErrMode::Incomplete(Needed::Unknown)
}
}
pub fn incomplete<T>(&self, context: &'static str) -> IonParseResult<'data, T> {
Err(self.incomplete_err_mode(context))
}
pub fn unrecognized(&self) -> IonParseError<'data> {
IonParseError {
input: *self,
context: DEFAULT_CONTEXT,
kind: IonParseErrorKind::Unrecognized(UnrecognizedInputError::new()),
}
}
pub fn invalid(&self, description: impl Into<Cow<'static, str>>) -> IonParseError<'data> {
IonParseError {
input: *self,
context: DEFAULT_CONTEXT,
kind: IonParseErrorKind::Invalid(InvalidInputError::new(description)),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum IonParseErrorKind {
Incomplete,
Unrecognized(UnrecognizedInputError),
Invalid(InvalidInputError),
}
#[derive(Clone, Debug, PartialEq)]
pub struct UnrecognizedInputError {
winnow_error_kind: Option<ErrorKind>,
}
impl UnrecognizedInputError {
fn new() -> Self {
Self {
winnow_error_kind: None,
}
}
fn with_winnow_error_kind(winnow_error_kind: ErrorKind) -> Self {
Self {
winnow_error_kind: Some(winnow_error_kind),
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct InvalidInputError {
description: Cow<'static, str>,
}
impl InvalidInputError {
pub(crate) fn new(description: impl Into<Cow<'static, str>>) -> Self {
InvalidInputError {
description: description.into(),
}
}
}
impl<'data> AddContext<TextBuffer<'data>> for IonParseError<'data> {
fn add_context(
mut self,
_input: &TextBuffer<'data>,
_token_start: &<TextBuffer<'data> as Stream>::Checkpoint,
context: &'static str,
) -> Self {
self.context = context;
self
}
}
impl From<IonParseError<'_>> for IonError {
fn from(ion_parse_error: IonParseError<'_>) -> Self {
use IonParseErrorKind::*;
let mut message: String = match ion_parse_error.kind {
Incomplete => {
return IonError::Incomplete(IncompleteError::new(
ion_parse_error.context,
ion_parse_error.input.offset(),
));
}
Invalid(e) => e.description.into(),
Unrecognized(..) => "found unrecognized syntax".into(),
};
message.push_str("\n while ");
message.push_str(ion_parse_error.context.as_ref());
use std::fmt::Write;
let input = ion_parse_error.input;
const NUM_CHARS_TO_SHOW: usize = 32;
let buffer_head = match input.as_text() {
Ok(text) => {
let mut head_chars = text.chars();
let mut head = (&mut head_chars)
.take(NUM_CHARS_TO_SHOW)
.collect::<String>()
.replace("\n", "\\n");
if head_chars.next().is_some() {
head.push_str("...");
}
head
}
Err(_) => {
let head = format!(
"{:X?}",
&input.bytes()[..NUM_CHARS_TO_SHOW.min(input.len())]
);
head
}
};
write!(
message,
r#"
offset={}
input={}
buffer len={}
"#,
input.offset(),
buffer_head,
input.len(),
)
.unwrap();
let position = Position::with_offset(input.offset()).with_length(input.len());
let decoding_error = DecodingError::new(message).with_position(position);
IonError::Decoding(decoding_error)
}
}
impl<'data> ParserError<TextBuffer<'data>> for IonParseError<'data> {
fn from_error_kind(input: &TextBuffer<'data>, error_kind: ErrorKind) -> Self {
IonParseError {
input: *input,
context: DEFAULT_CONTEXT,
kind: IonParseErrorKind::Unrecognized(UnrecognizedInputError::with_winnow_error_kind(
error_kind,
)),
}
}
fn append(
self,
_input: &TextBuffer<'data>,
_checkpoint: &<TextBuffer<'data> as Stream>::Checkpoint,
_kind: ErrorKind,
) -> Self {
self
}
}
pub(crate) trait WithContext<'data, T> {
fn with_context(self, label: &'static str, input: TextBuffer<'data>) -> IonResult<T>;
}
impl<'data, T> WithContext<'data, T> for ErrMode<IonParseError<'data>> {
fn with_context(self, label: &'static str, input: TextBuffer<'data>) -> IonResult<T> {
use ErrMode::*;
let ion_parse_error = match self {
Incomplete(_) => IonParseError {
input,
context: label,
kind: IonParseErrorKind::Incomplete,
},
Backtrack(e) | Cut(e) => e,
};
let ion_error = IonError::from(ion_parse_error);
Err(ion_error)
}
}
impl<'data, T> WithContext<'data, T> for IonParseError<'data> {
fn with_context(mut self, label: &'static str, input: TextBuffer<'data>) -> IonResult<T> {
if self.kind == IonParseErrorKind::Incomplete {
self.input = input;
self.context = label;
}
Err(IonError::from(self))
}
}
impl<'data, T> WithContext<'data, T> for IonParseResult<'data, T> {
fn with_context(self, label: &'static str, input: TextBuffer<'data>) -> IonResult<T> {
match self {
Ok(matched) => Ok(matched),
Err(e) => e.with_context(label, input),
}
}
}