Skip to content

Commit ae1f858

Browse files
authored
Merge pull request #71 from tomhoule/multiple-operations
Support selecting an operation inside a query document
2 parents 5aed14a + a96a19a commit ae1f858

File tree

20 files changed

+377
-314
lines changed

20 files changed

+377
-314
lines changed

examples/github/src/main.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,7 @@ fn main() -> Result<(), failure::Error> {
6969
.header(reqwest::header::Authorization(format!(
7070
"bearer {}",
7171
config.github_api_token
72-
)))
73-
.json(&q)
72+
))).json(&q)
7473
.send()?;
7574

7675
let response_body: GraphQLResponse<query1::ResponseData> = res.json()?;

graphql_query_derive/src/codegen.rs

Lines changed: 123 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,129 @@
1-
use field_type::FieldType;
2-
use std::collections::BTreeMap;
3-
4-
#[derive(Debug)]
5-
pub struct StructFieldDescriptor {
6-
pub attributes: TokenStream,
7-
pub name: String,
8-
pub ty: FieldType,
9-
}
1+
use failure;
2+
use fragments::GqlFragment;
3+
use graphql_parser::query;
4+
use operations::Operation;
5+
use proc_macro2::TokenStream;
6+
use query::QueryContext;
7+
use schema;
8+
use selection::Selection;
109

