Skip to main content

query_lang/
error.rs

1//! The error a query resolution returns when it cannot complete.
2
3use core::fmt;
4
5/// The error returned from [`Database::get`](crate::Database::get) and
6/// [`System::compute`](crate::System::compute) when a query cannot be resolved.
7///
8/// The engine resolves a derived query by running its
9/// [`compute`](crate::System::compute), which in turn reads other queries. That
10/// recursion terminates only when the dependency graph is acyclic. If a query
11/// asks — directly or through a chain of other queries — for a result that is
12/// itself still being computed, there is no value to return and no way to make
13/// progress: the queries form a cycle. Rather than recurse without bound or
14/// panic, the engine unwinds the whole chain with [`QueryError::Cycle`].
15///
16/// The type is [`non_exhaustive`](https://doc.rust-lang.org/reference/attributes/type_system.html):
17/// new engine-level failure modes can be added later without breaking a
18/// `match` that has a wildcard arm.
19///
20/// # Examples
21///
22/// A query that reads itself cannot resolve:
23///
24/// ```
25/// use query_lang::{Database, System, QueryError};
26///
27/// struct SelfReferential;
28/// impl System for SelfReferential {
29///     type Key = u32;
30///     type Value = u32;
31///     fn compute(&self, db: &Database<Self>, key: &u32) -> Result<u32, QueryError> {
32///         // Asking for the very key being computed closes a cycle.
33///         db.get(key)
34///     }
35/// }
36///
37/// let db = Database::new(SelfReferential);
38/// assert_eq!(db.get(&1), Err(QueryError::Cycle));
39/// ```
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41#[non_exhaustive]
42pub enum QueryError {
43    /// A query depended on itself, directly or through a chain of other queries.
44    ///
45    /// The dependency graph must be acyclic. When it is not, the query that
46    /// closed the cycle — and every query waiting on it — resolves to this
47    /// error. The fix is in the query definitions: break the cyclic dependency,
48    /// usually by splitting the offending query so the two halves no longer wait
49    /// on each other.
50    Cycle,
51
52    /// Resolving a query needed more nested levels than the database allows.
53    ///
54    /// Returned only when a limit was set with
55    /// [`Database::with_max_depth`](crate::Database::with_max_depth). Each query
56    /// that is computing, or revalidating what it read, takes one level. The
57    /// usual cause is a long chain of queries that each read the next one.
58    TooDeep,
59}
60
61impl fmt::Display for QueryError {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        match self {
64            QueryError::Cycle => f.write_str("query cycle detected: a query depends on itself"),
65            QueryError::TooDeep => f.write_str("query resolution exceeded the depth limit"),
66        }
67    }
68}
69
70impl core::error::Error for QueryError {}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use alloc::string::ToString;
76
77    #[test]
78    fn test_cycle_display_is_descriptive() {
79        assert_eq!(
80            QueryError::Cycle.to_string(),
81            "query cycle detected: a query depends on itself"
82        );
83    }
84
85    #[test]
86    fn test_error_trait_is_implemented() {
87        fn assert_error<E: core::error::Error>(_: &E) {}
88        assert_error(&QueryError::Cycle);
89    }
90
91    #[test]
92    fn test_too_deep_display_is_descriptive() {
93        assert_eq!(
94            QueryError::TooDeep.to_string(),
95            "query resolution exceeded the depth limit"
96        );
97    }
98
99    #[test]
100    fn test_equality() {
101        assert_eq!(QueryError::Cycle, QueryError::Cycle);
102    }
103}