Skip to content

Commit

Permalink
Make graphql_object support derive resolvers for struct fields (gra…
Browse files Browse the repository at this point in the history
  • Loading branch information
zhenwenc committed May 5, 2020
1 parent 46b5de7 commit 2e5805f
Show file tree
Hide file tree
Showing 13 changed files with 969 additions and 165 deletions.

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions integration_tests/juniper_tests/src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod derive_object;
mod derive_object_with_raw_idents;
mod derive_union;
mod impl_object;
mod impl_object_with_derive_fields;
mod impl_scalar;
mod impl_union;
mod scalar_value_transparent;
4 changes: 4 additions & 0 deletions juniper/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ See [#618](https://github.com/graphql-rust/juniper/pull/618).
- Better error messages for all proc macros (see
[#631](https://github.com/graphql-rust/juniper/pull/631)

- Procedural macro `graphql_object` supports deriving resolvers for fields in
struct (see [#553](https://github.com/graphql-rust/juniper/issues/553))
- requires derive macro `GraphQLObjectInfo`.

## Breaking Changes

- `juniper::graphiql` has moved to `juniper::http::graphiql`
Expand Down
4 changes: 2 additions & 2 deletions juniper/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ extern crate bson;
// functionality automatically.
pub use juniper_codegen::{
graphql_object, graphql_scalar, graphql_subscription, graphql_union, GraphQLEnum,
GraphQLInputObject, GraphQLObject, GraphQLScalarValue, GraphQLUnion,
GraphQLInputObject, GraphQLObject, GraphQLObjectInfo, GraphQLScalarValue, GraphQLUnion,
};
// Internal macros are not exported,
// but declared at the root to make them easier to use.
Expand Down Expand Up @@ -178,7 +178,7 @@ pub use crate::{
},
types::{
async_await::GraphQLTypeAsync,
base::{Arguments, GraphQLType, TypeKind},
base::{Arguments, GraphQLType, GraphQLTypeInfo, TypeKind},
marker,
scalars::{EmptyMutation, EmptySubscription, ID},
subscriptions::{GraphQLSubscriptionType, SubscriptionConnection, SubscriptionCoordinator},
Expand Down
50 changes: 49 additions & 1 deletion juniper/src/types/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
ast::{Directive, FromInputValue, InputValue, Selection},
executor::{ExecutionResult, Executor, Registry, Variables},
parser::Spanning,
schema::meta::{Argument, MetaType},
schema::meta::{Argument, Field, MetaType},
value::{DefaultScalarValue, Object, ScalarValue, Value},
};

Expand Down Expand Up @@ -341,6 +341,54 @@ where
}
}

/// `GraphQLTypeInfo` holds the meta information for the given type.
///
/// The macros remove duplicated definitions of fields and arguments, and add
/// type checks on all resolve functions automatically.
pub trait GraphQLTypeInfo<S = DefaultScalarValue>: Sized
where
S: ScalarValue,
{
/// The expected context type for this GraphQL type
///
/// The context is threaded through query execution to all affected nodes,
/// and can be used to hold common data, e.g. database connections or
/// request session information.
type Context;

/// Type that may carry additional schema information
///
/// This can be used to implement a schema that is partly dynamic,
/// meaning that it can use information that is not known at compile time,
/// for instance by reading it from a configuration file at start-up.
type TypeInfo;

/// The field definitions of fields for fields derived from the struct of
/// this GraphQL type.
fn fields<'r>(info: &Self::TypeInfo, registry: &mut Registry<'r, S>) -> Vec<Field<'r, S>>
where
S: 'r;

/// Resolve the value of a single field on this type.
///
/// The arguments object contain all specified arguments, with default
/// values substituted for the ones not provided by the query.
///
/// The executor can be used to drive selections into sub-objects.
///
/// The default implementation panics.
#[allow(unused_variables)]
fn resolve_field(
&self,
info: &Self::TypeInfo,
field_name: &str,
arguments: &Arguments<S>,
executor: &Executor<Self::Context, S>,
) -> ExecutionResult<S> {
panic!("resolve_field must be implemented by object types");
}
}

/// Resolver logic for queries'/mutations' selection set.
/// Calls appropriate resolver method for each field or fragment found
/// and then merges returned values into `result` or pushes errors to
Expand Down
1 change: 1 addition & 0 deletions juniper_codegen/src/derive_enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ pub fn impl_enum(
generics: syn::Generics::default(),
interfaces: None,
include_type_generics: true,
include_struct_fields: false,
generic_scalar: true,
no_async: attrs.no_async.is_some(),
};
Expand Down
1 change: 1 addition & 0 deletions juniper_codegen/src/derive_input_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ pub fn impl_input_object(
generics: ast.generics,
interfaces: None,
include_type_generics: true,
include_struct_fields: false,
generic_scalar: true,
no_async: attrs.no_async.is_some(),
};
Expand Down
188 changes: 121 additions & 67 deletions juniper_codegen/src/derive_object.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,68 @@
use crate::{
result::{GraphQLScope, UnsupportedAttribute},
util::{self, span_container::SpanContainer},
util::{self, duplicate::Duplicate, span_container::SpanContainer},
};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{self, ext::IdentExt, spanned::Spanned, Data, Fields};

pub fn create_field_definition(
field: syn::Field,
error: &GraphQLScope,
) -> Option<util::GraphQLTypeDefinitionField> {
let span = field.span();
let field_attrs = match util::FieldAttributes::from_attrs(
&field.attrs,
util::FieldAttributeParseMode::Object,
) {
Ok(attrs) => attrs,
Err(e) => {
proc_macro_error::emit_error!(e);
return None;
}
};

if field_attrs.skip.is_some() {
return None;
}

let field_name = &field.ident.unwrap();
let name = field_attrs
.name
.clone()
.map(SpanContainer::into_inner)
.unwrap_or_else(|| util::to_camel_case(&field_name.unraw().to_string()));

if name.starts_with("__") {
error.no_double_underscore(if let Some(name) = field_attrs.name {
name.span_ident()
} else {
field_name.span()
});
}

if let Some(default) = field_attrs.default {
error.unsupported_attribute_within(default.span_ident(), UnsupportedAttribute::Default);
}

let resolver_code = quote!(
&self . #field_name
);

Some(util::GraphQLTypeDefinitionField {
name,
_type: field.ty,
args: Vec::new(),
description: field_attrs.description.map(SpanContainer::into_inner),
deprecation: field_attrs.deprecation.map(SpanContainer::into_inner),
resolver_code,
default: None,
is_type_inferred: true,
is_async: false,
span,
})
}

pub fn build_derive_object(
ast: syn::DeriveInput,
is_internal: bool,
Expand All @@ -32,64 +89,17 @@ pub fn build_derive_object(

let fields = struct_fields
.into_iter()
.filter_map(|field| {
let span = field.span();
let field_attrs = match util::FieldAttributes::from_attrs(
&field.attrs,
util::FieldAttributeParseMode::Object,
) {
Ok(attrs) => attrs,
Err(e) => {
proc_macro_error::emit_error!(e);
return None;
}
};

if field_attrs.skip.is_some() {
return None;
}

let field_name = &field.ident.unwrap();
let name = field_attrs
.name
.clone()
.map(SpanContainer::into_inner)
.unwrap_or_else(|| util::to_camel_case(&field_name.unraw().to_string()));

if name.starts_with("__") {
error.no_double_underscore(if let Some(name) = field_attrs.name {
name.span_ident()
} else {
field_name.span()
});
}

if let Some(default) = field_attrs.default {
error.unsupported_attribute_within(
default.span_ident(),
UnsupportedAttribute::Default,
);
}

let resolver_code = quote!(
&self . #field_name
);

Some(util::GraphQLTypeDefinitionField {
name,
_type: field.ty,
args: Vec::new(),
description: field_attrs.description.map(SpanContainer::into_inner),
deprecation: field_attrs.deprecation.map(SpanContainer::into_inner),
resolver_code,
default: None,
is_type_inferred: true,
is_async: false,
span,
})
})
.filter_map(|field| create_field_definition(field, &error))
.collect::<Vec<_>>();

if let Some(duplicates) = Duplicate::find_by_key(&fields, |field| field.name.as_str()) {
error.duplicate(duplicates.iter());
}

if fields.is_empty() {
error.not_empty(ast_span);
}

// Early abort after checking all fields
proc_macro_error::abort_if_dirty();

Expand All @@ -99,12 +109,6 @@ pub fn build_derive_object(
});
}

if let Some(duplicates) =
crate::util::duplicate::Duplicate::find_by_key(&fields, |field| field.name.as_str())
{
error.duplicate(duplicates.iter());
}

if name.starts_with("__") && !is_internal {
error.no_double_underscore(if let Some(name) = attrs.name {
name.span_ident()
Expand All @@ -113,10 +117,6 @@ pub fn build_derive_object(
});
}

if fields.is_empty() {
error.not_empty(ast_span);
}

// Early abort after GraphQL properties
proc_macro_error::abort_if_dirty();

Expand All @@ -130,10 +130,64 @@ pub fn build_derive_object(
generics: ast.generics,
interfaces: None,
include_type_generics: true,
include_struct_fields: false,
generic_scalar: true,
no_async: attrs.no_async.is_some(),
};

let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
Ok(definition.into_tokens(juniper_crate_name))
}

pub fn build_derive_object_info(
ast: syn::DeriveInput,
is_internal: bool,
error: GraphQLScope,
) -> syn::Result<TokenStream> {
let ast_span = ast.span();
let struct_fields = match ast.data {
Data::Struct(data) => match data.fields {
Fields::Named(fields) => fields.named,
_ => return Err(error.custom_error(ast_span, "only named fields are allowed")),
},
_ => return Err(error.custom_error(ast_span, "can only be applied to structs")),
};

// Parse attributes.
let attrs = util::ObjectInfoAttributes::from_attrs(&ast.attrs)?;

let ident = &ast.ident;
let fields = struct_fields
.into_iter()
.filter_map(|field| create_field_definition(field, &error))
.collect::<Vec<_>>();

if let Some(duplicates) = Duplicate::find_by_key(&fields, |field| field.name.as_str()) {
error.duplicate(duplicates.iter());
}

if fields.is_empty() {
error.not_empty(ast_span);
}

// Early abort after checking all fields
proc_macro_error::abort_if_dirty();

let definition = util::GraphQLTypeDefiniton {
name: ident.unraw().to_string(),
_type: syn::parse_str(&ast.ident.to_string()).unwrap(),
context: attrs.context.map(SpanContainer::into_inner),
scalar: attrs.scalar.map(SpanContainer::into_inner),
description: None,
fields,
generics: ast.generics,
interfaces: None,
include_type_generics: true,
include_struct_fields: false,
generic_scalar: true,
no_async: false,
};

let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
Ok(definition.into_info_tokens(juniper_crate_name))
}
1 change: 1 addition & 0 deletions juniper_codegen/src/derive_union.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ pub fn build_derive_union(
generics: ast.generics,
interfaces: None,
include_type_generics: true,
include_struct_fields: false,
generic_scalar: true,
no_async: attrs.no_async.is_some(),
};
Expand Down
1 change: 1 addition & 0 deletions juniper_codegen/src/impl_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ fn create(
None
},
include_type_generics: false,
include_struct_fields: _impl.attrs.derive_fields.is_some(),
generic_scalar: false,
no_async: _impl.attrs.no_async.is_some(),
};
Expand Down
Loading

0 comments on commit 2e5805f

Please sign in to comment.