/rust/registry/src/index.crates.io-1949cf8c6b5b557f/async-graphql-7.0.17/src/http/mod.rs
Line | Count | Source |
1 | | //! A helper module that supports HTTP |
2 | | |
3 | | #[cfg(feature = "altair")] |
4 | | mod altair_source; |
5 | | #[cfg(feature = "graphiql")] |
6 | | mod graphiql_plugin; |
7 | | #[cfg(feature = "graphiql")] |
8 | | mod graphiql_source; |
9 | | #[cfg(feature = "graphiql")] |
10 | | mod graphiql_v2_source; |
11 | | mod multipart; |
12 | | mod multipart_subscribe; |
13 | | #[cfg(feature = "playground")] |
14 | | mod playground_source; |
15 | | mod websocket; |
16 | | |
17 | | #[cfg(feature = "altair")] |
18 | | pub use altair_source::*; |
19 | | use futures_util::io::{AsyncRead, AsyncReadExt}; |
20 | | #[cfg(feature = "graphiql")] |
21 | | pub use graphiql_plugin::{GraphiQLPlugin, graphiql_plugin_explorer}; |
22 | | #[cfg(feature = "graphiql")] |
23 | | pub use graphiql_source::graphiql_source; |
24 | | #[cfg(feature = "graphiql")] |
25 | | pub use graphiql_v2_source::{Credentials, GraphiQLSource}; |
26 | | pub use multipart::MultipartOptions; |
27 | | pub use multipart_subscribe::{create_multipart_mixed_stream, is_accept_multipart_mixed}; |
28 | | #[cfg(feature = "playground")] |
29 | | pub use playground_source::{GraphQLPlaygroundConfig, playground_source}; |
30 | | use serde::Deserialize; |
31 | | pub use websocket::{ |
32 | | ALL_WEBSOCKET_PROTOCOLS, ClientMessage, DefaultOnConnInitType, DefaultOnPingType, |
33 | | Protocols as WebSocketProtocols, WebSocket, WsMessage, default_on_connection_init, |
34 | | default_on_ping, |
35 | | }; |
36 | | |
37 | | use crate::{BatchRequest, ParseRequestError, Request}; |
38 | | |
39 | | /// Parse a GraphQL request from a query string. |
40 | 0 | pub fn parse_query_string(input: &str) -> Result<Request, ParseRequestError> { |
41 | | #[derive(Deserialize)] |
42 | | struct RequestSerde { |
43 | | #[serde(default)] |
44 | | pub query: String, |
45 | | pub operation_name: Option<String>, |
46 | | pub variables: Option<String>, |
47 | | pub extensions: Option<String>, |
48 | | } |
49 | | |
50 | 0 | let request: RequestSerde = serde_urlencoded::from_str(input).map_err(std::io::Error::other)?; |
51 | 0 | let variables = request |
52 | 0 | .variables |
53 | 0 | .map(|data| serde_json::from_str(&data)) |
54 | 0 | .transpose() |
55 | 0 | .map_err(|err| std::io::Error::other(format!("invalid variables: {}", err)))? |
56 | 0 | .unwrap_or_default(); |
57 | 0 | let extensions = request |
58 | 0 | .extensions |
59 | 0 | .map(|data| serde_json::from_str(&data)) |
60 | 0 | .transpose() |
61 | 0 | .map_err(|err| std::io::Error::other(format!("invalid extensions: {}", err)))? |
62 | 0 | .unwrap_or_default(); |
63 | | |
64 | 0 | Ok(Request { |
65 | 0 | operation_name: request.operation_name, |
66 | 0 | variables, |
67 | 0 | extensions, |
68 | 0 | ..Request::new(request.query) |
69 | 0 | }) |
70 | 0 | } |
71 | | |
72 | | /// Receive a GraphQL request from a content type and body. |
73 | 0 | pub async fn receive_body( |
74 | 0 | content_type: Option<impl AsRef<str>>, |
75 | 0 | body: impl AsyncRead + Send, |
76 | 0 | opts: MultipartOptions, |
77 | 0 | ) -> Result<Request, ParseRequestError> { |
78 | 0 | receive_batch_body(content_type, body, opts) |
79 | 0 | .await? |
80 | 0 | .into_single() |
81 | 0 | } |
82 | | |
83 | | /// Receive a GraphQL request from a content type and body. |
84 | 0 | pub async fn receive_batch_body( |
85 | 0 | content_type: Option<impl AsRef<str>>, |
86 | 0 | body: impl AsyncRead + Send, |
87 | 0 | opts: MultipartOptions, |
88 | 0 | ) -> Result<BatchRequest, ParseRequestError> { |
89 | | // if no content-type header is set, we default to json |
90 | 0 | let content_type = content_type |
91 | 0 | .as_ref() |
92 | 0 | .map(AsRef::as_ref) |
93 | 0 | .unwrap_or("application/graphql-response+json"); |
94 | | |
95 | 0 | let content_type: mime::Mime = content_type.parse()?; |
96 | | |
97 | 0 | match (content_type.type_(), content_type.subtype()) { |
98 | | // try to use multipart |
99 | 0 | (mime::MULTIPART, _) => { |
100 | 0 | if let Some(boundary) = content_type.get_param("boundary") { |
101 | 0 | multipart::receive_batch_multipart(body, boundary.to_string(), opts).await |
102 | | } else { |
103 | 0 | Err(ParseRequestError::InvalidMultipart( |
104 | 0 | multer::Error::NoBoundary, |
105 | 0 | )) |
106 | | } |
107 | | } |
108 | | // application/json or cbor (currently) |
109 | | // cbor is in application/octet-stream. |
110 | | // Note: cbor will only match if feature ``cbor`` is active |
111 | | // TODO: wait for mime to add application/cbor and match against that too |
112 | 0 | _ => receive_batch_body_no_multipart(&content_type, body).await, |
113 | | } |
114 | 0 | } |
115 | | |
116 | | /// Receives a GraphQL query which is either cbor or json but NOT multipart |
117 | | /// This method is only to avoid recursive calls with [``receive_batch_body``] |
118 | | /// and [``multipart::receive_batch_multipart``] |
119 | 0 | pub(super) async fn receive_batch_body_no_multipart( |
120 | 0 | content_type: &mime::Mime, |
121 | 0 | body: impl AsyncRead + Send, |
122 | 0 | ) -> Result<BatchRequest, ParseRequestError> { |
123 | 0 | assert_ne!(content_type.type_(), mime::MULTIPART, "received multipart"); |
124 | 0 | match (content_type.type_(), content_type.subtype()) { |
125 | | #[cfg(feature = "cbor")] |
126 | | // cbor is in application/octet-stream. |
127 | | // TODO: wait for mime to add application/cbor and match against that too |
128 | | (mime::OCTET_STREAM, _) | (mime::APPLICATION, mime::OCTET_STREAM) => { |
129 | | receive_batch_cbor(body).await |
130 | | } |
131 | | // default to json |
132 | 0 | _ => receive_batch_json(body).await, |
133 | | } |
134 | 0 | } |
135 | | /// Receive a GraphQL request from a body as JSON. |
136 | 0 | pub async fn receive_json(body: impl AsyncRead) -> Result<Request, ParseRequestError> { |
137 | 0 | receive_batch_json(body).await?.into_single() |
138 | 0 | } |
139 | | |
140 | | /// Receive a GraphQL batch request from a body as JSON. |
141 | 0 | pub async fn receive_batch_json(body: impl AsyncRead) -> Result<BatchRequest, ParseRequestError> { |
142 | 0 | let mut data = Vec::new(); |
143 | 0 | futures_util::pin_mut!(body); |
144 | 0 | body.read_to_end(&mut data) |
145 | 0 | .await |
146 | 0 | .map_err(ParseRequestError::Io)?; |
147 | 0 | serde_json::from_slice::<BatchRequest>(&data) |
148 | 0 | .map_err(|e| ParseRequestError::InvalidRequest(Box::new(e))) |
149 | 0 | } |
150 | | |
151 | | /// Receive a GraphQL request from a body as CBOR. |
152 | | #[cfg(feature = "cbor")] |
153 | | #[cfg_attr(docsrs, doc(cfg(feature = "cbor")))] |
154 | | pub async fn receive_cbor(body: impl AsyncRead) -> Result<Request, ParseRequestError> { |
155 | | receive_batch_cbor(body).await?.into_single() |
156 | | } |
157 | | |
158 | | /// Receive a GraphQL batch request from a body as CBOR |
159 | | #[cfg(feature = "cbor")] |
160 | | #[cfg_attr(docsrs, doc(cfg(feature = "cbor")))] |
161 | | pub async fn receive_batch_cbor(body: impl AsyncRead) -> Result<BatchRequest, ParseRequestError> { |
162 | | let mut data = Vec::new(); |
163 | | futures_util::pin_mut!(body); |
164 | | body.read_to_end(&mut data) |
165 | | .await |
166 | | .map_err(ParseRequestError::Io)?; |
167 | | serde_cbor::from_slice::<BatchRequest>(&data) |
168 | | .map_err(|e| ParseRequestError::InvalidRequest(Box::new(e))) |
169 | | } |
170 | | |
171 | | #[cfg(test)] |
172 | | mod tests { |
173 | | use std::collections::HashMap; |
174 | | |
175 | | use async_graphql_value::Extensions; |
176 | | |
177 | | use super::*; |
178 | | use crate::{Variables, value}; |
179 | | |
180 | | #[test] |
181 | | fn test_parse_query_string() { |
182 | | let request = parse_query_string("variables=%7B%7D&extensions=%7B%22persistedQuery%22%3A%7B%22sha256Hash%22%3A%22cde5de0a350a19c59f8ddcd9646e5f260b2a7d5649ff6be8e63e9462934542c3%22%2C%22version%22%3A1%7D%7D").unwrap(); |
183 | | assert_eq!(request.query.as_str(), ""); |
184 | | assert_eq!(request.variables, Variables::default()); |
185 | | assert_eq!(request.extensions, { |
186 | | let mut extensions = HashMap::new(); |
187 | | extensions.insert("persistedQuery".to_string(), value!({ |
188 | | "sha256Hash": "cde5de0a350a19c59f8ddcd9646e5f260b2a7d5649ff6be8e63e9462934542c3", |
189 | | "version": 1, |
190 | | })); |
191 | | Extensions(extensions) |
192 | | }); |
193 | | |
194 | | let request = parse_query_string("query={a}&variables=%7B%22a%22%3A10%7D").unwrap(); |
195 | | assert_eq!(request.query.as_str(), "{a}"); |
196 | | assert_eq!( |
197 | | request.variables, |
198 | | Variables::from_value(value!({ "a" : 10 })) |
199 | | ); |
200 | | } |
201 | | } |