hive_router_plan_executor/plugins/hooks/on_graphql_params.rs
1use core::fmt;
2
3use std::collections::HashMap;
4
5use ntex::util::Bytes;
6use serde::Serialize;
7use serde::{de, Deserialize, Deserializer};
8use sonic_rs::Value;
9
10use crate::plugin_context::PluginContext;
11use crate::plugin_context::RouterHttpRequest;
12use crate::plugin_trait::EndHookPayload;
13use crate::plugin_trait::EndHookResult;
14use crate::plugin_trait::StartHookPayload;
15use crate::plugin_trait::StartHookResult;
16use ntex::http::Response;
17
18#[derive(Debug, Default, Serialize)]
19/// The GraphQL parameters parsed from the HTTP request body by the router.
20/// This includes the `query`, `operationName`, `variables`, and `extensions`
21/// [Learn more about GraphQL-over-HTTP params](https://graphql.org/learn/serving-over-http/#request-format)
22pub struct GraphQLParams {
23 #[serde(skip_serializing_if = "Option::is_none")]
24 /// The GraphQL query string parsed from the HTTP request body by the router
25 /// This contains the source text of a GraphQL query, mutation, or subscription sent by the client in the request body.
26 /// It can be `None` if the client did not send a query string in the request body.
27 pub query: Option<String>,
28 #[serde(rename = "operationName", skip_serializing_if = "Option::is_none")]
29 /// The operation name parsed from the HTTP request body by the router
30 /// This is the name of the operation that the client wants to execute, sent in the request body.
31 /// It is optional and can be `None` if the client did not specify an operation
32 pub operation_name: Option<String>,
33 #[serde(skip_serializing_if = "HashMap::is_empty")]
34 /// The variables map parsed from the HTTP request body by the router
35 /// This is a map of variable names to their values sent by the client in the request
36 /// [Learn more about GraphQL variables](https://graphql.org/learn/queries/#variables)
37 pub variables: HashMap<String, Value>,
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub extensions: Option<HashMap<String, Value>>,
40}
41
42// Workaround for https://github.com/cloudwego/sonic-rs/issues/114
43
44impl<'de> Deserialize<'de> for GraphQLParams {
45 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
46 where
47 D: Deserializer<'de>,
48 {
49 struct GraphQLParamsVisitor;
50
51 impl<'de> de::Visitor<'de> for GraphQLParamsVisitor {
52 type Value = GraphQLParams;
53
54 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
55 formatter.write_str("a map for GraphQLParams")
56 }
57
58 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
59 where
60 A: de::MapAccess<'de>,
61 {
62 let mut query = None;
63 let mut operation_name = None;
64 let mut variables: Option<HashMap<String, Value>> = None;
65 let mut extensions: Option<HashMap<String, Value>> = None;
66 let mut extra_params = HashMap::new();
67
68 while let Some(key) = map.next_key::<String>()? {
69 match key.as_str() {
70 "query" => {
71 if query.is_some() {
72 return Err(de::Error::duplicate_field("query"));
73 }
74 query = map.next_value::<Option<String>>()?;
75 }
76 "operationName" => {
77 if operation_name.is_some() {
78 return Err(de::Error::duplicate_field("operationName"));
79 }
80 operation_name = map.next_value::<Option<String>>()?;
81 }
82 "variables" => {
83 if variables.is_some() {
84 return Err(de::Error::duplicate_field("variables"));
85 }
86 variables = map.next_value::<Option<HashMap<String, Value>>>()?;
87 }
88 "extensions" => {
89 if extensions.is_some() {
90 return Err(de::Error::duplicate_field("extensions"));
91 }
92 extensions = map.next_value::<Option<HashMap<String, Value>>>()?;
93 }
94 other => {
95 let value: Value = map.next_value()?;
96 extra_params.insert(other.to_string(), value);
97 }
98 }
99 }
100
101 Ok(GraphQLParams {
102 query,
103 operation_name,
104 variables: variables.unwrap_or_default(),
105 extensions,
106 })
107 }
108 }
109
110 deserializer.deserialize_map(GraphQLParamsVisitor)
111 }
112}
113
114pub struct OnGraphQLParamsStartHookPayload<'exec> {
115 /// The incoming HTTP request to the router for which the GraphQL execution is happening.
116 /// It includes all the details of the request such as headers, body, etc.
117 ///
118 /// Example:
119 /// ```
120 /// let my_header = payload.router_http_request.headers.get("my-header");
121 /// // do something with the header...
122 /// payload.proceed()
123 /// ```
124 pub router_http_request: &'exec RouterHttpRequest<'exec>,
125 /// The context object that can be used to share data across different plugin hooks for the same request.
126 /// It is unique per request and is dropped after the response is sent.
127 ///
128 /// [Learn more about the context data sharing in the docs](https://the-guild.dev/graphql/hive/docs/router/extensibility/plugin_system#context-data-sharing)
129 pub context: &'exec PluginContext,
130 /// The raw body of the incoming HTTP request.
131 /// This is useful for plugins that want to parse the body in a custom way,
132 /// or want to access the raw body for logging or other purposes.
133 pub body: Bytes,
134 /// The overriden GraphQL parameters to be used in the execution instead of the ones parsed from the HTTP request.
135 /// If this is `None`, the router will use the GraphQL parameters parsed from the HTTP request.
136 /// This is useful for plugins that want to parse the GraphQL parameters in a custom way,
137 /// or want to override the GraphQL parameters for testing or other purposes.
138 ///
139 /// [Learn more about overriding the default behavior](https://the-guild.dev/graphql/hive/docs/router/extensibility/plugin_system#overriding-default-behavior)
140 pub graphql_params: Option<GraphQLParams>,
141}
142
143impl<'exec> OnGraphQLParamsStartHookPayload<'exec> {
144 /// Overrides GraphQL parameters to be used in the execution instead of the ones parsed from the HTTP request.
145 /// If this is `None`, the router will use the GraphQL parameters parsed from the HTTP request.
146 /// This is useful for plugins that want to parse the GraphQL parameters in a custom way,
147 /// or want to override the GraphQL parameters for testing or other purposes.
148 ///
149 /// [Learn more about overriding the default behavior](https://the-guild.dev/graphql/hive/docs/router/extensibility/plugin_system#overriding-default-behavior)
150 pub fn with_graphql_params(mut self, graphql_params: GraphQLParams) -> Self {
151 self.graphql_params = Some(graphql_params);
152 self
153 }
154}
155
156impl<'exec> StartHookPayload<OnGraphQLParamsEndHookPayload<'exec>, Response>
157 for OnGraphQLParamsStartHookPayload<'exec>
158{
159}
160
161pub type OnGraphQLParamsStartHookResult<'exec> = StartHookResult<
162 'exec,
163 OnGraphQLParamsStartHookPayload<'exec>,
164 OnGraphQLParamsEndHookPayload<'exec>,
165 Response,
166>;
167
168pub struct OnGraphQLParamsEndHookPayload<'exec> {
169 /// Parsed GraphQL parameters to be used in the execution.
170 /// This is either the result of parsing the HTTP request body by the router,
171 /// or the overridden GraphQL parameters set by the plugin in the `OnGraphQLParamsStartHookPayload`.
172 ///
173 /// [Learn more about overriding the default behavior](https://the-guild.dev/graphql/hive/docs/router/extensibility/plugin_system#overriding-default-behavior)
174 pub graphql_params: GraphQLParams,
175 /// The context object that can be used to share data across different plugin hooks for the same request.
176 /// It is unique per request and is dropped after the response is sent.
177 ///
178 /// [Learn more about the context data sharing in the docs](https://the-guild.dev/graphql/hive/docs/router/extensibility/plugin_system#context-data-sharing)
179 pub context: &'exec PluginContext,
180}
181
182impl<'exec> EndHookPayload<Response> for OnGraphQLParamsEndHookPayload<'exec> {}
183
184pub type OnGraphQLParamsEndHookResult<'exec> =
185 EndHookResult<OnGraphQLParamsEndHookPayload<'exec>, Response>;
186
187#[cfg(test)]
188use ntex::web::test;
189
190#[cfg(test)]
191impl Into<test::TestRequest> for GraphQLParams {
192 fn into(self) -> test::TestRequest {
193 let body = self;
194 test::TestRequest::post().uri("/graphql").set_json(&body)
195 }
196}