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
37pub trait StartHookPayload<TEndPayload: EndHookPayload<TResponse>, TResponse>
43where
44 Self: Sized,
45 TResponse: FromGraphQLErrorToResponse,
46{
47 fn proceed<'exec>(self) -> StartHookResult<'exec, Self, TEndPayload, TResponse> {
61 StartHookResult {
62 payload: self,
63 control_flow: StartControlFlow::Proceed,
64 }
65 }
66
67 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 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 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 fn proceed(self) -> EndHookResult<Self, TResponse> {
160 EndHookResult {
161 payload: self,
162 control_flow: EndControlFlow::Proceed,
163 }
164 }
165
166 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 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}