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