11-
#[derive(Debug)]
12-
pub struct StructDescriptor {
13-
pub attributes: TokenStream,
14-
pub fields: Vec<StructFieldDescriptor>,
15-
pub name: String,
16-
}
10+
pub(crate) fn response_for_query(
11+
schema: schema::Schema,
12+
query: query::Document,
13+
selected_operation: String,
14+
) -> Result<TokenStream, failure::Error> {
15+
let mut context = QueryContext::new(schema);
16+
let mut definitions = Vec::new();
17+
let mut operations: Vec<Operation> = Vec::new();
1718

18-
impl StructDescriptor {
19-
pub fn field_by_name(&self, name: &str) -> Option<StructFieldDescriptor> {
20-
unimplemented!()
19+
for definition in query.definitions {
20+
match definition {
21+
query::Definition::Operation(op) => {
22+
operations.push(op.into());
23+
}
24+
query::Definition::Fragment(fragment) => {
25+
let query::TypeCondition::On(on) = fragment.type_condition;
26+
context.fragments.insert(
27+
fragment.name.clone(),
28+
GqlFragment {
29+
name: fragment.name,
30+
selection: Selection::from(&fragment.selection_set),
31+
on,
32+
},
33+
);
34+
}
35+
}
2136
}
22-
}
2337

24-
pub struct EnumVariantDescriptor {
25-
pub attributes: TokenStream,
26-
pub name: String,
27-
}
38+
context.selected_operation = operations
39+
.iter()
40+
.find(|op| op.name == selected_operation)
41+
.map(|i| i.to_owned());
42+
43+
let operation = context.selected_operation.clone().unwrap_or_else(|| {
44+
operations
45+
.iter()
46+
.next()
47+
.map(|i| i.to_owned())
48+
.expect("no operation in query document")
49+
});
50+
51+
let response_data_fields = {
52+
let root_name: String = operation
53+
.root_name(&context.schema)
54+
.expect("operation type not in schema");
55+
let definition = context
56+
.schema
57+
.objects
58+
.get(&root_name)
59+
.expect("schema declaration is invalid");
60+
let prefix = format!("RUST_{}", operation.name);
61+
let selection = &operation.selection;
62+
63+
if operation.is_subscription() && selection.0.len() > 1 {
64+
Err(format_err!(
65+
"{}",
66+
::constants::MULTIPLE_SUBSCRIPTION_FIELDS_ERROR
67+
))?
68+
}
69+
70+
definitions.extend(
71+
definition
72+
.field_impls_for_selection(&context, &selection, &prefix)
73+
.unwrap(),
74+
);
75+
definition
76+
.response_fields_for_selection(&context, &selection, &prefix)
77+
.unwrap()
78+
};
79+
80+
let enum_definitions = context.schema.enums.values().map(|enm| enm.to_rust());
81+
let fragment_definitions: Result<Vec<TokenStream>, _> = context
82+
.fragments
83+
.values()
84+
.map(|fragment| fragment.to_rust(&context))
85+
.collect();
86+
let fragment_definitions = fragment_definitions?;
87+
let variables_struct = operation.expand_variables(&context);
88+
89+
let input_object_definitions: Result<Vec<TokenStream>, _> = context
90+
.schema
91+
.inputs
92+
.values()
93+
.map(|i| i.to_rust(&context))
94+
.collect();
95+
let input_object_definitions = input_object_definitions?;
96+
97+
let scalar_definitions: Vec<TokenStream> = context
98+
.schema
99+
.scalars
100+
.values()
101+
.map(|s| s.to_rust())
102+
.collect();
103+
104+
Ok(quote! {
105+
type Boolean = bool;
106+
type Float = f64;
107+
type Int = i64;
108+
type ID = String;
109+
110+
#(#scalar_definitions)*
111+
112+
#(#input_object_definitions)*
113+
114+
#(#enum_definitions)*
115+
116+
#(#fragment_definitions)*
117+
118+
#(#definitions)*
119+
120+
#variables_struct
121+
122+
#[derive(Debug, Serialize, Deserialize)]
123+
#[serde(rename_all = "camelCase")]
124+
pub struct ResponseData {
125+
#(#response_data_fields,)*
126+
}
28127

29-
#[derive(Debug)]
30-
pub struct EnumDescriptor {
31-
pub name: String,
32-
pub variants: Vec<String>,
128+
})
33129
}

graphql_query_derive/src/constants.rs

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@ use field_type::FieldType;
22
use objects::GqlObjectField;
33
use proc_macro2::{Ident, Span};
44

5-
pub const TYPENAME_FIELD: &str = "__typename";
5+
pub(crate) const TYPENAME_FIELD: &str = "__typename";
66

7-
pub fn string_type() -> Ident {
7+
pub(crate) fn string_type() -> Ident {
88
Ident::new("String", Span::call_site())
99
}
1010

1111
#[cfg(test)]
12-
pub fn float_type() -> Ident {
12+
pub(crate) fn float_type() -> Ident {
1313
Ident::new("Float", Span::call_site())
1414
}
1515

16-
pub fn typename_field() -> GqlObjectField {
16+
pub(crate) fn typename_field() -> GqlObjectField {
1717
GqlObjectField {
1818
description: None,
1919
name: TYPENAME_FIELD.to_string(),
@@ -23,8 +23,37 @@ pub fn typename_field() -> GqlObjectField {
2323
}
2424
}
2525

26-
pub const MULTIPLE_SUBSCRIPTION_FIELDS_ERROR: &str = r##"
26+
pub(crate) const MULTIPLE_SUBSCRIPTION_FIELDS_ERROR: &str = r##"
2727
Multiple-field queries on the root subscription field are forbidden by the spec.
2828
2929
See: https://github.com/facebook/graphql/blob/master/spec/Section%205%20--%20Validation.md#subscription-operation-definitions
3030
"##;
31+
32+
/// Error message when a selection set is the root of a query.
33+
pub(crate) const SELECTION_SET_AT_ROOT: &str = r#"
34+
Operations in queries must be named.
35+
36+
Instead of this:
37+
38+
{
39+
user {
40+
name
41+
repositories {
42+
name
43+
commits
44+
}
45+
}
46+
}
47+
48+
Write this:
49+
50+
query UserRepositories {
51+
user {
52+
name
53+
repositories {
54+
name
55+
commits
56+
}
57+
}
58+
}
59+
"#;

graphql_query_derive/src/enums.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ impl GqlEnum {
2525
let description = &v.description;
2626
let description = description.as_ref().map(|d| quote!(#[doc = #d]));
2727
quote!(#description #name)
28-
})
29-
.collect();
28+
}).collect();
3029
let variant_names = &variant_names;
3130
let name_ident = Ident::new(&format!("{}{}", ENUMS_PREFIX, self.name), Span::call_site());
3231
let constructors: Vec<_> = self
@@ -35,8 +34,7 @@ impl GqlEnum {
3534
.map(|v| {
3635
let v = Ident::new(&v.name, Span::call_site());
3736
quote!(#name_ident::#v)
38-
})
39-
.collect();
37+
}).collect();
4038
let constructors = &constructors;
4139
let variant_str: Vec<&str> = self.variants.iter().map(|v| v.name.as_str()).collect();
4240
let variant_str = &variant_str;

graphql_query_derive/src/field_type.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub enum FieldType {
1414

1515
impl FieldType {
1616
/// Takes a field type with its name
17-
pub fn to_rust(&self, context: &QueryContext, prefix: &str) -> TokenStream {
17+
pub(crate) fn to_rust(&self, context: &QueryContext, prefix: &str) -> TokenStream {
1818
let prefix: String = if prefix.is_empty() {
1919
self.inner_name_string()
2020
} else {
@@ -24,10 +24,9 @@ impl FieldType {
2424
FieldType::Named(name) => {
2525
let name_string = name.to_string();
2626

27-
let name = if context.schema.scalars.contains_key(&name_string)
28-
|| DEFAULT_SCALARS
29-
.iter()
30-
.any(|elem| elem == &name_string.as_str())
27+
let name = if context.schema.scalars.contains_key(&name_string) || DEFAULT_SCALARS
28+
.iter()
29+
.any(|elem| elem == &name_string.as_str())
3130
{
3231
name.clone()
3332
} else if context.schema.enums.contains_key(&name_string) {

graphql_query_derive/src/fragments.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub struct GqlFragment {
1010
}
1111

1212
impl GqlFragment {
13-
pub fn to_rust(&self, context: &QueryContext) -> Result<TokenStream, ::failure::Error> {
13+
pub(crate) fn to_rust(&self, context: &QueryContext) -> Result<TokenStream, ::failure::Error> {
1414
let name_ident = Ident::new(&self.name, Span::call_site());
1515
let object = context.schema.objects.get(&self.on).expect("oh, noes");
1616
let field_impls = object.field_impls_for_selection(context, &self.selection, &self.name)?;

graphql_query_derive/src/inputs.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub struct GqlInput {
1616
}
1717

1818
impl GqlInput {
19-
pub fn to_rust(&self, context: &QueryContext) -> Result<TokenStream, failure::Error> {
19+
pub(crate) fn to_rust(&self, context: &QueryContext) -> Result<TokenStream, failure::Error> {
2020
let name = Ident::new(&self.name, Span::call_site());
2121
let mut fields: Vec<&GqlObjectField> = self.fields.values().collect();
2222
fields.sort_unstable_by(|a, b| a.name.cmp(&b.name));
@@ -52,8 +52,7 @@ impl ::std::convert::From<graphql_parser::schema::InputObjectType> for GqlInput
5252
type_: field.value_type.into(),
5353
};
5454
(name, field)
55-
})
56-
.collect(),
55+
}).collect(),
5756
}
5857
}
5958
}
@@ -80,8 +79,7 @@ impl ::std::convert::From<introspection_response::FullType> for GqlInput {
8079
.into(),
8180
};
8281
(name, field)
83-
})
84-
.collect(),
82+
}).collect(),
8583
}
8684
}
8785
}
@@ -129,7 +127,7 @@ mod tests {
129127
},
130128
),
131129
].into_iter()
132-
.collect(),
130+
.collect(),
133131
};
134132

135133
let expected: String = vec![
@@ -141,7 +139,7 @@ mod tests {
141139
"pub requirements : Option < CatRequirements > , ",
142140
"}",
143141
].into_iter()
144-
.collect();
142+
.collect();
145143

146144
let mut context = QueryContext::new_empty();
147145
context.schema.inputs.insert(cat.name.clone(), cat);

graphql_query_derive/src/interfaces.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ impl GqlInterface {
2626
}
2727
}
2828

29-
pub fn response_for_selection(
29+
pub(crate) fn response_for_selection(
3030
&self,
3131
query_context: &QueryContext,
3232
selection: &Selection,

graphql_query_derive/src/lib.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ extern crate quote;
1616

1717
use proc_macro2::TokenStream;
1818

19+
mod codegen;
1920
mod constants;
2021
mod enums;
2122
mod field_type;
@@ -24,6 +25,7 @@ mod inputs;
2425
mod interfaces;
2526
mod introspection_response;
2627
mod objects;
28+
mod operations;
2729
mod query;
2830
mod scalars;
2931
mod schema;
@@ -106,7 +108,7 @@ fn impl_gql_query(input: &syn::DeriveInput) -> Result<TokenStream, failure::Erro
106108

107109
let module_name = Ident::new(&input.ident.to_string().to_snake_case(), Span::call_site());
108110
let struct_name = &input.ident;
109-
let schema_output = schema.response_for_query(query)?;
111+
let schema_output = codegen::response_for_query(schema, query, input.ident.to_string())?;
110112

111113
let result = quote!(
112114
pub mod #module_name {
@@ -145,8 +147,7 @@ fn extract_attr(ast: &syn::DeriveInput, attr: &str) -> Result<String, failure::E
145147
.find(|attr| {
146148
let path = &attr.path;
147149
quote!(#path).to_string() == "graphql"
148-
})
149-
.ok_or_else(|| format_err!("The graphql attribute is missing"))?;
150+
}).ok_or_else(|| format_err!("The graphql attribute is missing"))?;
150151
if let syn::Meta::List(items) = &attribute
151152
.interpret_meta()
152153
.expect("Attribute is well formatted")

graphql_query_derive/src/objects.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ impl GqlObject {
6161
item
6262
}
6363

64-
pub fn response_for_selection(
64+
pub(crate) fn response_for_selection(
6565
&self,
6666
query_context: &QueryContext,
6767
selection: &Selection,
@@ -83,7 +83,7 @@ impl GqlObject {
8383
})
8484
}
8585

86-
pub fn field_impls_for_selection(
86+
pub(crate) fn field_impls_for_selection(
8787
&self,
8888
query_context: &QueryContext,
8989
selection: &Selection,
@@ -92,7 +92,7 @@ impl GqlObject {
9292
field_impls_for_selection(&self.fields, query_context, selection, prefix)
9393
}
9494

95-
pub fn response_fields_for_selection(
95+
pub(crate) fn response_fields_for_selection(
9696
&self,
9797
query_context: &QueryContext,
9898
selection: &Selection,

0 commit comments

Comments
 (0)