Skip to main content

hive_router_plan_executor/plugins/
plugin_trait.rs

1use crate::{
2    hooks::{
3        on_execute::{OnExecuteStartHookPayload, OnExecuteStartHookResult},
4        on_graphql_error::{OnGraphQLErrorHookPayload, OnGraphQLErrorHookResult},
5        on_graphql_params::{OnGraphQLParamsStartHookPayload, OnGraphQLParamsStartHookResult},
6        on_graphql_parse::{OnGraphQLParseHookResult, OnGraphQLParseStartHookPayload},
7        on_graphql_validation::{
8            OnGraphQLValidationStartHookPayload, OnGraphQLValidationStartHookResult,
9        },
10        on_http_request::{OnHttpRequestHookPayload, OnHttpRequestHookResult},
11        on_plugin_init::{OnPluginInitPayload, OnPluginInitResult},
12        on_query_plan::{OnQueryPlanStartHookPayload, OnQueryPlanStartHookResult},
13        on_subgraph_execute::{
14            OnSubgraphExecuteStartHookPayload, OnSubgraphExecuteStartHookResult,
15        },
16        on_subgraph_http_request::{
17            OnSubgraphHttpRequestHookPayload, OnSubgraphHttpRequestHookResult,
18        },
19        on_supergraph_load::{OnSupergraphLoadStartHookPayload, OnSupergraphLoadStartHookResult},
20    },
21    response::graphql_error::GraphQLError,
22};
23use serde::de::DeserializeOwned;
24use sonic_rs::json;
25
26pub struct StartHookResult<'exec, TStartPayload, TEndPayload, TResponse> {
27    pub payload: TStartPayload,
28    pub control_flow: StartControlFlow<'exec, TEndPayload, TResponse>,
29}
30
31pub enum StartControlFlow<'exec, TEndPayload, TResponse> {
32    Proceed,
33    EndWithResponse(TResponse),
34    OnEnd(Box<dyn FnOnce(TEndPayload) -> EndHookResult<TEndPayload, TResponse> + Send + 'exec>),
35}
36
37// Override using methods (Like builder pattern)
38// Async Drop
39// Re-export Plugin related types from router crate (graphql_tools validation stuff, plugin stuff from internal crate)
40// Move Plugin stuff from executor to internal
41
42pub trait StartHookPayload<TEndPayload: EndHookPayload<TResponse>, TResponse>
43where
44    Self: Sized,
45    TResponse: FromGraphQLErrorToResponse,
46{
47    /// Continue with the regular flow of the hook
48    /// This is called in most cases when you don't short-circuit the hook with a response or an error.
49    ///
50    /// Example:
51    /// ```
52    /// async fn on_graphql_params<'exec>(
53    ///     &'exec self,
54    ///    payload: OnGraphQLParamsStartHookPayload<'exec>,
55    /// ) -> OnGraphQLParamsStartHookResult<'exec> {
56    ///    // manipulate payload if needed...
57    ///    payload.proceed()
58    /// }
59    /// ```
60    fn proceed<'exec>(self) -> StartHookResult<'exec, Self, TEndPayload, TResponse> {
61        StartHookResult {
62            payload: self,
63            control_flow: StartControlFlow::Proceed,
64        }
65    }
66
67    /// End the hook execution and return a response to the client immediately, skipping the rest of the execution flow.
68    fn end_with_response<'exec>(
69        self,
70        output: TResponse,
71    ) -> StartHookResult<'exec, Self, TEndPayload, TResponse> {
72        StartHookResult {
73            payload: self,
74            control_flow: StartControlFlow::EndWithResponse(output),
75        }
76    }
77
78    /// End the hook execution with a GraphQL error,
79    /// returning a response with the appropriate error format to the client immediately, skipping the rest of the execution flow.
80    ///
81    /// Example:
82    /// ```
83    /// fn on_http_request<'req>(
84    ///     &'req self,
85    ///     payload: OnHttpRequestHookPayload<'req>,
86    /// ) -> OnHttpRequestHookResult<'req> {
87    ///     if payload.router_http_request.headers().get("authorization").is_none() {
88    ///         return payload.end_with_graphql_error(
89    ///             GraphQLError::from_message_and_code("Unauthorized", "UNAUTHORIZED"),
90    ///             StatusCode::UNAUTHORIZED,
91    ///         );
92    ///     }
93    ///  
94    ///     payload.proceed()
95    /// }
96    /// ```
97    fn end_with_graphql_error<'exec>(
98        self,
99        error: GraphQLError,
100        status_code: http::StatusCode,
101    ) -> StartHookResult<'exec, Self, TEndPayload, TResponse>
102    where
103        TResponse: FromGraphQLErrorToResponse,
104    {
105        self.end_with_response(TResponse::from_graphql_error_to_response(
106            error,
107            status_code,
108        ))
109    }
110
111    /// Attach a callback to be executed at the end of the hook, allowing you to manipulate the end payload or response.
112    /// This is useful when you want to execute some logic after the main execution of the hook
113    ///
114    /// Example:
115    /// ```
116    /// fn on_http_request<'req>(
117    ///     &'req self,
118    ///     payload: OnHttpRequestHookPayload<'req>,
119    /// ) -> OnHttpRequestHookResult<'req> {
120    ///     payload.on_end(|payload| {
121    ///         payload.map_response(|mut response| {
122    ///             response.response_mut().headers_mut().insert(
123    ///                 "x-served-by",
124    ///                 "hive-router".parse().unwrap(),
125    ///             );
126    ///             response
127    ///         }).proceed()
128    ///     })
129    /// }
130    /// ```
131    fn on_end<'exec, F>(self, f: F) -> StartHookResult<'exec, Self, TEndPayload, TResponse>
132    where
133        F: FnOnce(TEndPayload) -> EndHookResult<TEndPayload, TResponse> + Send + 'exec,
134    {
135        StartHookResult {
136            payload: self,
137            control_flow: StartControlFlow::OnEnd(Box::new(f)),
138        }
139    }
140}
141
142pub struct EndHookResult<TEndPayload, TResponse> {
143    pub payload: TEndPayload,
144    pub control_flow: EndControlFlow<TResponse>,
145}
146
147pub enum EndControlFlow<TResponse> {
148    Proceed,
149    EndWithResponse(TResponse),
150}
151
152pub trait EndHookPayload<TResponse>
153where
154    Self: Sized,
155    TResponse: FromGraphQLErrorToResponse,
156{
157    /// Continue with the regular flow of the hook
158    /// This is called in most cases when you don't short-circuit the hook with a response or an error.
159    fn proceed(self) -> EndHookResult<Self, TResponse> {
160        EndHookResult {
161            payload: self,
162            control_flow: EndControlFlow::Proceed,
163        }
164    }
165
166    /// End the hook execution and return a response to the client immediately, skipping the rest of the execution flow.
167    fn end_with_response(self, output: TResponse) -> EndHookResult<Self, TResponse> {
168        EndHookResult {
169            payload: self,
170            control_flow: EndControlFlow::EndWithResponse(output),
171        }
172    }
173
174    /// End the hook execution with a GraphQL error,
175    /// returning a response with the appropriate error format to the client immediately, skipping the rest of the execution flow.
176    ///
177    /// Example:
178    /// ```
179    /// use hive_router::{
180    ///     plugins::hooks::on_http_request::{OnHttpRequestHookPayload, OnHttpRequestHookResult},
181    /// };
182    ///
183    /// fn on_http_request<'req>(
184    ///     &'req self,
185    ///     payload: OnHttpRequestHookPayload<'req>,
186    /// ) -> OnHttpRequestHookResult<'req> {
187    ///     if payload.router_http_request.headers().get("authorization").is_none() {
188    ///         return payload.end_with_graphql_error(
189    ///             GraphQLError::from_message_and_code("Unauthorized", "UNAUTHORIZED"),
190    ///             StatusCode::UNAUTHORIZED,
191    ///         );
192    ///     }
193    ///  
194    ///     payload.proceed()
195    /// }
196    /// ```
197    fn end_with_graphql_error(
198        self,
199        error: GraphQLError,
200        status_code: http::StatusCode,
201    ) -> EndHookResult<Self, TResponse> {
202        self.end_with_response(TResponse::from_graphql_error_to_response(
203            error,
204            status_code,
205        ))
206    }
207}
208
209pub trait FromGraphQLErrorToResponse {
210    fn from_graphql_error_to_response(error: GraphQLError, status_code: http::StatusCode) -> Self;
211}
212
213pub fn from_graphql_error_to_bytes(error: GraphQLError) -> Vec<u8> {
214    let body = json!({
215        "errors": [error]
216    });
217    sonic_rs::to_vec(&body).unwrap_or_default()
218}
219
220impl FromGraphQLErrorToResponse for ntex::http::Response {
221    fn from_graphql_error_to_response(error: GraphQLError, status_code: http::StatusCode) -> Self {
222        let body = from_graphql_error_to_bytes(error);
223        ntex::http::Response::build(ntex::http::StatusCode::OK)
224            .content_type("application/json")
225            .status(status_code)
226            .body(body)
227    }
228}
229
230#[async_trait::async_trait]
231pub trait RouterPlugin: Send + Sync + 'static {
232    fn plugin_name() -> &'static str;
233
234    type Config: DeserializeOwned + Sync;
235
236    fn on_plugin_init(payload: OnPluginInitPayload<Self>) -> OnPluginInitResult<Self>
237    where
238        Self: Sized;
239
240    #[inline]
241    fn on_http_request<'req>(
242        &'req self,
243        start_payload: OnHttpRequestHookPayload<'req>,
244    ) -> OnHttpRequestHookResult<'req> {
245        start_payload.proceed()
246    }
247    #[inline]
248    async fn on_graphql_params<'exec>(
249        &'exec self,
250        start_payload: OnGraphQLParamsStartHookPayload<'exec>,
251    ) -> OnGraphQLParamsStartHookResult<'exec> {
252        start_payload.proceed()
253    }
254    #[inline]
255    async fn on_graphql_parse<'exec>(
256        &'exec self,
257        start_payload: OnGraphQLParseStartHookPayload<'exec>,
258    ) -> OnGraphQLParseHookResult<'exec> {
259        start_payload.proceed()
260    }
261    #[inline]
262    async fn on_graphql_validation<'exec>(
263        &'exec self,
264        start_payload: OnGraphQLValidationStartHookPayload<'exec>,
265    ) -> OnGraphQLValidationStartHookResult<'exec> {
266        start_payload.proceed()
267    }
268    #[inline]
269    async fn on_query_plan<'exec>(
270        &'exec self,
271        start_payload: OnQueryPlanStartHookPayload<'exec>,
272    ) -> OnQueryPlanStartHookResult<'exec> {
273        start_payload.proceed()
274    }
275    #[inline]
276    async fn on_execute<'exec>(
277        &'exec self,
278        start_payload: OnExecuteStartHookPayload<'exec>,
279    ) -> OnExecuteStartHookResult<'exec> {
280        start_payload.proceed()
281    }
282    #[inline]
283    async fn on_subgraph_execute<'exec>(
284        &'exec self,
285        start_payload: OnSubgraphExecuteStartHookPayload<'exec>,
286    ) -> OnSubgraphExecuteStartHookResult<'exec> {
287        start_payload.proceed()
288    }
289    #[inline]
290    async fn on_subgraph_http_request<'exec>(
291        &'exec self,
292        start_payload: OnSubgraphHttpRequestHookPayload<'exec>,
293    ) -> OnSubgraphHttpRequestHookResult<'exec> {
294        start_payload.proceed()
295    }
296    #[inline]
297    fn on_supergraph_reload<'exec>(
298        &'exec self,
299        start_payload: OnSupergraphLoadStartHookPayload,
300    ) -> OnSupergraphLoadStartHookResult<'exec> {
301        start_payload.proceed()
302    }
303    #[inline]
304    fn on_graphql_error(&self, payload: OnGraphQLErrorHookPayload) -> OnGraphQLErrorHookResult {
305        payload.proceed()
306    }
307    #[inline]
308    async fn on_shutdown<'exec>(&'exec self) {}
309}
310
311#[async_trait::async_trait]
312pub trait DynRouterPlugin: Send + Sync + 'static {
313    fn on_http_request<'req>(
314        &'req self,
315        start_payload: OnHttpRequestHookPayload<'req>,
316    ) -> OnHttpRequestHookResult<'req>;
317    async fn on_graphql_params<'exec>(
318        &'exec self,
319        start_payload: OnGraphQLParamsStartHookPayload<'exec>,
320    ) -> OnGraphQLParamsStartHookResult<'exec>;
321    async fn on_graphql_parse<'exec>(
322        &'exec self,
323        start_payload: OnGraphQLParseStartHookPayload<'exec>,
324    ) -> OnGraphQLParseHookResult<'exec>;
325    async fn on_graphql_validation<'exec>(
326        &'exec self,
327        start_payload: OnGraphQLValidationStartHookPayload<'exec>,
328    ) -> OnGraphQLValidationStartHookResult<'exec>;
329    async fn on_query_plan<'exec>(
330        &'exec self,
331        start_payload: OnQueryPlanStartHookPayload<'exec>,
332    ) -> OnQueryPlanStartHookResult<'exec>;
333    async fn on_execute<'exec>(
334        &'exec self,
335        start_payload: OnExecuteStartHookPayload<'exec>,
336    ) -> OnExecuteStartHookResult<'exec>;
337    async fn on_subgraph_execute<'exec>(
338        &'exec self,
339        start_payload: OnSubgraphExecuteStartHookPayload<'exec>,
340    ) -> OnSubgraphExecuteStartHookResult<'exec>;
341    async fn on_subgraph_http_request<'exec>(
342        &'exec self,
343        start_payload: OnSubgraphHttpRequestHookPayload<'exec>,
344    ) -> OnSubgraphHttpRequestHookResult<'exec>;
345    fn on_supergraph_reload<'exec>(
346        &'exec self,
347        start_payload: OnSupergraphLoadStartHookPayload,
348    ) -> OnSupergraphLoadStartHookResult<'exec>;
349    fn on_graphql_error(&self, payload: OnGraphQLErrorHookPayload) -> OnGraphQLErrorHookResult;
350    async fn on_shutdown<'exec>(&'exec self);
351}
352
353#[async_trait::async_trait]
354impl<P> DynRouterPlugin for P
355where
356    P: RouterPlugin,
357{
358    #[inline]
359    fn on_http_request<'req>(
360        &'req self,
361        start_payload: OnHttpRequestHookPayload<'req>,
362    ) -> OnHttpRequestHookResult<'req> {
363        RouterPlugin::on_http_request(self, start_payload)
364    }
365    #[inline]
366    async fn on_graphql_params<'exec>(
367        &'exec self,
368        start_payload: OnGraphQLParamsStartHookPayload<'exec>,
369    ) -> OnGraphQLParamsStartHookResult<'exec> {
370        RouterPlugin::on_graphql_params(self, start_payload).await
371    }
372    #[inline]
373    async fn on_graphql_parse<'exec>(
374        &'exec self,
375        start_payload: OnGraphQLParseStartHookPayload<'exec>,
376    ) -> OnGraphQLParseHookResult<'exec> {
377        RouterPlugin::on_graphql_parse(self, start_payload).await
378    }
379    #[inline]
380    async fn on_graphql_validation<'exec>(
381        &'exec self,
382        start_payload: OnGraphQLValidationStartHookPayload<'exec>,
383    ) -> OnGraphQLValidationStartHookResult<'exec> {
384        RouterPlugin::on_graphql_validation(self, start_payload).await
385    }
386    #[inline]
387    async fn on_query_plan<'exec>(
388        &'exec self,
389        start_payload: OnQueryPlanStartHookPayload<'exec>,
390    ) -> OnQueryPlanStartHookResult<'exec> {
391        RouterPlugin::on_query_plan(self, start_payload).await
392    }
393    #[inline]
394    async fn on_execute<'exec>(
395        &'exec self,
396        start_payload: OnExecuteStartHookPayload<'exec>,
397    ) -> OnExecuteStartHookResult<'exec> {
398        RouterPlugin::on_execute(self, start_payload).await
399    }
400    #[inline]
401    async fn on_subgraph_execute<'exec>(
402        &'exec self,
403        start_payload: OnSubgraphExecuteStartHookPayload<'exec>,
404    ) -> OnSubgraphExecuteStartHookResult<'exec> {
405        RouterPlugin::on_subgraph_execute(self, start_payload).await
406    }
407    #[inline]
408    async fn on_subgraph_http_request<'exec>(
409        &'exec self,
410        start_payload: OnSubgraphHttpRequestHookPayload<'exec>,
411    ) -> OnSubgraphHttpRequestHookResult<'exec> {
412        RouterPlugin::on_subgraph_http_request(self, start_payload).await
413    }
414    #[inline]
415    fn on_supergraph_reload<'exec>(
416        &'exec self,
417        start_payload: OnSupergraphLoadStartHookPayload,
418    ) -> OnSupergraphLoadStartHookResult<'exec> {
419        RouterPlugin::on_supergraph_reload(self, start_payload)
420    }
421    #[inline]
422    fn on_graphql_error(&self, payload: OnGraphQLErrorHookPayload) -> OnGraphQLErrorHookResult {
423        RouterPlugin::on_graphql_error(self, payload)
424    }
425    #[inline]
426    async fn on_shutdown<'exec>(&'exec self) {
427        RouterPlugin::on_shutdown(self).await;
428    }
429}
430
431pub type RouterPluginBoxed = Box<dyn DynRouterPlugin>;
432
433pub enum CacheHint {
434    Hit,
435    Miss,
436}