Skip to content

Commit 35c0bc2

Browse files
committed
Implementation of the integration between actix-web and juniper
Changes: - Changed the place of implementation for GraphQLBatchRequest, changed it from the juniper_warp to juniper crate since it could be reused in other http integrations
1 parent 1412561 commit 35c0bc2

File tree

13 files changed

+1420
-97
lines changed

13 files changed

+1420
-97
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ members = [
1111
"juniper_rocket",
1212
"juniper_subscriptions",
1313
"juniper_warp",
14+
"juniper_actix",
1415
]
1516
exclude = [
1617
"docs/book/tests",

juniper/src/http/mod.rs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,125 @@ where
217217
}
218218
}
219219

220+
/// The Expected structure of the decoded JSON document for either POST or GET requests
221+
/// considering Batch request.
222+
///
223+
/// You can use Serde to deserialize the incoming JSON data directly into this
224+
/// struct - it derives Deserialize for exactly this reason.
225+
///
226+
#[derive(Debug, serde_derive::Deserialize, PartialEq)]
227+
#[serde(untagged)]
228+
#[serde(bound = "InputValue<S>: Deserialize<'de>")]
229+
pub enum GraphQLBatchRequest<S = DefaultScalarValue>
230+
where
231+
S: ScalarValue,
232+
{
233+
/// A single operation request.
234+
Single(GraphQLRequest<S>),
235+
/// A batch operation request.
236+
Batch(Vec<GraphQLRequest<S>>),
237+
}
238+
239+
impl<S> GraphQLBatchRequest<S>
240+
where
241+
S: ScalarValue,
242+
{
243+
/// Execute a GraphQL batch request synchronously using the specified schema and context
244+
///
245+
/// This is a simple wrapper around the `execute_sync` function exposed in GraphQLRequest.
246+
pub fn execute_sync<'a, CtxT, QueryT, MutationT, SubscriptionT>(
247+
&'a self,
248+
root_node: &'a crate::RootNode<QueryT, MutationT, SubscriptionT, S>,
249+
context: &CtxT,
250+
) -> GraphQLBatchResponse<'a, S>
251+
where
252+
QueryT: crate::GraphQLType<S, Context = CtxT>,
253+
MutationT: crate::GraphQLType<S, Context = CtxT>,
254+
SubscriptionT: crate::GraphQLType<S, Context = CtxT>,
255+
SubscriptionT::TypeInfo: Send + Sync,
256+
CtxT: Send + Sync,
257+
{
258+
match *self {
259+
GraphQLBatchRequest::Single(ref request) => {
260+
GraphQLBatchResponse::Single(request.execute_sync(root_node, context))
261+
}
262+
GraphQLBatchRequest::Batch(ref requests) => GraphQLBatchResponse::Batch(
263+
requests
264+
.iter()
265+
.map(|request| request.execute_sync(root_node, context))
266+
.collect(),
267+
),
268+
}
269+
}
270+
271+
/// Executes a GraphQL request using the specified schema and context
272+
///
273+
/// This is a simple wrapper around the `execute` function exposed in
274+
/// GraphQLRequest
275+
pub async fn execute<'a, CtxT, QueryT, MutationT, SubscriptionT>(
276+
&'a self,
277+
root_node: &'a crate::RootNode<'a, QueryT, MutationT, SubscriptionT, S>,
278+
context: &'a CtxT,
279+
) -> GraphQLBatchResponse<'a, S>
280+
where
281+
QueryT: crate::GraphQLTypeAsync<S, Context = CtxT> + Send + Sync,
282+
QueryT::TypeInfo: Send + Sync,
283+
MutationT: crate::GraphQLTypeAsync<S, Context = CtxT> + Send + Sync,
284+
MutationT::TypeInfo: Send + Sync,
285+
SubscriptionT: crate::GraphQLSubscriptionType<S, Context = CtxT> + Send + Sync,
286+
SubscriptionT::TypeInfo: Send + Sync,
287+
CtxT: Send + Sync,
288+
S: Send + Sync,
289+
{
290+
match *self {
291+
GraphQLBatchRequest::Single(ref request) => {
292+
let res = request.execute(root_node, context).await;
293+
GraphQLBatchResponse::Single(res)
294+
}
295+
GraphQLBatchRequest::Batch(ref requests) => {
296+
let futures = requests
297+
.iter()
298+
.map(|request| request.execute(root_node, context))
299+
.collect::<Vec<_>>();
300+
let responses = futures::future::join_all(futures).await;
301+
302+
GraphQLBatchResponse::Batch(responses)
303+
}
304+
}
305+
}
306+
}
307+
308+
/// Simple wrapper around the result (GraphQLResponse) from executing a GraphQLBatchRequest
309+
///
310+
/// This struct implements Serialize, so you can simply serialize this
311+
/// to JSON and send it over the wire. use the `is_ok` to determine
312+
/// wheter to send a 200 or 400 HTTP status code.
313+
#[derive(serde_derive::Serialize)]
314+
#[serde(untagged)]
315+
pub enum GraphQLBatchResponse<'a, S = DefaultScalarValue>
316+
where
317+
S: ScalarValue,
318+
{
319+
/// Result of a single operation in a GraphQL request.
320+
Single(GraphQLResponse<'a, S>),
321+
/// Result of a batch operation in a GraphQL request.
322+
Batch(Vec<GraphQLResponse<'a, S>>),
323+
}
324+
325+
impl<'a, S> GraphQLBatchResponse<'a, S>
326+
where
327+
S: ScalarValue,
328+
{
329+
/// Returns if all the GraphQLResponse in this operation are ok,
330+
/// you can use it to determine wheter to send a 200 or 400 HTTP status code.
331+
pub fn is_ok(&self) -> bool {
332+
match self {
333+
GraphQLBatchResponse::Single(res) => res.is_ok(),
334+
GraphQLBatchResponse::Batch(reses) => reses.iter().all(|res| res.is_ok()),
335+
}
336+
}
337+
}
338+
220339
#[cfg(any(test, feature = "expose-test-schema"))]
221340
#[allow(missing_docs)]
222341
pub mod tests {

juniper_actix/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/target
2+
/examples/**/target/**/*
3+
**/*.rs.bk
4+
Cargo.lock

juniper_actix/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# master
2+
3+
- Initial Release

juniper_actix/Cargo.toml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
[package]
2+
name = "juniper_actix"
3+
version = "0.1.0"
4+
authors = ["Jordao Rosario <[email protected]>"]
5+
description = "Juniper GraphQL integration with Actix"
6+
license = "BSD-2-Clause"
7+
documentation = "https://docs.rs/juniper_actix"
8+
repository = "https://github.com/graphql-rust/juniper"
9+
edition = "2018"
10+
11+
[features]
12+
subscriptions = ["juniper_subscriptions"]
13+
14+
[dependencies]
15+
actix = "0.9.0"
16+
actix-rt = "1.0.0"
17+
actix-web = { version = "2.0.0", features = ["rustls"] }
18+
actix-web-actors = "2.0.0"
19+
futures = { version = "0.3.1", features = ["compat"] }
20+
juniper = { version = "0.14.2", path = "../juniper", default-features = false }
21+
juniper_subscriptions = { path = "../juniper_subscriptions", optional = true}
22+
tokio = { version = "0.2", features = ["time"] }
23+
serde_json = "1.0.24"
24+
serde_derive = "1.0.75"
25+
failure = "0.1.7"
26+
serde = "1.0.75"
27+
28+
[dev-dependencies]
29+
juniper = { version = "0.14.2", path = "../juniper", features = ["expose-test-schema", "serde_json"] }
30+
env_logger = "0.5.11"
31+
log = "0.4.3"
32+
percent-encoding = "1.0"
33+
tokio = { version = "0.2", features = ["rt-core", "macros", "blocking"] }
34+
actix-cors = "0.2.0"
35+
actix-identity = "0.2.0"
36+
bytes = "0.5.4"
37+
38+
[[example]]
39+
name="actix_subscriptions"
40+
required-features=["subscriptions"]

juniper_actix/LICENSE

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
BSD 2-Clause License
2+
3+
Copyright (c) 2018, Jordao Rosario
4+
All rights reserved.
5+
6+
Redistribution and use in source and binary forms, with or without
7+
modification, are permitted provided that the following conditions are met:
8+
9+
* Redistributions of source code must retain the above copyright notice, this
10+
list of conditions and the following disclaimer.
11+
12+
* Redistributions in binary form must reproduce the above copyright notice,
13+
this list of conditions and the following disclaimer in the documentation
14+
and/or other materials provided with the distribution.
15+
16+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

juniper_actix/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# juniper_actix
2+
3+
This repository contains the [actix][actix] web server integration for
4+
[Juniper][Juniper], a [GraphQL][GraphQL] implementation for Rust, its inspired and some parts are copied from [juniper_warp][juniper_warp].
5+
6+
## Documentation
7+
8+
For documentation, including guides and examples, check out [Juniper][Juniper].
9+
10+
A basic usage example can also be found in the [API documentation][documentation].
11+
12+
## Examples
13+
14+
Check [examples/actix_server][example] for example code of a working actix
15+
server with GraphQL handlers.
16+
17+
## Links
18+
19+
* [Juniper][Juniper]
20+
* [API Reference][documentation]
21+
* [actix][actix]
22+
23+
## License
24+
25+
This project is under the BSD-2 license.
26+
27+
Check the LICENSE file for details.
28+
29+
[actix]: https://github.com/actix/actix-web
30+
[Juniper]: https://github.com/graphql-rust/juniper
31+
[GraphQL]: http://graphql.org
32+
[documentation]: https://docs.rs/juniper_actix
33+
[example]: https://github.com/graphql-rust/juniper/blob/master/juniper_actix/examples/actix_server.rs
34+
[juniper_warp]:https://docs.rs/juniper_warp
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#![deny(warnings)]
2+
3+
extern crate log;
4+
use actix_cors::Cors;
5+
use actix_web::{middleware, web, App, Error, HttpResponse, HttpServer};
6+
use juniper::{
7+
http::{GraphQLBatchRequest, GraphQLRequest},
8+
tests::{model::Database, schema::Query},
9+
DefaultScalarValue, EmptyMutation, EmptySubscription, RootNode,
10+
};
11+
use juniper_actix::{
12+
get_graphql_handler, graphiql_handler as gqli_handler, playground_handler as play_handler,
13+
post_graphql_handler,
14+
};
15+
16+
type Schema = RootNode<'static, Query, EmptyMutation<Database>, EmptySubscription<Database>>;
17+
18+
fn schema() -> Schema {
19+
Schema::new(
20+
Query,
21+
EmptyMutation::<Database>::new(),
22+
EmptySubscription::<Database>::new(),
23+
)
24+
}
25+
26+
async fn graphiql_handler() -> Result<HttpResponse, Error> {
27+
gqli_handler("/").await
28+
}
29+
async fn playground_handler() -> Result<HttpResponse, Error> {
30+
play_handler("/", None).await
31+
}
32+
async fn graphql(
33+
req: web::Json<GraphQLBatchRequest<DefaultScalarValue>>,
34+
schema: web::Data<Schema>,
35+
) -> Result<HttpResponse, Error> {
36+
let context = Database::new();
37+
post_graphql_handler(&schema, &context, req).await
38+
}
39+
40+
async fn graphql_get(
41+
req: web::Query<GraphQLRequest<DefaultScalarValue>>,
42+
schema: web::Data<Schema>,
43+
) -> Result<HttpResponse, Error> {
44+
let context = Database::new();
45+
get_graphql_handler(&schema, &context, req).await
46+
}
47+
48+
#[actix_rt::main]
49+
async fn main() -> std::io::Result<()> {
50+
::std::env::set_var("RUST_LOG", "actix_web=info");
51+
env_logger::init();
52+
let server = HttpServer::new(move || {
53+
App::new()
54+
.data(schema())
55+
.wrap(middleware::Compress::default())
56+
.wrap(middleware::Logger::default())
57+
.wrap(
58+
Cors::new()
59+
.allowed_methods(vec!["POST", "GET"])
60+
.supports_credentials()
61+
.max_age(3600)
62+
.finish(),
63+
)
64+
.service(
65+
web::resource("/")
66+
.route(web::post().to(graphql))
67+
.route(web::get().to(graphql_get)),
68+
)
69+
.service(web::resource("/playground").route(web::get().to(playground_handler)))
70+
.service(web::resource("/graphiql").route(web::get().to(graphiql_handler)))
71+
});
72+
server.bind("127.0.0.1:8080").unwrap().run().await
73+
}

0 commit comments

Comments
 (0)