Skip to main content

forge_core/
context.rs

1//! Sealed trait family for Forge handler contexts.
2//!
3//! These traits let shared helper functions accept any context type without
4//! knowing the concrete type. Three levels, each building on the previous:
5//!
6//! - [`HandlerContext`]: database access available to every handler kind.
7//! - [`AuthenticatedContext`]: adds auth accessors for contexts that carry an
8//!   authenticated user (queries, mutations, MCP tools).
9//!
10//! All traits are sealed — they cannot be implemented outside forge-core.
11//! The proc macros emit the required impls automatically.
12//!
13//! # Example
14//!
15//! ```ignore
16//! use forge_core::context::HandlerContext;
17//!
18//! async fn count_rows<C: HandlerContext>(ctx: &C) -> forge_core::Result<i64> {
19//!     sqlx::query_scalar!("SELECT COUNT(*) FROM items")
20//!         .fetch_one(ctx.db())
21//!         .await
22//!         .map_err(forge_core::ForgeError::Database)
23//! }
24//! ```
25
26use uuid::Uuid;
27
28use crate::__sealed::Sealed;
29use crate::function::{DbConn, ForgeDb};
30
31/// Base trait for all Forge handler contexts.
32///
33/// Provides access to the database pool — the one capability shared by every
34/// handler kind (queries, mutations, jobs, crons, daemons, webhooks, workflows,
35/// MCP tools).
36///
37/// Sealed: only forge-core can implement this trait.
38pub trait HandlerContext: Sealed {
39    /// Database handle with automatic `db.query` tracing spans.
40    ///
41    /// Works directly with sqlx compile-time checked macros:
42    /// ```ignore
43    /// sqlx::query_as!(Item, "SELECT * FROM items")
44    ///     .fetch_all(ctx.db())
45    ///     .await?
46    /// ```
47    fn db(&self) -> ForgeDb;
48
49    /// Unified connection handle for shared helper functions.
50    ///
51    /// Prefer this over `db()` when writing helpers that need to work with
52    /// both pool-backed and transaction-backed contexts.
53    fn db_conn(&self) -> DbConn<'_>;
54}
55
56/// Trait for contexts that carry an authenticated user.
57///
58/// Implemented by [`QueryContext`], [`MutationContext`], and [`McpToolContext`].
59/// Extends [`HandlerContext`] with user identity and tenant accessors.
60///
61/// Sealed: only forge-core can implement this trait.
62///
63/// [`QueryContext`]: crate::function::QueryContext
64/// [`MutationContext`]: crate::function::MutationContext
65/// [`McpToolContext`]: crate::mcp::McpToolContext
66pub trait AuthenticatedContext: HandlerContext {
67    /// Returns the authenticated user's UUID, or `Unauthorized` if the request
68    /// is not authenticated or the subject is not a UUID.
69    fn user_id(&self) -> crate::error::Result<Uuid>;
70
71    /// Returns the tenant ID from the `tenant_id` JWT claim, if present.
72    fn tenant_id(&self) -> Option<Uuid>;
73}
74
75impl HandlerContext for crate::function::QueryContext {
76    fn db(&self) -> ForgeDb {
77        self.db()
78    }
79
80    fn db_conn(&self) -> DbConn<'_> {
81        self.db_conn()
82    }
83}
84
85impl HandlerContext for crate::function::MutationContext {
86    fn db(&self) -> ForgeDb {
87        // MutationContext::tx() returns DbConn, not ForgeDb.
88        // For HandlerContext we expose the pool-backed ForgeDb view, which
89        // intentionally bypasses the active transaction.
90        crate::function::ForgeDb::from_pool(self.bypass_pool())
91    }
92
93    fn db_conn(&self) -> DbConn<'_> {
94        self.db_conn()
95    }
96}
97
98impl HandlerContext for crate::job::JobContext {
99    fn db(&self) -> ForgeDb {
100        self.db()
101    }
102
103    fn db_conn(&self) -> DbConn<'_> {
104        self.db_conn()
105    }
106}
107
108impl HandlerContext for crate::cron::CronContext {
109    fn db(&self) -> ForgeDb {
110        self.db()
111    }
112
113    fn db_conn(&self) -> DbConn<'_> {
114        self.db_conn()
115    }
116}
117
118impl HandlerContext for crate::daemon::DaemonContext {
119    fn db(&self) -> ForgeDb {
120        self.db()
121    }
122
123    fn db_conn(&self) -> DbConn<'_> {
124        self.db_conn()
125    }
126}
127
128impl HandlerContext for crate::webhook::WebhookContext {
129    fn db(&self) -> ForgeDb {
130        self.db()
131    }
132
133    fn db_conn(&self) -> DbConn<'_> {
134        self.db_conn()
135    }
136}
137
138impl HandlerContext for crate::workflow::WorkflowContext {
139    fn db(&self) -> ForgeDb {
140        self.db()
141    }
142
143    fn db_conn(&self) -> DbConn<'_> {
144        self.db_conn()
145    }
146}
147
148impl HandlerContext for crate::mcp::McpToolContext {
149    fn db(&self) -> ForgeDb {
150        self.db()
151    }
152
153    fn db_conn(&self) -> DbConn<'_> {
154        self.db_conn()
155    }
156}
157
158impl AuthenticatedContext for crate::function::QueryContext {
159    fn user_id(&self) -> crate::error::Result<Uuid> {
160        self.user_id()
161    }
162
163    fn tenant_id(&self) -> Option<Uuid> {
164        self.tenant_id()
165    }
166}
167
168impl AuthenticatedContext for crate::function::MutationContext {
169    fn user_id(&self) -> crate::error::Result<Uuid> {
170        self.user_id()
171    }
172
173    fn tenant_id(&self) -> Option<Uuid> {
174        self.tenant_id()
175    }
176}
177
178impl AuthenticatedContext for crate::mcp::McpToolContext {
179    fn user_id(&self) -> crate::error::Result<Uuid> {
180        self.user_id()
181    }
182
183    fn tenant_id(&self) -> Option<Uuid> {
184        self.tenant_id()
185    }
186}
187
188impl Sealed for crate::function::QueryContext {}
189impl Sealed for crate::function::MutationContext {}
190impl Sealed for crate::job::JobContext {}
191impl Sealed for crate::cron::CronContext {}
192impl Sealed for crate::daemon::DaemonContext {}
193impl Sealed for crate::webhook::WebhookContext {}
194impl Sealed for crate::workflow::WorkflowContext {}
195impl Sealed for crate::mcp::McpToolContext {}