prax_query/middleware/
types.rs

1//! Core middleware types and traits.
2
3use crate::QueryError;
4use std::future::Future;
5use std::pin::Pin;
6use std::sync::Arc;
7
8use super::context::QueryContext;
9
10/// Result type for middleware operations.
11pub type MiddlewareResult<T> = Result<T, QueryError>;
12
13/// A boxed future for async middleware operations.
14pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
15
16/// The next handler in the middleware chain.
17///
18/// Call this to continue processing to the next middleware or the actual query.
19pub struct Next<'a> {
20    pub(crate) inner: Box<dyn FnOnce(QueryContext) -> BoxFuture<'a, MiddlewareResult<QueryResponse>> + Send + 'a>,
21}
22
23impl<'a> Next<'a> {
24    /// Execute the next handler in the chain.
25    pub fn run(self, ctx: QueryContext) -> BoxFuture<'a, MiddlewareResult<QueryResponse>> {
26        (self.inner)(ctx)
27    }
28}
29
30/// Response from a query execution.
31#[derive(Debug, Clone)]
32pub struct QueryResponse {
33    /// The raw response data (typically JSON).
34    pub data: serde_json::Value,
35    /// Number of rows affected (for mutations).
36    pub rows_affected: Option<u64>,
37    /// Execution time in microseconds.
38    pub execution_time_us: u64,
39    /// Whether the query was served from cache.
40    pub from_cache: bool,
41    /// Additional metadata.
42    pub metadata: serde_json::Map<String, serde_json::Value>,
43}
44
45impl QueryResponse {
46    /// Create a new query response with data.
47    pub fn new(data: serde_json::Value) -> Self {
48        Self {
49            data,
50            rows_affected: None,
51            execution_time_us: 0,
52            from_cache: false,
53            metadata: serde_json::Map::new(),
54        }
55    }
56
57    /// Create an empty response.
58    pub fn empty() -> Self {
59        Self::new(serde_json::Value::Null)
60    }
61
62    /// Create a response with affected rows count.
63    pub fn with_affected(count: u64) -> Self {
64        Self {
65            data: serde_json::Value::Null,
66            rows_affected: Some(count),
67            execution_time_us: 0,
68            from_cache: false,
69            metadata: serde_json::Map::new(),
70        }
71    }
72
73    /// Set execution time.
74    pub fn with_execution_time(mut self, us: u64) -> Self {
75        self.execution_time_us = us;
76        self
77    }
78
79    /// Mark as from cache.
80    pub fn from_cache(mut self) -> Self {
81        self.from_cache = true;
82        self
83    }
84
85    /// Add metadata.
86    pub fn with_metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
87        self.metadata.insert(key.into(), value);
88        self
89    }
90}
91
92/// Middleware trait for intercepting queries.
93///
94/// Implement this trait to create custom middleware that can:
95/// - Modify queries before execution
96/// - Modify responses after execution
97/// - Short-circuit execution (e.g., for caching)
98/// - Add logging, metrics, or other side effects
99///
100/// # Example
101///
102/// ```rust,ignore
103/// use prax_query::middleware::{Middleware, QueryContext, QueryResponse, Next, MiddlewareResult};
104///
105/// struct MyMiddleware;
106///
107/// impl Middleware for MyMiddleware {
108///     fn handle<'a>(
109///         &'a self,
110///         ctx: QueryContext,
111///         next: Next<'a>,
112///     ) -> BoxFuture<'a, MiddlewareResult<QueryResponse>> {
113///         Box::pin(async move {
114///             // Before query
115///             println!("Executing: {}", ctx.sql());
116///
117///             // Call next middleware or execute query
118///             let response = next.run(ctx).await?;
119///
120///             // After query
121///             println!("Completed in {}us", response.execution_time_us);
122///
123///             Ok(response)
124///         })
125///     }
126/// }
127/// ```
128pub trait Middleware: Send + Sync {
129    /// Handle a query, optionally calling the next handler.
130    fn handle<'a>(
131        &'a self,
132        ctx: QueryContext,
133        next: Next<'a>,
134    ) -> BoxFuture<'a, MiddlewareResult<QueryResponse>>;
135
136    /// Name of this middleware (for debugging/logging).
137    fn name(&self) -> &'static str {
138        std::any::type_name::<Self>()
139    }
140
141    /// Whether this middleware is enabled.
142    fn enabled(&self) -> bool {
143        true
144    }
145}
146
147/// A middleware that can be shared across threads.
148pub type SharedMiddleware = Arc<dyn Middleware>;
149
150/// Convenience trait for boxing middleware.
151pub trait IntoSharedMiddleware {
152    /// Convert into a shared middleware.
153    fn into_shared(self) -> SharedMiddleware;
154}
155
156impl<T: Middleware + 'static> IntoSharedMiddleware for T {
157    fn into_shared(self) -> SharedMiddleware {
158        Arc::new(self)
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    #[test]
167    fn test_query_response_builder() {
168        let response = QueryResponse::new(serde_json::json!({"id": 1}))
169            .with_execution_time(1000)
170            .with_metadata("cache_hit", serde_json::Value::Bool(false));
171
172        assert_eq!(response.execution_time_us, 1000);
173        assert!(!response.from_cache);
174        assert!(response.metadata.contains_key("cache_hit"));
175    }
176
177    #[test]
178    fn test_query_response_affected() {
179        let response = QueryResponse::with_affected(5);
180        assert_eq!(response.rows_affected, Some(5));
181    }
182
183    #[test]
184    fn test_query_response_from_cache() {
185        let response = QueryResponse::empty().from_cache();
186        assert!(response.from_cache);
187    }
188}
189