query_flow/
query.rs

1//! Query trait definition.
2
3use crate::key::Key;
4use crate::runtime::QueryContext;
5use crate::QueryError;
6
7/// A query that can be executed and cached.
8///
9/// Queries are the fundamental unit of computation in query-flow. Each query:
10/// - Has a cache key that uniquely identifies the computation
11/// - Produces an output value
12/// - Can depend on other queries via `QueryContext::query()`
13///
14/// # Sync by Design
15///
16/// The `query` method is intentionally synchronous. This avoids the "function
17/// coloring" problem where async infects the entire call stack. For async
18/// operations, use the suspense pattern with `AssetLoadingState`.
19///
20/// # Error Handling
21///
22/// The `query` method returns `Result<Output, QueryError>` where:
23/// - `QueryError` represents system errors (Suspend, Cycle, Cancelled)
24/// - User domain errors should be wrapped in `Output`, e.g., `type Output = Result<T, MyError>`
25///
26/// This means fallible queries return `Ok(Ok(value))` on success and `Ok(Err(error))` on user error.
27///
28/// # Example
29///
30/// ```ignore
31/// use query_flow::{Query, QueryContext, QueryError, Key};
32///
33/// // Simple infallible query
34/// struct Add { a: i32, b: i32 }
35///
36/// impl Query for Add {
37///     type CacheKey = (i32, i32);
38///     type Output = i32;
39///
40///     fn cache_key(&self) -> Self::CacheKey {
41///         (self.a, self.b)
42///     }
43///
44///     fn query(&self, _ctx: &mut QueryContext) -> Result<Self::Output, QueryError> {
45///         Ok(self.a + self.b)
46///     }
47/// }
48///
49/// // Fallible query with user errors
50/// struct ParseInt { input: String }
51///
52/// impl Query for ParseInt {
53///     type CacheKey = String;
54///     type Output = Result<i32, std::num::ParseIntError>;
55///
56///     fn cache_key(&self) -> Self::CacheKey {
57///         self.input.clone()
58///     }
59///
60///     fn query(&self, _ctx: &mut QueryContext) -> Result<Self::Output, QueryError> {
61///         Ok(self.input.parse())  // Ok(Ok(n)) or Ok(Err(parse_error))
62///     }
63/// }
64/// ```
65pub trait Query: Clone + Send + Sync + 'static {
66    /// The cache key type for this query.
67    ///
68    /// Two queries with the same cache key are considered equivalent and
69    /// will share cached results.
70    type CacheKey: Key;
71
72    /// The output type of this query.
73    ///
74    /// For fallible queries, use `Result<T, E>` here.
75    type Output: Send + Sync + 'static;
76
77    /// Get the cache key for this query instance.
78    fn cache_key(&self) -> Self::CacheKey;
79
80    /// Execute the query, returning the output or a system error.
81    ///
82    /// # Arguments
83    ///
84    /// * `ctx` - The query context for accessing dependencies
85    ///
86    /// # Returns
87    ///
88    /// * `Ok(output)` - Query completed successfully
89    /// * `Err(QueryError::Suspend)` - Query is waiting for async loading
90    /// * `Err(QueryError::Cycle)` - Dependency cycle detected
91    fn query(&self, ctx: &mut QueryContext) -> Result<Self::Output, QueryError>;
92
93    /// Durability hint for this query.
94    ///
95    /// Higher values indicate the query's output changes less frequently.
96    /// This is used for optimization in the dependency tracking layer.
97    ///
98    /// - 0: Volatile (changes frequently)
99    /// - Higher: More stable
100    ///
101    /// Default: `0` (volatile)
102    fn durability(&self) -> u8 {
103        0
104    }
105
106    /// Compare two outputs for equality (for early cutoff optimization).
107    ///
108    /// When a query is recomputed and the output is equal to the previous
109    /// output, downstream queries can skip recomputation (early cutoff).
110    ///
111    /// The `#[query]` macro generates this using `PartialEq` by default.
112    /// Use `output_eq = custom_fn` for types without `PartialEq`.
113    fn output_eq(old: &Self::Output, new: &Self::Output) -> bool;
114}