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 {
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                }
81            })
82            .collect()
83    }
84
85    #[cfg(feature = "format-errors")]
86    pub fn format_errors<E: Into<Error>>(
87        document: &str,
88        filename: Option<&str>,
89        errors: impl IntoIterator<Item = E>,
90    ) -> String {
91        let filename = filename.unwrap_or("<unknown>");
92        let mut file_cache = (filename, Source::from(document));
93
94        let mut buf: Vec<u8> = Vec::new();
95
96        errors
97            .into_iter()
98            .enumerate()
99            .try_for_each(|(idx, error)| {
100                let error: Error = error.into();
101                if idx != 0 {
102                    buf.extend("\n".as_bytes());
103                }
104                Report::build(
105                    ReportKind::Error,
106                    filename,
107                    error
108                        .primary_annotation
109                        .as_ref()
110                        .map(|a| a.span().byte_range().start)
111                        .unwrap_or(0),
112                )
113                .with_config(
114                    Config::default()
115                        .with_color(false)
116                        .with_index_type(IndexType::Byte),
117                )
118                .with_message(error.message)
119                .with_labels(
120                    error
121                        .primary_annotation
122                        .map(|Annotation { message, span }| {
123                            Label::new((filename, span.into()))
124                                .with_message(message.as_ref())
125                                .with_priority(1)
126                        }),
127                )
128                .with_labels(error.secondary_annotations.into_iter().map(
129                    |Annotation { message, span }| {
130                        Label::new((filename, span.into())).with_message(message.as_ref())
131                    },
132                ))
133                .finish()
134                .write(&mut file_cache, &mut buf)
135            })
136            .unwrap();
137
138        String::from_utf8(buf).unwrap()
139    }
140
141    pub fn message(&self) -> &str {
142        self.message.as_ref()
143    }
144}