gel_errors/
error.rs

1use std::any::{Any, TypeId};
2use std::borrow::Cow;
3use std::collections::HashMap;
4use std::error::Error as StdError;
5use std::fmt;
6use std::str;
7
8use crate::kinds::UserError;
9use crate::kinds::{error_name, tag_check};
10use crate::traits::{ErrorKind, Field};
11
12const FIELD_HINT: u16 = 0x_00_01;
13const FIELD_DETAILS: u16 = 0x_00_02;
14const FIELD_SERVER_TRACEBACK: u16 = 0x_01_01;
15
16// TODO(tailhook) these might be deprecated?
17const FIELD_POSITION_START: u16 = 0x_FF_F1;
18const FIELD_POSITION_END: u16 = 0x_FF_F2;
19const FIELD_LINE: u16 = 0x_FF_F3;
20const FIELD_COLUMN: u16 = 0x_FF_F4;
21
22/// Error type returned from Gel database calls.
23// This includes boxed error, because propagating through call chain is
24// faster when error is just one pointer
25#[derive(Debug)]
26pub struct Error(pub(crate) Box<Inner>);
27
28pub struct Chain<'a>(Option<&'a (dyn StdError + 'static)>);
29
30/// Tag that is used to group similar errors.
31#[derive(Clone, Copy)]
32pub struct Tag {
33    pub(crate) bit: u32,
34}
35
36pub(crate) enum Source {
37    Box(Box<dyn StdError + Send + Sync + 'static>),
38    Ref(Box<dyn AsRef<dyn StdError + Send + Sync + 'static> + Send + Sync + 'static>),
39}
40
41#[derive(Debug)]
42pub(crate) struct Inner {
43    pub code: u32,
44    // TODO(tailhook) possibly put message into the fields too
45    pub messages: Vec<Cow<'static, str>>,
46    pub error: Option<Source>,
47    // TODO(tailhook) put headers into the fields
48    pub headers: HashMap<u16, bytes::Bytes>,
49    pub fields: HashMap<(&'static str, TypeId), Box<dyn Any + Send + Sync>>,
50}
51
52impl Error {
53    pub fn is<T: ErrorKind>(&self) -> bool {
54        T::is_superclass_of(self.0.code)
55    }
56    pub fn has_tag(&self, tag: Tag) -> bool {
57        tag_check(self.0.code, tag.bit)
58    }
59    pub fn chain(&self) -> Chain {
60        Chain(Some(self))
61    }
62    pub fn context<S: Into<Cow<'static, str>>>(mut self, msg: S) -> Error {
63        self.0.messages.push(msg.into());
64        self
65    }
66    pub fn headers(&self) -> &HashMap<u16, bytes::Bytes> {
67        &self.0.headers
68    }
69    pub fn with_headers(mut self, headers: HashMap<u16, bytes::Bytes>) -> Error {
70        self.0.headers = headers;
71        self
72    }
73    pub fn kind_name(&self) -> &str {
74        error_name(self.0.code)
75    }
76    pub fn kind_debug(&self) -> impl fmt::Display {
77        format!("{} [0x{:08X}]", error_name(self.0.code), self.0.code)
78    }
79    pub fn initial_message(&self) -> Option<&str> {
80        self.0.messages.first().map(|m| &m[..])
81    }
82    pub fn contexts(&self) -> impl DoubleEndedIterator<Item = &str> {
83        self.0.messages.iter().skip(1).map(|m| &m[..])
84    }
85    fn header(&self, field: u16) -> Option<&str> {
86        if let Some(value) = self.headers().get(&field) {
87            if let Ok(value) = str::from_utf8(value) {
88                return Some(value);
89            }
90        }
91        None
92    }
93    fn usize_header(&self, field: u16) -> Option<usize> {
94        self.header(field)
95            .and_then(|x| x.parse::<u32>().ok())
96            .map(|x| x as usize)
97    }
98    pub fn hint(&self) -> Option<&str> {
99        self.header(FIELD_HINT)
100    }
101    pub fn details(&self) -> Option<&str> {
102        self.header(FIELD_DETAILS)
103    }
104    pub fn server_traceback(&self) -> Option<&str> {
105        self.header(FIELD_SERVER_TRACEBACK)
106    }
107    pub fn position_start(&self) -> Option<usize> {
108        self.usize_header(FIELD_POSITION_START)
109    }
110    pub fn position_end(&self) -> Option<usize> {
111        self.usize_header(FIELD_POSITION_END)
112    }
113    pub fn line(&self) -> Option<usize> {
114        self.usize_header(FIELD_LINE)
115    }
116    pub fn column(&self) -> Option<usize> {
117        self.usize_header(FIELD_COLUMN)
118    }
119    pub(crate) fn unknown_headers(&self) -> impl Iterator<Item = (&u16, &bytes::Bytes)> {
120        self.headers().iter().filter(|(key, _)| {
121            **key != FIELD_HINT
122                && **key != FIELD_DETAILS
123                && **key != FIELD_POSITION_START
124                && **key != FIELD_POSITION_END
125                && **key != FIELD_LINE
126                && **key != FIELD_COLUMN
127        })
128    }
129    pub fn from_code(code: u32) -> Error {
130        Error(Box::new(Inner {
131            code,
132            messages: Vec::new(),
133            error: None,
134            headers: HashMap::new(),
135            fields: HashMap::new(),
136        }))
137    }
138    pub fn code(&self) -> u32 {
139        self.0.code
140    }
141    pub fn refine_kind<T: ErrorKind>(mut self) -> Error {
142        self.0.code = T::CODE;
143        self
144    }
145    pub fn set<T: Field>(mut self, value: impl Into<T::Value>) -> Error {
146        self.0
147            .fields
148            .insert((T::NAME, TypeId::of::<T::Value>()), Box::new(value.into()));
149        self
150    }
151    pub fn get<T: Field>(&self) -> Option<&T::Value> {
152        self.0
153            .fields
154            .get(&(T::NAME, TypeId::of::<T::Value>()))
155            .and_then(|bx| bx.downcast_ref::<T::Value>())
156    }
157}
158
159impl fmt::Display for Error {
160    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
161        let kind = self.kind_name();
162        if f.alternate() {
163            write!(f, "{}", kind)?;
164            for msg in self.0.messages.iter().rev() {
165                write!(f, ": {}", msg)?;
166            }
167            if let Some(mut src) = self.source() {
168                write!(f, ": {}", src)?;
169                while let Some(next) = src.source() {
170                    write!(f, ": {}", next)?;
171                    src = next;
172                }
173            }
174        } else if let Some(last) = self.0.messages.last() {
175            write!(f, "{}: {}", kind, last)?;
176        } else {
177            write!(f, "{}", kind)?;
178        }
179        if let Some((line, col)) = self.line().zip(self.column()) {
180            write!(f, " (on line {}, column {})", line, col)?;
181        }
182        if let Some(hint) = self.hint() {
183            write!(f, "\n  Hint: {}", hint)?;
184        }
185        if let Some(detail) = self.details() {
186            write!(f, "\n  Detail: {}", detail)?;
187        }
188        Ok(())
189    }
190}
191
192impl StdError for Error {
193    fn source(&self) -> Option<&(dyn StdError + 'static)> {
194        self.0.error.as_ref().map(|s| match s {
195            Source::Box(b) => b.as_ref() as &dyn std::error::Error,
196            Source::Ref(b) => (**b).as_ref() as &dyn std::error::Error,
197        })
198    }
199}
200
201impl fmt::Debug for Source {
202    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
203        match self {
204            Source::Box(b) => fmt::Debug::fmt(b.as_ref(), f),
205            Source::Ref(b) => fmt::Debug::fmt((**b).as_ref(), f),
206        }
207    }
208}
209
210impl<T> From<T> for Error
211where
212    T: AsRef<dyn StdError + Send + Sync + 'static> + Send + Sync + 'static,
213{
214    fn from(err: T) -> Error {
215        UserError::with_source_ref(err)
216    }
217}
218
219impl<'a> Iterator for Chain<'a> {
220    type Item = &'a (dyn StdError + 'static);
221    fn next(&mut self) -> Option<Self::Item> {
222        let result = self.0.take();
223        self.0 = result.and_then(|e| e.source());
224        result
225    }
226}