tako_rs_core/graphql/
request.rs1use async_graphql::BatchRequest as GqlBatchRequest;
5use async_graphql::http::MultipartOptions;
6use http::StatusCode;
7use http_body_util::BodyExt;
8
9use crate::extractors::FromRequest;
10use crate::responder::Responder;
11use crate::types::Request;
12use crate::types::Response;
13
14pub struct GraphQLRequest(pub async_graphql::Request);
16
17impl GraphQLRequest {
18 pub fn into_inner(self) -> async_graphql::Request {
19 self.0
20 }
21}
22
23pub struct GraphQLBatchRequest(pub GqlBatchRequest);
25
26impl GraphQLBatchRequest {
27 pub fn into_inner(self) -> GqlBatchRequest {
28 self.0
29 }
30}
31
32pub const MAX_GRAPHQL_BODY_SIZE: usize = 4 * 1024 * 1024;
40
41#[derive(Debug)]
43pub enum GraphQLError {
44 MissingQuery,
45 BodyRead(String),
46 BodyTooLarge,
49 InvalidJson(String),
50 Parse(String),
51 UnsupportedMediaType(String),
52}
53
54#[derive(Clone, Default)]
56pub struct GraphQLOptions {
57 pub multipart: MultipartOptions,
58}
59
60impl Responder for GraphQLError {
61 fn into_response(self) -> Response {
62 match self {
63 GraphQLError::MissingQuery => {
64 (StatusCode::BAD_REQUEST, "Missing GraphQL query").into_response()
65 }
66 GraphQLError::BodyRead(e) => {
67 (StatusCode::BAD_REQUEST, format!("Failed to read body: {e}")).into_response()
68 }
69 GraphQLError::BodyTooLarge => (
70 StatusCode::PAYLOAD_TOO_LARGE,
71 format!("GraphQL body exceeds {MAX_GRAPHQL_BODY_SIZE} bytes"),
72 )
73 .into_response(),
74 GraphQLError::InvalidJson(e) => {
75 (StatusCode::BAD_REQUEST, format!("Invalid JSON: {e}")).into_response()
76 }
77 GraphQLError::Parse(e) => {
78 (StatusCode::BAD_REQUEST, format!("Invalid request: {e}")).into_response()
79 }
80 GraphQLError::UnsupportedMediaType(ct) => (
81 StatusCode::UNSUPPORTED_MEDIA_TYPE,
82 format!("Unsupported GraphQL content-type: {ct}"),
83 )
84 .into_response(),
85 }
86 }
87}
88
89fn classify_graphql_content_type(ct: Option<&str>) -> Result<GraphQLBodyKind, GraphQLError> {
94 let raw = ct.unwrap_or("").trim();
95 if raw.is_empty() {
96 return Err(GraphQLError::UnsupportedMediaType("<missing>".to_string()));
97 }
98 let essence = raw
99 .split(';')
100 .next()
101 .unwrap_or("")
102 .trim()
103 .to_ascii_lowercase();
104 match essence.as_str() {
105 "application/json" => Ok(GraphQLBodyKind::Json),
106 "application/graphql" | "application/graphql-response+json" => Ok(GraphQLBodyKind::Graphql),
107 "multipart/form-data" => Ok(GraphQLBodyKind::Multipart),
108 _ => Err(GraphQLError::UnsupportedMediaType(raw.to_string())),
109 }
110}
111
112#[derive(Copy, Clone, Debug, PartialEq, Eq)]
113enum GraphQLBodyKind {
114 Json,
115 Graphql,
116 Multipart,
117}
118
119#[inline]
120fn resolve_opts(req: &Request) -> MultipartOptions {
121 if let Some(opts) = req.extensions().get::<GraphQLOptions>() {
123 return opts.multipart;
124 }
125 if let Some(global) = crate::state::get_state::<GraphQLOptions>() {
127 return global.as_ref().multipart;
128 }
129 MultipartOptions::default()
130}
131
132fn parse_get_request(req: &Request) -> Result<async_graphql::Request, GraphQLError> {
133 let qs = req.uri().query().unwrap_or("");
134 async_graphql::http::parse_query_string(qs).map_err(|e| GraphQLError::Parse(e.to_string()))
135}
136
137async fn read_body_bytes(req: &mut Request) -> Result<bytes::Bytes, GraphQLError> {
138 if let Some(cl) = req.headers().get(http::header::CONTENT_LENGTH)
142 && let Some(n) = cl.to_str().ok().and_then(|s| s.parse::<usize>().ok())
143 && n > MAX_GRAPHQL_BODY_SIZE
144 {
145 return Err(GraphQLError::BodyTooLarge);
146 }
147
148 let body = std::mem::take(req.body_mut());
152 let limited = http_body_util::Limited::new(body, MAX_GRAPHQL_BODY_SIZE);
153 match limited.collect().await {
154 Ok(c) => Ok(c.to_bytes()),
155 Err(e) => {
156 if e
160 .downcast_ref::<http_body_util::LengthLimitError>()
161 .is_some()
162 {
163 Err(GraphQLError::BodyTooLarge)
164 } else {
165 Err(GraphQLError::BodyRead(e.to_string()))
166 }
167 }
168 }
169}
170
171impl<'a> FromRequest<'a> for GraphQLRequest {
172 type Error = GraphQLError;
173
174 fn from_request(
175 req: &'a mut Request,
176 ) -> impl core::future::Future<Output = core::result::Result<Self, Self::Error>> + Send + 'a {
177 async move {
178 if req.method() == http::Method::GET {
179 return Ok(GraphQLRequest(parse_get_request(req)?));
180 }
181
182 let opts = resolve_opts(req);
184
185 let content_type = req
186 .headers()
187 .get(http::header::CONTENT_TYPE)
188 .and_then(|v| v.to_str().ok())
189 .map(std::string::ToString::to_string);
190 classify_graphql_content_type(content_type.as_deref())?;
191
192 let body = read_body_bytes(req).await?;
193 if body.is_empty() {
194 return Err(GraphQLError::Parse("empty request body".to_string()));
195 }
196
197 let reader = futures_util::io::Cursor::new(body.to_vec());
198 let req = async_graphql::http::receive_body(content_type.as_deref(), reader, opts)
199 .await
200 .map_err(|e| GraphQLError::Parse(e.to_string()))?;
201 Ok(GraphQLRequest(req))
202 }
203 }
204}
205
206pub fn attach_graphql_options(req: &mut Request, opts: GraphQLOptions) {
209 req.extensions_mut().insert(opts);
210}
211
212pub fn set_global_graphql_options(opts: GraphQLOptions) {
214 crate::state::set_state::<GraphQLOptions>(opts);
215}
216
217pub async fn receive_graphql(
218 req: &mut Request,
219 opts: MultipartOptions,
220) -> Result<async_graphql::Request, GraphQLError> {
221 if req.method() == http::Method::GET {
222 return parse_get_request(req);
223 }
224 let body = read_body_bytes(req).await?;
225 let content_type = req
226 .headers()
227 .get(http::header::CONTENT_TYPE)
228 .and_then(|v| v.to_str().ok())
229 .map(std::string::ToString::to_string);
230 let reader = futures_util::io::Cursor::new(body.to_vec());
231 async_graphql::http::receive_body(content_type.as_deref(), reader, opts)
232 .await
233 .map_err(|e| GraphQLError::Parse(e.to_string()))
234}
235
236pub async fn receive_graphql_batch(
238 req: &mut Request,
239 opts: MultipartOptions,
240) -> Result<GqlBatchRequest, GraphQLError> {
241 if req.method() == http::Method::GET {
242 let single = parse_get_request(req)?;
243 return Ok(GqlBatchRequest::Single(single));
244 }
245 let content_type = req
246 .headers()
247 .get(http::header::CONTENT_TYPE)
248 .and_then(|v| v.to_str().ok())
249 .map(std::string::ToString::to_string);
250 classify_graphql_content_type(content_type.as_deref())?;
251 let body = read_body_bytes(req).await?;
252 if body.is_empty() {
253 return Err(GraphQLError::Parse("empty request body".to_string()));
254 }
255 let reader = futures_util::io::Cursor::new(body.to_vec());
256 async_graphql::http::receive_batch_body(content_type.as_deref(), reader, opts)
257 .await
258 .map_err(|e| GraphQLError::Parse(e.to_string()))
259}
260
261impl<'a> FromRequest<'a> for GraphQLBatchRequest {
262 type Error = GraphQLError;
263
264 fn from_request(
265 req: &'a mut Request,
266 ) -> impl core::future::Future<Output = core::result::Result<Self, Self::Error>> + Send + 'a {
267 async move {
268 if req.method() == http::Method::GET {
269 let single = parse_get_request(req)?;
271 return Ok(GraphQLBatchRequest(GqlBatchRequest::Single(single)));
272 }
273
274 let opts = resolve_opts(req);
276
277 let content_type = req
278 .headers()
279 .get(http::header::CONTENT_TYPE)
280 .and_then(|v| v.to_str().ok())
281 .map(std::string::ToString::to_string);
282 classify_graphql_content_type(content_type.as_deref())?;
283 let body = read_body_bytes(req).await?;
284 if body.is_empty() {
285 return Err(GraphQLError::Parse("empty request body".to_string()));
286 }
287 let reader = futures_util::io::Cursor::new(body.to_vec());
288 let batch = async_graphql::http::receive_batch_body(content_type.as_deref(), reader, opts)
289 .await
290 .map_err(|e| GraphQLError::Parse(e.to_string()))?;
291 Ok(GraphQLBatchRequest(batch))
292 }
293 }
294}