Skip to content

Subscriptions support #61

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 22, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Added

- Copy documentation from the GraphQL schema to the generated types (including their fields) as normal Rust documentation. Documentation will show up in the generated docs as well as IDEs that support expanding derive macros (which does not include the RLS yet).
- Implement and test deserializing subscription responses. We also try to provide helpful error messages when a subscription query is not valid (i.e. when it has more than one top-level field).

### Fixed

- The generated `ResponseData` structs did not convert between snake and camel case.

## [0.1.0] - 2018-07-04

Expand Down
6 changes: 6 additions & 0 deletions graphql_query_derive/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,9 @@ pub fn typename_field() -> GqlObjectField {
type_: FieldType::Named(string_type()),
}
}

pub const MULTIPLE_SUBSCRIPTION_FIELDS_ERROR: &str = r##"
Multiple-field queries on the root subscription field are forbidden by the spec.

See: https://github.com/facebook/graphql/blob/master/spec/Section%205%20--%20Validation.md#subscription-operation-definitions
"##;
1 change: 1 addition & 0 deletions graphql_query_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ mod inputs;
mod interfaces;
mod introspection_response;
mod objects;
mod operations;
mod query;
mod scalars;
mod schema;
Expand Down
21 changes: 21 additions & 0 deletions graphql_query_derive/src/operations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use selection::Selection;

pub enum OperationType {
Query,
Mutation,
Subscription,
}

#[derive(Debug)]
pub struct Operation {
pub name: String,
pub selection: Selection,
}

pub struct Operations(Vec<Operation>);

impl Operations {
fn from_document(doc: ::graphql_parser::query::Document) -> Result<Self, ::failure::Error> {
unimplemented!()
}
}
12 changes: 3 additions & 9 deletions graphql_query_derive/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@ use variables::Variable;

/// This holds all the information we need during the code generation phase.
pub struct QueryContext {
pub _subscription_root: Option<Vec<TokenStream>>,
pub root: Option<Vec<TokenStream>>,
pub fragments: BTreeMap<String, GqlFragment>,
pub mutation_root: Option<Vec<TokenStream>>,
pub query_root: Option<Vec<TokenStream>>,
pub schema: Schema,
pub variables: Vec<Variable>,
}
Expand All @@ -21,10 +19,8 @@ impl QueryContext {
/// Create a QueryContext with the given Schema.
pub fn new(schema: Schema) -> QueryContext {
QueryContext {
_subscription_root: None,
root: None,
fragments: BTreeMap::new(),
mutation_root: None,
query_root: None,
schema,
variables: Vec::new(),
}
Expand Down Expand Up @@ -72,10 +68,8 @@ impl QueryContext {
#[cfg(test)]
pub fn new_empty() -> QueryContext {
QueryContext {
_subscription_root: None,
fragments: BTreeMap::new(),
mutation_root: None,
query_root: None,
root: None,
schema: Schema::new(),
variables: Vec::new(),
}
Expand Down
21 changes: 12 additions & 9 deletions graphql_query_derive/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ impl Schema {
for definition in query.definitions {
match definition {
query::Definition::Operation(query::OperationDefinition::Query(q)) => {
context.query_root = {
context.root = {
let definition = context
.schema
.query_type
Expand All @@ -101,7 +101,7 @@ impl Schema {
context.register_variables(&q.variable_definitions);
}
query::Definition::Operation(query::OperationDefinition::Mutation(q)) => {
context.mutation_root = {
context.root = {
let definition = context
.schema
.mutation_type
Expand All @@ -124,7 +124,7 @@ impl Schema {
context.register_variables(&q.variable_definitions);
}
query::Definition::Operation(query::OperationDefinition::Subscription(q)) => {
context._subscription_root = {
context.root = {
let definition = context
.schema
.subscription_type
Expand All @@ -137,6 +137,13 @@ impl Schema {
let prefix = format!("RUST_{}", prefix);
let selection = Selection::from(&q.selection_set);

if selection.0.len() > 1 {
Err(format_err!(
"{}",
::constants::MULTIPLE_SUBSCRIPTION_FIELDS_ERROR
))?
}

definitions.extend(
definition.field_impls_for_selection(&context, &selection, &prefix)?,
);
Expand Down Expand Up @@ -173,12 +180,7 @@ impl Schema {
.collect();
let fragment_definitions = fragment_definitions?;
let variables_struct = context.expand_variables();
let response_data_fields = context
.query_root
.as_ref()
.or_else(|| context.mutation_root.as_ref())
.or_else(|| context._subscription_root.as_ref())
.expect("no selection defined");
let response_data_fields = context.root.as_ref().expect("no selection defined");

let input_object_definitions: Result<Vec<TokenStream>, _> = context
.schema
Expand Down Expand Up @@ -214,6 +216,7 @@ impl Schema {
#variables_struct

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResponseData {
#(#response_data_fields)*,
}
Expand Down
8 changes: 8 additions & 0 deletions tests/subscription/subscription_invalid_query.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
subscription InvalidSubscription($filter: String) {
newDogs {
name
}
dogBirthdays(filter: $filter) {
name
}
}
5 changes: 5 additions & 0 deletions tests/subscription/subscription_query.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
subscription Birthdays($filter: String) {
dogBirthdays(filter: $filter) {
name
}
}
16 changes: 16 additions & 0 deletions tests/subscription/subscription_query_response.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"dogBirthdays": [
{
"name": "Maya"
},
{
"name": "Norbert"
},
{
"name": "Strelka"
},
{
"name": "Belka"
}
]
}
33 changes: 33 additions & 0 deletions tests/subscription/subscription_schema.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
schema {
query: SimpleQuery
mutation: SimpleMutation
subscription: SimpleSubscription
}

type SimpleQuery {
dogByName(name: String): Dog
}

type SimpleMutation {
petDog(dogName: String): Dog
}

type SimpleSubscription {
newDogs: [Dog]
dogBirthdays(filter: String): [DogBirthday!]
}

type DogBirthday {
name: String
date: String
age: Int
treats: [String]
}

type Dog {
name: String!
"""
Always returns true
"""
isGoodDog: Boolean!
}
38 changes: 38 additions & 0 deletions tests/subscriptions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#[macro_use]
extern crate graphql_client;
#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;

const RESPONSE: &str = include_str!("subscription/subscription_query_response.json");

// If you uncomment this, it will not compile because the query is not valid. We need to investigate how we can make this a real test.
//
// #[derive(GraphQLQuery)]
// #[graphql(
// schema_path = "tests/subscription/subscription_schema.graphql",
// query_path = "tests/subscription/subscription_invalid_query.graphql"
// )]
// struct SubscriptionInvalidQuery;

#[derive(GraphQLQuery)]
#[graphql(
schema_path = "tests/subscription/subscription_schema.graphql",
query_path = "tests/subscription/subscription_query.graphql",
)]
struct SubscriptionQuery;

#[test]
fn subscriptions_work() {
let response_data: subscription_query::ResponseData = serde_json::from_str(RESPONSE).unwrap();

let expected = r##"ResponseData { dog_birthdays: Some([RustBirthdaysDogBirthdays { name: Some("Maya") }, RustBirthdaysDogBirthdays { name: Some("Norbert") }, RustBirthdaysDogBirthdays { name: Some("Strelka") }, RustBirthdaysDogBirthdays { name: Some("Belka") }]) }"##;

assert_eq!(format!("{:?}", response_data), expected);

assert_eq!(
response_data.dog_birthdays.map(|birthdays| birthdays.len()),
Some(4)
);
}