finchers_juniper/
request.rs

1//! Endpoint for parsing GraphQL request.
2
3use 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
22/// Create an endpoint which parses a GraphQL request from the client.
23///
24/// This endpoint validates if the HTTP method is GET or POST and if the iterator over remaining
25/// segments is empty, and skips if the request is not acceptable.
26/// If the validation is successed, it will return a Future which behaves as follows:
27///
28/// * If the method is `GET`, the query in the request is parsed as a single GraphQL query.
29///   If the query string is missing, it will return an error.
30/// * If the method is `POST`, receives the all contents of the request body and then converts
31///   it into a value of `GraphQLRequest`.
32///   - When `content-type` is `application/json`, the body is parsed as a JSON object which
33///     contains a GraphQL query and supplemental fields if needed.
34///   - When `content-type` is `application/graphql`, the body is parsed as a single GraphQL query.
35pub 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// ==== GraphQLRequest ====
111
112/// A type representing the decoded GraphQL query obtained by parsing an HTTP request.
113#[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    /// Executes a GraphQL query represented by this value using the specified schema and context.
135    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/// A type representing the result from executing a GraphQL query.
209#[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}