diff --git a/examples/github/src/main.rs b/examples/github/src/main.rs index faf78592b..151c53259 100644 --- a/examples/github/src/main.rs +++ b/examples/github/src/main.rs @@ -69,8 +69,7 @@ fn main() -> Result<(), failure::Error> { .header(reqwest::header::Authorization(format!( "bearer {}", config.github_api_token - ))) - .json(&q) + ))).json(&q) .send()?; let response_body: GraphQLResponse = res.json()?; diff --git a/graphql_query_derive/src/codegen.rs b/graphql_query_derive/src/codegen.rs index a7ea42030..f9501caec 100644 --- a/graphql_query_derive/src/codegen.rs +++ b/graphql_query_derive/src/codegen.rs @@ -1,33 +1,129 @@ -use field_type::FieldType; -use std::collections::BTreeMap; - -#[derive(Debug)] -pub struct StructFieldDescriptor { - pub attributes: TokenStream, - pub name: String, - pub ty: FieldType, -} +use failure; +use fragments::GqlFragment; +use graphql_parser::query; +use operations::Operation; +use proc_macro2::TokenStream; +use query::QueryContext; +use schema; +use selection::Selection; -#[derive(Debug)] -pub struct StructDescriptor { - pub attributes: TokenStream, - pub fields: Vec, - pub name: String, -} +pub(crate) fn response_for_query( + schema: schema::Schema, + query: query::Document, + selected_operation: String, +) -> Result { + let mut context = QueryContext::new(schema); + let mut definitions = Vec::new(); + let mut operations: Vec = Vec::new(); -impl StructDescriptor { - pub fn field_by_name(&self, name: &str) -> Option { - unimplemented!() + for definition in query.definitions { + match definition { + query::Definition::Operation(op) => { + operations.push(op.into()); + } + query::Definition::Fragment(fragment) => { + let query::TypeCondition::On(on) = fragment.type_condition; + context.fragments.insert( + fragment.name.clone(), + GqlFragment { + name: fragment.name, + selection: Selection::from(&fragment.selection_set), + on, + }, + ); + } + } } -} -pub struct EnumVariantDescriptor { - pub attributes: TokenStream, - pub name: String, -} + context.selected_operation = operations + .iter() + .find(|op| op.name == selected_operation) + .map(|i| i.to_owned()); + + let operation = context.selected_operation.clone().unwrap_or_else(|| { + operations + .iter() + .next() + .map(|i| i.to_owned()) + .expect("no operation in query document") + }); + + let response_data_fields = { + let root_name: String = operation + .root_name(&context.schema) + .expect("operation type not in schema"); + let definition = context + .schema + .objects + .get(&root_name) + .expect("schema declaration is invalid"); + let prefix = format!("RUST_{}", operation.name); + let selection = &operation.selection; + + if operation.is_subscription() && selection.0.len() > 1 { + Err(format_err!( + "{}", + ::constants::MULTIPLE_SUBSCRIPTION_FIELDS_ERROR + ))? + } + + definitions.extend( + definition + .field_impls_for_selection(&context, &selection, &prefix) + .unwrap(), + ); + definition + .response_fields_for_selection(&context, &selection, &prefix) + .unwrap() + }; + + let enum_definitions = context.schema.enums.values().map(|enm| enm.to_rust()); + let fragment_definitions: Result, _> = context + .fragments + .values() + .map(|fragment| fragment.to_rust(&context)) + .collect(); + let fragment_definitions = fragment_definitions?; + let variables_struct = operation.expand_variables(&context); + + let input_object_definitions: Result, _> = context + .schema + .inputs + .values() + .map(|i| i.to_rust(&context)) + .collect(); + let input_object_definitions = input_object_definitions?; + + let scalar_definitions: Vec = context + .schema + .scalars + .values() + .map(|s| s.to_rust()) + .collect(); + + Ok(quote! { + type Boolean = bool; + type Float = f64; + type Int = i64; + type ID = String; + + #(#scalar_definitions)* + + #(#input_object_definitions)* + + #(#enum_definitions)* + + #(#fragment_definitions)* + + #(#definitions)* + + #variables_struct + + #[derive(Debug, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct ResponseData { + #(#response_data_fields,)* + } -#[derive(Debug)] -pub struct EnumDescriptor { - pub name: String, - pub variants: Vec, + }) } diff --git a/graphql_query_derive/src/constants.rs b/graphql_query_derive/src/constants.rs index c98ca7d53..1e202e140 100644 --- a/graphql_query_derive/src/constants.rs +++ b/graphql_query_derive/src/constants.rs @@ -2,18 +2,18 @@ use field_type::FieldType; use objects::GqlObjectField; use proc_macro2::{Ident, Span}; -pub const TYPENAME_FIELD: &str = "__typename"; +pub(crate) const TYPENAME_FIELD: &str = "__typename"; -pub fn string_type() -> Ident { +pub(crate) fn string_type() -> Ident { Ident::new("String", Span::call_site()) } #[cfg(test)] -pub fn float_type() -> Ident { +pub(crate) fn float_type() -> Ident { Ident::new("Float", Span::call_site()) } -pub fn typename_field() -> GqlObjectField { +pub(crate) fn typename_field() -> GqlObjectField { GqlObjectField { description: None, name: TYPENAME_FIELD.to_string(), @@ -23,8 +23,37 @@ pub fn typename_field() -> GqlObjectField { } } -pub const MULTIPLE_SUBSCRIPTION_FIELDS_ERROR: &str = r##" +pub(crate) 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 "##; + +/// Error message when a selection set is the root of a query. +pub(crate) const SELECTION_SET_AT_ROOT: &str = r#" +Operations in queries must be named. + +Instead of this: + +{ + user { + name + repositories { + name + commits + } + } +} + +Write this: + +query UserRepositories { + user { + name + repositories { + name + commits + } + } +} +"#; diff --git a/graphql_query_derive/src/enums.rs b/graphql_query_derive/src/enums.rs index b46b6e6cc..aeabba7b1 100644 --- a/graphql_query_derive/src/enums.rs +++ b/graphql_query_derive/src/enums.rs @@ -25,8 +25,7 @@ impl GqlEnum { let description = &v.description; let description = description.as_ref().map(|d| quote!(#[doc = #d])); quote!(#description #name) - }) - .collect(); + }).collect(); let variant_names = &variant_names; let name_ident = Ident::new(&format!("{}{}", ENUMS_PREFIX, self.name), Span::call_site()); let constructors: Vec<_> = self @@ -35,8 +34,7 @@ impl GqlEnum { .map(|v| { let v = Ident::new(&v.name, Span::call_site()); quote!(#name_ident::#v) - }) - .collect(); + }).collect(); let constructors = &constructors; let variant_str: Vec<&str> = self.variants.iter().map(|v| v.name.as_str()).collect(); let variant_str = &variant_str; diff --git a/graphql_query_derive/src/field_type.rs b/graphql_query_derive/src/field_type.rs index bdcb0d423..9f966fe89 100644 --- a/graphql_query_derive/src/field_type.rs +++ b/graphql_query_derive/src/field_type.rs @@ -14,7 +14,7 @@ pub enum FieldType { impl FieldType { /// Takes a field type with its name - pub fn to_rust(&self, context: &QueryContext, prefix: &str) -> TokenStream { + pub(crate) fn to_rust(&self, context: &QueryContext, prefix: &str) -> TokenStream { let prefix: String = if prefix.is_empty() { self.inner_name_string() } else { @@ -24,10 +24,9 @@ impl FieldType { FieldType::Named(name) => { let name_string = name.to_string(); - let name = if context.schema.scalars.contains_key(&name_string) - || DEFAULT_SCALARS - .iter() - .any(|elem| elem == &name_string.as_str()) + let name = if context.schema.scalars.contains_key(&name_string) || DEFAULT_SCALARS + .iter() + .any(|elem| elem == &name_string.as_str()) { name.clone() } else if context.schema.enums.contains_key(&name_string) { diff --git a/graphql_query_derive/src/fragments.rs b/graphql_query_derive/src/fragments.rs index 0580d2026..9387af156 100644 --- a/graphql_query_derive/src/fragments.rs +++ b/graphql_query_derive/src/fragments.rs @@ -10,7 +10,7 @@ pub struct GqlFragment { } impl GqlFragment { - pub fn to_rust(&self, context: &QueryContext) -> Result { + pub(crate) fn to_rust(&self, context: &QueryContext) -> Result { let name_ident = Ident::new(&self.name, Span::call_site()); let object = context.schema.objects.get(&self.on).expect("oh, noes"); let field_impls = object.field_impls_for_selection(context, &self.selection, &self.name)?; diff --git a/graphql_query_derive/src/inputs.rs b/graphql_query_derive/src/inputs.rs index 1c594d7c9..1112d7184 100644 --- a/graphql_query_derive/src/inputs.rs +++ b/graphql_query_derive/src/inputs.rs @@ -16,7 +16,7 @@ pub struct GqlInput { } impl GqlInput { - pub fn to_rust(&self, context: &QueryContext) -> Result { + pub(crate) fn to_rust(&self, context: &QueryContext) -> Result { let name = Ident::new(&self.name, Span::call_site()); let mut fields: Vec<&GqlObjectField> = self.fields.values().collect(); fields.sort_unstable_by(|a, b| a.name.cmp(&b.name)); @@ -52,8 +52,7 @@ impl ::std::convert::From for GqlInput type_: field.value_type.into(), }; (name, field) - }) - .collect(), + }).collect(), } } } @@ -80,8 +79,7 @@ impl ::std::convert::From for GqlInput { .into(), }; (name, field) - }) - .collect(), + }).collect(), } } } @@ -129,7 +127,7 @@ mod tests { }, ), ].into_iter() - .collect(), + .collect(), }; let expected: String = vec![ @@ -141,7 +139,7 @@ mod tests { "pub requirements : Option < CatRequirements > , ", "}", ].into_iter() - .collect(); + .collect(); let mut context = QueryContext::new_empty(); context.schema.inputs.insert(cat.name.clone(), cat); diff --git a/graphql_query_derive/src/interfaces.rs b/graphql_query_derive/src/interfaces.rs index 199ae1bfd..c3d2698ef 100644 --- a/graphql_query_derive/src/interfaces.rs +++ b/graphql_query_derive/src/interfaces.rs @@ -26,7 +26,7 @@ impl GqlInterface { } } - pub fn response_for_selection( + pub(crate) fn response_for_selection( &self, query_context: &QueryContext, selection: &Selection, diff --git a/graphql_query_derive/src/lib.rs b/graphql_query_derive/src/lib.rs index 087330410..15f78c79e 100644 --- a/graphql_query_derive/src/lib.rs +++ b/graphql_query_derive/src/lib.rs @@ -16,6 +16,7 @@ extern crate quote; use proc_macro2::TokenStream; +mod codegen; mod constants; mod enums; mod field_type; @@ -24,6 +25,7 @@ mod inputs; mod interfaces; mod introspection_response; mod objects; +mod operations; mod query; mod scalars; mod schema; @@ -106,7 +108,7 @@ fn impl_gql_query(input: &syn::DeriveInput) -> Result Result, + pub selection: Selection, +} + +impl Operation { + pub(crate) fn root_name(&self, schema: &::schema::Schema) -> Option { + match self.operation_type { + OperationType::Query => schema.query_type.clone(), + OperationType::Mutation => schema.mutation_type.clone(), + OperationType::Subscription => schema.subscription_type.clone(), + } + } + + pub(crate) fn is_subscription(&self) -> bool { + match self.operation_type { + OperationType::Subscription => true, + _ => false, + } + } + + /// Generate the Variables struct and all the necessary supporting code. + pub(crate) fn expand_variables(&self, context: &QueryContext) -> TokenStream { + let variables = &self.variables; + + if variables.is_empty() { + return quote!(#[derive(Serialize)] + pub struct Variables;); + } + + let fields = variables.iter().map(|variable| { + let name = &variable.name; + let ty = variable.ty.to_rust(context, ""); + let name = Ident::new(&name.to_snake_case(), Span::call_site()); + quote!(pub #name: #ty) + }); + + let default_constructors = variables + .iter() + .map(|variable| variable.generate_default_value_constructor(context)); + + quote! { + #[derive(Serialize)] + #[serde(rename_all = "camelCase")] + pub struct Variables { + #(#fields,)* + } + + impl Variables { + #(#default_constructors)* + } + } + } +} + +impl ::std::convert::From for Operation { + fn from(definition: OperationDefinition) -> Operation { + match definition { + OperationDefinition::Query(q) => Operation { + name: q.name.expect("unnamed operation"), + operation_type: OperationType::Query, + variables: q + .variable_definitions + .iter() + .map(|v| v.clone().into()) + .collect(), + selection: (&q.selection_set).into(), + }, + OperationDefinition::Mutation(m) => Operation { + name: m.name.expect("unnamed operation"), + operation_type: OperationType::Mutation, + variables: m + .variable_definitions + .iter() + .map(|v| v.clone().into()) + .collect(), + selection: (&m.selection_set).into(), + }, + OperationDefinition::Subscription(s) => Operation { + name: s.name.expect("unnamed operation"), + operation_type: OperationType::Subscription, + variables: s + .variable_definitions + .iter() + .map(|v| v.clone().into()) + .collect(), + selection: (&s.selection_set).into(), + }, + OperationDefinition::SelectionSet(_) => panic!(SELECTION_SET_AT_ROOT), + } + } +} diff --git a/graphql_query_derive/src/query.rs b/graphql_query_derive/src/query.rs index 3368a6139..a16538d34 100644 --- a/graphql_query_derive/src/query.rs +++ b/graphql_query_derive/src/query.rs @@ -2,82 +2,40 @@ use failure; use fragments::GqlFragment; use graphql_parser; use heck::SnakeCase; +use operations::Operation; use proc_macro2::{Ident, Span, TokenStream}; use schema::Schema; use selection::Selection; use std::collections::BTreeMap; -use variables::Variable; /// This holds all the information we need during the code generation phase. -pub struct QueryContext { - pub root: Option>, +pub(crate) struct QueryContext { pub fragments: BTreeMap, pub schema: Schema, - pub variables: Vec, + pub selected_operation: Option, } impl QueryContext { /// Create a QueryContext with the given Schema. - pub fn new(schema: Schema) -> QueryContext { + pub(crate) fn new(schema: Schema) -> QueryContext { QueryContext { - root: None, fragments: BTreeMap::new(), schema, - variables: Vec::new(), - } - } - - /// Ingest the variable definitions for the query we are generating code for. - pub fn register_variables(&mut self, variables: &[graphql_parser::query::VariableDefinition]) { - variables.iter().for_each(|variable| { - self.variables.push(variable.clone().into()); - }); - } - - /// Generate the Variables struct and all the necessary supporting code. - pub fn expand_variables(&self) -> TokenStream { - if self.variables.is_empty() { - return quote!(#[derive(Serialize)] - pub struct Variables;); - } - - let fields = self.variables.iter().map(|variable| { - let name = &variable.name; - let ty = variable.ty.to_rust(self, ""); - let name = Ident::new(&name.to_snake_case(), Span::call_site()); - quote!(pub #name: #ty) - }); - - let default_constructors = self - .variables - .iter() - .map(|variable| variable.generate_default_value_constructor(self)); - - quote! { - #[derive(Serialize)] - #[serde(rename_all = "camelCase")] - pub struct Variables { - #(#fields,)* - } - - impl Variables { - #(#default_constructors)* - } + selected_operation: None, } } /// For testing only. creates an empty QueryContext with an empty Schema. #[cfg(test)] - pub fn new_empty() -> QueryContext { + pub(crate) fn new_empty() -> QueryContext { QueryContext { fragments: BTreeMap::new(), - root: None, schema: Schema::new(), - variables: Vec::new(), + selected_operation: None, } } - pub fn maybe_expand_field( + pub(crate) fn maybe_expand_field( &self, ty: &str, selection: &Selection, diff --git a/graphql_query_derive/src/schema.rs b/graphql_query_derive/src/schema.rs index b0c5a0eb2..01585ce46 100644 --- a/graphql_query_derive/src/schema.rs +++ b/graphql_query_derive/src/schema.rs @@ -1,48 +1,16 @@ use enums::{EnumVariant, GqlEnum}; use failure; use field_type::FieldType; -use fragments::GqlFragment; -use graphql_parser::{self, query, schema}; +use graphql_parser::{self, schema}; use inputs::GqlInput; use interfaces::GqlInterface; use objects::{GqlObject, GqlObjectField}; -use proc_macro2::TokenStream; -use query::QueryContext; use scalars::Scalar; -use selection::Selection; use std::collections::{BTreeMap, BTreeSet}; use unions::GqlUnion; pub const DEFAULT_SCALARS: &[&str] = &["ID", "String", "Int", "Float", "Boolean"]; -const SELECTION_SET_AT_ROOT: &str = r#" -Operations in queries must be named. - -Instead of this: - -{ - user { - name - repositories { - name - commits - } - } -} - -Write this: - -query UserRepositories { - user { - name - repositories { - name - commits - } - } -} -"#; - #[derive(Debug, PartialEq)] pub struct Schema { pub enums: BTreeMap, @@ -71,159 +39,6 @@ impl Schema { } } - pub fn response_for_query(self, query: query::Document) -> Result { - let mut context = QueryContext::new(self); - let mut definitions = Vec::new(); - - for definition in query.definitions { - match definition { - query::Definition::Operation(query::OperationDefinition::Query(q)) => { - context.root = { - let definition = context - .schema - .query_type - .clone() - .and_then(|query_type| context.schema.objects.get(&query_type)) - .expect("query type is defined"); - let prefix = &q.name.expect("unnamed operation"); - let prefix = format!("RUST_{}", prefix); - let selection = Selection::from(&q.selection_set); - - definitions.extend( - definition.field_impls_for_selection(&context, &selection, &prefix)?, - ); - Some( - definition - .response_fields_for_selection(&context, &selection, &prefix)?, - ) - }; - - context.register_variables(&q.variable_definitions); - } - query::Definition::Operation(query::OperationDefinition::Mutation(q)) => { - context.root = { - let definition = context - .schema - .mutation_type - .clone() - .and_then(|mutation_type| context.schema.objects.get(&mutation_type)) - .expect("mutation type is defined"); - let prefix = &q.name.expect("unnamed operation"); - let prefix = format!("RUST_{}", prefix); - let selection = Selection::from(&q.selection_set); - - definitions.extend( - definition.field_impls_for_selection(&context, &selection, &prefix)?, - ); - Some( - definition - .response_fields_for_selection(&context, &selection, &prefix)?, - ) - }; - - context.register_variables(&q.variable_definitions); - } - query::Definition::Operation(query::OperationDefinition::Subscription(q)) => { - context.root = { - let definition = context - .schema - .subscription_type - .clone() - .and_then(|subscription_type| { - context.schema.objects.get(&subscription_type) - }) - .expect("subscription type is defined"); - let prefix = &q.name.expect("unnamed operation"); - 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)?, - ); - Some( - definition - .response_fields_for_selection(&context, &selection, &prefix)?, - ) - }; - - context.register_variables(&q.variable_definitions); - } - query::Definition::Operation(query::OperationDefinition::SelectionSet(_)) => { - panic!(SELECTION_SET_AT_ROOT) - } - query::Definition::Fragment(fragment) => { - let query::TypeCondition::On(on) = fragment.type_condition; - context.fragments.insert( - fragment.name.clone(), - GqlFragment { - name: fragment.name, - selection: Selection::from(&fragment.selection_set), - on, - }, - ); - } - } - } - - let enum_definitions = context.schema.enums.values().map(|enm| enm.to_rust()); - let fragment_definitions: Result, _> = context - .fragments - .values() - .map(|fragment| fragment.to_rust(&context)) - .collect(); - let fragment_definitions = fragment_definitions?; - let variables_struct = context.expand_variables(); - let response_data_fields = context.root.as_ref().expect("no selection defined"); - - let input_object_definitions: Result, _> = context - .schema - .inputs - .values() - .map(|i| i.to_rust(&context)) - .collect(); - let input_object_definitions = input_object_definitions?; - - let scalar_definitions: Vec = context - .schema - .scalars - .values() - .map(|s| s.to_rust()) - .collect(); - - Ok(quote! { - type Boolean = bool; - type Float = f64; - type Int = i64; - type ID = String; - - #(#scalar_definitions)* - - #(#input_object_definitions)* - - #(#enum_definitions)* - - #(#fragment_definitions)* - - #(#definitions)* - - #variables_struct - - #[derive(Debug, Serialize, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct ResponseData { - #(#response_data_fields)*, - } - - }) - } - pub fn ingest_interface_implementations( &mut self, impls: BTreeMap>, @@ -237,8 +52,7 @@ impl Schema { .ok_or_else(|| format_err!("interface not found: {}", iface_name))?; iface.implemented_by = implementors.into_iter().collect(); Ok(()) - }) - .collect() + }).collect() } } @@ -278,8 +92,7 @@ impl ::std::convert::From for Schema { .map(|v| EnumVariant { description: v.description.clone(), name: v.name.clone(), - }) - .collect(), + }).collect(), }, ); } @@ -375,8 +188,7 @@ impl ::std::convert::From<::introspection_response::IntrospectionResponse> for S description: t.description, name: t.name.expect("enum variant name"), }) - }) - .filter_map(|t| t) + }).filter_map(|t| t) .collect(); let mut enm = GqlEnum { name: name.clone(), diff --git a/graphql_query_derive/src/selection.rs b/graphql_query_derive/src/selection.rs index 0749e7b66..e3d28b1b1 100644 --- a/graphql_query_derive/src/selection.rs +++ b/graphql_query_derive/src/selection.rs @@ -115,8 +115,7 @@ mod tests { } else { None } - }) - .next() + }).next() .unwrap(); let selection: Selection = selection_set.into(); diff --git a/graphql_query_derive/src/shared.rs b/graphql_query_derive/src/shared.rs index d10a352dd..73332ff51 100644 --- a/graphql_query_derive/src/shared.rs +++ b/graphql_query_derive/src/shared.rs @@ -51,11 +51,10 @@ pub(crate) fn field_impls_for_selection( } else { Ok(quote!()) } - }) - .collect() + }).collect() } -pub fn response_fields_for_selection( +pub(crate) fn response_fields_for_selection( schema_fields: &[GqlObjectField], context: &QueryContext, selection: &Selection, @@ -95,6 +94,5 @@ pub fn response_fields_for_selection( SelectionItem::InlineFragment(_) => { Err(format_err!("inline fragment on object field"))? } - }) - .collect() + }).collect() } diff --git a/graphql_query_derive/src/unions.rs b/graphql_query_derive/src/unions.rs index fe99dda20..b19ad164c 100644 --- a/graphql_query_derive/src/unions.rs +++ b/graphql_query_derive/src/unions.rs @@ -23,7 +23,7 @@ enum UnionError { MissingTypename { union_name: String }, } -pub fn union_variants( +pub(crate) fn union_variants( selection: &Selection, query_context: &QueryContext, prefix: &str, @@ -95,7 +95,7 @@ pub fn union_variants( } impl GqlUnion { - pub fn response_for_selection( + pub(crate) fn response_for_selection( &self, query_context: &QueryContext, selection: &Selection, @@ -332,7 +332,7 @@ mod tests { "# [ serde ( tag = \"__typename\" ) ] ", "pub enum Meow { User ( MeowOnUser ) , Organization ( MeowOnOrganization ) }", ].into_iter() - .collect::(), + .collect::(), ); } } diff --git a/graphql_query_derive/src/variables.rs b/graphql_query_derive/src/variables.rs index a0c9185b5..95bacb80b 100644 --- a/graphql_query_derive/src/variables.rs +++ b/graphql_query_derive/src/variables.rs @@ -4,7 +4,7 @@ use proc_macro2::{Ident, Span, TokenStream}; use query::QueryContext; use std::collections::BTreeMap; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Variable { pub name: String, pub ty: FieldType, @@ -12,7 +12,7 @@ pub struct Variable { } impl Variable { - pub fn generate_default_value_constructor(&self, context: &QueryContext) -> TokenStream { + pub(crate) fn generate_default_value_constructor(&self, context: &QueryContext) -> TokenStream { match &self.default { Some(default) => { let fn_name = Ident::new(&format!("default_{}", self.name), Span::call_site()); @@ -118,8 +118,7 @@ fn render_object_literal( } None => quote!(#field_name: None), } - }) - .collect(); + }).collect(); quote!(#constructor { #(#fields,)* diff --git a/tests/operation_selection.rs b/tests/operation_selection.rs new file mode 100644 index 000000000..5b2c5c0e2 --- /dev/null +++ b/tests/operation_selection.rs @@ -0,0 +1,48 @@ +#[macro_use] +extern crate graphql_client; +#[macro_use] +extern crate serde_derive; +extern crate serde; +extern crate serde_json; + +#[derive(GraphQLQuery)] +#[graphql( + query_path = "tests/operation_selection/queries.graphql", + schema_path = "tests/operation_selection/schema.graphql" +)] +#[allow(dead_code)] +struct Heights; + +#[derive(GraphQLQuery)] +#[graphql( + query_path = "tests/operation_selection/queries.graphql", + schema_path = "tests/operation_selection/schema.graphql" +)] +#[allow(dead_code)] +struct Echo; + +const HEIGHTS_RESPONSE: &'static str = r##"{"mountainHeight": 224, "buildingHeight": 12}"##; +const ECHO_RESPONSE: &'static str = r##"{"echo": "tiramisù"}"##; + +#[test] +fn operation_selection_works() { + let heights_response_data: heights::ResponseData = + serde_json::from_str(HEIGHTS_RESPONSE).unwrap(); + let echo_response_data: echo::ResponseData = serde_json::from_str(ECHO_RESPONSE).unwrap(); + + let _echo_variables = echo::Variables { + msg: Some("hi".to_string()), + }; + + let _height_variables = heights::Variables { + building_id: "12".to_string(), + mountain_name: Some("canigou".to_string()), + }; + + let expected_echo = r##"ResponseData { echo: Some("tiramisù") }"##; + let expected_heights = + r##"ResponseData { mountain_height: Some(224), building_height: Some(12) }"##; + + assert_eq!(expected_echo, format!("{:?}", echo_response_data)); + assert_eq!(expected_heights, format!("{:?}", heights_response_data)); +} diff --git a/tests/operation_selection/queries.graphql b/tests/operation_selection/queries.graphql new file mode 100644 index 000000000..a2601f4ef --- /dev/null +++ b/tests/operation_selection/queries.graphql @@ -0,0 +1,8 @@ +query Heights($buildingId: ID!, $mountainName: String) { + mountainHeight(name: $mountainName) + buildingHeight(id: $buildingId) +} + +query Echo($msg: String) { + echo(msg: $msg) +} diff --git a/tests/operation_selection/schema.graphql b/tests/operation_selection/schema.graphql new file mode 100644 index 000000000..754c847fa --- /dev/null +++ b/tests/operation_selection/schema.graphql @@ -0,0 +1,9 @@ +schema { + query: QRoot +} + +type QRoot { + mountainHeight(name: String!): Int + buildingHeight(id: Id!): Int + echo(msg: String!): String +}