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}