2020-03-18 22:31:36 -05:00
|
|
|
//! This example demonstrates asynchronous subscriptions with warp and tokio 0.2
|
|
|
|
|
2020-06-30 10:13:15 -05:00
|
|
|
use std::{env, pin::Pin, sync::Arc, time::Duration};
|
2020-03-18 22:31:36 -05:00
|
|
|
|
2020-07-29 03:23:44 -05:00
|
|
|
use futures::{FutureExt as _, Stream};
|
2020-11-06 20:15:18 -06:00
|
|
|
use juniper::{
|
|
|
|
graphql_object, graphql_subscription, DefaultScalarValue, EmptyMutation, FieldError,
|
2021-08-11 09:41:49 -05:00
|
|
|
GraphQLEnum, RootNode, Value,
|
2020-11-06 20:15:18 -06:00
|
|
|
};
|
2020-07-29 03:23:44 -05:00
|
|
|
use juniper_graphql_ws::ConnectionConfig;
|
|
|
|
use juniper_warp::{playground_filter, subscriptions::serve_graphql_ws};
|
2020-03-18 22:31:36 -05:00
|
|
|
use warp::{http::Response, Filter};
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
struct Context {}
|
|
|
|
|
|
|
|
impl juniper::Context for Context {}
|
|
|
|
|
2020-11-06 20:15:18 -06:00
|
|
|
#[derive(Clone, Copy, GraphQLEnum)]
|
2020-03-18 22:31:36 -05:00
|
|
|
enum UserKind {
|
|
|
|
Admin,
|
|
|
|
User,
|
|
|
|
Guest,
|
|
|
|
}
|
|
|
|
|
|
|
|
struct User {
|
|
|
|
id: i32,
|
|
|
|
kind: UserKind,
|
|
|
|
name: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Field resolvers implementation
|
2020-11-06 20:15:18 -06:00
|
|
|
#[graphql_object(context = Context)]
|
2020-03-18 22:31:36 -05:00
|
|
|
impl User {
|
|
|
|
fn id(&self) -> i32 {
|
|
|
|
self.id
|
|
|
|
}
|
|
|
|
|
|
|
|
fn kind(&self) -> UserKind {
|
|
|
|
self.kind
|
|
|
|
}
|
|
|
|
|
|
|
|
fn name(&self) -> &str {
|
|
|
|
&self.name
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn friends(&self) -> Vec<User> {
|
|
|
|
if self.id == 1 {
|
|
|
|
return vec![
|
|
|
|
User {
|
|
|
|
id: 11,
|
|
|
|
kind: UserKind::User,
|
|
|
|
name: "user11".into(),
|
|
|
|
},
|
|
|
|
User {
|
|
|
|
id: 12,
|
|
|
|
kind: UserKind::Admin,
|
|
|
|
name: "user12".into(),
|
|
|
|
},
|
|
|
|
User {
|
|
|
|
id: 13,
|
|
|
|
kind: UserKind::Guest,
|
|
|
|
name: "user13".into(),
|
|
|
|
},
|
|
|
|
];
|
|
|
|
} else if self.id == 2 {
|
|
|
|
return vec![User {
|
|
|
|
id: 21,
|
|
|
|
kind: UserKind::User,
|
|
|
|
name: "user21".into(),
|
|
|
|
}];
|
|
|
|
} else if self.id == 3 {
|
|
|
|
return vec![
|
|
|
|
User {
|
|
|
|
id: 31,
|
|
|
|
kind: UserKind::User,
|
|
|
|
name: "user31".into(),
|
|
|
|
},
|
|
|
|
User {
|
|
|
|
id: 32,
|
|
|
|
kind: UserKind::Guest,
|
|
|
|
name: "user32".into(),
|
|
|
|
},
|
|
|
|
];
|
|
|
|
} else {
|
|
|
|
return vec![];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Query;
|
|
|
|
|
2020-11-06 20:15:18 -06:00
|
|
|
#[graphql_object(context = Context)]
|
2020-03-18 22:31:36 -05:00
|
|
|
impl Query {
|
|
|
|
async fn users(id: i32) -> Vec<User> {
|
|
|
|
vec![User {
|
|
|
|
id,
|
|
|
|
kind: UserKind::Admin,
|
|
|
|
name: "User Name".into(),
|
|
|
|
}]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type UsersStream = Pin<Box<dyn Stream<Item = Result<User, FieldError>> + Send>>;
|
|
|
|
|
|
|
|
struct Subscription;
|
|
|
|
|
2020-11-06 20:15:18 -06:00
|
|
|
#[graphql_subscription(context = Context)]
|
2020-03-18 22:31:36 -05:00
|
|
|
impl Subscription {
|
|
|
|
async fn users() -> UsersStream {
|
|
|
|
let mut counter = 0;
|
2021-06-29 01:22:45 -05:00
|
|
|
let mut interval = tokio::time::interval(Duration::from_secs(5));
|
|
|
|
let stream = async_stream::stream! {
|
2020-03-18 22:31:36 -05:00
|
|
|
counter += 1;
|
2021-06-29 01:22:45 -05:00
|
|
|
loop {
|
|
|
|
interval.tick().await;
|
|
|
|
if counter == 2 {
|
|
|
|
yield Err(FieldError::new(
|
|
|
|
"some field error from handler",
|
|
|
|
Value::Scalar(DefaultScalarValue::String(
|
|
|
|
"some additional string".to_string(),
|
|
|
|
)),
|
|
|
|
))
|
|
|
|
} else {
|
|
|
|
yield Ok(User {
|
|
|
|
id: counter,
|
|
|
|
kind: UserKind::Admin,
|
|
|
|
name: "stream user".to_string(),
|
|
|
|
})
|
|
|
|
}
|
2020-03-18 22:31:36 -05:00
|
|
|
}
|
2021-06-29 01:22:45 -05:00
|
|
|
};
|
2020-03-18 22:31:36 -05:00
|
|
|
|
|
|
|
Box::pin(stream)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type Schema = RootNode<'static, Query, EmptyMutation<Context>, Subscription>;
|
|
|
|
|
|
|
|
fn schema() -> Schema {
|
|
|
|
Schema::new(Query, EmptyMutation::new(), Subscription)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::main]
|
|
|
|
async fn main() {
|
2020-06-30 10:13:15 -05:00
|
|
|
env::set_var("RUST_LOG", "warp_subscriptions");
|
2020-03-18 22:31:36 -05:00
|
|
|
env_logger::init();
|
|
|
|
|
2020-06-30 10:13:15 -05:00
|
|
|
let log = warp::log("warp_subscriptions");
|
2020-03-18 22:31:36 -05:00
|
|
|
|
|
|
|
let homepage = warp::path::end().map(|| {
|
|
|
|
Response::builder()
|
|
|
|
.header("content-type", "text/html")
|
2020-03-20 11:11:06 -05:00
|
|
|
.body("<html><h1>juniper_subscriptions demo</h1><div>visit <a href=\"/playground\">graphql playground</a></html>".to_string())
|
2020-03-18 22:31:36 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
let qm_schema = schema();
|
|
|
|
let qm_state = warp::any().map(move || Context {});
|
|
|
|
let qm_graphql_filter = juniper_warp::make_graphql_filter(qm_schema, qm_state.boxed());
|
|
|
|
|
2020-07-29 03:23:44 -05:00
|
|
|
let root_node = Arc::new(schema());
|
2020-03-18 22:31:36 -05:00
|
|
|
|
|
|
|
log::info!("Listening on 127.0.0.1:8080");
|
|
|
|
|
|
|
|
let routes = (warp::path("subscriptions")
|
|
|
|
.and(warp::ws())
|
2020-07-29 03:23:44 -05:00
|
|
|
.map(move |ws: warp::ws::Ws| {
|
|
|
|
let root_node = root_node.clone();
|
|
|
|
ws.on_upgrade(move |websocket| async move {
|
|
|
|
serve_graphql_ws(websocket, root_node, ConnectionConfig::new(Context {}))
|
|
|
|
.map(|r| {
|
|
|
|
if let Err(e) = r {
|
|
|
|
println!("Websocket error: {}", e);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.await
|
|
|
|
})
|
|
|
|
}))
|
2020-03-30 22:43:00 -05:00
|
|
|
.map(|reply| {
|
|
|
|
// TODO#584: remove this workaround
|
|
|
|
warp::reply::with_header(reply, "Sec-WebSocket-Protocol", "graphql-ws")
|
|
|
|
})
|
2020-03-18 22:31:36 -05:00
|
|
|
.or(warp::post()
|
|
|
|
.and(warp::path("graphql"))
|
|
|
|
.and(qm_graphql_filter))
|
|
|
|
.or(warp::get()
|
|
|
|
.and(warp::path("playground"))
|
|
|
|
.and(playground_filter("/graphql", Some("/subscriptions"))))
|
|
|
|
.or(homepage)
|
|
|
|
.with(log);
|
|
|
|
|
|
|
|
warp::serve(routes).run(([127, 0, 0, 1], 8080)).await;
|
|
|
|
}
|