bluejay_parser/
error.rs

1use ariadne::{Config, IndexType, Label, Report, ReportKind, Source};
2use itertools::Either;
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5use std::borrow::Cow;
6use std::cell::RefCell;
7
8mod annotation;
9mod format_errors;
10
11pub use annotation::Annotation;
12pub use format_errors::SpanToLocation;
13
14#[derive(Debug, PartialEq)]
15pub struct Error {
16    message: Cow<'static, str>,
17    primary_annotation: Option<Annotation>,
18    secondary_annotations: Vec<Annotation>,
19}
20
21#[derive(Debug, PartialEq, Eq)]
22#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
23pub struct Location {
24    pub line: usize,
25    pub col: usize,
26}
27
28/// A [spec compliant GraphQL Error](https://spec.graphql.org/draft/#sec-Errors.Error-Result-Format)
29#[derive(Debug, PartialEq, Eq)]
30#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
31pub struct GraphQLError {
32    pub message: Cow<'static, str>,
33    pub locations: Vec<Location>,
34}
35
36impl Error {
37    pub fn new(
38        message: impl Into<Cow<'static, str>>,
39        primary_annotation: Option<Annotation>,
40        secondary_annotations: Vec<Annotation>,
41    ) -> Self {
42        Self {
43            message: message.into(),
44            primary_annotation,
45            secondary_annotations,
46        }
47    }
48
49    pub fn into_graphql_errors<E: Into<Error>>(
50        document: &str,
51        errors: impl IntoIterator<Item = E>,
52    ) -> Vec<GraphQLError> {
53        let converter = RefCell::new(SpanToLocation::new(document));
54        errors
55            .into_iter()
56            .flat_map(|err| {
57                let err: Error = err.into();
58                if let Some(primary_annotation) = err.primary_annotation {
59                    let (line, col) = converter
60                        .borrow_mut()
61                        .convert(primary_annotation.span())
62                        .unwrap_or((0, 0));
63                    Either::Left(std::iter::once(GraphQLError {
64                        message: primary_annotation.message,
65                        locations: vec![Location { line, col }],
66                    }))
67                } else if !err.secondary_annotations.is_empty() {
68                    Either::Right(err.secondary_annotations.into_iter().map(
69                        |secondary_annotation| {
70                            let (line, col) = converter
71                                .borrow_mut()
72                                .convert(secondary_annotation.span())
73                                .unwrap_or((0, 0));
74                            GraphQLError {
75                                message: secondary_annotation.message,
76                                locations: vec![Location { line, col }],
77                            }
78                        },
79                    ))
80                } else {
81                    Either::Left(std::iter::once(GraphQLError {
82                        message: err.message,
83                        locations: vec![],
84                    }))
85                }
86            })
87            .collect()
88    }
89
90    #[cfg(feature = "format-errors")]
91    pub fn format_errors<E: Into<Error>>(
92        document: &str,
93        filename: Option<&str>,
94        errors: impl IntoIterator<Item = E>,
95    ) -> String {
96        let filename = filename.unwrap_or("<unknown>");
97        let mut file_cache = (filename, Source::from(document));
98
99        let mut buf: Vec<u8> = Vec::new();
100
101        errors
102            .into_iter()
103            .enumerate()
104            .try_for_each(|(idx, error)| {
105                let error: Error = error.into();
106                if idx != 0 {
107                    buf.extend("\n".as_bytes());
108                }
109                Report::<(&str, logos::Span)>::build(
110                    ReportKind::Error,
111                    (
112                        filename,
113                        error
114                            .primary_annotation
115                            .as_ref()
116                            .map(|a| a.span().clone().into())
117                            .unwrap_or(0..0),
118                    ),
119                )
120                .with_config(
121                    Config::default()
122                        .with_color(false)
123                        .with_index_type(IndexType::Byte),
124                )
125                .with_message(error.message)
126                .with_labels(
127                    error
128                        .primary_annotation
129                        .map(|Annotation { message, span }| {
130                            Label::new((filename, span.into()))
131                                .with_message(message.as_ref())
132                                .with_priority(1)
133                        }),
134                )
135                .with_labels(error.secondary_annotations.into_iter().map(
136                    |Annotation { message, span }| {
137                        Label::new((filename, span.into())).with_message(message.as_ref())
138                    },
139                ))
140                .finish()
141                .write(&mut file_cache, &mut buf)
142            })
143            .unwrap();
144
145        String::from_utf8(buf).unwrap()
146    }
147
148    pub fn message(&self) -> &str {
149        self.message.as_ref()
150    }
151}