Skip to main content

hive_router_plan_executor/plugins/hooks/
on_execute.rs

1use std::collections::HashMap;
2
3use hive_router_query_planner::ast::operation::OperationDefinition;
4use hive_router_query_planner::planner::plan_nodes::QueryPlan;
5use serde::Serialize;
6use sonic_rs::json;
7
8use crate::execution::plan::PlanExecutionOutput;
9use crate::plugin_context::{PluginContext, RouterHttpRequest};
10use crate::plugin_trait::{
11    EndHookPayload, EndHookResult, FromGraphQLErrorToResponse, StartHookPayload, StartHookResult,
12};
13use crate::response::graphql_error::GraphQLError;
14use crate::response::value::Value;
15
16pub struct OnExecuteStartHookPayload<'exec> {
17    /// The incoming HTTP request to the router for which the GraphQL execution is happening.
18    /// It includes all the details of the request such as headers, body, etc.
19    ///
20    /// Example:
21    /// ```
22    ///  let my_header = payload.router_http_request.headers.get("my-header");
23    ///  // do something with the header...
24    ///  payload.proceed()
25    /// ```
26    pub router_http_request: &'exec RouterHttpRequest<'exec>,
27    /// The context object that can be used to share data across different plugin hooks for the same request.
28    /// It is unique per request and is dropped after the response is sent.
29    ///
30    /// [Learn more about the context data sharing in the docs](https://the-guild.dev/graphql/hive/docs/router/extensibility/plugin_system#context-data-sharing)
31    pub context: &'exec PluginContext,
32    /// The query plan generated for the incoming GraphQL request.
33    /// It includes the details of how the router plans to execute the request across the subgraphs.
34    pub query_plan: &'exec QueryPlan,
35    /// The operation definition from the GraphQL document that is being executed.
36    /// It includes the details of the operation such as its name, type (query/mutation/subscription), etc.
37    pub operation_for_plan: &'exec OperationDefinition,
38
39    /// The root value of the execution
40    /// Anything here will be merged into the execution result
41    pub data: Value<'exec>,
42    /// Initial set of GraphQL errors in the execution result
43    /// Any error passed here will be merged into the execution result errors list
44    pub errors: Vec<GraphQLError>,
45    /// Initial set of GraphQL extensions in the execution result
46    /// Any extension passed here will be merged into the execution result extensions map
47    pub extensions: HashMap<String, sonic_rs::Value>,
48
49    /// Coerced variable values for the execution
50    /// This includes all the variables from the request that have been coerced according to the variable definitions in the GraphQL document.
51    /// [Learn more about coercion](https://graphql.org/learn/execution/#scalar-coercion)
52    pub variable_values: &'exec Option<HashMap<String, sonic_rs::Value>>,
53
54    pub dedupe_subgraph_requests: bool,
55}
56
57impl<'exec> OnExecuteStartHookPayload<'exec> {
58    /// Add a GraphQL error to the execution result. This error will be merged into the execution result errors list.
59    pub fn add_error(&mut self, error: GraphQLError) {
60        self.errors.push(error);
61    }
62    /// Filter the GraphQL errors in the execution result.
63    /// The provided closure should return `true` for the errors that should be kept,
64    /// and `false` for the errors that should be removed.
65    ///
66    /// Example:
67    /// ```
68    /// fn on_execute<'exec>(&'exec self, mut payload: OnExecuteStartHookPayload<'exec>) -> OnExecuteStartHookResult<'exec> {
69    ///    // Remove all errors with the message "Internal error"
70    ///    payload.filter_errors(|error| error.message != "Internal error");
71    ///    payload.proceed()
72    /// }
73    /// ```
74    pub fn filter_errors<F>(&mut self, mut f: F)
75    where
76        F: FnMut(&GraphQLError) -> bool,
77    {
78        self.errors.retain(|error| f(error))
79    }
80    /// Add a GraphQL extension to the execution result. This extension will be merged into the execution result extensions map.
81    /// Example:
82    /// ```
83    /// fn on_execute<'exec>(&'exec self, mut payload: OnExecuteStartHookPayload<'exec>) -> OnExecuteStartHookResult<'exec> {
84    ///   // Add an extension with the key "my_extension" and value {"foo": "bar"}
85    ///   payload.add_extension("my_extension", json!({"foo": "bar"}));
86    ///   payload.proceed()
87    /// }
88    /// ```
89    ///
90    /// Then the result sent to the client will include this extension:
91    /// ```json
92    /// {
93    ///   "data": { ... },
94    ///   "errors": [ ... ],
95    ///   "extensions": {
96    ///     "my_extension": {
97    ///       "foo": "bar"
98    ///     }
99    ///   }
100    /// }
101    /// ```
102    pub fn add_extension<T: Serialize>(&mut self, key: &str, value: T) -> Option<sonic_rs::Value> {
103        self.extensions.insert(key.into(), json!(value))
104    }
105    /// Get a reference to a GraphQL extension value from the execution result extensions map by its key.
106    pub fn get_extension(&self, key: &str) -> Option<&sonic_rs::Value> {
107        self.extensions.get(key)
108    }
109    /// Remove a GraphQL extension from the execution result extensions map by its key.
110    /// This will remove the extension from the execution result.
111    pub fn remove_extension(&mut self, key: &str) -> Option<sonic_rs::Value> {
112        self.extensions.remove(key)
113    }
114}
115
116impl<'exec> StartHookPayload<OnExecuteEndHookPayload<'exec>, PlanExecutionOutput>
117    for OnExecuteStartHookPayload<'exec>
118{
119}
120
121pub type OnExecuteStartHookResult<'exec> = StartHookResult<
122    'exec,
123    OnExecuteStartHookPayload<'exec>,
124    OnExecuteEndHookPayload<'exec>,
125    PlanExecutionOutput,
126>;
127
128pub struct OnExecuteEndHookPayload<'exec> {
129    /// The final value of the execution result. This will be sent to the client as the "data" field in the GraphQL response.
130    /// Plugins can modify this value before proceeding, and the modified value will be sent to the client.
131    pub data: Value<'exec>,
132    /// The final list of GraphQL errors in the execution result.
133    /// This will be sent to the client as the "errors" field in the GraphQL response.
134    /// Plugins can modify this list before proceeding, and the modified list will be sent to the client.
135    pub errors: Vec<GraphQLError>,
136    /// The final map of GraphQL extensions in the execution result.
137    /// This will be sent to the client as the "extensions" field in the GraphQL response.
138    /// Plugins can modify this map before proceeding, and the modified map will be sent to the client.
139    pub extensions: HashMap<String, sonic_rs::Value>,
140
141    /// An estimate of the response size in bytes.
142    /// This is calculated based on the subgraph responses
143    pub response_size_estimate: usize,
144}
145
146impl<'exec> OnExecuteEndHookPayload<'exec> {
147    pub fn with_error(&mut self, error: GraphQLError) {
148        self.errors.push(error);
149    }
150    pub fn filter_errors<F>(&mut self, mut f: F)
151    where
152        F: FnMut(&GraphQLError) -> bool,
153    {
154        self.errors.retain(|error| f(error))
155    }
156    pub fn add_extension<T: Serialize>(&mut self, key: &str, value: T) -> Option<sonic_rs::Value> {
157        self.extensions.insert(key.into(), json!(value))
158    }
159    pub fn get_extension(&self, key: &str) -> Option<&sonic_rs::Value> {
160        self.extensions.get(key)
161    }
162    pub fn remove_extension(&mut self, key: &str) -> Option<sonic_rs::Value> {
163        self.extensions.remove(key)
164    }
165}
166
167impl<'exec> EndHookPayload<PlanExecutionOutput> for OnExecuteEndHookPayload<'exec> {}
168
169pub type OnExecuteEndHookResult<'exec> =
170    EndHookResult<OnExecuteEndHookPayload<'exec>, PlanExecutionOutput>;
171
172impl FromGraphQLErrorToResponse for PlanExecutionOutput {
173    fn from_graphql_error_to_response(error: GraphQLError, status_code: http::StatusCode) -> Self {
174        let body_json = json!({
175            "errors": [error],
176        });
177        PlanExecutionOutput {
178            body: sonic_rs::to_vec(&body_json).unwrap_or_default(),
179            error_count: 1,
180            response_headers_aggregator: None,
181            status_code,
182        }
183    }
184}