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