graphql_query/
error.rs

1//! # Error and Result for this crate
2//!
3//! This crate defines a common [Error] structure that's used across this trait, or that certain
4//! utilities convert their errors to.
5
6use logos::Span;
7use std::{error, fmt, result};
8
9/// This crate's result type using the [Error] structure.
10pub type Result<T> = result::Result<T, Error>;
11
12/// This crate's error structure which internal errors are converted into.
13///
14/// The error is split into a general message and a context string. For parsing, for instance, the
15/// context string is populated with a snippet of the source text, while for validation the context
16/// is populated with a list of errors.
17///
18/// The Error implements both the [`fmt::Display`] and [`fmt::Debug`] traits. It also implements
19// [`error::Error`] so that it can be used with existing patterns for error handling.
20#[derive(PartialEq, Eq, Clone)]
21pub struct Error {
22    pub(crate) message: String,
23    pub(crate) location: Option<Location>,
24    pub(crate) context: Option<String>,
25    pub(crate) error_type: ErrorType,
26}
27
28#[derive(PartialEq, Eq, Clone)]
29pub enum ErrorType {
30    GraphQL,
31    Syntax,
32}
33
34impl Error {
35    /// Create a new Error with only a main message from an input string.
36    pub fn new<S: Into<String>>(message: S, error_type: Option<ErrorType>) -> Self {
37        Self {
38            message: message.into(),
39            location: None,
40            context: None,
41            error_type: error_type.unwrap_or(ErrorType::GraphQL),
42        }
43    }
44
45    /// Create a new Error with a main message and a context string from two input strings.
46    pub fn new_with_context<S: Into<String>>(
47        message: S,
48        location: Option<Location>,
49        context: S,
50        error_type: Option<ErrorType>,
51    ) -> Self {
52        Self {
53            message: message.into(),
54            location,
55            context: Some(context.into()),
56            error_type: error_type.unwrap_or(ErrorType::GraphQL),
57        }
58    }
59
60    /// Returns the message of the current error. The context is discarded.
61    pub fn message(&self) -> &str {
62        self.message.as_ref()
63    }
64
65    /// Returns the location of the current error.
66    pub fn location(&self) -> &Option<Location> {
67        &self.location
68    }
69
70    /// Formats this error, with the option to include the context information as well,
71    /// which will cause the string to be multi-line.
72    pub fn print(&self, include_ctx: bool) -> String {
73        let formatted = match self.error_type {
74            ErrorType::GraphQL => {
75                format!("GraphQL Error: {}", self.message)
76            }
77            ErrorType::Syntax => {
78                format!("Syntax Error: {}", self.message)
79            }
80        };
81
82        match self.context {
83            Some(ref context) if include_ctx => format!("{}\n{}", formatted, context),
84            _ => formatted,
85        }
86    }
87}
88
89pub(crate) fn print_span(source: &str, span: Span) -> String {
90    let mut out = String::new();
91    let start_line = source[..span.start].lines().count();
92
93    let start = source[..span.start]
94        .rfind('\n')
95        .and_then(|start| source[..start].rfind('\n'))
96        .map_or(0, |idx| idx + 1);
97
98    let end = source[span.end..]
99        .find('\n')
100        .map_or(source.len(), |idx| idx + span.end);
101
102    let snippet = &source[start..end];
103    let line_num_pad = (start_line + snippet.lines().count() - 1).to_string().len();
104    for (index, line) in snippet.lines().enumerate() {
105        if index > 0 {
106            out.push('\n');
107        }
108        let line_num = (start_line + index).to_string();
109        out.push_str(&" ".repeat(line_num_pad - line_num.len() + 1));
110        out.push_str(&line_num);
111        out.push_str(" | ");
112        out.push_str(line);
113    }
114    if source[span.start..span.end].find('\n').is_none() {
115        let start = source[..span.start].rfind('\n').map_or(0, |idx| idx + 1);
116        out.push('\n');
117        out.push_str(&" ".repeat(line_num_pad + 1));
118        out.push_str(" | ");
119        out.push_str(&" ".repeat(span.start - start));
120        out.push_str(&"^".repeat(span.end - span.start));
121    };
122
123    out
124}
125
126#[derive(Debug, PartialEq, Eq, Clone)]
127pub struct Location {
128    pub line: usize,
129    pub column: usize,
130}
131
132pub(crate) fn get_location(source: &str, span: Span) -> Location {
133    let line = source[..span.start].lines().count();
134    let col = source[..span.start]
135        .lines()
136        .last()
137        .map_or(span.start, |x| x.len());
138
139    Location { line, column: col }
140}
141
142impl fmt::Display for Error {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        write!(f, "{}", self.print(true))
145    }
146}
147
148impl fmt::Debug for Error {
149    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150        write!(f, "\n{}\n", self)
151    }
152}
153
154impl error::Error for Error {}