Skip to content

Commit 1790bc0

Browse files
committed
Unify GraphQLObject and impl_object macros with shared utiliies.
1 parent 42cd004 commit 1790bc0

File tree

8 files changed

+253
-426
lines changed

8 files changed

+253
-426
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ members = [
55
"juniper",
66
"integration_tests/juniper_tests",
77
"integration_tests/juniper_2018_edition_tests",
8-
"juniper_hyper",
8+
# "juniper_hyper",
99
"juniper_iron",
1010
"juniper_rocket",
1111
"juniper_warp",

integration_tests/juniper_tests/Cargo.toml

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,8 @@ version = "0.1.0"
44
publish = false
55
edition = "2018"
66

7-
[dependencies]
7+
[dev-dependencies]
88
juniper = { version = "0.11.0", path = "../../juniper" }
99
serde_json = { version = "1" }
10-
11-
[dev-dependencies]
1210
fnv = "1.0.3"
13-
indexmap = "1.0"
14-
15-
[[test]]
16-
name = "integration_tests"
17-
path = "src/lib.rs"
18-
harness = true
11+
indexmap = "1.0"

integration_tests/juniper_tests/src/codegen/derive_object.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use juniper::{self, execute, EmptyMutation, GraphQLType, RootNode, Value, Variab
1212
#[graphql(
1313
name = "MyObj",
1414
description = "obj descr",
15-
scalar = "DefaultScalarValue"
15+
scalar = DefaultScalarValue
1616
)]
1717
struct Obj {
1818
regular_field: bool,
@@ -25,7 +25,7 @@ struct Obj {
2525
}
2626

2727
#[derive(GraphQLObject, Debug, PartialEq)]
28-
#[graphql(scalar = "DefaultScalarValue")]
28+
#[graphql(scalar = DefaultScalarValue)]
2929
struct Nested {
3030
obj: Obj,
3131
}
Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
1-
extern crate juniper;
2-
#[cfg(test)]
3-
extern crate serde_json;
4-
5-
#[cfg(test)]
6-
extern crate fnv;
7-
#[cfg(test)]
8-
extern crate indexmap;
9-
101
#[cfg(test)]
112
mod codegen;
3+
#[cfg(test)]
124
mod custom_scalar;

juniper_codegen/src/derive_object.rs

Lines changed: 49 additions & 266 deletions
Original file line numberDiff line numberDiff line change
@@ -1,143 +1,14 @@
1-
use proc_macro2::{Span, TokenStream};
1+
use proc_macro2::{TokenStream};
22
use quote::quote;
3-
use syn::{self, parse_quote, Data, DeriveInput, Field, Fields, Ident};
3+
use syn::{self, Data, Fields};
44

5-
use crate::util::*;
5+
use crate::util;
66

7-
#[derive(Default, Debug)]
8-
struct ObjAttrs {
9-
name: Option<String>,
10-
description: Option<String>,
11-
context: Option<Ident>,
12-
scalar: Option<Ident>,
13-
}
14-
15-
impl ObjAttrs {
16-
fn from_input(input: &DeriveInput) -> ObjAttrs {
17-
let mut res = ObjAttrs::default();
18-
19-
// Check doc comments for description.
20-
res.description = get_doc_comment(&input.attrs);
21-
22-
// Check attributes for name and description.
23-
if let Some(items) = get_graphql_attr(&input.attrs) {
24-
for item in items {
25-
if let Some(AttributeValue::String(val)) =
26-
keyed_item_value(&item, "name", AttributeValidation::String)
27-
{
28-
if is_valid_name(&*val) {
29-
res.name = Some(val);
30-
continue;
31-
} else {
32-
panic!(
33-
"Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \"{}\" does not",
34-
&*val
35-
);
36-
}
37-
}
38-
if let Some(AttributeValue::String(val)) =
39-
keyed_item_value(&item, "description", AttributeValidation::String)
40-
{
41-
res.description = Some(val);
42-
continue;
43-
}
44-
if let Some(AttributeValue::String(scalar)) =
45-
keyed_item_value(&item, "scalar", AttributeValidation::String)
46-
{
47-
res.scalar = Some(Ident::new(&scalar as &str, Span::call_site()));
48-
continue;
49-
}
50-
if let Some(AttributeValue::String(ctx)) =
51-
keyed_item_value(&item, "Context", AttributeValidation::String)
52-
{
53-
res.context = Some(Ident::new(&ctx as &str, Span::call_site()));
54-
continue;
55-
}
56-
panic!(format!(
57-
"Unknown struct attribute for #[derive(GraphQLObject)]: {:?}",
58-
item
59-
));
60-
}
61-
}
62-
res
63-
}
64-
}
65-
66-
#[derive(Default)]
67-
struct ObjFieldAttrs {
68-
name: Option<String>,
69-
description: Option<String>,
70-
deprecation: Option<DeprecationAttr>,
71-
skip: bool,
72-
}
73-
74-
impl ObjFieldAttrs {
75-
fn from_input(variant: &Field) -> ObjFieldAttrs {
76-
let mut res = ObjFieldAttrs::default();
77-
78-
// Check doc comments for description.
79-
res.description = get_doc_comment(&variant.attrs);
80-
81-
// Check builtin deprecated attribute for deprecation.
82-
res.deprecation = get_deprecated(&variant.attrs);
837

84-
// Check attributes.
85-
if let Some(items) = get_graphql_attr(&variant.attrs) {
86-
for item in items {
87-
if let Some(AttributeValue::String(val)) =
88-
keyed_item_value(&item, "name", AttributeValidation::String)
89-
{
90-
if is_valid_name(&*val) {
91-
res.name = Some(val);
92-
continue;
93-
} else {
94-
panic!(
95-
"Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \"{}\" does not",
96-
&*val
97-
);
98-
}
99-
}
100-
if let Some(AttributeValue::String(val)) =
101-
keyed_item_value(&item, "description", AttributeValidation::String)
102-
{
103-
res.description = Some(val);
104-
continue;
105-
}
106-
if let Some(AttributeValue::String(val)) =
107-
keyed_item_value(&item, "deprecation", AttributeValidation::String)
108-
{
109-
res.deprecation = Some(DeprecationAttr { reason: Some(val) });
110-
continue;
111-
}
112-
match keyed_item_value(&item, "deprecated", AttributeValidation::String) {
113-
Some(AttributeValue::String(val)) => {
114-
res.deprecation = Some(DeprecationAttr { reason: Some(val) });
115-
continue;
116-
}
117-
Some(AttributeValue::Bare) => {
118-
res.deprecation = Some(DeprecationAttr { reason: None });
119-
continue;
120-
}
121-
None => {}
122-
}
123-
if let Some(_) = keyed_item_value(&item, "skip", AttributeValidation::Bare) {
124-
res.skip = true;
125-
continue;
126-
}
127-
panic!(format!(
128-
"Unknown field attribute for #[derive(GraphQLObject)]: {:?}",
129-
item
130-
));
131-
}
132-
}
133-
res
134-
}
135-
}
136-
137-
pub fn impl_object(ast: &syn::DeriveInput) -> TokenStream {
138-
let fields = match ast.data {
139-
Data::Struct(ref data) => match data.fields {
140-
Fields::Named(ref fields) => fields.named.iter().collect::<Vec<_>>(),
8+
pub fn build_derive_object(ast: syn::DeriveInput) -> TokenStream {
9+
let struct_fields = match ast.data {
10+
Data::Struct(data) => match data.fields {
11+
Fields::Named(fields) => fields.named,
14112
_ => {
14213
panic!("#[derive(GraphQLObject)] may only be used on regular structs with fields");
14314
}
@@ -148,140 +19,52 @@ pub fn impl_object(ast: &syn::DeriveInput) -> TokenStream {
14819
};
14920

15021
// Parse attributes.
151-
let ident = &ast.ident;
152-
let generics = &ast.generics;
153-
let ident_name = ident.to_string();
154-
let attrs = ObjAttrs::from_input(ast);
155-
let name = attrs.name.unwrap_or(ast.ident.to_string());
156-
let build_description = match attrs.description {
157-
Some(s) => quote! { builder.description(#s) },
158-
None => quote! { builder },
159-
};
160-
161-
let mut meta_fields = TokenStream::new();
162-
let mut resolvers = TokenStream::new();
163-
164-
for field in fields {
165-
let field_ty = &field.ty;
166-
let field_attrs = ObjFieldAttrs::from_input(field);
167-
let field_ident = field.ident.as_ref().unwrap();
168-
169-
// Check if we should skip this field.
170-
if field_attrs.skip {
171-
continue;
22+
let attrs = match util::ObjectAttributes::from_attrs(&ast.attrs) {
23+
Ok(a) => a,
24+
Err(e) => {
25+
panic!("Invalid #[graphql(...)] attribute: {}", e);
17226
}
27+
};
28+
let name = attrs.name.unwrap_or(ast.ident.to_string());
17329

174-
// Build value.
175-
let name = match field_attrs.name {
176-
Some(ref name) => {
177-
// Custom name specified.
178-
name.to_string()
179-
}
180-
None => {
181-
// Note: auto camel casing when no custom name specified.
182-
crate::util::to_camel_case(&field_ident.to_string())
30+
let fields = struct_fields
31+
.into_iter()
32+
.filter_map(|field| {
33+
let field_attrs = util::FieldAttributes::from_attrs(&field.attrs);
34+
35+
if field_attrs.skip {
36+
None
37+
} else {
38+
39+
let field_name = field.ident.unwrap();
40+
let name = field_attrs
41+
.name
42+
.clone()
43+
.unwrap_or_else(|| util::to_camel_case(&field_name.to_string()));
44+
45+
let resolver_code = quote!(
46+
&self . #field_name
47+
);
48+
49+
Some(util::GraphQLTypeDefinitionField{
50+
name,
51+
_type: field.ty,
52+
args: Vec::new(),
53+
description: field_attrs.description,
54+
deprecation: field_attrs.deprecation,
55+
resolver_code,
56+
})
18357
}
184-
};
185-
let build_description = match field_attrs.description {
186-
Some(s) => quote! { field.description(#s) },
187-
None => quote! { field },
188-
};
189-
190-
let build_deprecation = match field_attrs.deprecation {
191-
Some(DeprecationAttr { reason: Some(s) }) => quote! { field.deprecated(Some(#s)) },
192-
Some(DeprecationAttr { reason: None }) => quote! { field.deprecated(None) },
193-
None => quote! { field },
194-
};
195-
196-
meta_fields.extend(quote! {
197-
{
198-
let field = registry.field::<#field_ty>(#name, &());
199-
let field = #build_description;
200-
let field = #build_deprecation;
201-
field
202-
},
203-
});
204-
205-
// Build from_input clause.
206-
207-
resolvers.extend(quote! {
208-
#name => executor.resolve_with_ctx(&(), &self.#field_ident),
20958
});
210-
}
211-
212-
let (_, ty_generics, _) = generics.split_for_impl();
213-
214-
let mut generics = generics.clone();
215-
216-
if attrs.scalar.is_none() {
217-
generics.params.push(parse_quote!(__S));
218-
{
219-
let where_clause = generics.where_clause.get_or_insert(parse_quote!(where));
220-
where_clause
221-
.predicates
222-
.push(parse_quote!(__S: juniper::ScalarValue));
223-
where_clause
224-
.predicates
225-
.push(parse_quote!(for<'__b> &'__b __S: juniper::ScalarRefValue<'__b>));
226-
}
227-
}
228-
229-
let scalar = attrs
230-
.scalar
231-
.unwrap_or_else(|| Ident::new("__S", Span::call_site()));
232-
233-
let ctx = attrs
234-
.context
235-
.map(|ident| quote!( #ident ))
236-
.unwrap_or(quote!(()));
237-
238-
let (impl_generics, _, where_clause) = generics.split_for_impl();
23959

240-
let body = quote! {
241-
impl#impl_generics juniper::GraphQLType<#scalar> for #ident #ty_generics
242-
#where_clause
243-
{
244-
type Context = #ctx;
245-
type TypeInfo = ();
246-
247-
fn name(_: &()) -> Option<&str> {
248-
Some(#name)
249-
}
250-
251-
fn concrete_type_name(&self, _: &Self::Context, _: &()) -> String {
252-
#name.to_string()
253-
}
254-
255-
fn meta<'r>(
256-
_: &(),
257-
registry: &mut juniper::Registry<'r, #scalar>
258-
) -> juniper::meta::MetaType<'r, #scalar>
259-
where #scalar: 'r
260-
{
261-
let fields = &[
262-
#(#meta_fields)*
263-
];
264-
let builder = registry.build_object_type::<#ident>(&(), fields);
265-
let builder = #build_description;
266-
builder.into_meta()
267-
}
268-
269-
fn resolve_field(
270-
&self,
271-
_: &(),
272-
field_name: &str,
273-
_: &juniper::Arguments<#scalar>,
274-
executor: &juniper::Executor<Self::Context, #scalar>
275-
) -> juniper::ExecutionResult<#scalar>
276-
{
277-
278-
match field_name {
279-
#(#resolvers)*
280-
_ => panic!("Field {} not found on type {}", field_name, #ident_name),
281-
}
282-
283-
}
284-
}
60+
let definition = util::GraphQLTypeDefiniton {
61+
name,
62+
_type: syn::parse_str(&ast.ident.to_string()).unwrap(),
63+
context: attrs.context,
64+
scalar: attrs.scalar,
65+
description: attrs.description,
66+
fields: fields.collect(),
67+
generics: ast.generics.clone(),
28568
};
286-
body
69+
definition.into_tokens()
28770
}

0 commit comments

Comments
 (0)