1use crate::GraphQLExecutor;
8use crate::error::GraphQLError;
9use axum::{
10 body::Body,
11 http::{Request, Response, StatusCode},
12};
13use serde::{Deserialize, Serialize};
14use serde_json::{Value, json};
15use spikard_http::{Handler, HandlerResult, RequestData};
16use std::future::Future;
17use std::pin::Pin;
18use std::sync::Arc;
19
20const MAX_QUERY_SIZE: usize = 1_048_576;
22
23#[derive(Debug, Deserialize, Serialize, Clone)]
28pub struct GraphQLRequestPayload {
29 pub query: String,
31
32 #[serde(default)]
34 pub variables: Option<Value>,
35
36 #[serde(rename = "operationName")]
39 pub operation_name: Option<String>,
40}
41
42#[derive(Debug, Serialize, Deserialize, Clone)]
47pub struct GraphQLResponsePayload {
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub data: Option<Value>,
51
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub errors: Option<Vec<GraphQLErrorResponse>>,
55
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub extensions: Option<Value>,
59}
60
61#[derive(Debug, Serialize, Deserialize, Clone)]
63pub struct GraphQLErrorResponse {
64 pub message: String,
66
67 #[serde(skip_serializing_if = "Option::is_none")]
69 pub locations: Option<Vec<Value>>,
70
71 #[serde(skip_serializing_if = "Option::is_none")]
73 pub path: Option<Vec<Value>>,
74
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub extensions: Option<Value>,
78}
79
80fn parse_graphql_request(raw_body: &[u8]) -> Result<GraphQLRequestPayload, GraphQLError> {
90 if raw_body.len() > MAX_QUERY_SIZE {
91 return Err(GraphQLError::RequestHandlingError(format!(
92 "Request body exceeds maximum size of {MAX_QUERY_SIZE} bytes"
93 )));
94 }
95 serde_json::from_slice(raw_body)
96 .map_err(|e| GraphQLError::RequestHandlingError(format!("Failed to parse GraphQL request: {e}")))
97}
98
99#[derive(Debug)]
121pub struct GraphQLHandler<Query, Mutation, Subscription> {
122 executor: Arc<GraphQLExecutor<Query, Mutation, Subscription>>,
123}
124
125impl<Query, Mutation, Subscription> GraphQLHandler<Query, Mutation, Subscription>
126where
127 Query: async_graphql::ObjectType + Send + Sync + 'static,
128 Mutation: async_graphql::ObjectType + Send + Sync + 'static,
129 Subscription: async_graphql::SubscriptionType + Send + Sync + 'static,
130{
131 #[must_use]
141 pub const fn new(executor: Arc<GraphQLExecutor<Query, Mutation, Subscription>>) -> Self {
142 Self { executor }
143 }
144
145 pub async fn handle_graphql(&self, request_data: &RequestData) -> Result<Value, GraphQLError> {
159 let body_bytes = request_data.raw_body.as_ref().map_or_else(
161 || serde_json::to_vec(&request_data.body).unwrap_or_default(),
162 |raw_body| raw_body.to_vec(),
163 );
164
165 let payload = parse_graphql_request(&body_bytes)?;
167
168 self.executor
170 .execute(
171 &payload.query,
172 payload.variables.as_ref(),
173 payload.operation_name.as_deref(),
174 )
175 .await
176 }
177
178 fn response_from_result(result: Result<Value, GraphQLError>) -> Response<Body> {
180 match result {
181 Ok(graphql_response) => {
182 let body = serde_json::to_vec(&graphql_response).unwrap_or_else(|_| {
183 serde_json::to_vec(&json!({"errors": [{"message": "Failed to serialize GraphQL response"}]}))
184 .unwrap_or_else(|_| b"{\"errors\":[{\"message\":\"Internal server error\"}]}".to_vec())
185 });
186 Response::builder()
187 .status(StatusCode::OK)
188 .header("content-type", "application/json")
189 .body(Body::from(body))
190 .unwrap_or_else(|_| {
191 Response::builder()
192 .status(StatusCode::INTERNAL_SERVER_ERROR)
193 .body(Body::from("Internal server error"))
194 .unwrap()
195 })
196 }
197 Err(e) => {
198 let status = StatusCode::from_u16(e.status_code()).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
199 let error_response = e.to_graphql_response();
200 let body = serde_json::to_vec(&error_response).unwrap_or_else(|_| {
201 serde_json::to_vec(&json!({"errors": [{"message": "Internal server error"}]}))
202 .unwrap_or_else(|_| b"{\"errors\":[{\"message\":\"Internal server error\"}]}".to_vec())
203 });
204
205 Response::builder()
206 .status(status)
207 .header("content-type", "application/json")
208 .body(Body::from(body))
209 .unwrap_or_else(|_| {
210 Response::builder()
211 .status(StatusCode::INTERNAL_SERVER_ERROR)
212 .body(Body::from("Internal server error"))
213 .unwrap()
214 })
215 }
216 }
217 }
218}
219
220impl<Query, Mutation, Subscription> Clone for GraphQLHandler<Query, Mutation, Subscription> {
221 fn clone(&self) -> Self {
222 Self {
223 executor: Arc::clone(&self.executor),
224 }
225 }
226}
227
228impl<Query, Mutation, Subscription> Handler for GraphQLHandler<Query, Mutation, Subscription>
229where
230 Query: async_graphql::ObjectType + Send + Sync + 'static,
231 Mutation: async_graphql::ObjectType + Send + Sync + 'static,
232 Subscription: async_graphql::SubscriptionType + Send + Sync + 'static,
233{
234 fn call(
235 &self,
236 _request: Request<Body>,
237 request_data: RequestData,
238 ) -> Pin<Box<dyn Future<Output = HandlerResult> + Send + '_>> {
239 Box::pin(async move {
240 let result = self.handle_graphql(&request_data).await;
241 Ok(Self::response_from_result(result))
242 })
243 }
244
245 fn prefers_raw_json_body(&self) -> bool {
246 true
247 }
248
249 fn wants_headers(&self) -> bool {
250 false
251 }
252
253 fn wants_cookies(&self) -> bool {
254 false
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261 use async_graphql::{EmptyMutation, EmptySubscription, Object, Schema};
262
263 #[derive(Default)]
264 struct TestQuery;
265
266 #[Object]
267 impl TestQuery {
268 async fn hello(&self) -> &'static str {
269 "world"
270 }
271 }
272
273 fn make_test_handler() -> GraphQLHandler<TestQuery, EmptyMutation, EmptySubscription> {
274 let schema = Schema::build(TestQuery, EmptyMutation, EmptySubscription).finish();
275 let executor = Arc::new(GraphQLExecutor::new(schema));
276 GraphQLHandler::new(executor)
277 }
278
279 #[test]
280 fn test_graphql_request_payload_parsing_minimal() {
281 let json = r#"{"query":"{ hello }"}"#;
282 let payload: GraphQLRequestPayload = serde_json::from_str(json).unwrap();
283
284 assert_eq!(payload.query, "{ hello }");
285 assert!(payload.variables.is_none());
286 assert!(payload.operation_name.is_none());
287 }
288
289 #[test]
290 fn test_graphql_request_payload_parsing_with_variables() {
291 let json = r#"{"query":"query GetUser($id: ID!) { user(id: $id) { name } }","variables":{"id":"123"}}"#;
292 let payload: GraphQLRequestPayload = serde_json::from_str(json).unwrap();
293
294 assert_eq!(payload.query, "query GetUser($id: ID!) { user(id: $id) { name } }");
295 assert!(payload.variables.is_some());
296 assert_eq!(payload.variables.as_ref().unwrap()["id"], "123");
297 }
298
299 #[test]
300 fn test_graphql_request_payload_parsing_with_operation_name() {
301 let json =
302 r#"{"query":"query GetUser { user { name } } query ListUsers { users { id } }","operationName":"GetUser"}"#;
303 let payload: GraphQLRequestPayload = serde_json::from_str(json).unwrap();
304
305 assert_eq!(payload.operation_name, Some("GetUser".to_string()));
306 }
307
308 #[test]
309 fn test_graphql_request_payload_serialization() {
310 let payload = GraphQLRequestPayload {
311 query: "{ hello }".to_string(),
312 variables: Some(json!({"test": "value"})),
313 operation_name: Some("TestOp".to_string()),
314 };
315
316 let json = serde_json::to_string(&payload).unwrap();
317 let parsed: GraphQLRequestPayload = serde_json::from_str(&json).unwrap();
318
319 assert_eq!(parsed.query, payload.query);
320 assert_eq!(parsed.variables, payload.variables);
321 assert_eq!(parsed.operation_name, payload.operation_name);
322 }
323
324 #[test]
325 fn test_graphql_response_payload_success() {
326 let payload = GraphQLResponsePayload {
327 data: Some(json!({"hello": "world"})),
328 errors: None,
329 extensions: None,
330 };
331
332 let json = serde_json::to_string(&payload).unwrap();
333 assert!(json.contains("\"data\""));
334 assert!(!json.contains("\"errors\""));
335 }
336
337 #[test]
338 fn test_graphql_response_payload_with_errors() {
339 let error = GraphQLErrorResponse {
340 message: "Test error".to_string(),
341 locations: None,
342 path: None,
343 extensions: None,
344 };
345
346 let payload = GraphQLResponsePayload {
347 data: None,
348 errors: Some(vec![error]),
349 extensions: None,
350 };
351
352 let json = serde_json::to_string(&payload).unwrap();
353 assert!(json.contains("\"errors\""));
354 assert!(json.contains("Test error"));
355 }
356
357 #[test]
358 fn test_parse_request_invalid_json() {
359 let result = parse_graphql_request(b"invalid json");
360 assert!(result.is_err());
361 }
362
363 #[test]
364 fn test_parse_request_missing_query() {
365 let result = parse_graphql_request(b"{}");
366 assert!(result.is_err());
367 }
368
369 #[test]
370 fn test_parse_request_exceeds_size_limit() {
371 let huge_request = vec![b'x'; MAX_QUERY_SIZE + 1];
372 let result = parse_graphql_request(&huge_request);
373 assert!(result.is_err());
374 match result.unwrap_err() {
375 GraphQLError::RequestHandlingError(msg) => {
376 assert!(msg.contains("exceeds maximum size"));
377 assert!(msg.contains("1048576"));
378 }
379 _ => panic!("Expected RequestHandlingError"),
380 }
381 }
382
383 #[test]
384 fn test_parse_request_at_size_limit() {
385 let request_at_limit = vec![b'x'; MAX_QUERY_SIZE];
386 let result = parse_graphql_request(&request_at_limit);
387 assert!(result.is_err());
388 match result.unwrap_err() {
389 GraphQLError::RequestHandlingError(msg) => {
390 assert!(!msg.contains("exceeds maximum size"));
391 assert!(msg.contains("Failed to parse GraphQL request"));
392 }
393 _ => panic!("Expected RequestHandlingError"),
394 }
395 }
396
397 #[test]
398 fn test_parse_request_normal_size_succeeds() {
399 let json = r#"{"query":"{ hello }"}"#;
400 let result = parse_graphql_request(json.as_bytes());
401 assert!(result.is_ok());
402 let payload = result.unwrap();
403 assert_eq!(payload.query, "{ hello }");
404 }
405
406 #[test]
407 fn test_handler_prefers_raw_json_body() {
408 let handler = make_test_handler();
409 assert!(handler.prefers_raw_json_body());
410 }
411
412 #[test]
413 fn test_handler_does_not_want_headers() {
414 let handler = make_test_handler();
415 assert!(!handler.wants_headers());
416 }
417
418 #[test]
419 fn test_handler_does_not_want_cookies() {
420 let handler = make_test_handler();
421 assert!(!handler.wants_cookies());
422 }
423
424 #[test]
425 fn test_handler_clone() {
426 let handler1 = make_test_handler();
427 let handler2 = handler1.clone();
428
429 assert!(Arc::ptr_eq(&handler1.executor, &handler2.executor));
431 }
432}