use crate::GraphQLExecutor;
use crate::error::GraphQLError;
use axum::{
body::Body,
http::{Request, Response, StatusCode},
};
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
use spikard_http::{Handler, HandlerResult, RequestData};
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
const MAX_QUERY_SIZE: usize = 1_048_576;
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct GraphQLRequestPayload {
pub query: String,
#[serde(default)]
pub variables: Option<Value>,
#[serde(rename = "operationName")]
pub operation_name: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct GraphQLResponsePayload {
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub errors: Option<Vec<GraphQLErrorResponse>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub extensions: Option<Value>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct GraphQLErrorResponse {
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub locations: Option<Vec<Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<Vec<Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub extensions: Option<Value>,
}
fn parse_graphql_request(raw_body: &[u8]) -> Result<GraphQLRequestPayload, GraphQLError> {
if raw_body.len() > MAX_QUERY_SIZE {
return Err(GraphQLError::RequestHandlingError(format!(
"Request body exceeds maximum size of {MAX_QUERY_SIZE} bytes"
)));
}
serde_json::from_slice(raw_body)
.map_err(|e| GraphQLError::RequestHandlingError(format!("Failed to parse GraphQL request: {e}")))
}
#[derive(Debug)]
pub struct GraphQLHandler<Query, Mutation, Subscription> {
executor: Arc<GraphQLExecutor<Query, Mutation, Subscription>>,
}
impl<Query, Mutation, Subscription> GraphQLHandler<Query, Mutation, Subscription>
where
Query: async_graphql::ObjectType + Send + Sync + 'static,
Mutation: async_graphql::ObjectType + Send + Sync + 'static,
Subscription: async_graphql::SubscriptionType + Send + Sync + 'static,
{
#[must_use]
pub const fn new(executor: Arc<GraphQLExecutor<Query, Mutation, Subscription>>) -> Self {
Self { executor }
}
pub async fn handle_graphql(&self, request_data: &RequestData) -> Result<Value, GraphQLError> {
let body_bytes = request_data.raw_body.as_ref().map_or_else(
|| serde_json::to_vec(&request_data.body).unwrap_or_default(),
|raw_body| raw_body.to_vec(),
);
let payload = parse_graphql_request(&body_bytes)?;
self.executor
.execute(
&payload.query,
payload.variables.as_ref(),
payload.operation_name.as_deref(),
)
.await
}
fn response_from_result(result: Result<Value, GraphQLError>) -> Response<Body> {
match result {
Ok(graphql_response) => {
let body = serde_json::to_vec(&graphql_response).unwrap_or_else(|_| {
serde_json::to_vec(&json!({"errors": [{"message": "Failed to serialize GraphQL response"}]}))
.unwrap_or_else(|_| b"{\"errors\":[{\"message\":\"Internal server error\"}]}".to_vec())
});
Response::builder()
.status(StatusCode::OK)
.header("content-type", "application/json")
.body(Body::from(body))
.unwrap_or_else(|_| {
Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(Body::from("Internal server error"))
.unwrap()
})
}
Err(e) => {
let status = StatusCode::from_u16(e.status_code()).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
let error_response = e.to_graphql_response();
let body = serde_json::to_vec(&error_response).unwrap_or_else(|_| {
serde_json::to_vec(&json!({"errors": [{"message": "Internal server error"}]}))
.unwrap_or_else(|_| b"{\"errors\":[{\"message\":\"Internal server error\"}]}".to_vec())
});
Response::builder()
.status(status)
.header("content-type", "application/json")
.body(Body::from(body))
.unwrap_or_else(|_| {
Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(Body::from("Internal server error"))
.unwrap()
})
}
}
}
}
impl<Query, Mutation, Subscription> Clone for GraphQLHandler<Query, Mutation, Subscription> {
fn clone(&self) -> Self {
Self {
executor: Arc::clone(&self.executor),
}
}
}
impl<Query, Mutation, Subscription> Handler for GraphQLHandler<Query, Mutation, Subscription>
where
Query: async_graphql::ObjectType + Send + Sync + 'static,
Mutation: async_graphql::ObjectType + Send + Sync + 'static,
Subscription: async_graphql::SubscriptionType + Send + Sync + 'static,
{
fn call(
&self,
_request: Request<Body>,
request_data: RequestData,
) -> Pin<Box<dyn Future<Output = HandlerResult> + Send + '_>> {
Box::pin(async move {
let result = self.handle_graphql(&request_data).await;
Ok(Self::response_from_result(result))
})
}
fn prefers_raw_json_body(&self) -> bool {
true
}
fn wants_headers(&self) -> bool {
false
}
fn wants_cookies(&self) -> bool {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
use async_graphql::{EmptyMutation, EmptySubscription, Object, Schema};
#[derive(Default)]
struct TestQuery;
#[Object]
impl TestQuery {
async fn hello(&self) -> &'static str {
"world"
}
}
fn make_test_handler() -> GraphQLHandler<TestQuery, EmptyMutation, EmptySubscription> {
let schema = Schema::build(TestQuery, EmptyMutation, EmptySubscription).finish();
let executor = Arc::new(GraphQLExecutor::new(schema));
GraphQLHandler::new(executor)
}
#[test]
fn test_graphql_request_payload_parsing_minimal() {
let json = r#"{"query":"{ hello }"}"#;
let payload: GraphQLRequestPayload = serde_json::from_str(json).unwrap();
assert_eq!(payload.query, "{ hello }");
assert!(payload.variables.is_none());
assert!(payload.operation_name.is_none());
}
#[test]
fn test_graphql_request_payload_parsing_with_variables() {
let json = r#"{"query":"query GetUser($id: ID!) { user(id: $id) { name } }","variables":{"id":"123"}}"#;
let payload: GraphQLRequestPayload = serde_json::from_str(json).unwrap();
assert_eq!(payload.query, "query GetUser($id: ID!) { user(id: $id) { name } }");
assert!(payload.variables.is_some());
assert_eq!(payload.variables.as_ref().unwrap()["id"], "123");
}
#[test]
fn test_graphql_request_payload_parsing_with_operation_name() {
let json =
r#"{"query":"query GetUser { user { name } } query ListUsers { users { id } }","operationName":"GetUser"}"#;
let payload: GraphQLRequestPayload = serde_json::from_str(json).unwrap();
assert_eq!(payload.operation_name, Some("GetUser".to_string()));
}
#[test]
fn test_graphql_request_payload_serialization() {
let payload = GraphQLRequestPayload {
query: "{ hello }".to_string(),
variables: Some(json!({"test": "value"})),
operation_name: Some("TestOp".to_string()),
};
let json = serde_json::to_string(&payload).unwrap();
let parsed: GraphQLRequestPayload = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.query, payload.query);
assert_eq!(parsed.variables, payload.variables);
assert_eq!(parsed.operation_name, payload.operation_name);
}
#[test]
fn test_graphql_response_payload_success() {
let payload = GraphQLResponsePayload {
data: Some(json!({"hello": "world"})),
errors: None,
extensions: None,
};
let json = serde_json::to_string(&payload).unwrap();
assert!(json.contains("\"data\""));
assert!(!json.contains("\"errors\""));
}
#[test]
fn test_graphql_response_payload_with_errors() {
let error = GraphQLErrorResponse {
message: "Test error".to_string(),
locations: None,
path: None,
extensions: None,
};
let payload = GraphQLResponsePayload {
data: None,
errors: Some(vec![error]),
extensions: None,
};
let json = serde_json::to_string(&payload).unwrap();
assert!(json.contains("\"errors\""));
assert!(json.contains("Test error"));
}
#[test]
fn test_parse_request_invalid_json() {
let result = parse_graphql_request(b"invalid json");
assert!(result.is_err());
}
#[test]
fn test_parse_request_missing_query() {
let result = parse_graphql_request(b"{}");
assert!(result.is_err());
}
#[test]
fn test_parse_request_exceeds_size_limit() {
let huge_request = vec![b'x'; MAX_QUERY_SIZE + 1];
let result = parse_graphql_request(&huge_request);
assert!(result.is_err());
match result.unwrap_err() {
GraphQLError::RequestHandlingError(msg) => {
assert!(msg.contains("exceeds maximum size"));
assert!(msg.contains("1048576"));
}
_ => panic!("Expected RequestHandlingError"),
}
}
#[test]
fn test_parse_request_at_size_limit() {
let request_at_limit = vec![b'x'; MAX_QUERY_SIZE];
let result = parse_graphql_request(&request_at_limit);
assert!(result.is_err());
match result.unwrap_err() {
GraphQLError::RequestHandlingError(msg) => {
assert!(!msg.contains("exceeds maximum size"));
assert!(msg.contains("Failed to parse GraphQL request"));
}
_ => panic!("Expected RequestHandlingError"),
}
}
#[test]
fn test_parse_request_normal_size_succeeds() {
let json = r#"{"query":"{ hello }"}"#;
let result = parse_graphql_request(json.as_bytes());
assert!(result.is_ok());
let payload = result.unwrap();
assert_eq!(payload.query, "{ hello }");
}
#[test]
fn test_handler_prefers_raw_json_body() {
let handler = make_test_handler();
assert!(handler.prefers_raw_json_body());
}
#[test]
fn test_handler_does_not_want_headers() {
let handler = make_test_handler();
assert!(!handler.wants_headers());
}
#[test]
fn test_handler_does_not_want_cookies() {
let handler = make_test_handler();
assert!(!handler.wants_cookies());
}
#[test]
fn test_handler_clone() {
let handler1 = make_test_handler();
let handler2 = handler1.clone();
assert!(Arc::ptr_eq(&handler1.executor, &handler2.executor));
}
}