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

POC: Object macro derive field resolvers #1

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
113 changes: 113 additions & 0 deletions integration_tests/juniper_tests/src/codegen/impl_object.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#[cfg(test)]
use fnv::FnvHashMap;
#[cfg(test)]
use juniper::Object;
use juniper::{DefaultScalarValue, GraphQLObject, GraphQLObjectInfo};

#[cfg(test)]
use juniper::{
self, execute, EmptyMutation, EmptySubscription, GraphQLType, RootNode, Value, Variables,
};

#[derive(GraphQLObjectInfo, Debug, PartialEq)]
#[graphql(scalar = DefaultScalarValue)]
struct Obj {
regular_field: bool,
#[graphql(
name = "renamedField",
description = "descr",
deprecated = "field deprecation"
)]
c: i32,
}

#[juniper::graphql_object(
name = "MyObj",
description = "obj descr",
scalar = DefaultScalarValue,
derive_fields
)]
impl Obj {
fn custom_field(&self) -> &str {
"custom field"
}
}

struct Query;

#[derive(GraphQLObject, Debug, PartialEq)]
struct SkippedFieldObj {
regular_field: bool,
#[graphql(skip)]
skipped: i32,
}

#[juniper::graphql_object]
impl Query {
fn obj() -> Obj {
Obj {
regular_field: true,
c: 22,
}
}

fn skipped_field_obj() -> SkippedFieldObj {
SkippedFieldObj {
regular_field: false,
skipped: 42,
}
}
}

#[tokio::test]
async fn test_derived_object() {
assert_eq!(
<Obj as GraphQLType<DefaultScalarValue>>::name(&()),
Some("MyObj")
);

// Verify meta info.
let mut registry: juniper::Registry = juniper::Registry::new(FnvHashMap::default());
let meta = Obj::meta(&(), &mut registry);

assert_eq!(meta.name(), Some("MyObj"));
assert_eq!(meta.description(), Some(&"obj descr".to_string()));

let doc = r#"
{
obj {
regularField
renamedField
customField
}
}"#;

let schema = RootNode::new(
Query,
EmptyMutation::<()>::new(),
EmptySubscription::<()>::new(),
);

assert_eq!(
execute(doc, None, &schema, &Variables::new(), &()).await,
Ok((
Value::object(
vec![(
"obj",
Value::object(
vec![
("regularField", Value::scalar(true)),
("renamedField", Value::scalar(22)),
("customField", Value::scalar("custom field")),
]
.into_iter()
.collect(),
),
)]
.into_iter()
.collect()
),
vec![]
))
);
}
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 @@ -3,6 +3,7 @@ mod derive_input_object;
mod derive_object;
mod derive_object_with_raw_idents;
mod derive_union;
mod impl_object;
mod impl_scalar;
mod impl_union;
mod scalar_value_transparent;
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},
scalars::{EmptyMutation, EmptySubscription, ID},
subscriptions::{GraphQLSubscriptionType, SubscriptionConnection, SubscriptionCoordinator},
},
Expand Down
46 changes: 45 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,50 @@ where
}
}

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 @@ -99,6 +99,7 @@ pub fn impl_enum(ast: syn::DeriveInput, is_internal: bool) -> TokenStream {
// NOTICE: only unit variants allow -> no generics possible
generics: syn::Generics::default(),
interfaces: None,
include_struct_fields: false,
include_type_generics: true,
generic_scalar: true,
no_async: attrs.no_async,
Expand Down
16 changes: 13 additions & 3 deletions juniper_codegen/src/derive_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use syn::{self, Data, Fields};

use crate::util;

pub fn build_derive_object(ast: syn::DeriveInput, is_internal: bool) -> TokenStream {
pub fn create_object_definition(ast: syn::DeriveInput) -> util::GraphQLTypeDefiniton {
let struct_fields = match ast.data {
Data::Struct(data) => match data.fields {
Fields::Named(fields) => fields.named,
Expand Down Expand Up @@ -65,7 +65,7 @@ pub fn build_derive_object(ast: syn::DeriveInput, is_internal: bool) -> TokenStr
}
});

let definition = util::GraphQLTypeDefiniton {
util::GraphQLTypeDefiniton {
name,
_type: syn::parse_str(&ast.ident.to_string()).unwrap(),
context: attrs.context,
Expand All @@ -74,11 +74,21 @@ pub fn build_derive_object(ast: syn::DeriveInput, is_internal: bool) -> TokenStr
fields: fields.collect(),
generics: ast.generics,
interfaces: None,
include_struct_fields: false,
include_type_generics: true,
generic_scalar: true,
no_async: attrs.no_async,
};
}
}

pub fn build_derive_object(ast: syn::DeriveInput, is_internal: bool) -> TokenStream {
let definition = create_object_definition(ast);
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
definition.into_tokens(juniper_crate_name)
}

pub fn build_derive_object_info(ast: syn::DeriveInput, is_internal: bool) -> TokenStream {
let definition = create_object_definition(ast);
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
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 @@ -112,6 +112,7 @@ pub fn build_derive_union(ast: syn::DeriveInput, is_internal: bool) -> TokenStre
fields,
generics: ast.generics,
interfaces: None,
include_struct_fields: false,
include_type_generics: true,
generic_scalar: true,
no_async: attrs.no_async,
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 @@ -42,6 +42,7 @@ fn create(args: TokenStream, body: TokenStream) -> util::GraphQLTypeDefiniton {
} else {
None
},
include_struct_fields: _impl.attrs.derive_fields.unwrap_or(false),
include_type_generics: false,
generic_scalar: false,
no_async: _impl.attrs.no_async,
Expand Down
16 changes: 15 additions & 1 deletion juniper_codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,20 @@ pub fn derive_object_internal(input: TokenStream) -> TokenStream {
gen.into()
}

#[proc_macro_derive(GraphQLObjectInfo, attributes(graphql))]
pub fn derive_object_info(input: TokenStream) -> TokenStream {
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
let gen = derive_object::build_derive_object_info(ast, false);
gen.into()
}

#[proc_macro_derive(GraphQLObjectInfoInternal, attributes(graphql))]
pub fn derive_object_info_internal(input: TokenStream) -> TokenStream {
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
let gen = derive_object::build_derive_object_info(ast, true);
gen.into()
}

#[proc_macro_derive(GraphQLUnion, attributes(graphql))]
pub fn derive_union(input: TokenStream) -> TokenStream {
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
Expand Down Expand Up @@ -415,7 +429,7 @@ pub fn graphql_object_internal(args: TokenStream, input: TokenStream) -> TokenSt
/// struct UserID(String);
///
/// #[juniper::graphql_scalar(
/// // You can rename the type for GraphQL by specifying the name here.
/// // You can rename the type for GraphQL by specifying the name here.
/// name = "MyName",
/// // You can also specify a description here.
/// // If present, doc comments will be ignored.
Expand Down
Loading