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}