use core::fmt::Display;
use alloc::{borrow::Cow, vec::Vec};
use crate::{Span, Spanned};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum Level {
Warning,
Error,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Fragment<'a> {
pub message: Cow<'static, str>,
pub span: Span,
pub sql_segment: &'a str,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Issue<'a> {
pub level: Level,
pub message: Cow<'static, str>,
pub span: Span,
pub sql_segment: &'a str,
pub fragments: Vec<Fragment<'a>>,
}
pub struct IssueHandle<'a, 'b> {
src: &'a str,
issue: &'b mut Issue<'a>,
}
impl<'a, 'b> IssueHandle<'a, 'b> {
pub fn frag(
&mut self,
message: impl Into<Cow<'static, str>>,
span: &impl Spanned,
) -> &mut Self {
let span: core::ops::Range<usize> = span.span();
self.issue.fragments.push(Fragment {
message: message.into(),
span: span.clone(),
sql_segment: &self.src[span.start..span.end],
});
self
}
}
#[derive(Debug)]
pub struct Issues<'a> {
pub src: &'a str,
pub issues: Vec<Issue<'a>>,
}
impl<'a> Issues<'a> {
pub fn err<'b>(
&'b mut self,
message: impl Into<Cow<'static, str>>,
span: &impl Spanned,
) -> IssueHandle<'a, 'b> {
let span: core::ops::Range<usize> = span.span();
self.issues.push(Issue {
level: Level::Error,
message: message.into(),
span: span.clone(),
sql_segment: self.segment(span),
fragments: Default::default(),
});
IssueHandle {
src: self.src,
issue: self.issues.last_mut().unwrap(),
}
}
pub fn warn<'b>(
&'b mut self,
message: impl Into<Cow<'static, str>>,
span: &impl Spanned,
) -> IssueHandle<'a, 'b> {
let span = span.span();
self.issues.push(Issue {
level: Level::Warning,
message: message.into(),
span: span.clone(),
sql_segment: self.segment(span),
fragments: Default::default(),
});
IssueHandle {
src: self.src,
issue: self.issues.last_mut().unwrap(),
}
}
pub fn segment(&self, span: Span) -> &'a str {
&self.src[span.start..span.end]
}
pub fn new(src: &'a str) -> Self {
Self {
src,
issues: Default::default(),
}
}
pub fn get(&self) -> &'_ [Issue<'a>] {
&self.issues
}
pub fn into_vec(self) -> Vec<Issue<'a>> {
self.issues
}
pub fn is_ok(&self) -> bool {
self.issues.is_empty()
}
}
impl<'a> Display for Issues<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if self.issues.is_empty() {
return writeln!(f, "No issues");
}
writeln!(f, "Issues:")?;
for issue in self.get() {
match issue.level {
Level::Warning => write!(f, " Warning ")?,
Level::Error => write!(f, " Error ")?,
}
writeln!(f, "{}", issue.message)?;
let line = self.src[..issue.span.start]
.chars()
.filter(|v| *v == '\n')
.count()
+ 1;
writeln!(f, " At line {}, code {}", line, issue.sql_segment)?;
for fragment in &issue.fragments {
writeln!(f, " {}", fragment.message)?;
let line = self.src[..fragment.span.start]
.chars()
.filter(|v| *v == '\n')
.count()
+ 1;
writeln!(f, " At line {}, code {}", line, fragment.sql_segment)?;
}
}
Ok(())
}
}