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#[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}