2020-03-17 09:26:59 +00:00
|
|
|
use crate::registry::Registry;
|
|
|
|
use crate::validation::check_rules;
|
2020-03-18 03:13:45 +00:00
|
|
|
use crate::{
|
2020-03-19 09:20:12 +00:00
|
|
|
ContextBase, ContextSelectionSet, QueryError, QueryParseError, Result, Schema, Type, Variables,
|
2020-03-18 03:13:45 +00:00
|
|
|
};
|
2020-03-17 09:26:59 +00:00
|
|
|
use graphql_parser::parse_query;
|
|
|
|
use graphql_parser::query::{
|
2020-03-18 03:13:45 +00:00
|
|
|
Definition, Field, FragmentDefinition, OperationDefinition, Selection, SelectionSet,
|
|
|
|
VariableDefinition,
|
2020-03-17 09:26:59 +00:00
|
|
|
};
|
|
|
|
use std::any::{Any, TypeId};
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
|
|
pub struct Subscribe {
|
2020-03-17 11:11:14 +00:00
|
|
|
types: HashMap<TypeId, Field>,
|
|
|
|
variables: Variables,
|
|
|
|
variable_definitions: Vec<VariableDefinition>,
|
|
|
|
fragments: HashMap<String, FragmentDefinition>,
|
2020-03-17 09:26:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Subscribe {
|
|
|
|
pub async fn resolve<Query, Mutation, Subscription>(
|
|
|
|
&self,
|
|
|
|
schema: &Schema<Query, Mutation, Subscription>,
|
|
|
|
msg: &(dyn Any + Send + Sync),
|
|
|
|
) -> Result<Option<serde_json::Value>>
|
|
|
|
where
|
2020-03-19 09:20:12 +00:00
|
|
|
Subscription: SubscriptionType + Sync + Send + 'static,
|
2020-03-17 09:26:59 +00:00
|
|
|
{
|
|
|
|
let ctx = ContextBase::<()> {
|
|
|
|
item: (),
|
|
|
|
variables: &self.variables,
|
|
|
|
variable_definitions: Some(&self.variable_definitions),
|
|
|
|
registry: &schema.registry,
|
2020-03-17 11:11:14 +00:00
|
|
|
data: &schema.data,
|
2020-03-17 09:26:59 +00:00
|
|
|
fragments: &self.fragments,
|
|
|
|
};
|
|
|
|
schema.subscription.resolve(&ctx, &self.types, msg).await
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Represents a GraphQL subscription object
|
|
|
|
#[async_trait::async_trait]
|
2020-03-19 09:20:12 +00:00
|
|
|
pub trait SubscriptionType: Type {
|
|
|
|
/// This function returns true of type `EmptySubscription` only
|
2020-03-17 09:26:59 +00:00
|
|
|
#[doc(hidden)]
|
|
|
|
fn is_empty() -> bool {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-03-18 03:13:45 +00:00
|
|
|
fn create_type(field: &Field, types: &mut HashMap<TypeId, Field>) -> Result<()>;
|
2020-03-17 09:26:59 +00:00
|
|
|
|
|
|
|
fn create_subscribe(
|
|
|
|
&self,
|
2020-03-18 03:13:45 +00:00
|
|
|
registry: &Registry,
|
2020-03-17 09:26:59 +00:00
|
|
|
selection_set: SelectionSet,
|
|
|
|
variables: Variables,
|
|
|
|
variable_definitions: Vec<VariableDefinition>,
|
|
|
|
fragments: HashMap<String, FragmentDefinition>,
|
2020-03-18 03:13:45 +00:00
|
|
|
) -> Result<Subscribe>
|
|
|
|
where
|
|
|
|
Self: Sized,
|
|
|
|
{
|
|
|
|
let mut types = HashMap::new();
|
|
|
|
let ctx = ContextSelectionSet {
|
|
|
|
item: &selection_set,
|
|
|
|
variables: &variables,
|
|
|
|
variable_definitions: Some(&variable_definitions),
|
|
|
|
registry,
|
|
|
|
data: &Default::default(),
|
|
|
|
fragments: &fragments,
|
|
|
|
};
|
|
|
|
create_types::<Self>(&ctx, &fragments, &mut types)?;
|
2020-03-17 09:26:59 +00:00
|
|
|
Ok(Subscribe {
|
2020-03-18 03:13:45 +00:00
|
|
|
types,
|
2020-03-17 09:26:59 +00:00
|
|
|
variables,
|
|
|
|
variable_definitions,
|
|
|
|
fragments,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Resolve a subscription message, If no message of this type is subscribed, None is returned.
|
|
|
|
async fn resolve(
|
|
|
|
&self,
|
|
|
|
ctx: &ContextBase<'_, ()>,
|
|
|
|
types: &HashMap<TypeId, Field>,
|
|
|
|
msg: &(dyn Any + Send + Sync),
|
|
|
|
) -> Result<Option<serde_json::Value>>;
|
|
|
|
}
|
|
|
|
|
2020-03-19 09:20:12 +00:00
|
|
|
fn create_types<T: SubscriptionType>(
|
2020-03-18 03:13:45 +00:00
|
|
|
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_types::<T>(&ctx.with_item(&fragment.selection_set), fragments, types)?;
|
|
|
|
} else {
|
|
|
|
return Err(QueryError::UnknownFragment {
|
|
|
|
name: fragment_spread.fragment_name.clone(),
|
|
|
|
}
|
|
|
|
.into());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Selection::InlineFragment(inline_fragment) => {
|
|
|
|
if ctx.is_skip(&inline_fragment.directives)? {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
create_types::<T>(
|
|
|
|
&ctx.with_item(&inline_fragment.selection_set),
|
|
|
|
fragments,
|
|
|
|
types,
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-03-19 09:20:12 +00:00
|
|
|
/// Subscribe builder
|
2020-03-17 09:26:59 +00:00
|
|
|
pub struct SubscribeBuilder<'a, Subscription> {
|
|
|
|
pub(crate) subscription: &'a Subscription,
|
|
|
|
pub(crate) registry: &'a Registry,
|
|
|
|
pub(crate) source: &'a str,
|
|
|
|
pub(crate) operation_name: Option<&'a str>,
|
|
|
|
pub(crate) variables: Option<Variables>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, Subscription> SubscribeBuilder<'a, Subscription>
|
|
|
|
where
|
2020-03-19 09:20:12 +00:00
|
|
|
Subscription: SubscriptionType,
|
2020-03-17 09:26:59 +00:00
|
|
|
{
|
|
|
|
/// Specify the operation name.
|
|
|
|
pub fn operator_name(self, name: &'a str) -> Self {
|
|
|
|
SubscribeBuilder {
|
|
|
|
operation_name: Some(name),
|
|
|
|
..self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Specify the variables.
|
|
|
|
pub fn variables(self, vars: Variables) -> Self {
|
|
|
|
SubscribeBuilder {
|
|
|
|
variables: Some(vars),
|
|
|
|
..self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn execute(self) -> Result<Subscribe> {
|
|
|
|
let document = parse_query(self.source).map_err(|err| QueryParseError(err.to_string()))?;
|
|
|
|
check_rules(self.registry, &document)?;
|
|
|
|
|
|
|
|
let mut fragments = HashMap::new();
|
|
|
|
let mut subscription = None;
|
|
|
|
|
|
|
|
for definition in document.definitions {
|
|
|
|
match definition {
|
|
|
|
Definition::Operation(OperationDefinition::Subscription(s)) => {
|
|
|
|
if s.name.as_deref() == self.operation_name {
|
|
|
|
subscription = Some(s);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Definition::Fragment(fragment) => {
|
|
|
|
fragments.insert(fragment.name.clone(), fragment);
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let subscription = subscription.ok_or(if let Some(name) = self.operation_name {
|
|
|
|
QueryError::UnknownOperationNamed {
|
|
|
|
name: name.to_string(),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
QueryError::MissingOperation
|
|
|
|
})?;
|
|
|
|
|
|
|
|
self.subscription.create_subscribe(
|
2020-03-18 03:13:45 +00:00
|
|
|
self.registry,
|
2020-03-17 09:26:59 +00:00
|
|
|
subscription.selection_set,
|
|
|
|
self.variables.unwrap_or_default(),
|
|
|
|
subscription.variable_definitions,
|
|
|
|
fragments,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|