Skip to content

Commit b35b0de

Browse files
committed
Implement deriving arbitrary traits on ResponseData
1 parent 4a0eb43 commit b35b0de

File tree

21 files changed

+304
-63
lines changed

21 files changed

+304
-63
lines changed

examples/example_module/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use custom_scalars::*;
1111
#[derive(GraphQLQuery)]
1212
#[graphql(
1313
schema_path = "../github/src/schema.graphql",
14-
query_path = "../github/src/query_1.graphql"
14+
query_path = "../github/src/query_1.graphql",
15+
response_derives = "Debug",
1516
)]
1617
pub struct ExampleModule;

examples/github/src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ type DateTime = String;
3232
#[derive(GraphQLQuery)]
3333
#[graphql(
3434
schema_path = "src/schema.graphql",
35-
query_path = "src/query_1.graphql"
35+
query_path = "src/query_1.graphql",
36+
response_derives = "Debug",
3637
)]
3738
struct Query1;
3839

graphql_client_cli/src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ use structopt::StructOpt;
1515
#[derive(GraphQLQuery)]
1616
#[graphql(
1717
schema_path = "src/introspection_schema.graphql",
18-
query_path = "src/introspection_query.graphql"
18+
query_path = "src/introspection_query.graphql",
19+
response_derives = "Serialize",
1920
)]
2021
struct IntrospectionQuery;
2122

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use failure;
2+
use syn;
3+
4+
pub(crate) fn extract_attr(ast: &syn::DeriveInput, attr: &str) -> Result<String, failure::Error> {
5+
let attributes = &ast.attrs;
6+
let attribute = attributes
7+
.iter()
8+
.find(|attr| {
9+
let path = &attr.path;
10+
quote!(#path).to_string() == "graphql"
11+
}).ok_or_else(|| format_err!("The graphql attribute is missing"))?;
12+
if let syn::Meta::List(items) = &attribute
13+
.interpret_meta()
14+
.expect("Attribute is well formatted")
15+
{
16+
for item in items.nested.iter() {
17+
if let syn::NestedMeta::Meta(syn::Meta::NameValue(name_value)) = item {
18+
let syn::MetaNameValue { ident, lit, .. } = name_value;
19+
if ident == attr {
20+
if let syn::Lit::Str(lit) = lit {
21+
return Ok(lit.value());
22+
}
23+
}
24+
}
25+
}
26+
}
27+
28+
Err(format_err!("attribute not found"))?
29+
}

graphql_query_derive/src/codegen.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,14 @@ pub(crate) fn response_for_query(
1111
schema: schema::Schema,
1212
query: query::Document,
1313
selected_operation: String,
14+
additional_derives: Option<String>,
1415
) -> Result<TokenStream, failure::Error> {
1516
let mut context = QueryContext::new(schema);
17+
18+
if let Some(derives) = additional_derives {
19+
context.ingest_additional_derives(&derives).unwrap();
20+
}
21+
1622
let mut definitions = Vec::new();
1723
let mut operations: Vec<Operation> = Vec::new();
1824

@@ -77,7 +83,11 @@ pub(crate) fn response_for_query(
7783
.unwrap()
7884
};
7985

80-
let enum_definitions = context.schema.enums.values().map(|enm| enm.to_rust());
86+
let enum_definitions = context
87+
.schema
88+
.enums
89+
.values()
90+
.map(|enm| enm.to_rust(&context));
8191
let fragment_definitions: Result<Vec<TokenStream>, _> = context
8292
.fragments
8393
.values()
@@ -101,6 +111,8 @@ pub(crate) fn response_for_query(
101111
.map(|s| s.to_rust())
102112
.collect();
103113

114+
let response_derives = context.response_derives();
115+
104116
Ok(quote! {
105117
type Boolean = bool;
106118
type Float = f64;
@@ -119,7 +131,7 @@ pub(crate) fn response_for_query(
119131

120132
#variables_struct
121133

122-
#[derive(Debug, Serialize, Deserialize)]
134+
#response_derives
123135
#[serde(rename_all = "camelCase")]
124136
pub struct ResponseData {
125137
#(#response_data_fields,)*

graphql_query_derive/src/enums.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ pub struct GqlEnum {
1616
}
1717

1818
impl GqlEnum {
19-
pub fn to_rust(&self) -> TokenStream {
19+
pub(crate) fn to_rust(&self, query_context: &::query::QueryContext) -> TokenStream {
20+
let derives = query_context.response_enum_derives();
2021
let variant_names: Vec<TokenStream> = self
2122
.variants
2223
.iter()
@@ -42,7 +43,7 @@ impl GqlEnum {
4243
let name = name_ident.clone();
4344

4445
quote! {
45-
#[derive(Debug)]
46+
#derives
4647
pub enum #name {
4748
#(#variant_names,)*
4849
Other(String),

graphql_query_derive/src/fragments.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ pub struct GqlFragment {
1111

1212
impl GqlFragment {
1313
pub(crate) fn to_rust(&self, context: &QueryContext) -> Result<TokenStream, ::failure::Error> {
14+
let derives = context.response_derives();
1415
let name_ident = Ident::new(&self.name, Span::call_site());
1516
let object = context.schema.objects.get(&self.on).expect("oh, noes");
1617
let field_impls = object.field_impls_for_selection(context, &self.selection, &self.name)?;
1718
let fields = object.response_fields_for_selection(context, &self.selection, &self.name)?;
1819

1920
Ok(quote!{
20-
#[derive(Debug, Deserialize, Serialize)]
21+
#derives
2122
pub struct #name_ident {
2223
#(#fields,)*
2324
}

graphql_query_derive/src/interfaces.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ impl GqlInterface {
3333
prefix: &str,
3434
) -> Result<TokenStream, failure::Error> {
3535
let name = Ident::new(&prefix, Span::call_site());
36+
let derives = query_context.response_derives();
3637

3738
selection
3839
.extract_typename()
@@ -78,13 +79,13 @@ impl GqlInterface {
7879
let attached_enum_name = Ident::new(&format!("{}On", name), Span::call_site());
7980
let (attached_enum, last_object_field) = if !union_variants.is_empty() {
8081
let attached_enum = quote! {
81-
#[derive(Deserialize, Debug, Serialize)]
82+
#derives
8283
#[serde(tag = "__typename")]
8384
pub enum #attached_enum_name {
8485
#(#union_variants,)*
8586
}
8687
};
87-
let last_object_field = quote!(#[serde(flatten)] on: #attached_enum_name,);
88+
let last_object_field = quote!(#[serde(flatten)] pub on: #attached_enum_name,);
8889
(attached_enum, last_object_field)
8990
} else {
9091
(quote!(), quote!())
@@ -98,7 +99,7 @@ impl GqlInterface {
9899

99100
#attached_enum
100101

101-
#[derive(Debug, Serialize, Deserialize)]
102+
#derives
102103
pub struct #name {
103104
#(#object_fields,)*
104105
#last_object_field

graphql_query_derive/src/lib.rs

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#![recursion_limit = "128"]
1+
#![recursion_limit = "512"]
22

33
#[macro_use]
44
extern crate failure;
@@ -16,6 +16,7 @@ extern crate quote;
1616

1717
use proc_macro2::TokenStream;
1818

19+
mod attributes;
1920
mod codegen;
2021
mod constants;
2122
mod enums;
@@ -77,8 +78,9 @@ fn impl_gql_query(input: &syn::DeriveInput) -> Result<TokenStream, failure::Erro
7778
let cargo_manifest_dir =
7879
::std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR env variable is defined");
7980

80-
let query_path = extract_attr(input, "query_path")?;
81-
let schema_path = extract_attr(input, "schema_path")?;
81+
let query_path = attributes::extract_attr(input, "query_path")?;
82+
let schema_path = attributes::extract_attr(input, "schema_path")?;
83+
let response_derives = attributes::extract_attr(input, "response_derives").ok();
8284

8385
// We need to qualify the query with the path to the crate it is part of
8486
let query_path = format!("{}/{}", cargo_manifest_dir, query_path);
@@ -108,7 +110,8 @@ fn impl_gql_query(input: &syn::DeriveInput) -> Result<TokenStream, failure::Erro
108110

109111
let module_name = Ident::new(&input.ident.to_string().to_snake_case(), Span::call_site());
110112
let struct_name = &input.ident;
111-
let schema_output = codegen::response_for_query(schema, query, input.ident.to_string())?;
113+
let schema_output =
114+
codegen::response_for_query(schema, query, input.ident.to_string(), response_derives)?;
112115

113116
let result = quote!(
114117
pub mod #module_name {
@@ -139,30 +142,3 @@ fn impl_gql_query(input: &syn::DeriveInput) -> Result<TokenStream, failure::Erro
139142

140143
Ok(result)
141144
}
142-
143-
fn extract_attr(ast: &syn::DeriveInput, attr: &str) -> Result<String, failure::Error> {
144-
let attributes = &ast.attrs;
145-
let attribute = attributes
146-
.iter()
147-
.find(|attr| {
148-
let path = &attr.path;
149-
quote!(#path).to_string() == "graphql"
150-
}).ok_or_else(|| format_err!("The graphql attribute is missing"))?;
151-
if let syn::Meta::List(items) = &attribute
152-
.interpret_meta()
153-
.expect("Attribute is well formatted")
154-
{
155-
for item in items.nested.iter() {
156-
if let syn::NestedMeta::Meta(syn::Meta::NameValue(name_value)) = item {
157-
let syn::MetaNameValue { ident, lit, .. } = name_value;
158-
if ident == attr {
159-
if let syn::Lit::Str(lit) = lit {
160-
return Ok(lit.value());
161-
}
162-
}
163-
}
164-
}
165-
}
166-
167-
Err(format_err!("attribute not found"))?
168-
}

graphql_query_derive/src/objects.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,15 @@ impl GqlObject {
6767
selection: &Selection,
6868
prefix: &str,
6969
) -> Result<TokenStream, failure::Error> {
70+
let derives = query_context.response_derives();
7071
let name = Ident::new(prefix, Span::call_site());
7172
let fields = self.response_fields_for_selection(query_context, selection, prefix)?;
7273
let field_impls = self.field_impls_for_selection(query_context, selection, &prefix)?;
7374
let description = self.description.as_ref().map(|desc| quote!(#[doc = #desc]));
7475
Ok(quote! {
7576
#(#field_impls)*
7677

77-
#[derive(Debug, Serialize, Deserialize)]
78+
#derives
7879
#[serde(rename_all = "camelCase")]
7980
#description
8081
pub struct #name {

graphql_query_derive/src/query.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
use failure;
22
use fragments::GqlFragment;
33
use operations::Operation;
4+
use proc_macro2::Span;
45
use proc_macro2::TokenStream;
56
use schema::Schema;
67
use selection::Selection;
78
use std::collections::BTreeMap;
9+
use syn::Ident;
810

911
/// This holds all the information we need during the code generation phase.
1012
pub(crate) struct QueryContext {
1113
pub fragments: BTreeMap<String, GqlFragment>,
1214
pub schema: Schema,
1315
pub selected_operation: Option<Operation>,
16+
response_derives: Vec<Ident>,
1417
}
1518

1619
impl QueryContext {
@@ -20,6 +23,7 @@ impl QueryContext {
2023
fragments: BTreeMap::new(),
2124
schema,
2225
selected_operation: None,
26+
response_derives: vec![Ident::new("Deserialize", Span::call_site())],
2327
}
2428
}
2529

@@ -30,6 +34,7 @@ impl QueryContext {
3034
fragments: BTreeMap::new(),
3135
schema: Schema::new(),
3236
selected_operation: None,
37+
response_derives: vec![Ident::new("Deserialize", Span::call_site())],
3338
}
3439
}
3540

@@ -51,4 +56,100 @@ impl QueryContext {
5156
Ok(quote!())
5257
}
5358
}
59+
60+
pub(crate) fn ingest_additional_derives(
61+
&mut self,
62+
attribute_value: &str,
63+
) -> Result<(), failure::Error> {
64+
if self.response_derives.len() > 1 {
65+
return Err(format_err!(
66+
"ingest_additional_derives should only be called once"
67+
));
68+
}
69+
70+
self.response_derives.extend(
71+
attribute_value
72+
.split(',')
73+
.map(|s| s.trim())
74+
.map(|s| Ident::new(s, Span::call_site())),
75+
);
76+
Ok(())
77+
}
78+
79+
pub(crate) fn response_derives(&self) -> TokenStream {
80+
let derives = &self.response_derives;
81+
82+
quote! {
83+
#[derive( #(#derives),* )]
84+
}
85+
}
86+
87+
pub(crate) fn response_enum_derives(&self) -> TokenStream {
88+
let enum_derives: Vec<_> = self
89+
.response_derives
90+
.iter()
91+
.filter(|derive| {
92+
!derive.to_string().contains("erialize")
93+
&& !derive.to_string().contains("Deserialize")
94+
}).collect();
95+
96+
if enum_derives.len() > 0 {
97+
quote! {
98+
#[derive( #(#enum_derives),* )]
99+
}
100+
} else {
101+
quote!()
102+
}
103+
}
104+
}
105+
106+
#[cfg(test)]
107+
mod tests {
108+
use super::*;
109+
110+
#[test]
111+
fn response_derives_ingestion_works() {
112+
let mut context = QueryContext::new_empty();
113+
114+
context
115+
.ingest_additional_derives("PartialEq, PartialOrd, Serialize")
116+
.unwrap();
117+
118+
assert_eq!(
119+
context.response_derives().to_string(),
120+
"# [ derive ( Deserialize , PartialEq , PartialOrd , Serialize ) ]"
121+
);
122+
}
123+
124+
#[test]
125+
fn response_enum_derives_does_not_produce_empty_list() {
126+
let context = QueryContext::new_empty();
127+
assert_eq!(context.response_enum_derives().to_string(), "");
128+
}
129+
130+
#[test]
131+
fn response_enum_derives_works() {
132+
let mut context = QueryContext::new_empty();
133+
134+
context
135+
.ingest_additional_derives("PartialEq, PartialOrd, Serialize")
136+
.unwrap();
137+
138+
assert_eq!(
139+
context.response_enum_derives().to_string(),
140+
"# [ derive ( PartialEq , PartialOrd ) ]"
141+
);
142+
}
143+
144+
#[test]
145+
fn response_derives_fails_when_called_twice() {
146+
let mut context = QueryContext::new_empty();
147+
148+
assert!(
149+
context
150+
.ingest_additional_derives("PartialEq, PartialOrd")
151+
.is_ok()
152+
);
153+
assert!(context.ingest_additional_derives("Serialize").is_err());
154+
}
54155
}

0 commit comments

Comments
 (0)