Skip to main content

graphrecords_query/error/
mod.rs

1pub mod aggregation;
2pub mod argument;
3pub mod arithmetic;
4pub mod comparison;
5pub mod conversion;
6#[cfg(feature = "dynamic")]
7pub mod dispatch;
8pub mod execution;
9pub mod grouping;
10pub mod groups;
11pub mod index;
12pub mod numeric;
13pub mod ordering;
14pub mod string;
15pub mod structure;
16
17use crate::{IndexDomain, OwnedIndex};
18use graphrecords_core::errors::GraphRecordError;
19use std::{
20    any::{Any, TypeId},
21    error::Error,
22    fmt::{self, Display, Formatter},
23    hash::{Hash, Hasher},
24    sync::Arc,
25};
26
27pub type QueryResult<T> = Result<T, Box<Failure>>;
28
29pub trait Diagnostic: Error + Send + Sync + 'static {
30    fn name() -> &'static str
31    where
32        Self: Sized;
33
34    fn help(&self) -> Option<String> {
35        None
36    }
37}
38
39#[derive(Clone, Copy, Debug)]
40pub struct FailureKind {
41    identifier: TypeId,
42    name: &'static str,
43}
44
45impl FailureKind {
46    #[must_use]
47    pub fn of<D: Diagnostic>() -> Self {
48        Self {
49            identifier: TypeId::of::<D>(),
50            name: D::name(),
51        }
52    }
53
54    #[must_use]
55    pub fn is<D: Diagnostic>(&self) -> bool {
56        self.identifier == TypeId::of::<D>()
57    }
58
59    #[must_use]
60    pub const fn name(&self) -> &'static str {
61        self.name
62    }
63}
64
65impl Display for FailureKind {
66    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
67        formatter.write_str(self.name)
68    }
69}
70
71impl PartialEq for FailureKind {
72    fn eq(&self, other: &Self) -> bool {
73        self.identifier == other.identifier
74    }
75}
76
77impl Eq for FailureKind {}
78
79impl Hash for FailureKind {
80    fn hash<H: Hasher>(&self, state: &mut H) {
81        self.identifier.hash(state);
82    }
83}
84
85pub trait ErrorGroup: 'static {
86    fn name() -> &'static str
87    where
88        Self: Sized;
89
90    fn contains(kind: &FailureKind) -> bool;
91}
92
93#[derive(Clone, Debug)]
94pub struct Failure {
95    operation: &'static str,
96    element: Option<Arc<dyn OwnedIndex>>,
97    kind: FailureKind,
98    cause: Arc<dyn Diagnostic>,
99}
100
101impl Failure {
102    pub fn new<D: Diagnostic>(operation: &'static str, cause: D) -> Box<Self> {
103        Box::new(Self {
104            operation,
105            element: None,
106            kind: FailureKind::of::<D>(),
107            cause: Arc::new(cause),
108        })
109    }
110
111    pub fn new_at<I: IndexDomain, D: Diagnostic>(
112        operation: &'static str,
113        cause: D,
114        index: &I::Index<'_>,
115    ) -> Box<Self> {
116        Box::new(Self {
117            operation,
118            element: Some(Arc::new(I::to_owned(index))),
119            kind: FailureKind::of::<D>(),
120            cause: Arc::new(cause),
121        })
122    }
123
124    #[must_use]
125    pub fn at<I: IndexDomain>(mut self: Box<Self>, index: &I::Index<'_>) -> Box<Self> {
126        self.element = Some(Arc::new(I::to_owned(index)));
127        self
128    }
129
130    #[must_use]
131    pub const fn operation(&self) -> &'static str {
132        self.operation
133    }
134
135    #[must_use]
136    pub fn element(&self) -> Option<&dyn OwnedIndex> {
137        self.element.as_deref()
138    }
139
140    #[must_use]
141    pub fn downcast_element<T: OwnedIndex>(&self) -> Option<&T> {
142        let element: &dyn Any = self.element.as_deref()?;
143
144        element.downcast_ref()
145    }
146
147    #[must_use]
148    pub fn cause(&self) -> &dyn Diagnostic {
149        self.cause.as_ref()
150    }
151
152    #[must_use]
153    pub const fn kind(&self) -> FailureKind {
154        self.kind
155    }
156
157    #[must_use]
158    pub fn is_kind<D: Diagnostic>(&self) -> bool {
159        self.kind.is::<D>()
160    }
161
162    #[must_use]
163    pub fn help(&self) -> Option<String> {
164        self.cause.help()
165    }
166
167    #[must_use]
168    pub fn downcast_cause<T: Error + 'static>(&self) -> Option<&T> {
169        let mut current: &(dyn Error + 'static) = self.cause.as_ref();
170
171        loop {
172            if let Some(cause) = current.downcast_ref() {
173                return Some(cause);
174            }
175
176            current = current.source()?;
177        }
178    }
179
180    #[must_use]
181    pub fn has_cause<T: Error + 'static>(&self) -> bool {
182        self.downcast_cause::<T>().is_some()
183    }
184}
185
186impl Display for Failure {
187    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
188        match self.element() {
189            Some(element) => write!(
190                formatter,
191                "operation `{}` failed at element `{element}`: {}",
192                self.operation, self.cause,
193            )?,
194            None => write!(
195                formatter,
196                "operation `{}` failed: {}",
197                self.operation, self.cause,
198            )?,
199        }
200
201        if let Some(help) = self.help() {
202            write!(formatter, "\nhelp: {help}")?;
203        }
204
205        Ok(())
206    }
207}
208
209impl Error for Failure {}
210
211#[derive(Debug)]
212pub struct External<E: Error + Send + Sync + 'static>(E);
213
214impl<E: Error + Send + Sync + 'static> External<E> {
215    #[must_use]
216    pub const fn new(error: E) -> Self {
217        Self(error)
218    }
219
220    #[must_use]
221    pub const fn error(&self) -> &E {
222        &self.0
223    }
224}
225
226impl<E: Error + Send + Sync + 'static> Display for External<E> {
227    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
228        Display::fmt(&self.0, formatter)
229    }
230}
231
232impl<E: Error + Send + Sync + 'static> Error for External<E> {
233    fn source(&self) -> Option<&(dyn Error + 'static)> {
234        Some(&self.0)
235    }
236}
237
238impl<E: Error + Send + Sync + 'static> Diagnostic for External<E> {
239    fn name() -> &'static str {
240        "External"
241    }
242}
243
244impl Diagnostic for GraphRecordError {
245    fn name() -> &'static str {
246        "GraphRecordError"
247    }
248
249    fn help(&self) -> Option<String> {
250        match self {
251            Self::IncompatibleValueOperands { .. } | Self::IncompatibleAttributeOperands { .. } => {
252                Some(
253                    "narrow the values down first using is_string(), is_int(), is_float(), is_bool(), is_datetime() or is_duration()"
254                        .to_string(),
255                )
256            }
257            Self::GroupNotFound { .. } => {
258                Some("add the group first or check `groups()` before querying".to_string())
259            }
260            _ => None,
261        }
262    }
263}