use logos::Span;
use std::{error, fmt, result};
pub type Result<T> = result::Result<T, Error>;
#[derive(PartialEq, Eq, Clone)]
pub struct Error {
pub(crate) message: String,
pub(crate) location: Option<Location>,
pub(crate) context: Option<String>,
pub(crate) error_type: ErrorType,
}
#[derive(PartialEq, Eq, Clone)]
pub enum ErrorType {
GraphQL,
Syntax,
}
impl Error {
pub fn new<S: Into<String>>(message: S, error_type: Option<ErrorType>) -> Self {
Self {
message: message.into(),
location: None,
context: None,
error_type: error_type.unwrap_or(ErrorType::GraphQL),
}
}
pub fn new_with_context<S: Into<String>>(
message: S,
location: Option<Location>,
context: S,
error_type: Option<ErrorType>,
) -> Self {
Self {
message: message.into(),
location,
context: Some(context.into()),
error_type: error_type.unwrap_or(ErrorType::GraphQL),
}
}
pub fn message(&self) -> &str {
self.message.as_ref()
}
pub fn location(&self) -> &Option<Location> {
&self.location
}
pub fn print(&self, include_ctx: bool) -> String {
let formatted = match self.error_type {
ErrorType::GraphQL => {
format!("GraphQL Error: {}", self.message)
}
ErrorType::Syntax => {
format!("Syntax Error: {}", self.message)
}
};
match self.context {
Some(ref context) if include_ctx => format!("{}\n{}", formatted, context),
_ => formatted,
}
}
}
pub(crate) fn print_span(source: &str, span: Span) -> String {
let mut out = String::new();
let start_line = source[..span.start].lines().count();
let start = source[..span.start]
.rfind('\n')
.and_then(|start| source[..start].rfind('\n'))
.map_or(0, |idx| idx + 1);
let end = source[span.end..]
.find('\n')
.map_or(source.len(), |idx| idx + span.end);
let snippet = &source[start..end];
let line_num_pad = (start_line + snippet.lines().count() - 1).to_string().len();
for (index, line) in snippet.lines().enumerate() {
if index > 0 {
out.push('\n');
}
let line_num = (start_line + index).to_string();
out.push_str(&" ".repeat(line_num_pad - line_num.len() + 1));
out.push_str(&line_num);
out.push_str(" | ");
out.push_str(line);
}
if source[span.start..span.end].find('\n').is_none() {
let start = source[..span.start].rfind('\n').map_or(0, |idx| idx + 1);
out.push('\n');
out.push_str(&" ".repeat(line_num_pad + 1));
out.push_str(" | ");
out.push_str(&" ".repeat(span.start - start));
out.push_str(&"^".repeat(span.end - span.start));
};
out
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Location {
pub line: usize,
pub column: usize,
}
pub(crate) fn get_location(source: &str, span: Span) -> Location {
let line = source[..span.start].lines().count();
let col = source[..span.start]
.lines()
.last()
.map_or(span.start, |x| x.len());
Location { line, column: col }
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.print(true))
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "\n{}\n", self)
}
}
impl error::Error for Error {}