Skip to content

Commit 2e5805f

Browse files
committed
Make graphql_object support derive resolvers for struct fields (#553)
1 parent 46b5de7 commit 2e5805f

File tree

13 files changed

+969
-165
lines changed

13 files changed

+969
-165
lines changed

integration_tests/juniper_tests/src/codegen/impl_object_with_derive_fields.rs

+418
Large diffs are not rendered by default.

integration_tests/juniper_tests/src/codegen/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod derive_object;
44
mod derive_object_with_raw_idents;
55
mod derive_union;
66
mod impl_object;
7+
mod impl_object_with_derive_fields;
78
mod impl_scalar;
89
mod impl_union;
910
mod scalar_value_transparent;

juniper/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ See [#618](https://github.com/graphql-rust/juniper/pull/618).
3232
- Better error messages for all proc macros (see
3333
[#631](https://github.com/graphql-rust/juniper/pull/631)
3434

35+
- Procedural macro `graphql_object` supports deriving resolvers for fields in
36+
struct (see [#553](https://github.com/graphql-rust/juniper/issues/553))
37+
- requires derive macro `GraphQLObjectInfo`.
38+
3539
## Breaking Changes
3640

3741
- `juniper::graphiql` has moved to `juniper::http::graphiql`

juniper/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ extern crate bson;
116116
// functionality automatically.
117117
pub use juniper_codegen::{
118118
graphql_object, graphql_scalar, graphql_subscription, graphql_union, GraphQLEnum,
119-
GraphQLInputObject, GraphQLObject, GraphQLScalarValue, GraphQLUnion,
119+
GraphQLInputObject, GraphQLObject, GraphQLObjectInfo, GraphQLScalarValue, GraphQLUnion,
120120
};
121121
// Internal macros are not exported,
122122
// but declared at the root to make them easier to use.
@@ -178,7 +178,7 @@ pub use crate::{
178178
},
179179
types::{
180180
async_await::GraphQLTypeAsync,
181-
base::{Arguments, GraphQLType, TypeKind},
181+
base::{Arguments, GraphQLType, GraphQLTypeInfo, TypeKind},
182182
marker,
183183
scalars::{EmptyMutation, EmptySubscription, ID},
184184
subscriptions::{GraphQLSubscriptionType, SubscriptionConnection, SubscriptionCoordinator},

juniper/src/types/base.rs

+49-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::{
66
ast::{Directive, FromInputValue, InputValue, Selection},
77
executor::{ExecutionResult, Executor, Registry, Variables},
88
parser::Spanning,
9-
schema::meta::{Argument, MetaType},
9+
schema::meta::{Argument, Field, MetaType},
1010
value::{DefaultScalarValue, Object, ScalarValue, Value},
1111
};
1212

@@ -341,6 +341,54 @@ where
341341
}
342342
}
343343

344+
/// `GraphQLTypeInfo` holds the meta information for the given type.
345+
///
346+
/// The macros remove duplicated definitions of fields and arguments, and add
347+
/// type checks on all resolve functions automatically.
348+
pub trait GraphQLTypeInfo<S = DefaultScalarValue>: Sized
349+
where
350+
S: ScalarValue,
351+
{
352+
/// The expected context type for this GraphQL type
353+
///
354+
/// The context is threaded through query execution to all affected nodes,
355+
/// and can be used to hold common data, e.g. database connections or
356+
/// request session information.
357+
type Context;
358+
359+
/// Type that may carry additional schema information
360+
///
361+
/// This can be used to implement a schema that is partly dynamic,
362+
/// meaning that it can use information that is not known at compile time,
363+
/// for instance by reading it from a configuration file at start-up.
364+
type TypeInfo;
365+
366+
/// The field definitions of fields for fields derived from the struct of
367+
/// this GraphQL type.
368+
fn fields<'r>(info: &Self::TypeInfo, registry: &mut Registry<'r, S>) -> Vec<Field<'r, S>>
369+
where
370+
S: 'r;
371+
372+
/// Resolve the value of a single field on this type.
373+
///
374+
/// The arguments object contain all specified arguments, with default
375+
/// values substituted for the ones not provided by the query.
376+
///
377+
/// The executor can be used to drive selections into sub-objects.
378+
///
379+
/// The default implementation panics.
380+
#[allow(unused_variables)]
381+
fn resolve_field(
382+
&self,
383+
info: &Self::TypeInfo,
384+
field_name: &str,
385+
arguments: &Arguments<S>,
386+
executor: &Executor<Self::Context, S>,
387+
) -> ExecutionResult<S> {
388+
panic!("resolve_field must be implemented by object types");
389+
}
390+
}
391+
344392
/// Resolver logic for queries'/mutations' selection set.
345393
/// Calls appropriate resolver method for each field or fragment found
346394
/// and then merges returned values into `result` or pushes errors to

juniper_codegen/src/derive_enum.rs

+1
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ pub fn impl_enum(
143143
generics: syn::Generics::default(),
144144
interfaces: None,
145145
include_type_generics: true,
146+
include_struct_fields: false,
146147
generic_scalar: true,
147148
no_async: attrs.no_async.is_some(),
148149
};

juniper_codegen/src/derive_input_object.rs

+1
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ pub fn impl_input_object(
143143
generics: ast.generics,
144144
interfaces: None,
145145
include_type_generics: true,
146+
include_struct_fields: false,
146147
generic_scalar: true,
147148
no_async: attrs.no_async.is_some(),
148149
};

juniper_codegen/src/derive_object.rs

+121-67
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,68 @@
11
use crate::{
22
result::{GraphQLScope, UnsupportedAttribute},
3-
util::{self, span_container::SpanContainer},
3+
util::{self, duplicate::Duplicate, span_container::SpanContainer},
44
};
55
use proc_macro2::TokenStream;
66
use quote::quote;
77
use syn::{self, ext::IdentExt, spanned::Spanned, Data, Fields};
88

9+
pub fn create_field_definition(
10+
field: syn::Field,
11+
error: &GraphQLScope,
12+
) -> Option<util::GraphQLTypeDefinitionField> {
13+
let span = field.span();
14+
let field_attrs = match util::FieldAttributes::from_attrs(
15+
&field.attrs,
16+
util::FieldAttributeParseMode::Object,
17+
) {
18+
Ok(attrs) => attrs,
19+
Err(e) => {
20+
proc_macro_error::emit_error!(e);
21+
return None;
22+
}
23+
};
24+
25+
if field_attrs.skip.is_some() {
26+
return None;
27+
}
28+
29+
let field_name = &field.ident.unwrap();
30+
let name = field_attrs
31+
.name
32+
.clone()
33+
.map(SpanContainer::into_inner)
34+
.unwrap_or_else(|| util::to_camel_case(&field_name.unraw().to_string()));
35+
36+
if name.starts_with("__") {
37+
error.no_double_underscore(if let Some(name) = field_attrs.name {
38+
name.span_ident()
39+
} else {
40+
field_name.span()
41+
});
42+
}
43+
44+
if let Some(default) = field_attrs.default {
45+
error.unsupported_attribute_within(default.span_ident(), UnsupportedAttribute::Default);
46+
}
47+
48+
let resolver_code = quote!(
49+
&self . #field_name
50+
);
51+
52+
Some(util::GraphQLTypeDefinitionField {
53+
name,
54+
_type: field.ty,
55+
args: Vec::new(),
56+
description: field_attrs.description.map(SpanContainer::into_inner),
57+
deprecation: field_attrs.deprecation.map(SpanContainer::into_inner),
58+
resolver_code,
59+
default: None,
60+
is_type_inferred: true,
61+
is_async: false,
62+
span,
63+
})
64+
}
65+
966
pub fn build_derive_object(
1067
ast: syn::DeriveInput,
1168
is_internal: bool,
@@ -32,64 +89,17 @@ pub fn build_derive_object(
3289

3390
let fields = struct_fields
3491
.into_iter()
35-
.filter_map(|field| {
36-
let span = field.span();
37-
let field_attrs = match util::FieldAttributes::from_attrs(
38-
&field.attrs,
39-
util::FieldAttributeParseMode::Object,
40-
) {
41-
Ok(attrs) => attrs,
42-
Err(e) => {
43-
proc_macro_error::emit_error!(e);
44-
return None;
45-
}
46-
};
47-
48-
if field_attrs.skip.is_some() {
49-
return None;
50-
}
51-
52-
let field_name = &field.ident.unwrap();
53-
let name = field_attrs
54-
.name
55-
.clone()
56-
.map(SpanContainer::into_inner)
57-
.unwrap_or_else(|| util::to_camel_case(&field_name.unraw().to_string()));
58-
59-
if name.starts_with("__") {
60-
error.no_double_underscore(if let Some(name) = field_attrs.name {
61-
name.span_ident()
62-
} else {
63-
field_name.span()
64-
});
65-
}
66-
67-
if let Some(default) = field_attrs.default {
68-
error.unsupported_attribute_within(
69-
default.span_ident(),
70-
UnsupportedAttribute::Default,
71-
);
72-
}
73-
74-
let resolver_code = quote!(
75-
&self . #field_name
76-
);
77-
78-
Some(util::GraphQLTypeDefinitionField {
79-
name,
80-
_type: field.ty,
81-
args: Vec::new(),
82-
description: field_attrs.description.map(SpanContainer::into_inner),
83-
deprecation: field_attrs.deprecation.map(SpanContainer::into_inner),
84-
resolver_code,
85-
default: None,
86-
is_type_inferred: true,
87-
is_async: false,
88-
span,
89-
})
90-
})
92+
.filter_map(|field| create_field_definition(field, &error))
9193
.collect::<Vec<_>>();
9294

95+
if let Some(duplicates) = Duplicate::find_by_key(&fields, |field| field.name.as_str()) {
96+
error.duplicate(duplicates.iter());
97+
}
98+
99+
if fields.is_empty() {
100+
error.not_empty(ast_span);
101+
}
102+
93103
// Early abort after checking all fields
94104
proc_macro_error::abort_if_dirty();
95105

@@ -99,12 +109,6 @@ pub fn build_derive_object(
99109
});
100110
}
101111

102-
if let Some(duplicates) =
103-
crate::util::duplicate::Duplicate::find_by_key(&fields, |field| field.name.as_str())
104-
{
105-
error.duplicate(duplicates.iter());
106-
}
107-
108112
if name.starts_with("__") && !is_internal {
109113
error.no_double_underscore(if let Some(name) = attrs.name {
110114
name.span_ident()
@@ -113,10 +117,6 @@ pub fn build_derive_object(
113117
});
114118
}
115119

116-
if fields.is_empty() {
117-
error.not_empty(ast_span);
118-
}
119-
120120
// Early abort after GraphQL properties
121121
proc_macro_error::abort_if_dirty();
122122

@@ -130,10 +130,64 @@ pub fn build_derive_object(
130130
generics: ast.generics,
131131
interfaces: None,
132132
include_type_generics: true,
133+
include_struct_fields: false,
133134
generic_scalar: true,
134135
no_async: attrs.no_async.is_some(),
135136
};
136137

137138
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
138139
Ok(definition.into_tokens(juniper_crate_name))
139140
}
141+
142+
pub fn build_derive_object_info(
143+
ast: syn::DeriveInput,
144+
is_internal: bool,
145+
error: GraphQLScope,
146+
) -> syn::Result<TokenStream> {
147+
let ast_span = ast.span();
148+
let struct_fields = match ast.data {
149+
Data::Struct(data) => match data.fields {
150+
Fields::Named(fields) => fields.named,
151+
_ => return Err(error.custom_error(ast_span, "only named fields are allowed")),
152+
},
153+
_ => return Err(error.custom_error(ast_span, "can only be applied to structs")),
154+
};
155+
156+
// Parse attributes.
157+
let attrs = util::ObjectInfoAttributes::from_attrs(&ast.attrs)?;
158+
159+
let ident = &ast.ident;
160+
let fields = struct_fields
161+
.into_iter()
162+
.filter_map(|field| create_field_definition(field, &error))
163+
.collect::<Vec<_>>();
164+
165+
if let Some(duplicates) = Duplicate::find_by_key(&fields, |field| field.name.as_str()) {
166+
error.duplicate(duplicates.iter());
167+
}
168+
169+
if fields.is_empty() {
170+
error.not_empty(ast_span);
171+
}
172+
173+
// Early abort after checking all fields
174+
proc_macro_error::abort_if_dirty();
175+
176+
let definition = util::GraphQLTypeDefiniton {
177+
name: ident.unraw().to_string(),
178+
_type: syn::parse_str(&ast.ident.to_string()).unwrap(),
179+
context: attrs.context.map(SpanContainer::into_inner),
180+
scalar: attrs.scalar.map(SpanContainer::into_inner),
181+
description: None,
182+
fields,
183+
generics: ast.generics,
184+
interfaces: None,
185+
include_type_generics: true,
186+
include_struct_fields: false,
187+
generic_scalar: true,
188+
no_async: false,
189+
};
190+
191+
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
192+
Ok(definition.into_info_tokens(juniper_crate_name))
193+
}

juniper_codegen/src/derive_union.rs

+1
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ pub fn build_derive_union(
173173
generics: ast.generics,
174174
interfaces: None,
175175
include_type_generics: true,
176+
include_struct_fields: false,
176177
generic_scalar: true,
177178
no_async: attrs.no_async.is_some(),
178179
};

juniper_codegen/src/impl_object.rs

+1
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ fn create(
226226
None
227227
},
228228
include_type_generics: false,
229+
include_struct_fields: _impl.attrs.derive_fields.is_some(),
229230
generic_scalar: false,
230231
no_async: _impl.attrs.no_async.is_some(),
231232
};

0 commit comments

Comments
 (0)