Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make graphql_object macro supports deriving object field resolvers #652

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

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