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, serde::Deserialize))]
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 fn with_field(
186 mut self,
187 key: impl Into<Cow<'static, str>>,
188 value: impl Into<Cow<'static, str>>,
189 ) -> Self {
190 self.fields.push(ErrorField::new(key, value));
191 self
192 }
193
194 pub fn with_collection(self, name: impl Into<Cow<'static, str>>) -> Self {
196 self.with_field("collection", name)
197 }
198
199 pub fn with_status(self, code: u16) -> Self {
201 self.with_field("status_code", alloc::format!("{code}"))
202 }
203
204 pub fn with_url(self, url: impl Into<Cow<'static, str>>) -> Self {
206 self.with_field("url", url)
207 }
208
209 pub fn with_field_name(self, name: impl Into<Cow<'static, str>>) -> Self {
211 self.with_field("field_name", name)
212 }
213
214 pub fn with_index_name(self, name: impl Into<Cow<'static, str>>) -> Self {
216 self.with_field("index_name", name)
217 }
218
219 pub fn with_vector_name(self, name: impl Into<Cow<'static, str>>) -> Self {
221 self.with_field("vector_name", name)
222 }
223
224 pub fn with_model(self, name: impl Into<Cow<'static, str>>) -> Self {
226 self.with_field("model", name)
227 }
228
229 pub fn with_span(mut self, span: Span) -> Self {
231 self.span = Some(span);
232 self
233 }
234
235 pub fn caused_by(mut self, source: QqlError) -> Self {
240 self.source = Some(Box::new(source));
241 self
242 }
243
244 pub fn field(&self, key: &str) -> Option<&str> {
246 self.fields
247 .iter()
248 .find(|f| f.key.eq_ignore_ascii_case(key))
249 .map(|f| f.value.as_ref())
250 }
251}
252
253impl fmt::Display for QqlError {
254 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255 write!(f, "[{}] {}", self.code, self.message)?;
256 if let Some(span) = self.span {
257 write!(f, " at {}..{}", span.start, span.end)?;
258 }
259 if !self.fields.is_empty() {
261 write!(f, " {{")?;
262 for (i, field) in self.fields.iter().enumerate() {
263 if i > 0 {
264 write!(f, ", ")?;
265 }
266 write!(f, "{}: {}", field.key, field.value)?;
267 }
268 write!(f, "}}")?;
269 }
270 if let Some(ref source) = self.source {
272 write!(f, "\n caused by: {source}")?;
273 }
274 Ok(())
275 }
276}
277
278impl core::error::Error for QqlError {
279 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
280 self.source
281 .as_ref()
282 .map(|s| s.as_ref() as &(dyn core::error::Error + 'static))
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 #[test]
291 fn test_core_error_impl() {
292 let err = QqlError::parse("QQL-PARSE-SYNTAX", "unexpected token", Span::point(0));
293 let dyn_err: &dyn core::error::Error = &err;
294 assert!(dyn_err.source().is_none());
295 assert!(!dyn_err.to_string().is_empty());
296 }
297}