grafbase_sdk/extension/hooks.rs
1use crate::{
2 component::AnyExtension,
3 host_io::{event_queue::EventQueue, http::StatusCode},
4 types::{
5 AuthorizedOperationContext, Configuration, Error, ErrorResponse, GatewayHeaders, Headers, HttpRequestParts,
6 OnRequestOutput, RequestContext,
7 },
8};
9
10/// The Hooks extension allows you to hook into an incoming request or an outgoing response.
11///
12/// You have mutable access to the headers, and information about the request or response
13/// to decide whether to continue processing or not.
14///
15/// Keep in mind this is not meant for authentication purposes.
16///
17/// # Example
18///
19/// ```rust
20/// use grafbase_sdk::{
21/// HooksExtension,
22/// types::{GatewayHeaders, Headers, Configuration, Error, ErrorResponse, RequestContext},
23/// host_io::event_queue::EventQueue,
24/// };
25///
26/// #[derive(HooksExtension)]
27/// struct MyHooks {
28/// config: Config,
29/// }
30///
31/// #[derive(serde::Deserialize)]
32/// struct Config {
33/// // Define your configuration fields here. They are parsed from
34/// // the grafbase.toml configuration.
35/// something: String,
36/// }
37///
38/// impl HooksExtension for MyHooks {
39/// fn new(config: Configuration) -> Result<Self, Error> {
40/// let config = config.deserialize()?;
41/// Ok(Self { config })
42/// }
43///
44/// #[allow(refining_impl_trait)]
45/// fn on_request(&mut self, url: &str, method: http::Method, headers: &mut GatewayHeaders) -> Result<(), ErrorResponse> {
46/// // Implement your request hook logic here.
47/// Ok(())
48/// }
49///
50/// fn on_response(
51/// &mut self,
52/// ctx: &RequestContext,
53/// status: http::StatusCode,
54/// headers: &mut Headers,
55/// event_queue: EventQueue,
56/// ) -> Result<(), Error> {
57/// // Implement your response hook logic here.
58/// Ok(())
59/// }
60/// }
61/// ```
62#[allow(unused_variables)]
63pub trait HooksExtension: Sized + 'static {
64 /// Creates a new instance of the extension. The [`Configuration`] will contain all the
65 /// configuration defined in the `grafbase.toml` by the extension user in a serialized format.
66 ///
67 /// # Example
68 ///
69 /// The following TOML configuration:
70 /// ```toml
71 /// [extensions.my-hooks.config]
72 /// my_custom_key = "value"
73 /// ```
74 ///
75 /// can be easily deserialized with:
76 ///
77 /// ```rust
78 /// # use grafbase_sdk::types::{Configuration, Error};
79 /// # fn dummy(config: Configuration) -> Result<(), Error> {
80 /// #[derive(serde::Deserialize)]
81 /// struct Config {
82 /// my_custom_key: String
83 /// }
84 ///
85 /// let config: Config = config.deserialize()?;
86 /// # Ok(())
87 /// # }
88 /// ```
89 fn new(config: Configuration) -> Result<Self, Error>;
90
91 /// Called immediately when a request is received, before entering the GraphQL engine.
92 ///
93 /// This hook can be used to modify the request headers before they are processed by the GraphQL engine, and provides a way to audit the headers, URL, and method before processing the operation.
94 ///
95 /// It can also be used to define the contract key to use for the contracts extension if you
96 /// have any configured:
97 ///
98 /// ```rust
99 /// # use grafbase_sdk::{host_io::http::Method, types::{ErrorResponse, GatewayHeaders, OnRequestOutput}};
100 /// # struct MyContract;
101 /// # impl MyContract {
102 /// #[allow(refining_impl_trait)]
103 /// fn on_request(&mut self, url: &str, method: Method, headers: &mut GatewayHeaders) -> Result<OnRequestOutput, ErrorResponse> {
104 /// Ok(OnRequestOutput::new().contract_key("my-contract-key"))
105 /// }
106 /// # }
107 /// ```
108 fn on_request(
109 &mut self,
110 url: &str,
111 method: http::Method,
112 headers: &mut GatewayHeaders,
113 ) -> Result<impl IntoOnRequestOutput, ErrorResponse> {
114 Ok(())
115 }
116
117 /// Called right before the response is sent back to the client.
118 ///
119 /// This hook can be used to modify the response headers before the response is sent back to the client.
120 fn on_response(
121 &mut self,
122 ctx: &RequestContext,
123 status: http::StatusCode,
124 headers: &mut Headers,
125 event_queue: EventQueue,
126 ) -> Result<(), Error> {
127 Ok(())
128 }
129
130 /// Called when a GraphQL subgraph request is made, allowing you to modify the request parts before they are sent to the subgraph.
131 fn on_graphql_subgraph_request(
132 &mut self,
133 ctx: &AuthorizedOperationContext,
134 subgraph_name: &str,
135 parts: &mut HttpRequestParts,
136 ) -> Result<(), Error> {
137 Ok(())
138 }
139
140 /// Called when a virtual subgraph request is made through an extension, allowing you to modify the request headers before sending it to the extension.
141 fn on_virtual_subgraph_request(
142 &mut self,
143 ctx: &AuthorizedOperationContext,
144 subgraph_name: &str,
145 headers: &mut Headers,
146 ) -> Result<(), Error> {
147 Ok(())
148 }
149}
150
151pub trait IntoOnRequestOutput {
152 fn into_on_request_output(self) -> OnRequestOutput;
153}
154
155impl IntoOnRequestOutput for OnRequestOutput {
156 fn into_on_request_output(self) -> OnRequestOutput {
157 self
158 }
159}
160
161impl IntoOnRequestOutput for () {
162 fn into_on_request_output(self) -> OnRequestOutput {
163 OnRequestOutput::default()
164 }
165}
166
167#[doc(hidden)]
168pub fn register<T: HooksExtension>() {
169 pub(super) struct Proxy<T: HooksExtension>(T);
170
171 impl<T: HooksExtension> AnyExtension for Proxy<T> {
172 fn on_request(
173 &mut self,
174 url: &str,
175 method: http::Method,
176 headers: &mut Headers,
177 ) -> Result<OnRequestOutput, ErrorResponse> {
178 self.0
179 .on_request(url, method, headers)
180 .map(|output| output.into_on_request_output())
181 }
182
183 fn on_response(
184 &mut self,
185 ctx: &RequestContext,
186 status: StatusCode,
187 headers: &mut Headers,
188 event_queue: EventQueue,
189 ) -> Result<(), Error> {
190 self.0.on_response(ctx, status, headers, event_queue)
191 }
192
193 fn on_graphql_subgraph_request(
194 &mut self,
195 ctx: &AuthorizedOperationContext,
196 subgraph_name: &str,
197 parts: &mut HttpRequestParts,
198 ) -> Result<(), Error> {
199 self.0.on_graphql_subgraph_request(ctx, subgraph_name, parts)
200 }
201
202 fn on_virtual_subgraph_request(
203 &mut self,
204 ctx: &AuthorizedOperationContext,
205 subgraph_name: &str,
206 headers: &mut Headers,
207 ) -> Result<(), Error> {
208 self.0.on_virtual_subgraph_request(ctx, subgraph_name, headers)
209 }
210 }
211
212 crate::component::register_extension(Box::new(|_, config| {
213 <T as HooksExtension>::new(config).map(|extension| Box::new(Proxy(extension)) as Box<dyn AnyExtension>)
214 }))
215}