finchers_juniper/
request.rs1use finchers::endpoint::with_get_cx;
4use finchers::endpoint::{ApplyContext, ApplyResult, Endpoint};
5use finchers::endpoints::body;
6use finchers::error;
7use finchers::error::Error;
8use finchers::output::{Output, OutputContext};
9
10use futures::{Future, Poll};
11
12use juniper;
13use juniper::{GraphQLType, InputValue, RootNode};
14
15use failure::SyncFailure;
16use http::Method;
17use http::{header, Response, StatusCode};
18use percent_encoding::percent_decode;
19use serde_json;
20use serde_qs;
21
22pub fn graphql_request() -> GraphQLRequestEndpoint {
36 GraphQLRequestEndpoint { _priv: () }
37}
38
39#[allow(missing_docs)]
40#[derive(Debug)]
41pub struct GraphQLRequestEndpoint {
42 _priv: (),
43}
44
45impl<'a> Endpoint<'a> for GraphQLRequestEndpoint {
46 type Output = (GraphQLRequest,);
47 type Future = RequestFuture<'a>;
48
49 fn apply(&'a self, cx: &mut ApplyContext<'_>) -> ApplyResult<Self::Future> {
50 if cx.input().method() == Method::GET {
51 Ok(RequestFuture {
52 kind: RequestKind::Get,
53 })
54 } else {
55 Ok(RequestFuture {
56 kind: RequestKind::Post(body::receive_all().apply(cx)?),
57 })
58 }
59 }
60}
61
62#[doc(hidden)]
63#[derive(Debug)]
64pub struct RequestFuture<'a> {
65 kind: RequestKind<'a>,
66}
67
68#[derive(Debug)]
69enum RequestKind<'a> {
70 Get,
71 Post(<body::ReceiveAll as Endpoint<'a>>::Future),
72}
73
74impl<'a> Future for RequestFuture<'a> {
75 type Item = (GraphQLRequest,);
76 type Error = Error;
77
78 fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
79 let result = match self.kind {
80 RequestKind::Get => with_get_cx(|input| {
81 let s = input
82 .uri()
83 .query()
84 .ok_or_else(|| error::bad_request("missing query string"))?;
85 parse_query_str(s)
86 }),
87 RequestKind::Post(ref mut f) => {
88 let (data,) = try_ready!(f.poll());
89 with_get_cx(
90 |input| match input.content_type().map_err(error::bad_request)? {
91 Some(m) if *m == "application/json" => {
92 serde_json::from_slice(&*data).map_err(error::bad_request)
93 }
94 Some(m) if *m == "application/graphql" => {
95 let query =
96 String::from_utf8(data.to_vec()).map_err(error::bad_request)?;
97 Ok(GraphQLRequest::single(query, None, None))
98 }
99 Some(_m) => Err(error::bad_request("unsupported content-type.")),
100 None => Err(error::bad_request("missing content-type.")),
101 },
102 )
103 }
104 };
105
106 result.map(|request| (request,).into())
107 }
108}
109
110#[derive(Debug, Deserialize)]
114pub struct GraphQLRequest(GraphQLRequestKind);
115
116#[derive(Debug, Deserialize)]
117#[serde(untagged)]
118enum GraphQLRequestKind {
119 Single(juniper::http::GraphQLRequest),
120 Batch(Vec<juniper::http::GraphQLRequest>),
121}
122
123impl GraphQLRequest {
124 fn single(
125 query: String,
126 operation_name: Option<String>,
127 variables: Option<InputValue>,
128 ) -> GraphQLRequest {
129 GraphQLRequest(GraphQLRequestKind::Single(
130 juniper::http::GraphQLRequest::new(query, operation_name, variables),
131 ))
132 }
133
134 pub fn execute<QueryT, MutationT, CtxT>(
136 &self,
137 root_node: &RootNode<'static, QueryT, MutationT>,
138 context: &CtxT,
139 ) -> GraphQLResponse
140 where
141 QueryT: GraphQLType<Context = CtxT>,
142 MutationT: GraphQLType<Context = CtxT>,
143 {
144 use self::GraphQLRequestKind::*;
145 match self.0 {
146 Single(ref request) => {
147 let response = request.execute(root_node, context);
148 GraphQLResponse {
149 is_ok: response.is_ok(),
150 body: serde_json::to_vec(&response),
151 }
152 }
153 Batch(ref requests) => {
154 let responses: Vec<_> = requests
155 .iter()
156 .map(|request| request.execute(root_node, context))
157 .collect();
158 GraphQLResponse {
159 is_ok: responses.iter().all(|response| response.is_ok()),
160 body: serde_json::to_vec(&responses),
161 }
162 }
163 }
164 }
165}
166
167fn parse_query_str(s: &str) -> Result<GraphQLRequest, Error> {
168 #[derive(Debug, Deserialize)]
169 struct ParsedQuery {
170 query: String,
171 operation_name: Option<String>,
172 variables: Option<String>,
173 }
174
175 let parsed: ParsedQuery =
176 serde_qs::from_str(s).map_err(|e| error::fail(SyncFailure::new(e)))?;
177
178 let query = percent_decode(parsed.query.as_bytes())
179 .decode_utf8()
180 .map_err(error::bad_request)?
181 .into_owned();
182
183 let operation_name = match parsed.operation_name {
184 Some(s) => Some(
185 percent_decode(s.as_bytes())
186 .decode_utf8()
187 .map_err(error::bad_request)?
188 .into_owned(),
189 ),
190 None => None,
191 };
192
193 let variables: Option<InputValue> = match parsed.variables {
194 Some(variables) => {
195 let decoded = percent_decode(variables.as_bytes())
196 .decode_utf8()
197 .map_err(error::bad_request)?;
198 serde_json::from_str(&*decoded)
199 .map(Some)
200 .map_err(error::bad_request)?
201 }
202 None => None,
203 };
204
205 Ok(GraphQLRequest::single(query, operation_name, variables))
206}
207
208#[derive(Debug)]
210pub struct GraphQLResponse {
211 is_ok: bool,
212 body: Result<Vec<u8>, serde_json::Error>,
213}
214
215impl Output for GraphQLResponse {
216 type Body = Vec<u8>;
217 type Error = Error;
218
219 fn respond(self, _: &mut OutputContext<'_>) -> Result<Response<Self::Body>, Self::Error> {
220 let status = if self.is_ok {
221 StatusCode::OK
222 } else {
223 StatusCode::BAD_REQUEST
224 };
225 let body = self.body.map_err(error::fail)?;
226 Ok(Response::builder()
227 .status(status)
228 .header(header::CONTENT_TYPE, "application/json")
229 .body(body)
230 .expect("should be a valid response"))
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use finchers::test;
237 use http::Request;
238
239 use super::{graphql_request, GraphQLRequest, GraphQLRequestKind};
240
241 #[test]
242 fn test_get_request() {
243 let mut runner = test::runner(graphql_request());
244 assert_matches!(
245 runner.apply(Request::get("/?query={{}}")),
246 Ok(GraphQLRequest(GraphQLRequestKind::Single(..)))
247 );
248 }
249
250 #[test]
251 fn test_json_request() {
252 let mut runner = test::runner(graphql_request());
253 assert_matches!(
254 runner.apply(
255 Request::post("/")
256 .header("content-type", "application/json")
257 .body(r#"{ "query": "{ apiVersion }" }"#),
258 ),
259 Ok(GraphQLRequest(GraphQLRequestKind::Single(..)))
260 );
261 }
262
263 #[test]
264 fn test_batch_json_request() {
265 let mut runner = test::runner(graphql_request());
266 assert_matches!(
267 runner.apply(
268 Request::post("/")
269 .header("content-type", "application/json")
270 .body(
271 r#"[
272 { "query": "{ apiVersion }" },
273 { "query": "{ me { id } }" }
274 ]"#,
275 ),
276 ),
277 Ok(GraphQLRequest(GraphQLRequestKind::Batch(..)))
278 );
279 }
280
281 #[test]
282 fn test_graphql_request() {
283 let mut runner = test::runner(graphql_request());
284 assert_matches!(
285 runner.apply(
286 Request::post("/")
287 .header("content-type", "application/graphql")
288 .body(r#"{ apiVersion }"#),
289 ),
290 Ok(GraphQLRequest(GraphQLRequestKind::Single(..)))
291 );
292 }
293}