1use alloc::borrow::Cow;
2use alloc::boxed::Box;
3use alloc::vec::Vec;
4use core::fmt;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize))]
8pub struct Span {
9 pub start: usize,
10 pub end: usize,
11}
12
13impl Span {
14 pub const fn new(start: usize, end: usize) -> Self {
15 Self { start, end }
16 }
17
18 pub const fn point(position: usize) -> Self {
19 Self::new(position, position)
20 }
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize))]
26pub enum ErrorKind {
27 Lex,
29 Parse,
31 Validation,
33 Execution,
35 Transport,
37 Backend,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
48#[cfg_attr(feature = "serde", derive(serde::Serialize))]
49pub struct ErrorField {
50 pub key: Cow<'static, str>,
51 pub value: Cow<'static, str>,
52}
53
54impl ErrorField {
55 pub fn new(key: impl Into<Cow<'static, str>>, value: impl Into<Cow<'static, str>>) -> Self {
56 Self {
57 key: key.into(),
58 value: value.into(),
59 }
60 }
61}
62
63#[derive(Debug, Clone, PartialEq, Eq)]
73#[cfg_attr(feature = "serde", derive(serde::Serialize))]
74pub struct QqlError {
75 pub kind: ErrorKind,
76 pub code: Cow<'static, str>,
77 pub message: Cow<'static, str>,
78 pub span: Option<Span>,
79 pub fields: Vec<ErrorField>,
82 #[cfg_attr(feature = "serde", serde(skip))]
84 pub source: Option<Box<QqlError>>,
85}
86
87impl QqlError {
88 pub fn new(
89 kind: ErrorKind,
90 code: impl Into<Cow<'static, str>>,
91 message: impl Into<Cow<'static, str>>,
92 span: Option<Span>,
93 ) -> Self {
94 Self {
95 kind,
96 code: code.into(),
97 message: message.into(),
98 span,
99 fields: Vec::new(),
100 source: None,
101 }
102 }
103
104 pub fn lex(
107 code: impl Into<Cow<'static, str>>,
108 message: impl Into<Cow<'static, str>>,
109 span: Span,
110 ) -> Self {
111 Self::new(ErrorKind::Lex, code, message, Some(span))
112 }
113
114 pub fn parse(
115 code: impl Into<Cow<'static, str>>,
116 message: impl Into<Cow<'static, str>>,
117 span: Span,
118 ) -> Self {
119 Self::new(ErrorKind::Parse, code, message, Some(span))
120 }
121
122 pub fn validation(
123 code: impl Into<Cow<'static, str>>,
124 message: impl Into<Cow<'static, str>>,
125 span: Option<Span>,
126 ) -> Self {
127 Self::new(ErrorKind::Validation, code, message, span)
128 }
129
130 pub fn execution(
131 code: impl Into<Cow<'static, str>>,
132 message: impl Into<Cow<'static, str>>,
133 span: Option<Span>,
134 ) -> Self {
135 Self::new(ErrorKind::Execution, code, message, span)
136 }
137
138 pub fn transport(
139 code: impl Into<Cow<'static, str>>,
140 message: impl Into<Cow<'static, str>>,
141 span: Option<Span>,
142 ) -> Self {
143 Self::new(ErrorKind::Transport, code, message, span)
144 }
145
146 pub fn backend(
147 code: impl Into<Cow<'static, str>>,
148 message: impl Into<Cow<'static, str>>,
149 span: Option<Span>,
150 ) -> Self {
151 Self::new(ErrorKind::Backend, code, message, span)
152 }
153
154 pub(crate) fn syntax(message: impl Into<Cow<'static, str>>, position: usize) -> Self {
155 Self::parse("QQL-PARSE-SYNTAX", message, Span::point(position))
156 }
157
158 pub fn with_field(
171 mut self,
172 key: impl Into<Cow<'static, str>>,
173 value: impl Into<Cow<'static, str>>,
174 ) -> Self {
175 self.fields.push(ErrorField::new(key, value));
176 self
177 }
178
179 pub fn with_collection(self, name: impl Into<Cow<'static, str>>) -> Self {
181 self.with_field("collection", name)
182 }
183
184 pub fn with_status(self, code: u16) -> Self {
186 self.with_field("status_code", alloc::format!("{code}"))
187 }
188
189 pub fn with_url(self, url: impl Into<Cow<'static, str>>) -> Self {
191 self.with_field("url", url)
192 }
193
194 pub fn with_field_name(self, name: impl Into<Cow<'static, str>>) -> Self {
196 self.with_field("field_name", name)
197 }
198
199 pub fn with_index_name(self, name: impl Into<Cow<'static, str>>) -> Self {
201 self.with_field("index_name", name)
202 }
203
204 pub fn with_vector_name(self, name: impl Into<Cow<'static, str>>) -> Self {
206 self.with_field("vector_name", name)
207 }
208
209 pub fn with_model(self, name: impl Into<Cow<'static, str>>) -> Self {
211 self.with_field("model", name)
212 }
213
214 pub fn with_span(mut self, span: Span) -> Self {
216 self.span = Some(span);
217 self
218 }
219
220 pub fn caused_by(mut self, source: QqlError) -> Self {
225 self.source = Some(Box::new(source));
226 self
227 }
228
229 pub fn field(&self, key: &str) -> Option<&str> {
231 self.fields
232 .iter()
233 .find(|f| f.key.eq_ignore_ascii_case(key))
234 .map(|f| f.value.as_ref())
235 }
236}
237
238impl fmt::Display for QqlError {
239 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240 write!(f, "[{}] {}", self.code, self.message)?;
241 if let Some(span) = self.span {
242 write!(f, " at {}..{}", span.start, span.end)?;
243 }
244 if !self.fields.is_empty() {
246 write!(f, " {{")?;
247 for (i, field) in self.fields.iter().enumerate() {
248 if i > 0 {
249 write!(f, ", ")?;
250 }
251 write!(f, "{}: {}", field.key, field.value)?;
252 }
253 write!(f, "}}")?;
254 }
255 if let Some(ref source) = self.source {
257 write!(f, "\n caused by: {source}")?;
258 }
259 Ok(())
260 }
261}
262
263#[cfg(feature = "std")]
264impl std::error::Error for QqlError {
265 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
266 self.source
267 .as_ref()
268 .map(|s| s.as_ref() as &(dyn std::error::Error + 'static))
269 }
270}