The subscription field now returns a stream
This commit is contained in:
parent
4225a03de0
commit
b88ac80084
|
@ -1,109 +1,15 @@
|
||||||
|
use actix::clock::Duration;
|
||||||
use actix_web::{web, App, HttpServer};
|
use actix_web::{web, App, HttpServer};
|
||||||
use async_graphql::{publish, Context, FieldResult, Schema, ID};
|
use async_graphql::{EmptyMutation, Schema};
|
||||||
use futures::lock::Mutex;
|
use futures::{Stream, StreamExt};
|
||||||
use slab::Slab;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct Book {
|
|
||||||
id: ID,
|
|
||||||
name: String,
|
|
||||||
author: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_graphql::Object]
|
|
||||||
impl Book {
|
|
||||||
#[field]
|
|
||||||
async fn id(&self) -> &str {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
#[field]
|
|
||||||
async fn name(&self) -> &str {
|
|
||||||
&self.name
|
|
||||||
}
|
|
||||||
|
|
||||||
#[field]
|
|
||||||
async fn author(&self) -> &str {
|
|
||||||
&self.author
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Storage = Arc<Mutex<Slab<Book>>>;
|
|
||||||
|
|
||||||
struct QueryRoot;
|
struct QueryRoot;
|
||||||
|
|
||||||
#[async_graphql::Object(cache_control(max_age = 5))]
|
#[async_graphql::Object]
|
||||||
impl QueryRoot {
|
impl QueryRoot {
|
||||||
#[field]
|
#[field]
|
||||||
async fn books(&self, ctx: &Context<'_>) -> Vec<Book> {
|
async fn value(&self) -> i32 {
|
||||||
let books = ctx.data::<Storage>().lock().await;
|
0
|
||||||
books.iter().map(|(_, book)| book).cloned().collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MutationRoot;
|
|
||||||
|
|
||||||
#[async_graphql::Object]
|
|
||||||
impl MutationRoot {
|
|
||||||
#[field]
|
|
||||||
async fn create_book(&self, ctx: &Context<'_>, name: String, author: String) -> ID {
|
|
||||||
let mut books = ctx.data::<Storage>().lock().await;
|
|
||||||
let entry = books.vacant_entry();
|
|
||||||
let id: ID = entry.key().into();
|
|
||||||
let book = Book {
|
|
||||||
id: id.clone(),
|
|
||||||
name,
|
|
||||||
author,
|
|
||||||
};
|
|
||||||
entry.insert(book);
|
|
||||||
publish(BookChanged {
|
|
||||||
mutation_type: MutationType::Created,
|
|
||||||
id: id.clone(),
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
id
|
|
||||||
}
|
|
||||||
|
|
||||||
#[field]
|
|
||||||
async fn delete_book(&self, ctx: &Context<'_>, id: ID) -> FieldResult<bool> {
|
|
||||||
let mut books = ctx.data::<Storage>().lock().await;
|
|
||||||
let id = id.parse::<usize>()?;
|
|
||||||
if books.contains(id) {
|
|
||||||
books.remove(id);
|
|
||||||
publish(BookChanged {
|
|
||||||
mutation_type: MutationType::Deleted,
|
|
||||||
id: id.into(),
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
Ok(true)
|
|
||||||
} else {
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_graphql::Enum]
|
|
||||||
enum MutationType {
|
|
||||||
Created,
|
|
||||||
Deleted,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct BookChanged {
|
|
||||||
mutation_type: MutationType,
|
|
||||||
id: ID,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_graphql::Object]
|
|
||||||
impl BookChanged {
|
|
||||||
#[field]
|
|
||||||
async fn mutation_type(&self) -> &MutationType {
|
|
||||||
&self.mutation_type
|
|
||||||
}
|
|
||||||
|
|
||||||
#[field]
|
|
||||||
async fn id(&self) -> &ID {
|
|
||||||
&self.id
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,20 +18,19 @@ struct SubscriptionRoot;
|
||||||
#[async_graphql::Subscription]
|
#[async_graphql::Subscription]
|
||||||
impl SubscriptionRoot {
|
impl SubscriptionRoot {
|
||||||
#[field]
|
#[field]
|
||||||
fn books(&self, changed: &BookChanged, mutation_type: Option<MutationType>) -> bool {
|
fn interval(&self, n: i32) -> impl Stream<Item = i32> {
|
||||||
if let Some(mutation_type) = mutation_type {
|
let mut value = 0;
|
||||||
return changed.mutation_type == mutation_type;
|
actix_rt::time::interval(Duration::from_secs(1)).map(move |_| {
|
||||||
}
|
value += n;
|
||||||
true
|
value
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::main]
|
#[actix_rt::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
let schema = Schema::build(QueryRoot, MutationRoot, SubscriptionRoot)
|
let schema = Schema::new(QueryRoot, EmptyMutation, SubscriptionRoot);
|
||||||
.data(Storage::default())
|
|
||||||
.finish();
|
|
||||||
let handler = async_graphql_actix_web::HandlerBuilder::new(schema)
|
let handler = async_graphql_actix_web::HandlerBuilder::new(schema)
|
||||||
.enable_ui("http://localhost:8000", Some("ws://localhost:8000"))
|
.enable_ui("http://localhost:8000", Some("ws://localhost:8000"))
|
||||||
.enable_subscription()
|
.enable_subscription()
|
||||||
|
|
|
@ -11,10 +11,7 @@ use actix_web::web::{BytesMut, Payload};
|
||||||
use actix_web::{web, FromRequest, HttpRequest, HttpResponse, Responder};
|
use actix_web::{web, FromRequest, HttpRequest, HttpResponse, Responder};
|
||||||
use actix_web_actors::ws;
|
use actix_web_actors::ws;
|
||||||
use async_graphql::http::{GQLRequest, GQLResponse};
|
use async_graphql::http::{GQLRequest, GQLResponse};
|
||||||
use async_graphql::{
|
use async_graphql::{ObjectType, QueryBuilder, Schema, SubscriptionType};
|
||||||
ObjectType, QueryBuilder, Schema, SubscriptionConnectionBuilder, SubscriptionType,
|
|
||||||
WebSocketTransport,
|
|
||||||
};
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
|
@ -30,13 +27,6 @@ type BoxOnRequestFn<Query, Mutation, Subscription> = Arc<
|
||||||
) -> QueryBuilder<Query, Mutation, Subscription>,
|
) -> QueryBuilder<Query, Mutation, Subscription>,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
type BoxOnConnectFn<Query, Mutation, Subscription> = Arc<
|
|
||||||
dyn Fn(
|
|
||||||
&HttpRequest,
|
|
||||||
SubscriptionConnectionBuilder<Query, Mutation, Subscription, WebSocketTransport>,
|
|
||||||
) -> SubscriptionConnectionBuilder<Query, Mutation, Subscription, WebSocketTransport>,
|
|
||||||
>;
|
|
||||||
|
|
||||||
/// Actix-web handler builder
|
/// Actix-web handler builder
|
||||||
pub struct HandlerBuilder<Query, Mutation, Subscription> {
|
pub struct HandlerBuilder<Query, Mutation, Subscription> {
|
||||||
schema: Schema<Query, Mutation, Subscription>,
|
schema: Schema<Query, Mutation, Subscription>,
|
||||||
|
@ -45,7 +35,6 @@ pub struct HandlerBuilder<Query, Mutation, Subscription> {
|
||||||
enable_subscription: bool,
|
enable_subscription: bool,
|
||||||
enable_ui: Option<(String, Option<String>)>,
|
enable_ui: Option<(String, Option<String>)>,
|
||||||
on_request: Option<BoxOnRequestFn<Query, Mutation, Subscription>>,
|
on_request: Option<BoxOnRequestFn<Query, Mutation, Subscription>>,
|
||||||
on_connect: Option<BoxOnConnectFn<Query, Mutation, Subscription>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Query, Mutation, Subscription> HandlerBuilder<Query, Mutation, Subscription>
|
impl<Query, Mutation, Subscription> HandlerBuilder<Query, Mutation, Subscription>
|
||||||
|
@ -63,7 +52,6 @@ where
|
||||||
enable_subscription: false,
|
enable_subscription: false,
|
||||||
enable_ui: None,
|
enable_ui: None,
|
||||||
on_request: None,
|
on_request: None,
|
||||||
on_connect: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,24 +110,6 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// When there is a new subscription connection, you can use this closure to append your own data to the `SubscriptionConnectionBuilder`.
|
|
||||||
pub fn on_connect<
|
|
||||||
F: Fn(
|
|
||||||
&HttpRequest,
|
|
||||||
SubscriptionConnectionBuilder<Query, Mutation, Subscription, WebSocketTransport>,
|
|
||||||
)
|
|
||||||
-> SubscriptionConnectionBuilder<Query, Mutation, Subscription, WebSocketTransport>
|
|
||||||
+ 'static,
|
|
||||||
>(
|
|
||||||
self,
|
|
||||||
f: F,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
on_connect: Some(Arc::new(f)),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create an HTTP handler.
|
/// Create an HTTP handler.
|
||||||
pub fn build(
|
pub fn build(
|
||||||
self,
|
self,
|
||||||
|
@ -155,13 +125,11 @@ where
|
||||||
let enable_ui = self.enable_ui;
|
let enable_ui = self.enable_ui;
|
||||||
let enable_subscription = self.enable_subscription;
|
let enable_subscription = self.enable_subscription;
|
||||||
let on_request = self.on_request;
|
let on_request = self.on_request;
|
||||||
let on_connect = self.on_connect;
|
|
||||||
|
|
||||||
move |req: HttpRequest, payload: Payload| {
|
move |req: HttpRequest, payload: Payload| {
|
||||||
let schema = schema.clone();
|
let schema = schema.clone();
|
||||||
let enable_ui = enable_ui.clone();
|
let enable_ui = enable_ui.clone();
|
||||||
let on_request = on_request.clone();
|
let on_request = on_request.clone();
|
||||||
let on_connect = on_connect.clone();
|
|
||||||
|
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
if req.method() == Method::GET {
|
if req.method() == Method::GET {
|
||||||
|
@ -170,11 +138,7 @@ where
|
||||||
if let Ok(s) = s.to_str() {
|
if let Ok(s) = s.to_str() {
|
||||||
if s.to_ascii_lowercase().contains("websocket") {
|
if s.to_ascii_lowercase().contains("websocket") {
|
||||||
return ws::start_with_protocols(
|
return ws::start_with_protocols(
|
||||||
WsSession::new(
|
WsSession::new(schema.clone()),
|
||||||
schema.clone(),
|
|
||||||
req.clone(),
|
|
||||||
on_connect.clone(),
|
|
||||||
),
|
|
||||||
&["graphql-ws"],
|
&["graphql-ws"],
|
||||||
&req,
|
&req,
|
||||||
payload,
|
payload,
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
use crate::BoxOnConnectFn;
|
|
||||||
use actix::{
|
use actix::{
|
||||||
Actor, ActorContext, ActorFuture, AsyncContext, ContextFutureSpawner, StreamHandler, WrapFuture,
|
Actor, ActorContext, ActorFuture, AsyncContext, ContextFutureSpawner, StreamHandler, WrapFuture,
|
||||||
};
|
};
|
||||||
use actix_web::HttpRequest;
|
|
||||||
use actix_web_actors::ws::{Message, ProtocolError, WebsocketContext};
|
use actix_web_actors::ws::{Message, ProtocolError, WebsocketContext};
|
||||||
use async_graphql::{ObjectType, Schema, SubscriptionType, WebSocketTransport};
|
use async_graphql::{ObjectType, Schema, SubscriptionType, WebSocketTransport};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
@ -11,11 +9,9 @@ use futures::SinkExt;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
pub struct WsSession<Query, Mutation, Subscription> {
|
pub struct WsSession<Query, Mutation, Subscription> {
|
||||||
req: HttpRequest,
|
|
||||||
schema: Schema<Query, Mutation, Subscription>,
|
schema: Schema<Query, Mutation, Subscription>,
|
||||||
hb: Instant,
|
hb: Instant,
|
||||||
sink: Option<mpsc::Sender<Bytes>>,
|
sink: Option<mpsc::Sender<Bytes>>,
|
||||||
on_connect: Option<BoxOnConnectFn<Query, Mutation, Subscription>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Query, Mutation, Subscription> WsSession<Query, Mutation, Subscription>
|
impl<Query, Mutation, Subscription> WsSession<Query, Mutation, Subscription>
|
||||||
|
@ -24,17 +20,11 @@ where
|
||||||
Mutation: ObjectType + Send + Sync + 'static,
|
Mutation: ObjectType + Send + Sync + 'static,
|
||||||
Subscription: SubscriptionType + Send + Sync + 'static,
|
Subscription: SubscriptionType + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
pub fn new(
|
pub fn new(schema: Schema<Query, Mutation, Subscription>) -> Self {
|
||||||
schema: Schema<Query, Mutation, Subscription>,
|
|
||||||
req: HttpRequest,
|
|
||||||
on_connect: Option<BoxOnConnectFn<Query, Mutation, Subscription>>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
req,
|
|
||||||
schema,
|
schema,
|
||||||
hb: Instant::now(),
|
hb: Instant::now(),
|
||||||
sink: None,
|
sink: None,
|
||||||
on_connect,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,24 +48,9 @@ where
|
||||||
fn started(&mut self, ctx: &mut Self::Context) {
|
fn started(&mut self, ctx: &mut Self::Context) {
|
||||||
self.hb(ctx);
|
self.hb(ctx);
|
||||||
let schema = self.schema.clone();
|
let schema = self.schema.clone();
|
||||||
let on_connect = self.on_connect.clone();
|
let (sink, stream) = schema.subscription_connection(WebSocketTransport::default());
|
||||||
let req = self.req.clone();
|
ctx.add_stream(stream);
|
||||||
async move {
|
self.sink = Some(sink);
|
||||||
let mut builder = schema
|
|
||||||
.clone()
|
|
||||||
.subscription_connection(WebSocketTransport::default());
|
|
||||||
if let Some(on_connect) = on_connect {
|
|
||||||
builder = on_connect(&req, builder);
|
|
||||||
}
|
|
||||||
builder.build().await
|
|
||||||
}
|
|
||||||
.into_actor(self)
|
|
||||||
.then(|(sink, stream), actor, ctx| {
|
|
||||||
actor.sink = Some(sink);
|
|
||||||
ctx.add_stream(stream);
|
|
||||||
async {}.into_actor(actor)
|
|
||||||
})
|
|
||||||
.wait(ctx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ use crate::utils::{build_value_repr, check_reserved_name, get_crate_name};
|
||||||
use inflector::Inflector;
|
use inflector::Inflector;
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{Error, FnArg, ImplItem, ItemImpl, Pat, Result, ReturnType, Type};
|
use syn::{Error, FnArg, ImplItem, ItemImpl, Pat, Result, ReturnType, Type, TypeImplTrait};
|
||||||
|
|
||||||
pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<TokenStream> {
|
pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<TokenStream> {
|
||||||
let crate_name = get_crate_name(object_args.internal);
|
let crate_name = get_crate_name(object_args.internal);
|
||||||
|
@ -32,8 +32,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
||||||
.map(|s| quote! {Some(#s)})
|
.map(|s| quote! {Some(#s)})
|
||||||
.unwrap_or_else(|| quote! {None});
|
.unwrap_or_else(|| quote! {None});
|
||||||
|
|
||||||
let mut create_types = Vec::new();
|
let mut create_stream = Vec::new();
|
||||||
let mut filters = Vec::new();
|
|
||||||
let mut schema_fields = Vec::new();
|
let mut schema_fields = Vec::new();
|
||||||
|
|
||||||
for item in &mut item_impl.items {
|
for item in &mut item_impl.items {
|
||||||
|
@ -55,32 +54,10 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
||||||
.map(|s| quote! {Some(#s)})
|
.map(|s| quote! {Some(#s)})
|
||||||
.unwrap_or_else(|| quote! {None});
|
.unwrap_or_else(|| quote! {None});
|
||||||
|
|
||||||
if method.sig.inputs.len() < 2 {
|
|
||||||
return Err(Error::new_spanned(
|
|
||||||
&method.sig.inputs,
|
|
||||||
"The filter function needs at least two arguments",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if method.sig.asyncness.is_some() {
|
if method.sig.asyncness.is_some() {
|
||||||
return Err(Error::new_spanned(
|
return Err(Error::new_spanned(
|
||||||
&method.sig.inputs,
|
&method.sig.asyncness,
|
||||||
"The filter function must be synchronous",
|
"The subscription stream function must be synchronous",
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut res_typ_ok = false;
|
|
||||||
if let ReturnType::Type(_, res_ty) = &method.sig.output {
|
|
||||||
if let Type::Path(p) = res_ty.as_ref() {
|
|
||||||
if p.path.is_ident("bool") {
|
|
||||||
res_typ_ok = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !res_typ_ok {
|
|
||||||
return Err(Error::new_spanned(
|
|
||||||
&method.sig.output,
|
|
||||||
"The filter function must return a boolean value",
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,23 +71,9 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let ty = if let FnArg::Typed(ty) = &method.sig.inputs[1] {
|
|
||||||
match ty.ty.as_ref() {
|
|
||||||
Type::Reference(r) => r.elem.as_ref().clone(),
|
|
||||||
_ => {
|
|
||||||
return Err(Error::new_spanned(ty, "Incorrect object type"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(Error::new_spanned(
|
|
||||||
&method.sig.inputs[1],
|
|
||||||
"Incorrect object type",
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut args = Vec::new();
|
let mut args = Vec::new();
|
||||||
|
|
||||||
for arg in method.sig.inputs.iter_mut().skip(2) {
|
for arg in method.sig.inputs.iter_mut().skip(1) {
|
||||||
if let FnArg::Typed(pat) = arg {
|
if let FnArg::Typed(pat) = arg {
|
||||||
match (&*pat.pat, &*pat.ty) {
|
match (&*pat.pat, &*pat.ty) {
|
||||||
(Pat::Ident(arg_ident), Type::Path(arg_ty)) => {
|
(Pat::Ident(arg_ident), Type::Path(arg_ty)) => {
|
||||||
|
@ -181,10 +144,26 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
||||||
};
|
};
|
||||||
|
|
||||||
get_params.push(quote! {
|
get_params.push(quote! {
|
||||||
let #ident: #ty = ctx_field.param_value(#name, field.position, #default)?;
|
let #ident: #ty = ctx.param_value(#name, ctx.position, #default)?;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let stream_ty = match &method.sig.output {
|
||||||
|
ReturnType::Default => {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
&method.sig.output,
|
||||||
|
"Must be return a stream type",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
ReturnType::Type(_, ty) => {
|
||||||
|
if let Type::ImplTrait(TypeImplTrait { bounds, .. }) = ty.as_ref() {
|
||||||
|
quote! { #bounds }
|
||||||
|
} else {
|
||||||
|
quote! { #ty }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
schema_fields.push(quote! {
|
schema_fields.push(quote! {
|
||||||
fields.insert(#field_name.to_string(), #crate_name::registry::Field {
|
fields.insert(#field_name.to_string(), #crate_name::registry::Field {
|
||||||
name: #field_name.to_string(),
|
name: #field_name.to_string(),
|
||||||
|
@ -194,30 +173,46 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
||||||
#(#schema_args)*
|
#(#schema_args)*
|
||||||
args
|
args
|
||||||
},
|
},
|
||||||
ty: <#ty as #crate_name::Type>::create_type_info(registry),
|
ty: <#stream_ty as #crate_name::futures::stream::Stream>::Item::create_type_info(registry),
|
||||||
deprecation: #field_deprecation,
|
deprecation: #field_deprecation,
|
||||||
cache_control: Default::default(),
|
cache_control: Default::default(),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
create_types.push(quote! {
|
create_stream.push(quote! {
|
||||||
if field.name.as_str() == #field_name {
|
if ctx.name.as_str() == #field_name {
|
||||||
types.insert(std::any::TypeId::of::<#ty>(), field.clone());
|
let field_name = ctx.result_name().to_string();
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
filters.push(quote! {
|
|
||||||
if let Some(msg) = msg.downcast_ref::<#ty>() {
|
|
||||||
#(#get_params)*
|
#(#get_params)*
|
||||||
if self.#ident(msg, #(#use_params)*) {
|
let field_selection_set = std::sync::Arc::new(ctx.selection_set.clone());
|
||||||
let ctx_selection_set = ctx_field.with_selection_set(&field.selection_set);
|
let schema = schema.clone();
|
||||||
let value =
|
let pos = ctx.position;
|
||||||
#crate_name::OutputValueType::resolve(msg, &ctx_selection_set, field.position).await?;
|
let environment = environment.clone();
|
||||||
let mut res = #crate_name::serde_json::Map::new();
|
let stream = #crate_name::futures::stream::StreamExt::then(self.#ident(#(#use_params)*).fuse(), move |msg| {
|
||||||
res.insert(ctx_field.result_name().to_string(), value);
|
let environment = environment.clone();
|
||||||
return Ok(Some(res.into()));
|
let field_selection_set = field_selection_set.clone();
|
||||||
}
|
let schema = schema.clone();
|
||||||
|
async move {
|
||||||
|
let resolve_id = std::sync::atomic::AtomicUsize::default();
|
||||||
|
let ctx_selection_set = environment.create_context(
|
||||||
|
&*field_selection_set,
|
||||||
|
Some(#crate_name::QueryPathNode {
|
||||||
|
parent: None,
|
||||||
|
segment: #crate_name::QueryPathSegment::Name("time"),
|
||||||
|
}),
|
||||||
|
&resolve_id,
|
||||||
|
schema.registry(),
|
||||||
|
schema.data(),
|
||||||
|
);
|
||||||
|
#crate_name::OutputValueType::resolve(&msg, &ctx_selection_set, pos).await
|
||||||
|
}
|
||||||
|
}).
|
||||||
|
filter_map(move |res| {
|
||||||
|
let res = res.ok().map(|value| {
|
||||||
|
#crate_name::serde_json::json!({ &field_name: value })
|
||||||
|
});
|
||||||
|
async move { res }
|
||||||
|
});
|
||||||
|
return Ok(Box::pin(stream));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -234,6 +229,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
||||||
std::borrow::Cow::Borrowed(#gql_typename)
|
std::borrow::Cow::Borrowed(#gql_typename)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(bare_trait_objects)]
|
||||||
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> String {
|
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> String {
|
||||||
registry.create_type::<Self, _>(|registry| #crate_name::registry::Type::Object {
|
registry.create_type::<Self, _>(|registry| #crate_name::registry::Type::Object {
|
||||||
name: #gql_typename.to_string(),
|
name: #gql_typename.to_string(),
|
||||||
|
@ -250,26 +246,24 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
||||||
|
|
||||||
#[#crate_name::async_trait::async_trait]
|
#[#crate_name::async_trait::async_trait]
|
||||||
impl #crate_name::SubscriptionType for SubscriptionRoot {
|
impl #crate_name::SubscriptionType for SubscriptionRoot {
|
||||||
fn create_type(field: &#crate_name::graphql_parser::query::Field, types: &mut std::collections::HashMap<std::any::TypeId, #crate_name::graphql_parser::query::Field>) -> #crate_name::Result<()> {
|
#[allow(unused_variables)]
|
||||||
#(#create_types)*
|
#[allow(bare_trait_objects)]
|
||||||
Err(#crate_name::QueryError::FieldNotFound {
|
fn create_field_stream<Query, Mutation>(
|
||||||
field_name: field.name.clone(),
|
|
||||||
object: #gql_typename.to_string(),
|
|
||||||
}.into_error(field.position))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn resolve(
|
|
||||||
&self,
|
&self,
|
||||||
ctx: &#crate_name::ContextBase<'_, ()>,
|
ctx: &#crate_name::Context<'_>,
|
||||||
types: &std::collections::HashMap<std::any::TypeId, #crate_name::graphql_parser::query::Field>,
|
schema: &#crate_name::Schema<Query, Mutation, Self>,
|
||||||
msg: &(dyn std::any::Any + Send + Sync),
|
environment: std::sync::Arc<#crate_name::Environment>,
|
||||||
) -> #crate_name::Result<Option<#crate_name::serde_json::Value>> {
|
) -> #crate_name::Result<std::pin::Pin<Box<dyn futures::Stream<Item = #crate_name::serde_json::Value>>>>
|
||||||
let tid = msg.type_id();
|
where
|
||||||
if let Some(field) = types.get(&tid) {
|
Query: #crate_name::ObjectType + Send + Sync + 'static,
|
||||||
let ctx_field = ctx.with_field(field);
|
Mutation: #crate_name::ObjectType + Send + Sync + 'static,
|
||||||
#(#filters)*
|
Self: Send + Sync + 'static + Sized,
|
||||||
}
|
{
|
||||||
Ok(None)
|
#(#create_stream)*
|
||||||
|
Err(#crate_name::QueryError::FieldNotFound {
|
||||||
|
field_name: ctx.name.clone(),
|
||||||
|
object: #gql_typename.to_string(),
|
||||||
|
}.into_error(ctx.position))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -242,7 +242,48 @@ impl<'a, T> Deref for ContextBase<'a, T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct Environment {
|
||||||
|
pub variables: Variables,
|
||||||
|
pub variable_definitions: Vec<VariableDefinition>,
|
||||||
|
pub fragments: HashMap<String, FragmentDefinition>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Environment {
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn create_context<'a, T>(
|
||||||
|
&'a self,
|
||||||
|
item: T,
|
||||||
|
path_node: Option<QueryPathNode<'a>>,
|
||||||
|
resolve_id: &'a AtomicUsize,
|
||||||
|
registry: &'a Registry,
|
||||||
|
data: &'a Data,
|
||||||
|
) -> ContextBase<'a, T> {
|
||||||
|
ContextBase {
|
||||||
|
path_node,
|
||||||
|
resolve_id,
|
||||||
|
extensions: &[],
|
||||||
|
item,
|
||||||
|
variables: &self.variables,
|
||||||
|
variable_definitions: &self.variable_definitions,
|
||||||
|
registry,
|
||||||
|
data,
|
||||||
|
ctx_data: None,
|
||||||
|
fragments: &self.fragments,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, T> ContextBase<'a, T> {
|
impl<'a, T> ContextBase<'a, T> {
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn create_environment(&self) -> Environment {
|
||||||
|
Environment {
|
||||||
|
variables: self.variables.clone(),
|
||||||
|
variable_definitions: self.variable_definitions.to_vec(),
|
||||||
|
fragments: self.fragments.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn get_resolve_id(&self) -> usize {
|
pub fn get_resolve_id(&self) -> usize {
|
||||||
self.resolve_id
|
self.resolve_id
|
||||||
|
|
12
src/lib.rs
12
src/lib.rs
|
@ -76,7 +76,6 @@ extern crate serde_derive;
|
||||||
mod base;
|
mod base;
|
||||||
mod context;
|
mod context;
|
||||||
mod error;
|
mod error;
|
||||||
pub mod extensions;
|
|
||||||
mod model;
|
mod model;
|
||||||
mod mutation_resolver;
|
mod mutation_resolver;
|
||||||
mod query;
|
mod query;
|
||||||
|
@ -87,7 +86,7 @@ mod subscription;
|
||||||
mod types;
|
mod types;
|
||||||
mod validation;
|
mod validation;
|
||||||
|
|
||||||
/// Input value validators
|
pub mod extensions;
|
||||||
pub mod validators;
|
pub mod validators;
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
@ -95,6 +94,8 @@ pub use anyhow;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use async_trait;
|
pub use async_trait;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
pub use futures;
|
||||||
|
#[doc(hidden)]
|
||||||
pub use graphql_parser;
|
pub use graphql_parser;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use serde_json;
|
pub use serde_json;
|
||||||
|
@ -102,17 +103,16 @@ pub use serde_json;
|
||||||
pub mod http;
|
pub mod http;
|
||||||
|
|
||||||
pub use base::{Scalar, Type};
|
pub use base::{Scalar, Type};
|
||||||
pub use context::{Context, QueryPathSegment, Variables};
|
pub use context::{Context, Environment, QueryPathNode, QueryPathSegment, Variables};
|
||||||
pub use error::{Error, ErrorExtensions, FieldError, FieldResult, QueryError, ResultExt};
|
pub use error::{Error, ErrorExtensions, FieldError, FieldResult, QueryError, ResultExt};
|
||||||
pub use graphql_parser::query::Value;
|
pub use graphql_parser::query::Value;
|
||||||
pub use graphql_parser::Pos;
|
pub use graphql_parser::Pos;
|
||||||
pub use query::{QueryBuilder, QueryResponse};
|
pub use query::{QueryBuilder, QueryResponse};
|
||||||
pub use registry::CacheControl;
|
pub use registry::CacheControl;
|
||||||
pub use scalars::ID;
|
pub use scalars::ID;
|
||||||
pub use schema::{publish, Schema};
|
pub use schema::Schema;
|
||||||
pub use subscription::{
|
pub use subscription::{
|
||||||
SubscriptionConnectionBuilder, SubscriptionStream, SubscriptionStub, SubscriptionStubs,
|
SubscriptionStream, SubscriptionStreams, SubscriptionTransport, WebSocketTransport,
|
||||||
SubscriptionTransport, WebSocketTransport,
|
|
||||||
};
|
};
|
||||||
pub use types::{
|
pub use types::{
|
||||||
Connection, DataSource, EmptyEdgeFields, EmptyMutation, EmptySubscription, QueryOperation,
|
Connection, DataSource, EmptyEdgeFields, EmptyMutation, EmptySubscription, QueryOperation,
|
||||||
|
|
132
src/schema.rs
132
src/schema.rs
|
@ -3,32 +3,25 @@ use crate::extensions::{BoxExtension, Extension};
|
||||||
use crate::model::__DirectiveLocation;
|
use crate::model::__DirectiveLocation;
|
||||||
use crate::query::QueryBuilder;
|
use crate::query::QueryBuilder;
|
||||||
use crate::registry::{Directive, InputValue, Registry};
|
use crate::registry::{Directive, InputValue, Registry};
|
||||||
use crate::subscription::{SubscriptionConnectionBuilder, SubscriptionStub, SubscriptionTransport};
|
use crate::subscription::{create_connection, create_subscription_stream, SubscriptionTransport};
|
||||||
use crate::types::QueryRoot;
|
use crate::types::QueryRoot;
|
||||||
use crate::validation::{check_rules, CheckResult};
|
use crate::validation::{check_rules, CheckResult};
|
||||||
use crate::{
|
use crate::{
|
||||||
ContextSelectionSet, Error, ObjectType, Pos, QueryError, QueryResponse, Result,
|
ContextSelectionSet, Error, ObjectType, Pos, QueryError, QueryResponse, Result,
|
||||||
SubscriptionType, Type, Variables,
|
SubscriptionStream, SubscriptionType, Type, Variables,
|
||||||
};
|
};
|
||||||
|
use bytes::Bytes;
|
||||||
use futures::channel::mpsc;
|
use futures::channel::mpsc;
|
||||||
use futures::lock::Mutex;
|
use futures::{Stream, TryFutureExt};
|
||||||
use futures::{SinkExt, TryFutureExt};
|
|
||||||
use graphql_parser::parse_query;
|
use graphql_parser::parse_query;
|
||||||
use graphql_parser::query::{
|
use graphql_parser::query::{Definition, OperationDefinition};
|
||||||
Definition, Field, FragmentDefinition, OperationDefinition, Selection,
|
|
||||||
};
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use once_cell::sync::Lazy;
|
use std::any::Any;
|
||||||
use slab::Slab;
|
|
||||||
use std::any::{Any, TypeId};
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::pin::Pin;
|
||||||
use std::sync::atomic::AtomicUsize;
|
use std::sync::atomic::AtomicUsize;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
type MsgSender = mpsc::Sender<Arc<dyn Any + Sync + Send>>;
|
|
||||||
|
|
||||||
pub(crate) static SUBSCRIPTION_SENDERS: Lazy<Mutex<Slab<MsgSender>>> = Lazy::new(Default::default);
|
|
||||||
|
|
||||||
pub(crate) struct SchemaInner<Query, Mutation, Subscription> {
|
pub(crate) struct SchemaInner<Query, Mutation, Subscription> {
|
||||||
pub(crate) query: QueryRoot<Query>,
|
pub(crate) query: QueryRoot<Query>,
|
||||||
pub(crate) mutation: Mutation,
|
pub(crate) mutation: Mutation,
|
||||||
|
@ -211,6 +204,16 @@ where
|
||||||
Self::build(query, mutation, subscription).finish()
|
Self::build(query, mutation, subscription).finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn data(&self) -> &Data {
|
||||||
|
&self.0.data
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn registry(&self) -> &Registry {
|
||||||
|
&self.0.registry
|
||||||
|
}
|
||||||
|
|
||||||
/// Start a query and return `QueryBuilder`.
|
/// Start a query and return `QueryBuilder`.
|
||||||
pub fn query(&self, source: &str) -> Result<QueryBuilder<Query, Mutation, Subscription>> {
|
pub fn query(&self, source: &str) -> Result<QueryBuilder<Query, Mutation, Subscription>> {
|
||||||
let extensions = self
|
let extensions = self
|
||||||
|
@ -233,13 +236,13 @@ where
|
||||||
|
|
||||||
if let Some(limit_complexity) = self.0.complexity {
|
if let Some(limit_complexity) = self.0.complexity {
|
||||||
if complexity > limit_complexity {
|
if complexity > limit_complexity {
|
||||||
return Err(QueryError::TooComplex.into_error(Pos { line: 0, column: 0 }));
|
return Err(QueryError::TooComplex.into_error(Pos::default()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(limit_depth) = self.0.depth {
|
if let Some(limit_depth) = self.0.depth {
|
||||||
if depth > limit_depth {
|
if depth > limit_depth {
|
||||||
return Err(QueryError::TooDeep.into_error(Pos { line: 0, column: 0 }));
|
return Err(QueryError::TooDeep.into_error(Pos::default()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,16 +264,13 @@ where
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create subscription stub, typically called inside the `SubscriptionTransport::handle_request` method/
|
/// Create subscription stream, typically called inside the `SubscriptionTransport::handle_request` method
|
||||||
pub fn create_subscription_stub(
|
pub fn create_subscription_stream(
|
||||||
&self,
|
&self,
|
||||||
source: &str,
|
source: &str,
|
||||||
operation_name: Option<&str>,
|
operation_name: Option<&str>,
|
||||||
variables: Variables,
|
variables: Variables,
|
||||||
) -> Result<SubscriptionStub<Query, Mutation, Subscription>>
|
) -> Result<Pin<Box<dyn Stream<Item = serde_json::Value>>>> {
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
let document = parse_query(source).map_err(Into::<Error>::into)?;
|
let document = parse_query(source).map_err(Into::<Error>::into)?;
|
||||||
check_rules(&self.0.registry, &document)?;
|
check_rules(&self.0.registry, &document)?;
|
||||||
|
|
||||||
|
@ -301,7 +301,6 @@ where
|
||||||
QueryError::MissingOperation.into_error(Pos::default())
|
QueryError::MissingOperation.into_error(Pos::default())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut types = HashMap::new();
|
|
||||||
let resolve_id = AtomicUsize::default();
|
let resolve_id = AtomicUsize::default();
|
||||||
let ctx = ContextSelectionSet {
|
let ctx = ContextSelectionSet {
|
||||||
path_node: None,
|
path_node: None,
|
||||||
|
@ -315,87 +314,20 @@ where
|
||||||
ctx_data: None,
|
ctx_data: None,
|
||||||
fragments: &fragments,
|
fragments: &fragments,
|
||||||
};
|
};
|
||||||
create_subscription_types::<Subscription>(&ctx, &fragments, &mut types)?;
|
|
||||||
Ok(SubscriptionStub {
|
let mut streams = Vec::new();
|
||||||
schema: self.clone(),
|
create_subscription_stream(self, Arc::new(ctx.create_environment()), &ctx, &mut streams)?;
|
||||||
types,
|
Ok(Box::pin(futures::stream::select_all(streams)))
|
||||||
variables,
|
|
||||||
variable_definitions: subscription.variable_definitions,
|
|
||||||
fragments,
|
|
||||||
ctx_data: None,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create subscription connection, returns `SubscriptionConnectionBuilder`.
|
/// Create subscription connection, returns `Sink` and `Stream`.
|
||||||
pub fn subscription_connection<T: SubscriptionTransport>(
|
pub fn subscription_connection<T: SubscriptionTransport>(
|
||||||
&self,
|
&self,
|
||||||
transport: T,
|
transport: T,
|
||||||
) -> SubscriptionConnectionBuilder<Query, Mutation, Subscription, T> {
|
) -> (
|
||||||
SubscriptionConnectionBuilder {
|
mpsc::Sender<Bytes>,
|
||||||
schema: self.clone(),
|
SubscriptionStream<Query, Mutation, Subscription, T>,
|
||||||
transport,
|
) {
|
||||||
ctx_data: None,
|
create_connection(self.clone(), transport)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_subscription_types<T: SubscriptionType>(
|
|
||||||
ctx: &ContextSelectionSet<'_>,
|
|
||||||
fragments: &HashMap<String, FragmentDefinition>,
|
|
||||||
types: &mut HashMap<TypeId, Field>,
|
|
||||||
) -> Result<()> {
|
|
||||||
for selection in &ctx.items {
|
|
||||||
match selection {
|
|
||||||
Selection::Field(field) => {
|
|
||||||
if ctx.is_skip(&field.directives)? {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
T::create_type(field, types)?;
|
|
||||||
}
|
|
||||||
Selection::FragmentSpread(fragment_spread) => {
|
|
||||||
if ctx.is_skip(&fragment_spread.directives)? {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(fragment) = fragments.get(&fragment_spread.fragment_name) {
|
|
||||||
create_subscription_types::<T>(
|
|
||||||
&ctx.with_selection_set(&fragment.selection_set),
|
|
||||||
fragments,
|
|
||||||
types,
|
|
||||||
)?;
|
|
||||||
} else {
|
|
||||||
return Err(QueryError::UnknownFragment {
|
|
||||||
name: fragment_spread.fragment_name.clone(),
|
|
||||||
}
|
|
||||||
.into_error(fragment_spread.position));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Selection::InlineFragment(inline_fragment) => {
|
|
||||||
if ctx.is_skip(&inline_fragment.directives)? {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
create_subscription_types::<T>(
|
|
||||||
&ctx.with_selection_set(&inline_fragment.selection_set),
|
|
||||||
fragments,
|
|
||||||
types,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Publish a message that will be pushed to all subscribed clients.
|
|
||||||
pub async fn publish<T: Any + Send + Sync + Sized>(msg: T) {
|
|
||||||
let mut senders = SUBSCRIPTION_SENDERS.lock().await;
|
|
||||||
let msg = Arc::new(msg);
|
|
||||||
let mut remove = Vec::new();
|
|
||||||
for (id, sender) in senders.iter_mut() {
|
|
||||||
if sender.send(msg.clone()).await.is_err() {
|
|
||||||
remove.push(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for id in remove {
|
|
||||||
senders.remove(id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,25 @@
|
||||||
use crate::context::Data;
|
use crate::{ObjectType, Schema, SubscriptionType};
|
||||||
use crate::schema::SUBSCRIPTION_SENDERS;
|
|
||||||
use crate::subscription::SubscriptionStub;
|
|
||||||
use crate::{ObjectType, Result, Schema, SubscriptionType};
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures::channel::mpsc;
|
use futures::channel::mpsc;
|
||||||
use futures::task::{Context, Poll};
|
use futures::task::{Context, Poll};
|
||||||
use futures::{Future, FutureExt, Stream};
|
use futures::Stream;
|
||||||
use slab::Slab;
|
use slab::Slab;
|
||||||
use std::any::Any;
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
/// Subscription stubs, use to hold all subscription information for the `SubscriptionConnection`
|
/// Use to hold all subscription stream for the `SubscriptionConnection`
|
||||||
pub struct SubscriptionStubs<Query, Mutation, Subscription> {
|
pub struct SubscriptionStreams {
|
||||||
stubs: Slab<SubscriptionStub<Query, Mutation, Subscription>>,
|
streams: Slab<Pin<Box<dyn Stream<Item = serde_json::Value>>>>,
|
||||||
ctx_data: Option<Arc<Data>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
impl<Query, Mutation, Subscription> SubscriptionStubs<Query, Mutation, Subscription> {
|
impl SubscriptionStreams {
|
||||||
pub fn add(&mut self, mut stub: SubscriptionStub<Query, Mutation, Subscription>) -> usize {
|
pub fn add(&mut self, stream: Pin<Box<dyn Stream<Item = serde_json::Value>>>) -> usize {
|
||||||
stub.ctx_data = self.ctx_data.clone();
|
self.streams.insert(stream)
|
||||||
self.stubs.insert(stub)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(&mut self, id: usize) {
|
pub fn remove(&mut self, id: usize) {
|
||||||
self.stubs.remove(id);
|
self.streams.remove(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,12 +31,12 @@ pub trait SubscriptionTransport: Send + Sync + Unpin + 'static {
|
||||||
type Error;
|
type Error;
|
||||||
|
|
||||||
/// Parse the request data here.
|
/// Parse the request data here.
|
||||||
/// If you have a new request, create a `SubscriptionStub` with the `Schema::create_subscription_stub`, and then call `SubscriptionStubs::add`.
|
/// If you have a new subscribe, create a stream with the `Schema::create_subscription_stream`, and then call `SubscriptionStreams::add`.
|
||||||
/// You can return a `Byte`, which will be sent to the client. If it returns an error, the connection will be broken.
|
/// You can return a `Byte`, which will be sent to the client. If it returns an error, the connection will be broken.
|
||||||
fn handle_request<Query, Mutation, Subscription>(
|
fn handle_request<Query, Mutation, Subscription>(
|
||||||
&mut self,
|
&mut self,
|
||||||
schema: &Schema<Query, Mutation, Subscription>,
|
schema: &Schema<Query, Mutation, Subscription>,
|
||||||
stubs: &mut SubscriptionStubs<Query, Mutation, Subscription>,
|
streams: &mut SubscriptionStreams,
|
||||||
data: Bytes,
|
data: Bytes,
|
||||||
) -> std::result::Result<Option<Bytes>, Self::Error>
|
) -> std::result::Result<Option<Bytes>, Self::Error>
|
||||||
where
|
where
|
||||||
|
@ -52,13 +45,12 @@ pub trait SubscriptionTransport: Send + Sync + Unpin + 'static {
|
||||||
Subscription: SubscriptionType + Sync + Send + 'static;
|
Subscription: SubscriptionType + Sync + Send + 'static;
|
||||||
|
|
||||||
/// When a response message is generated, you can convert the message to the format you want here.
|
/// When a response message is generated, you can convert the message to the format you want here.
|
||||||
fn handle_response(&mut self, id: usize, result: Result<serde_json::Value>) -> Option<Bytes>;
|
fn handle_response(&mut self, id: usize, value: serde_json::Value) -> Option<Bytes>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_connection<Query, Mutation, Subscription, T: SubscriptionTransport>(
|
pub fn create_connection<Query, Mutation, Subscription, T: SubscriptionTransport>(
|
||||||
schema: Schema<Query, Mutation, Subscription>,
|
schema: Schema<Query, Mutation, Subscription>,
|
||||||
transport: T,
|
transport: T,
|
||||||
ctx_data: Option<Data>,
|
|
||||||
) -> (
|
) -> (
|
||||||
mpsc::Sender<Bytes>,
|
mpsc::Sender<Bytes>,
|
||||||
SubscriptionStream<Query, Mutation, Subscription, T>,
|
SubscriptionStream<Query, Mutation, Subscription, T>,
|
||||||
|
@ -69,23 +61,16 @@ where
|
||||||
Subscription: SubscriptionType + Sync + Send + 'static,
|
Subscription: SubscriptionType + Sync + Send + 'static,
|
||||||
{
|
{
|
||||||
let (tx_bytes, rx_bytes) = mpsc::channel(8);
|
let (tx_bytes, rx_bytes) = mpsc::channel(8);
|
||||||
let (tx_msg, rx_msg) = mpsc::channel(8);
|
|
||||||
let mut senders = SUBSCRIPTION_SENDERS.lock().await;
|
|
||||||
senders.insert(tx_msg);
|
|
||||||
(
|
(
|
||||||
tx_bytes.clone(),
|
tx_bytes.clone(),
|
||||||
SubscriptionStream {
|
SubscriptionStream {
|
||||||
schema,
|
schema,
|
||||||
transport,
|
transport,
|
||||||
stubs: SubscriptionStubs {
|
streams: SubscriptionStreams {
|
||||||
stubs: Default::default(),
|
streams: Default::default(),
|
||||||
ctx_data: ctx_data.map(Arc::new),
|
|
||||||
},
|
},
|
||||||
rx_bytes,
|
rx_bytes,
|
||||||
rx_msg,
|
|
||||||
send_queue: VecDeque::new(),
|
send_queue: VecDeque::new(),
|
||||||
resolve_queue: VecDeque::default(),
|
|
||||||
resolve_fut: None,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -94,12 +79,9 @@ where
|
||||||
pub struct SubscriptionStream<Query, Mutation, Subscription, T: SubscriptionTransport> {
|
pub struct SubscriptionStream<Query, Mutation, Subscription, T: SubscriptionTransport> {
|
||||||
schema: Schema<Query, Mutation, Subscription>,
|
schema: Schema<Query, Mutation, Subscription>,
|
||||||
transport: T,
|
transport: T,
|
||||||
stubs: SubscriptionStubs<Query, Mutation, Subscription>,
|
streams: SubscriptionStreams,
|
||||||
rx_bytes: mpsc::Receiver<Bytes>,
|
rx_bytes: mpsc::Receiver<Bytes>,
|
||||||
rx_msg: mpsc::Receiver<Arc<dyn Any + Sync + Send>>,
|
|
||||||
send_queue: VecDeque<Bytes>,
|
send_queue: VecDeque<Bytes>,
|
||||||
resolve_queue: VecDeque<Arc<dyn Any + Sync + Send>>,
|
|
||||||
resolve_fut: Option<Pin<Box<dyn Future<Output = ()>>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Query, Mutation, Subscription, T> Stream
|
impl<Query, Mutation, Subscription, T> Stream
|
||||||
|
@ -125,7 +107,7 @@ where
|
||||||
let this = &mut *self;
|
let this = &mut *self;
|
||||||
match this
|
match this
|
||||||
.transport
|
.transport
|
||||||
.handle_request(&this.schema, &mut this.stubs, data)
|
.handle_request(&this.schema, &mut this.streams, data)
|
||||||
{
|
{
|
||||||
Ok(Some(bytes)) => {
|
Ok(Some(bytes)) => {
|
||||||
this.send_queue.push_back(bytes);
|
this.send_queue.push_back(bytes);
|
||||||
|
@ -139,44 +121,38 @@ where
|
||||||
Poll::Pending => {}
|
Poll::Pending => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(resolve_fut) = &mut self.resolve_fut {
|
// receive msg
|
||||||
match resolve_fut.poll_unpin(cx) {
|
let this = &mut *self;
|
||||||
Poll::Ready(_) => {
|
if !this.streams.streams.is_empty() {
|
||||||
self.resolve_fut = None;
|
loop {
|
||||||
}
|
let mut num_closed = 0;
|
||||||
Poll::Pending => return Poll::Pending,
|
let mut num_pending = 0;
|
||||||
}
|
|
||||||
} else if let Some(msg) = self.resolve_queue.pop_front() {
|
for (id, incoming_stream) in &mut this.streams.streams {
|
||||||
// FIXME: I think this code is safe, but I don't know how to implement it in safe code.
|
match incoming_stream.as_mut().poll_next(cx) {
|
||||||
let this = &mut *self;
|
Poll::Ready(Some(value)) => {
|
||||||
let stubs = &this.stubs as *const SubscriptionStubs<Query, Mutation, Subscription>;
|
if let Some(bytes) = this.transport.handle_response(id, value) {
|
||||||
let transport = &mut this.transport as *mut T;
|
this.send_queue.push_back(bytes);
|
||||||
let send_queue = &mut this.send_queue as *mut VecDeque<Bytes>;
|
|
||||||
let fut = async move {
|
|
||||||
unsafe {
|
|
||||||
for (id, stub) in (*stubs).stubs.iter() {
|
|
||||||
if let Some(res) = stub.resolve(msg.as_ref()).await.transpose() {
|
|
||||||
if let Some(bytes) = (*transport).handle_response(id, res) {
|
|
||||||
(*send_queue).push_back(bytes);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Poll::Ready(None) => {
|
||||||
|
num_closed += 1;
|
||||||
|
}
|
||||||
|
Poll::Pending => {
|
||||||
|
num_pending += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
self.resolve_fut = Some(Box::pin(fut));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// receive msg
|
if num_closed == this.streams.streams.len() {
|
||||||
match Pin::new(&mut self.rx_msg).poll_next(cx) {
|
// all closed
|
||||||
Poll::Ready(Some(msg)) => {
|
return Poll::Ready(None);
|
||||||
self.resolve_queue.push_back(msg);
|
} else if num_pending == this.streams.streams.len() {
|
||||||
}
|
return Poll::Pending;
|
||||||
Poll::Ready(None) => return Poll::Ready(None),
|
}
|
||||||
Poll::Pending => {
|
|
||||||
// all pending
|
|
||||||
return Poll::Pending;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return Poll::Pending;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
use crate::context::Data;
|
|
||||||
use crate::subscription::create_connection;
|
|
||||||
use crate::{ObjectType, Schema, SubscriptionStream, SubscriptionTransport, SubscriptionType};
|
|
||||||
use bytes::Bytes;
|
|
||||||
use futures::channel::mpsc;
|
|
||||||
use std::any::Any;
|
|
||||||
|
|
||||||
/// SubscriptionConnection builder
|
|
||||||
pub struct SubscriptionConnectionBuilder<Query, Mutation, Subscription, T: SubscriptionTransport> {
|
|
||||||
pub(crate) schema: Schema<Query, Mutation, Subscription>,
|
|
||||||
pub(crate) transport: T,
|
|
||||||
pub(crate) ctx_data: Option<Data>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Query, Mutation, Subscription, T: SubscriptionTransport>
|
|
||||||
SubscriptionConnectionBuilder<Query, Mutation, Subscription, T>
|
|
||||||
where
|
|
||||||
Query: ObjectType + Send + Sync + 'static,
|
|
||||||
Mutation: ObjectType + Send + Sync + 'static,
|
|
||||||
Subscription: SubscriptionType + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
/// Add a context data that can be accessed in the `Context`, you access it with `Context::data`.
|
|
||||||
pub fn data<D: Any + Send + Sync>(mut self, data: D) -> Self {
|
|
||||||
if let Some(ctx_data) = &mut self.ctx_data {
|
|
||||||
ctx_data.insert(data);
|
|
||||||
} else {
|
|
||||||
let mut ctx_data = Data::default();
|
|
||||||
ctx_data.insert(data);
|
|
||||||
self.ctx_data = Some(ctx_data);
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create subscription connection, returns `Sink` and `Stream`.
|
|
||||||
pub async fn build(
|
|
||||||
self,
|
|
||||||
) -> (
|
|
||||||
mpsc::Sender<Bytes>,
|
|
||||||
SubscriptionStream<Query, Mutation, Subscription, T>,
|
|
||||||
) {
|
|
||||||
create_connection(self.schema, self.transport, self.ctx_data).await
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +1,9 @@
|
||||||
mod connection;
|
mod connection;
|
||||||
mod connection_builder;
|
|
||||||
mod subscription_stub;
|
|
||||||
mod subscription_type;
|
mod subscription_type;
|
||||||
mod ws_transport;
|
mod ws_transport;
|
||||||
|
|
||||||
pub use connection::{
|
pub use connection::{
|
||||||
create_connection, SubscriptionStream, SubscriptionStubs, SubscriptionTransport,
|
create_connection, SubscriptionStream, SubscriptionStreams, SubscriptionTransport,
|
||||||
};
|
};
|
||||||
pub use connection_builder::SubscriptionConnectionBuilder;
|
pub use subscription_type::{create_subscription_stream, SubscriptionType};
|
||||||
pub use subscription_stub::SubscriptionStub;
|
|
||||||
pub use subscription_type::SubscriptionType;
|
|
||||||
pub use ws_transport::WebSocketTransport;
|
pub use ws_transport::WebSocketTransport;
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
use crate::context::Data;
|
|
||||||
use crate::{ContextBase, ObjectType, Result, Schema, SubscriptionType, Variables};
|
|
||||||
use graphql_parser::query::{Field, FragmentDefinition, VariableDefinition};
|
|
||||||
use std::any::{Any, TypeId};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::atomic::AtomicUsize;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
/// Subscription stub
|
|
||||||
///
|
|
||||||
/// When a new push message is generated, a JSON object that needs to be pushed can be obtained by
|
|
||||||
/// `Subscribe::resolve`, and if None is returned, the Subscribe is not subscribed to a message of this type.
|
|
||||||
pub struct SubscriptionStub<Query, Mutation, Subscription> {
|
|
||||||
pub(crate) schema: Schema<Query, Mutation, Subscription>,
|
|
||||||
pub(crate) types: HashMap<TypeId, Field>,
|
|
||||||
pub(crate) variables: Variables,
|
|
||||||
pub(crate) variable_definitions: Vec<VariableDefinition>,
|
|
||||||
pub(crate) fragments: HashMap<String, FragmentDefinition>,
|
|
||||||
pub(crate) ctx_data: Option<Arc<Data>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Query, Mutation, Subscription> SubscriptionStub<Query, Mutation, Subscription>
|
|
||||||
where
|
|
||||||
Query: ObjectType + Send + Sync + 'static,
|
|
||||||
Mutation: ObjectType + Send + Sync + 'static,
|
|
||||||
Subscription: SubscriptionType + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub async fn resolve(
|
|
||||||
&self,
|
|
||||||
msg: &(dyn Any + Send + Sync),
|
|
||||||
) -> Result<Option<serde_json::Value>> {
|
|
||||||
let resolve_id = AtomicUsize::default();
|
|
||||||
let ctx = ContextBase::<()> {
|
|
||||||
path_node: None,
|
|
||||||
extensions: &[],
|
|
||||||
item: (),
|
|
||||||
resolve_id: &resolve_id,
|
|
||||||
variables: &self.variables,
|
|
||||||
variable_definitions: &self.variable_definitions,
|
|
||||||
registry: &self.schema.0.registry,
|
|
||||||
data: &self.schema.0.data,
|
|
||||||
ctx_data: self.ctx_data.as_deref(),
|
|
||||||
fragments: &self.fragments,
|
|
||||||
};
|
|
||||||
self.schema
|
|
||||||
.0
|
|
||||||
.subscription
|
|
||||||
.resolve(&ctx, &self.types, msg)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,9 @@
|
||||||
use crate::{ContextBase, Result, Type};
|
use crate::context::Environment;
|
||||||
use graphql_parser::query::Field;
|
use crate::{Context, ContextSelectionSet, ObjectType, Result, Schema, Type};
|
||||||
use std::any::{Any, TypeId};
|
use futures::Stream;
|
||||||
use std::collections::HashMap;
|
use graphql_parser::query::{Selection, TypeCondition};
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Represents a GraphQL subscription object
|
/// Represents a GraphQL subscription object
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
@ -13,13 +15,79 @@ pub trait SubscriptionType: Type {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
fn create_type(field: &Field, types: &mut HashMap<TypeId, Field>) -> Result<()>;
|
fn create_field_stream<Query, Mutation>(
|
||||||
|
|
||||||
/// Resolve a subscription message, If no message of this type is subscribed, None is returned.
|
|
||||||
async fn resolve(
|
|
||||||
&self,
|
&self,
|
||||||
ctx: &ContextBase<'_, ()>,
|
ctx: &Context<'_>,
|
||||||
types: &HashMap<TypeId, Field>,
|
schema: &Schema<Query, Mutation, Self>,
|
||||||
msg: &(dyn Any + Send + Sync),
|
environment: Arc<Environment>,
|
||||||
) -> Result<Option<serde_json::Value>>;
|
) -> Result<Pin<Box<dyn Stream<Item = serde_json::Value>>>>
|
||||||
|
where
|
||||||
|
Query: ObjectType + Send + Sync + 'static,
|
||||||
|
Mutation: ObjectType + Send + Sync + 'static,
|
||||||
|
Self: Send + Sync + 'static + Sized;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_subscription_stream<Query, Mutation, Subscription>(
|
||||||
|
schema: &Schema<Query, Mutation, Subscription>,
|
||||||
|
environment: Arc<Environment>,
|
||||||
|
ctx: &ContextSelectionSet<'_>,
|
||||||
|
streams: &mut Vec<Pin<Box<dyn Stream<Item = serde_json::Value>>>>,
|
||||||
|
) -> Result<()>
|
||||||
|
where
|
||||||
|
Query: ObjectType + Send + Sync + 'static,
|
||||||
|
Mutation: ObjectType + Send + Sync + 'static,
|
||||||
|
Subscription: SubscriptionType + Send + Sync + 'static + Sized,
|
||||||
|
{
|
||||||
|
for selection in &ctx.items {
|
||||||
|
match selection {
|
||||||
|
Selection::Field(field) => {
|
||||||
|
if ctx.is_skip(&field.directives)? {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
streams.push(schema.0.subscription.create_field_stream(
|
||||||
|
&ctx.with_field(field),
|
||||||
|
schema,
|
||||||
|
environment.clone(),
|
||||||
|
)?)
|
||||||
|
}
|
||||||
|
Selection::FragmentSpread(fragment_spread) => {
|
||||||
|
if ctx.is_skip(&fragment_spread.directives)? {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(fragment) = ctx.fragments.get(fragment_spread.fragment_name.as_str()) {
|
||||||
|
create_subscription_stream(
|
||||||
|
schema,
|
||||||
|
environment.clone(),
|
||||||
|
&ctx.with_selection_set(&fragment.selection_set),
|
||||||
|
streams,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Selection::InlineFragment(inline_fragment) => {
|
||||||
|
if ctx.is_skip(&inline_fragment.directives)? {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(TypeCondition::On(name)) = &inline_fragment.type_condition {
|
||||||
|
if name.as_str() == Subscription::type_name() {
|
||||||
|
create_subscription_stream(
|
||||||
|
schema,
|
||||||
|
environment.clone(),
|
||||||
|
&ctx.with_selection_set(&inline_fragment.selection_set),
|
||||||
|
streams,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
create_subscription_stream(
|
||||||
|
schema,
|
||||||
|
environment.clone(),
|
||||||
|
&ctx.with_selection_set(&inline_fragment.selection_set),
|
||||||
|
streams,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::http::{GQLError, GQLRequest, GQLResponse};
|
use crate::http::{GQLError, GQLRequest, GQLResponse};
|
||||||
use crate::{
|
use crate::{
|
||||||
ObjectType, QueryResponse, Result, Schema, SubscriptionStubs, SubscriptionTransport,
|
ObjectType, QueryResponse, Schema, SubscriptionStreams, SubscriptionTransport,
|
||||||
SubscriptionType, Variables,
|
SubscriptionType, Variables,
|
||||||
};
|
};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
@ -27,7 +27,7 @@ impl SubscriptionTransport for WebSocketTransport {
|
||||||
fn handle_request<Query, Mutation, Subscription>(
|
fn handle_request<Query, Mutation, Subscription>(
|
||||||
&mut self,
|
&mut self,
|
||||||
schema: &Schema<Query, Mutation, Subscription>,
|
schema: &Schema<Query, Mutation, Subscription>,
|
||||||
stubs: &mut SubscriptionStubs<Query, Mutation, Subscription>,
|
streams: &mut SubscriptionStreams,
|
||||||
data: Bytes,
|
data: Bytes,
|
||||||
) -> std::result::Result<Option<Bytes>, Self::Error>
|
) -> std::result::Result<Option<Bytes>, Self::Error>
|
||||||
where
|
where
|
||||||
|
@ -54,16 +54,15 @@ impl SubscriptionTransport for WebSocketTransport {
|
||||||
.map(|value| Variables::parse_from_json(value).ok())
|
.map(|value| Variables::parse_from_json(value).ok())
|
||||||
.flatten()
|
.flatten()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
match schema.create_subscription_stream(
|
||||||
match schema.create_subscription_stub(
|
|
||||||
&request.query,
|
&request.query,
|
||||||
request.operation_name.as_deref(),
|
request.operation_name.as_deref(),
|
||||||
variables,
|
variables,
|
||||||
) {
|
) {
|
||||||
Ok(stub) => {
|
Ok(stream) => {
|
||||||
let stub_id = stubs.add(stub);
|
let stream_id = streams.add(stream);
|
||||||
self.id_to_sid.insert(id.clone(), stub_id);
|
self.id_to_sid.insert(id.clone(), stream_id);
|
||||||
self.sid_to_id.insert(stub_id, id);
|
self.sid_to_id.insert(stream_id, id);
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
Err(err) => Ok(Some(
|
Err(err) => Ok(Some(
|
||||||
|
@ -89,7 +88,7 @@ impl SubscriptionTransport for WebSocketTransport {
|
||||||
if let Some(id) = msg.id {
|
if let Some(id) = msg.id {
|
||||||
if let Some(id) = self.id_to_sid.remove(&id) {
|
if let Some(id) = self.id_to_sid.remove(&id) {
|
||||||
self.sid_to_id.remove(&id);
|
self.sid_to_id.remove(&id);
|
||||||
stubs.remove(id);
|
streams.remove(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
@ -101,15 +100,15 @@ impl SubscriptionTransport for WebSocketTransport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_response(&mut self, id: usize, result: Result<serde_json::Value>) -> Option<Bytes> {
|
fn handle_response(&mut self, id: usize, value: serde_json::Value) -> Option<Bytes> {
|
||||||
if let Some(id) = self.sid_to_id.get(&id) {
|
if let Some(id) = self.sid_to_id.get(&id) {
|
||||||
Some(
|
Some(
|
||||||
serde_json::to_vec(&OperationMessage {
|
serde_json::to_vec(&OperationMessage {
|
||||||
ty: "data".to_string(),
|
ty: "data".to_string(),
|
||||||
id: Some(id.clone()),
|
id: Some(id.clone()),
|
||||||
payload: Some(
|
payload: Some(
|
||||||
serde_json::to_value(GQLResponse(result.map(|data| QueryResponse {
|
serde_json::to_value(GQLResponse(Ok(QueryResponse {
|
||||||
data,
|
data: value,
|
||||||
extensions: None,
|
extensions: None,
|
||||||
})))
|
})))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
|
use crate::context::Environment;
|
||||||
use crate::{
|
use crate::{
|
||||||
registry, ContextBase, ContextSelectionSet, Error, OutputValueType, QueryError, Result,
|
registry, Context, ContextSelectionSet, Error, ObjectType, OutputValueType, QueryError, Result,
|
||||||
SubscriptionType, Type,
|
Schema, SubscriptionType, Type,
|
||||||
};
|
};
|
||||||
use graphql_parser::query::Field;
|
use futures::Stream;
|
||||||
use graphql_parser::Pos;
|
use graphql_parser::Pos;
|
||||||
use serde_json::Value;
|
|
||||||
use std::any::{Any, TypeId};
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::hash_map::RandomState;
|
use std::pin::Pin;
|
||||||
use std::collections::HashMap;
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Empty subscription
|
/// Empty subscription
|
||||||
///
|
///
|
||||||
|
@ -36,17 +35,22 @@ impl SubscriptionType for EmptySubscription {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_type(_field: &Field, _types: &mut HashMap<TypeId, Field>) -> Result<()> {
|
fn create_field_stream<Query, Mutation>(
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn resolve(
|
|
||||||
&self,
|
&self,
|
||||||
_ctx: &ContextBase<'_, ()>,
|
_ctx: &Context<'_>,
|
||||||
_types: &HashMap<TypeId, Field, RandomState>,
|
_schema: &Schema<Query, Mutation, Self>,
|
||||||
_msg: &(dyn Any + Send + Sync),
|
_environment: Arc<Environment>,
|
||||||
) -> Result<Option<Value>> {
|
) -> Result<Pin<Box<dyn Stream<Item = serde_json::Value>>>>
|
||||||
unreachable!()
|
where
|
||||||
|
Query: ObjectType + Send + Sync + 'static,
|
||||||
|
Mutation: ObjectType + Send + Sync + 'static,
|
||||||
|
Self: Send + Sync + 'static + Sized,
|
||||||
|
{
|
||||||
|
Err(Error::Query {
|
||||||
|
pos: Pos::default(),
|
||||||
|
path: None,
|
||||||
|
err: QueryError::NotConfiguredSubscriptions,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! Input value validators
|
||||||
|
|
||||||
mod int_validators;
|
mod int_validators;
|
||||||
mod list_validators;
|
mod list_validators;
|
||||||
mod string_validators;
|
mod string_validators;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user