1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
use std::collections::HashMap;
use crate::query_planner::ast::operation::OperationDefinition;
use crate::query_planner::planner::plan_nodes::QueryPlan;
use serde::Serialize;
use sonic_rs::json;
use crate::executor::execution::plan::PlanExecutionOutput;
use crate::executor::plugin_context::{PluginContext, RouterHttpRequest};
use crate::executor::plugin_trait::{
from_graphql_errors_to_bytes, EarlyHTTPResponse, EndHookPayload, EndHookResult,
FromGraphQLErrorToResponse, FromGraphQLErrorsToResponse, StartHookPayload, StartHookResult,
};
use crate::executor::request_context::RequestContextPluginApi;
use crate::executor::response::graphql_error::GraphQLError;
use crate::executor::response::value::Value;
type RequestContextApi = RequestContextPluginApi<super::OnExecute>;
/// Demand-control cost estimate available *before* execution.
///
/// Present on [`OnExecuteStartHookPayload`] when demand control is enabled.
/// The `actual` cost is not known at this stage — see [`DemandControlCost`].
#[derive(Debug, Clone, Copy)]
pub struct DemandControlEstimatedCost {
/// The pre-execution estimated cost of the operation.
pub estimated: u64,
/// The configured supergraph-wide maximum cost.
pub max: u64,
}
/// Demand-control cost available *after* execution.
///
/// Present on [`OnExecuteEndHookPayload`] when demand control is enabled.
#[derive(Debug, Clone, Copy)]
pub struct DemandControlCost {
/// The pre-execution estimated cost of the operation.
pub estimated: u64,
/// The configured supergraph-wide maximum cost.
pub max: u64,
/// The post-execution actual cost, computed from the real response.
pub actual: u64,
}
pub enum OnExecuteResponse {
Output(PlanExecutionOutput),
EarlyResponse(EarlyHTTPResponse),
}
impl From<PlanExecutionOutput> for OnExecuteResponse {
fn from(response: PlanExecutionOutput) -> Self {
Self::Output(response)
}
}
impl From<EarlyHTTPResponse> for OnExecuteResponse {
fn from(response: EarlyHTTPResponse) -> Self {
Self::EarlyResponse(response)
}
}
pub struct OnExecuteStartHookPayload<'exec> {
/// The incoming HTTP request to the router for which the GraphQL execution is happening.
/// It includes all the details of the request such as headers, body, etc.
///
/// Example:
/// ```
/// let my_header = payload.router_http_request.headers.get("my-header");
/// // do something with the header...
/// payload.proceed()
/// ```
pub router_http_request: &'exec RouterHttpRequest<'exec>,
/// The context object that can be used to share data across different plugin hooks for the same request.
/// It is unique per request and is dropped after the response is sent.
///
/// [Learn more about the context data sharing in the docs](https://the-guild.dev/graphql/hive/docs/router/extensibility/plugin_system#context-data-sharing)
pub context: &'exec PluginContext,
pub request_context: RequestContextApi,
/// The query plan generated for the incoming GraphQL request.
/// It includes the details of how the router plans to execute the request across the subgraphs.
pub query_plan: &'exec QueryPlan,
/// The operation definition from the GraphQL document that is being executed.
/// It includes the details of the operation such as its name, type (query/mutation/subscription), etc.
pub operation_for_plan: &'exec OperationDefinition,
/// The root value of the execution
/// Anything here will be merged into the execution result
pub data: Value<'exec>,
/// Initial set of GraphQL errors in the execution result
/// Any error passed here will be merged into the execution result errors list
pub errors: Vec<GraphQLError>,
/// Initial set of GraphQL extensions in the execution result
/// Any extension passed here will be merged into the execution result extensions map
pub extensions: HashMap<String, sonic_rs::Value>,
/// Coerced variable values for the execution
/// This includes all the variables from the request that have been coerced according to the variable definitions in the GraphQL document.
/// [Learn more about coercion](https://graphql.org/learn/execution/#scalar-coercion)
pub variable_values: &'exec Option<HashMap<String, sonic_rs::Value>>,
pub dedupe_subgraph_requests: bool,
/// The demand-control cost estimate for this operation, computed before execution.
/// `None` when demand control is disabled. The `actual` cost is not available
/// here — see [`OnExecuteEndHookPayload::demand_control_cost`].
pub demand_control_estimate: Option<DemandControlEstimatedCost>,
}
impl<'exec> OnExecuteStartHookPayload<'exec> {
/// Add a GraphQL error to the execution result. This error will be merged into the execution result errors list.
pub fn add_error(&mut self, error: GraphQLError) {
self.errors.push(error);
}
/// Filter the GraphQL errors in the execution result.
/// The provided closure should return `true` for the errors that should be kept,
/// and `false` for the errors that should be removed.
///
/// Example:
/// ```
/// fn on_execute<'exec>(&'exec self, mut payload: OnExecuteStartHookPayload<'exec>) -> OnExecuteStartHookResult<'exec> {
/// // Remove all errors with the message "Internal error"
/// payload.filter_errors(|error| error.message != "Internal error");
/// payload.proceed()
/// }
/// ```
pub fn filter_errors<F>(&mut self, mut f: F)
where
F: FnMut(&GraphQLError) -> bool,
{
self.errors.retain(|error| f(error))
}
/// Add a GraphQL extension to the execution result. This extension will be merged into the execution result extensions map.
/// Example:
/// ```
/// fn on_execute<'exec>(&'exec self, mut payload: OnExecuteStartHookPayload<'exec>) -> OnExecuteStartHookResult<'exec> {
/// // Add an extension with the key "my_extension" and value {"foo": "bar"}
/// payload.add_extension("my_extension", json!({"foo": "bar"}));
/// payload.proceed()
/// }
/// ```
///
/// Then the result sent to the client will include this extension:
/// ```json
/// {
/// "data": { ... },
/// "errors": [ ... ],
/// "extensions": {
/// "my_extension": {
/// "foo": "bar"
/// }
/// }
/// }
/// ```
pub fn add_extension<T: Serialize>(&mut self, key: &str, value: T) -> Option<sonic_rs::Value> {
self.extensions.insert(key.into(), json!(value))
}
/// Get a reference to a GraphQL extension value from the execution result extensions map by its key.
pub fn get_extension(&self, key: &str) -> Option<&sonic_rs::Value> {
self.extensions.get(key)
}
/// Remove a GraphQL extension from the execution result extensions map by its key.
/// This will remove the extension from the execution result.
pub fn remove_extension(&mut self, key: &str) -> Option<sonic_rs::Value> {
self.extensions.remove(key)
}
}
impl<'exec> StartHookPayload<OnExecuteEndHookPayload<'exec>, OnExecuteResponse>
for OnExecuteStartHookPayload<'exec>
{
}
pub type OnExecuteStartHookResult<'exec> = StartHookResult<
'exec,
OnExecuteStartHookPayload<'exec>,
OnExecuteEndHookPayload<'exec>,
OnExecuteResponse,
>;
pub struct OnExecuteEndHookPayload<'exec> {
/// The final value of the execution result. This will be sent to the client as the "data" field in the GraphQL response.
/// Plugins can modify this value before proceeding, and the modified value will be sent to the client.
pub data: Value<'exec>,
/// The final list of GraphQL errors in the execution result.
/// This will be sent to the client as the "errors" field in the GraphQL response.
/// Plugins can modify this list before proceeding, and the modified list will be sent to the client.
pub errors: Vec<GraphQLError>,
/// The final map of GraphQL extensions in the execution result.
/// This will be sent to the client as the "extensions" field in the GraphQL response.
/// Plugins can modify this map before proceeding, and the modified map will be sent to the client.
pub extensions: HashMap<String, sonic_rs::Value>,
pub request_context: RequestContextApi,
/// An estimate of the response size in bytes.
/// This is calculated based on the subgraph responses
pub response_size_estimate: usize,
/// The demand-control cost for this operation, including the post-execution
/// `actual` cost. `None` when demand control is disabled.
pub demand_control_cost: Option<DemandControlCost>,
}
impl<'exec> OnExecuteEndHookPayload<'exec> {
pub fn with_error(&mut self, error: GraphQLError) {
self.errors.push(error);
}
pub fn filter_errors<F>(&mut self, mut f: F)
where
F: FnMut(&GraphQLError) -> bool,
{
self.errors.retain(|error| f(error))
}
pub fn add_extension<T: Serialize>(&mut self, key: &str, value: T) -> Option<sonic_rs::Value> {
self.extensions.insert(key.into(), json!(value))
}
pub fn get_extension(&self, key: &str) -> Option<&sonic_rs::Value> {
self.extensions.get(key)
}
pub fn remove_extension(&mut self, key: &str) -> Option<sonic_rs::Value> {
self.extensions.remove(key)
}
}
impl<'exec> EndHookPayload<OnExecuteResponse> for OnExecuteEndHookPayload<'exec> {}
pub type OnExecuteEndHookResult<'exec> =
EndHookResult<OnExecuteEndHookPayload<'exec>, OnExecuteResponse>;
impl FromGraphQLErrorToResponse for OnExecuteResponse {
fn from_graphql_error_to_response(error: GraphQLError, status_code: http::StatusCode) -> Self {
Self::Output(PlanExecutionOutput::from_graphql_error_to_response(
error,
status_code,
))
}
}
impl FromGraphQLErrorsToResponse for OnExecuteResponse {
fn from_graphql_errors_to_response(
errors: Vec<GraphQLError>,
status_code: http::StatusCode,
) -> Self {
Self::Output(PlanExecutionOutput::from_graphql_errors_to_response(
errors,
status_code,
))
}
}
impl FromGraphQLErrorToResponse for PlanExecutionOutput {
fn from_graphql_error_to_response(error: GraphQLError, status_code: http::StatusCode) -> Self {
Self::from_graphql_errors_to_response(vec![error], status_code)
}
}
impl FromGraphQLErrorsToResponse for PlanExecutionOutput {
fn from_graphql_errors_to_response(
errors: Vec<GraphQLError>,
status_code: http::StatusCode,
) -> Self {
let error_count = errors.len();
PlanExecutionOutput {
body: from_graphql_errors_to_bytes(errors),
error_count,
status_code,
}
}
}