grafbase_hooks/hooks.rs
1mod authorization;
2
3use crate::Component;
4pub use crate::wit::{
5 Context, EdgeDefinition, Error, ErrorResponse, ExecutedHttpRequest, ExecutedOperation, ExecutedSubgraphRequest,
6 Guest, Headers, NodeDefinition, SharedContext, SubgraphRequest,
7};
8pub use authorization::{
9 EdgeNodePostExecutionArguments, EdgePostExecutionArguments, EdgePreExecutionArguments, NodePreExecutionArguments,
10 ParentEdgePostExecutionArguments,
11};
12
13pub(super) static mut HOOKS: Option<Box<dyn Hooks>> = None;
14
15#[doc(hidden)]
16pub fn hooks() -> &'static mut dyn Hooks {
17 // SAFETY: a hook instance is single-threaded. This is created only once during initialization.
18 // Every hook call happens in the same thread, there can ever be one caller at a time. Therefore
19 // this is safe.
20 #[allow(static_mut_refs)]
21 unsafe {
22 HOOKS.as_deref_mut().unwrap()
23 }
24}
25
26impl Guest for Component {
27 fn on_gateway_request(context: Context, url: String, headers: Headers) -> Result<(), ErrorResponse> {
28 hooks().on_gateway_request(context, url, headers)
29 }
30
31 fn on_subgraph_request(
32 context: SharedContext,
33 subgraph_name: String,
34 request: SubgraphRequest,
35 ) -> Result<(), Error> {
36 hooks().on_subgraph_request(context, subgraph_name, request)
37 }
38
39 fn authorize_edge_pre_execution(
40 context: SharedContext,
41 definition: EdgeDefinition,
42 arguments: String,
43 metadata: String,
44 ) -> Result<(), Error> {
45 let arguments = EdgePreExecutionArguments::new(definition, arguments, metadata);
46 hooks().authorize_edge_pre_execution(context, arguments)
47 }
48
49 fn authorize_node_pre_execution(
50 context: SharedContext,
51 definition: NodeDefinition,
52 metadata: String,
53 ) -> Result<(), Error> {
54 let arguments = NodePreExecutionArguments::new(definition, metadata);
55 hooks().authorize_node_pre_execution(context, arguments)
56 }
57
58 fn authorize_parent_edge_post_execution(
59 context: SharedContext,
60 definition: EdgeDefinition,
61 parents: Vec<String>,
62 metadata: String,
63 ) -> Vec<Result<(), Error>> {
64 let arguments = ParentEdgePostExecutionArguments::new(definition, parents, metadata);
65 hooks().authorize_parent_edge_post_execution(context, arguments)
66 }
67
68 fn authorize_edge_node_post_execution(
69 context: SharedContext,
70 definition: EdgeDefinition,
71 nodes: Vec<String>,
72 metadata: String,
73 ) -> Vec<Result<(), Error>> {
74 let arguments = EdgeNodePostExecutionArguments::new(definition, nodes, metadata);
75 hooks().authorize_edge_node_post_execution(context, arguments)
76 }
77
78 fn authorize_edge_post_execution(
79 context: SharedContext,
80 definition: EdgeDefinition,
81 edges: Vec<(String, Vec<String>)>,
82 metadata: String,
83 ) -> Vec<Result<(), Error>> {
84 let arguments = EdgePostExecutionArguments::new(definition, edges, metadata);
85 hooks().authorize_edge_post_execution(context, arguments)
86 }
87
88 fn on_subgraph_response(context: SharedContext, request: ExecutedSubgraphRequest) -> Vec<u8> {
89 hooks().on_subgraph_response(context, request)
90 }
91
92 fn on_operation_response(context: SharedContext, request: ExecutedOperation) -> Vec<u8> {
93 hooks().on_operation_response(context, request)
94 }
95
96 fn on_http_response(context: SharedContext, request: ExecutedHttpRequest) {
97 hooks().on_http_response(context, request)
98 }
99}
100
101#[doc(hidden)]
102#[diagnostic::on_unimplemented(
103 message = "Missing grafbase_hooks macro on the Hooks implementation",
104 label = "For this type",
105 note = "Add #[grafbase_hooks] to the Hooks trait implementation for {Self}"
106)]
107pub trait HookImpls {
108 fn hook_implementations(&self) -> u32;
109}
110
111#[doc(hidden)]
112#[diagnostic::on_unimplemented(
113 message = "Missing register_hooks! macro invocation for the hooks implementation",
114 label = "On this trait implementation",
115 note = "Call register_hooks!({Self}) at the end of the file where the hooks implementation is defined"
116)]
117pub trait HookExports {}
118
119/// Hooks are the main extension point for Grafbase. They allow you to intercept execution in various points of the request lifecycle.
120///
121/// To add a hook, you need to overload the default implementations of the hook functions in this trait and add the `#[grafbase_hooks]` attribute to the implementation.
122#[allow(unused_variables)]
123pub trait Hooks: HookImpls + HookExports {
124 /// Initializes the hook. This is called once when a new hook instance is created.
125 fn new() -> Self
126 where
127 Self: Sized;
128
129 /// The gateway calls this hook just before authentication. You can use it
130 /// to read and modify the request headers. You can store values in the context
131 /// object for subsequent hooks to read.
132 ///
133 /// When the hook returns an error, processing stops and returns the error to the client.
134 fn on_gateway_request(&mut self, context: Context, url: String, headers: Headers) -> Result<(), ErrorResponse> {
135 todo!()
136 }
137
138 /// This hook runs before every subgraph request and after rate limiting. Use this hook to
139 /// read and modify subgraph request headers. A returned error prevents the subgraph request.
140 fn on_subgraph_request(
141 &mut self,
142 context: SharedContext,
143 subgraph_name: String,
144 request: SubgraphRequest,
145 ) -> Result<(), Error> {
146 todo!()
147 }
148
149 /// The request cycle calls this hook when the schema defines an authorization directive on
150 /// an edge. The hook receives the edge's directive arguments, edge definition,
151 /// and directive metadata.
152 ///
153 /// This hook runs before fetching any data.
154 ///
155 /// An example GraphQL schema which will trigger this hook:
156 ///
157 /// ```graphql
158 /// type Query {
159 /// user(id: ID!): User @authorized(arguments: "id")
160 /// }
161 /// ```
162 ///
163 /// If an authorized directive is defined with the `arguments` argument,
164 /// you must implement this hook.
165 ///
166 /// Every call to the `user` field will trigger this hook.
167 ///
168 /// An error result stops request execution and returns the error to the user.
169 /// The edge result becomes null for error responses.
170 fn authorize_edge_pre_execution(
171 &mut self,
172 context: SharedContext,
173 arguments: EdgePreExecutionArguments,
174 ) -> Result<(), Error> {
175 todo!()
176 }
177
178 /// The gateway calls this hook during the request cycle when the schema defines an authorization directive for
179 /// a node. The hook receives the node definition and directive metadata.
180 ///
181 /// This hook runs before any data fetching.
182 ///
183 /// The hook is called when an edge is about to be executed and the node
184 /// has an `@authorized` directive defined:
185 ///
186 /// ```graphql
187 /// type User @authorized {
188 /// id: Int!
189 /// name: String!
190 /// }
191 /// ```
192 ///
193 /// If an authorized directive is defined to a node, you must implement this hook.
194 ///
195 /// An error result stops request execution and returns the error to the user.
196 /// The edge value will be null for error responses.
197 fn authorize_node_pre_execution(
198 &mut self,
199 context: SharedContext,
200 arguments: NodePreExecutionArguments,
201 ) -> Result<(), Error> {
202 todo!()
203 }
204
205 /// The request cycle runs this hook when the schema defines an authorization directive on
206 /// an edge with the fields argument. The fields argument provides fields from the parent node.
207 /// The hook receives parent type information and a list of data with the defined fields of
208 /// the parent for every child that the parent query loads.
209 ///
210 /// The hook is called when edge data is fetched, before returning the data to the
211 /// client and the `@authorized` directive is defined with the `fields` argument defined:
212 ///
213 /// ```graphql
214 /// type User {
215 /// id: Int!
216 /// name: String! @authorized(fields: "id")
217 /// }
218 ///
219 /// type Query {
220 /// users: [User!]!
221 /// }
222 /// ```
223 ///
224 /// If an authorized directive is defined with the `fields` argument, you must
225 /// implement this hook.
226 ///
227 /// The hook returns one of the following:
228 ///
229 /// - A single-item list that defines the result for every child loaded from the edge
230 /// - A multi-item list where each item defines child visibility
231 ///
232 /// Any other response causes the authorization hook to fail and prevents returning data to
233 /// the user.
234 ///
235 /// A list item can be:
236 ///
237 /// - An empty Ok that returns edge data to the client
238 /// - An error that denies edge access and propagates error data to response errors
239 fn authorize_parent_edge_post_execution(
240 &mut self,
241 context: SharedContext,
242 arguments: ParentEdgePostExecutionArguments,
243 ) -> Vec<Result<(), Error>> {
244 todo!()
245 }
246
247 /// The request cycle runs this hook when the schema defines an authorization directive on
248 /// an edge with the node argument, providing fields from the child node. This hook receives parent type information
249 /// and a list of data with defined fields for every child the parent query loads.
250 ///
251 /// The hook is called when edge data is fetched, before returning the data to the
252 /// client and the `@authorized` directive is defined with the `node` argument defined:
253 ///
254 /// ```graphql
255 /// type User {
256 /// id: Int!
257 /// name: String!
258 /// }
259 ///
260 /// type Query {
261 /// users: [User!]! @authorized(node: "id")
262 /// }
263 /// ```
264 ///
265 /// If an authorized directive is defined with the `node` argument, you must
266 /// implement this hook.
267 ///
268 /// The result must be one of:
269 ///
270 /// - A single-item list that defines the result for every child loaded from the edge
271 /// - A multi-item list where each item defines child visibility
272 ///
273 /// Any other response causes the authorization hook to fail and prevents returning data to
274 /// the user.
275 ///
276 /// A list item can be:
277 ///
278 /// - An empty Ok that returns edge data to the client
279 /// - An error that denies edge access and propagates error data to response errors
280 fn authorize_edge_node_post_execution(
281 &mut self,
282 context: SharedContext,
283 arguments: EdgeNodePostExecutionArguments,
284 ) -> Vec<Result<(), Error>> {
285 todo!()
286 }
287
288 /// The request cycle calls this hook when the schema defines an authorization directive on
289 /// an edge with node and fields arguments, and provides fields from the child node. The hook receives
290 /// parent type information and a list of data containing tuples of parent data and child data lists.
291 ///
292 /// The directive's fields argument defines the first part of the tuple and the node
293 /// argument defines the second part.
294 ///
295 /// The hook is called when edge data is fetched, before returning the data to the
296 /// client and the `@authorized` directive is defined with the `fields` and `node`
297 /// arguments defined:
298 ///
299 /// ```graphql
300 /// type Address {
301 /// street: String!
302 /// }
303 ///
304 /// type User {
305 /// id: Int!
306 /// addresses: [Address!]! @authorized(fields: "id", node: "street")
307 /// }
308 ///
309 /// type Query {
310 /// users: [User!]!
311 /// }
312 /// ```
313 ///
314 /// If an authorized directive is defined with the `fields` and `node` arguments,
315 /// you must implement this hook.
316 ///
317 /// The hook must return one of:
318 ///
319 /// - A single-item list that defines the result for every child loaded from the edge
320 /// - A multi-item list where each item defines child visibility
321 ///
322 /// Any other response causes the authorization hook to fail and prevents returning data to
323 /// the user.
324 ///
325 /// A list item can be:
326 ///
327 /// - An empty Ok that returns edge data to the client
328 /// - An error that denies edge access and propagates error data to response errors
329 fn authorize_edge_post_execution(
330 &mut self,
331 context: SharedContext,
332 arguments: EdgePostExecutionArguments,
333 ) -> Vec<Result<(), Error>> {
334 todo!()
335 }
336
337 /// This hook runs after the gateway requests a subgraph entity.
338 /// It returns a byte vector that you can access in the `on_operation_response` hook.
339 fn on_subgraph_response(&mut self, context: SharedContext, request: ExecutedSubgraphRequest) -> Vec<u8> {
340 todo!()
341 }
342
343 /// The gateway calls this hook after it handles a request. The hook returns a list of bytes that
344 /// the `on_http_response` hook can access.
345 fn on_operation_response(&mut self, context: SharedContext, operation: ExecutedOperation) -> Vec<u8> {
346 todo!()
347 }
348
349 /// The hook is called right before a response is sent to the user.
350 fn on_http_response(&mut self, context: SharedContext, response: ExecutedHttpRequest) {
351 todo!()
352 }
353}