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 {
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}