1use alloc::borrow::Cow;
2use alloc::boxed::Box;
3use alloc::vec::Vec;
4use core::fmt;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize))]
9pub struct Span {
10 pub start: usize,
12 pub end: usize,
14}
15
16impl Span {
17 pub const fn new(start: usize, end: usize) -> Self {
19 Self { start, end }
20 }
21
22 pub const fn point(position: usize) -> Self {
24 Self::new(position, position)
25 }
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30#[cfg_attr(feature = "serde", derive(serde::Serialize))]
31pub enum ErrorKind {
32 Lex,
34 Parse,
36 Validation,
38 Execution,
40 Transport,
42 Backend,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
53#[cfg_attr(feature = "serde", derive(serde::Serialize))]
54pub struct ErrorField {
55 pub key: Cow<'static, str>,
57 pub value: Cow<'static, str>,
59}
60
61impl ErrorField {
62 pub fn new(key: impl Into<Cow<'static, str>>, value: impl Into<Cow<'static, str>>) -> Self {
64 Self {
65 key: key.into(),
66 value: value.into(),
67 }
68 }
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
81#[cfg_attr(feature = "serde", derive(serde::Serialize))]
82pub struct QqlError {
83 pub kind: ErrorKind,
85 pub code: Cow<'static, str>,
87 pub message: Cow<'static, str>,
89 pub span: Option<Span>,
91 pub fields: Vec<ErrorField>,
94 #[cfg_attr(feature = "serde", serde(skip))]
96 pub source: Option<Box<QqlError>>,
97}
98
99impl QqlError {
100 pub fn new(
102 kind: ErrorKind,
103 code: impl Into<Cow<'static, str>>,
104 message: impl Into<Cow<'static, str>>,
105 span: Option<Span>,
106 ) -> Self {
107 Self {
108 kind,
109 code: code.into(),
110 message: message.into(),
111 span,
112 fields: Vec::new(),
113 source: None,
114 }
115 }
116
117 pub fn lex(
121 code: impl Into<Cow<'static, str>>,
122 message: impl Into<Cow<'static, str>>,
123 span: Span,
124 ) -> Self {
125 Self::new(ErrorKind::Lex, code, message, Some(span))
126 }
127
128 pub fn parse(
130 code: impl Into<Cow<'static, str>>,
131 message: impl Into<Cow<'static, str>>,
132 span: Span,
133 ) -> Self {
134 Self::new(ErrorKind::Parse, code, message, Some(span))
135 }
136
137 pub fn validation(
139 code: impl Into<Cow<'static, str>>,
140 message: impl Into<Cow<'static, str>>,
141 span: Option<Span>,
142 ) -> Self {
143 Self::new(ErrorKind::Validation, code, message, span)
144 }
145
146 pub fn execution(
148 code: impl Into<Cow<'static, str>>,
149 message: impl Into<Cow<'static, str>>,
150 span: Option<Span>,
151 ) -> Self {
152 Self::new(ErrorKind::Execution, code, message, span)
153 }
154
155 pub fn transport(
157 code: impl Into<Cow<'static, str>>,
158 message: impl Into<Cow<'static, str>>,
159 span: Option<Span>,
160 ) -> Self {
161 Self::new(ErrorKind::Transport, code, message, span)
162 }
163
164 pub fn backend(
166 code: impl Into<Cow<'static, str>>,
167 message: impl Into<Cow<'static, str>>,
168 span: Option<Span>,
169 ) -> Self {
170 Self::new(ErrorKind::Backend, code, message, span)
171 }
172
173 pub(crate) fn syntax(message: impl Into<Cow<'static, str>>, position: usize) -> Self {
174 Self::parse("QQL-PARSE-SYNTAX", message, Span::point(position))
175 }
176
177 pub fn with_field(
190 mut self,
191 key: impl Into<Cow<'static, str>>,
192 value: impl Into<Cow<'static, str>>,
193 ) -> Self {
194 self.fields.push(ErrorField::new(key, value));
195 self
196 }
197
198 pub fn with_collection(self, name: impl Into<Cow<'static, str>>) -> Self {
200 self.with_field("collection", name)
201 }
202
203 pub fn with_status(self, code: u16) -> Self {
205 self.with_field("status_code", alloc::format!("{code}"))
206 }
207
208 pub fn with_url(self, url: impl Into<Cow<'static, str>>) -> Self {
210 self.with_field("url", url)
211 }
212
213 pub fn with_field_name(self, name: impl Into<Cow<'static, str>>) -> Self {
215 self.with_field("field_name", name)
216 }
217
218 pub fn with_index_name(self, name: impl Into<Cow<'static, str>>) -> Self {
220 self.with_field("index_name", name)
221 }
222
223 pub fn with_vector_name(self, name: impl Into<Cow<'static, str>>) -> Self {
225 self.with_field("vector_name", name)
226 }
227
228 pub fn with_model(self, name: impl Into<Cow<'static, str>>) -> Self {
230 self.with_field("model", name)
231 }
232
233 pub fn with_span(mut self, span: Span) -> Self {
235 self.span = Some(span);
236 self
237 }
238
239 pub fn caused_by(mut self, source: QqlError) -> Self {
244 self.source = Some(Box::new(source));
245 self
246 }
247
248 pub fn field(&self, key: &str) -> Option<&str> {
250 self.fields
251 .iter()
252 .find(|f| f.key.eq_ignore_ascii_case(key))
253 .map(|f| f.value.as_ref())
254 }
255}
256
257impl fmt::Display for QqlError {
258 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259 write!(f, "[{}] {}", self.code, self.message)?;
260 if let Some(span) = self.span {
261 write!(f, " at {}..{}", span.start, span.end)?;
262 }
263 if !self.fields.is_empty() {
265 write!(f, " {{")?;
266 for (i, field) in self.fields.iter().enumerate() {
267 if i > 0 {
268 write!(f, ", ")?;
269 }
270 write!(f, "{}: {}", field.key, field.value)?;
271 }
272 write!(f, "}}")?;
273 }
274 if let Some(ref source) = self.source {
276 write!(f, "\n caused by: {source}")?;
277 }
278 Ok(())
279 }
280}
281
282impl core::error::Error for QqlError {
283 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
284 self.source
285 .as_ref()
286 .map(|s| s.as_ref() as &(dyn core::error::Error + 'static))
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293
294 #[test]
295 fn test_core_error_impl() {
296 let err = QqlError::syntax("unexpected token", 0);
297 let dyn_err: &dyn core::error::Error = &err;
298 assert!(dyn_err.source().is_none());
299 assert!(!dyn_err.to_string().is_empty());
300 }
301}