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 {}