Closed
Description
Describe the bug
The self parameter is not required in resolvers, even if they use self.
This isn't a bug per se, but it leads to some pretty confusing code, so I don't think it should be allowed.
To Reproduce
The following compiles:
struct Human {
name: String,
}
#[juniper::graphql_object]
impl Human {
// Note the lack of &self
fn name() -> &str {
self.name.as_ref()
}
}
Expected behaviour
Compilation failure.
Additional context
This happens because of how the graphql_object
macro transforms the resolvers.
Using cargo expand
, the example above expands to:
Click to see full output
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2018::*;
#[macro_use]
extern crate std;
fn main() {}
struct Human {
name: String,
}
impl<__S> ::juniper::marker::IsOutputType<__S> for Human
where
__S: ::juniper::ScalarValue,
{
fn mark() {
<<&str as ::juniper::IntoResolvable<
'_,
__S,
_,
<Self as ::juniper::GraphQLValue<__S>>::Context,
>>::Type as ::juniper::marker::IsOutputType<__S>>::mark();
}
}
impl<__S> ::juniper::marker::GraphQLObjectType<__S> for Human where __S: ::juniper::ScalarValue {}
impl<__S> ::juniper::GraphQLType<__S> for Human
where
__S: ::juniper::ScalarValue,
{
fn name(_: &Self::TypeInfo) -> Option<&'static str> {
Some("Human")
}
fn meta<'r>(
info: &Self::TypeInfo,
registry: &mut ::juniper::Registry<'r, __S>,
) -> ::juniper::meta::MetaType<'r, __S>
where
__S: 'r,
{
let fields = [registry.field_convert::<&str, _, Self::Context>("name", info)];
let meta = registry.build_object_type::<Human>(info, &fields);
meta.into_meta()
}
}
impl<__S> ::juniper::GraphQLValue<__S> for Human
where
__S: ::juniper::ScalarValue,
{
type Context = ();
type TypeInfo = ();
fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> {
<Self as ::juniper::GraphQLType<__S>>::name(info)
}
#[allow(unused_variables)]
#[allow(unused_mut)]
fn resolve_field(
&self,
_info: &(),
field: &str,
args: &::juniper::Arguments<__S>,
executor: &::juniper::Executor<Self::Context, __S>,
) -> ::juniper::ExecutionResult<__S> {
match field {
"name" => {
let res: &str = (|| self.name.as_ref())();
::juniper::IntoResolvable::into(res, executor.context()).and_then(|res| match res {
Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(&(), &r),
None => Ok(::juniper::Value::null()),
})
}
_ => {
{
::std::rt::panic_fmt(::core::fmt::Arguments::new_v1(
&["Field ", " not found on type "],
&match (&field, &<Self as ::juniper::GraphQLType<__S>>::name(_info)) {
_args => [
::core::fmt::ArgumentV1::new(_args.0, ::core::fmt::Display::fmt),
::core::fmt::ArgumentV1::new(_args.1, ::core::fmt::Debug::fmt),
],
},
))
};
}
}
}
fn concrete_type_name(&self, _: &Self::Context, _: &Self::TypeInfo) -> String {
"Human".to_string()
}
}
impl<__S> ::juniper::GraphQLValueAsync<__S> for Human
where
__S: ::juniper::ScalarValue,
__S: Send + Sync,
Self: Sync,
{
fn resolve_field_async<'b>(
&'b self,
info: &'b Self::TypeInfo,
field: &'b str,
args: &'b ::juniper::Arguments<__S>,
executor: &'b ::juniper::Executor<Self::Context, __S>,
) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<__S>>
where
__S: Send + Sync,
{
use ::juniper::futures::future;
use ::juniper::GraphQLType;
match field {
"name" => {
let res: &str = (|| self.name.as_ref())();
let res2 = ::juniper::IntoResolvable::into(res, executor.context());
let f = async move {
match res2 {
Ok(Some((ctx, r))) => {
let sub = executor.replaced_context(ctx);
sub.resolve_with_ctx_async(&(), &r).await
}
Ok(None) => Ok(::juniper::Value::null()),
Err(e) => Err(e),
}
};
use ::juniper::futures::future;
future::FutureExt::boxed(f)
}
_ => {
{
::std::rt::panic_fmt(::core::fmt::Arguments::new_v1(
&["Field ", " not found on type "],
&match (&field, &<Self as ::juniper::GraphQLType<__S>>::name(info)) {
_args => [
::core::fmt::ArgumentV1::new(_args.0, ::core::fmt::Display::fmt),
::core::fmt::ArgumentV1::new(_args.1, ::core::fmt::Debug::fmt),
],
},
))
};
}
}
}
}
The important part is:
fn resolve_field_async<'b>(
&'b self,
info: &'b Self::TypeInfo,
field: &'b str,
args: &'b ::juniper::Arguments<__S>,
executor: &'b ::juniper::Executor<Self::Context, __S>,
) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<__S>>
where
__S: Send + Sync,
{
use ::juniper::futures::future;
use ::juniper::GraphQLType;
match field {
"name" => {
let res: &str = (|| self.name.as_ref())();
let res2 = ::juniper::IntoResolvable::into(res, executor.context());
let f = async move {
match res2 {
Ok(Some((ctx, r))) => {
let sub = executor.replaced_context(ctx);
sub.resolve_with_ctx_async(&(), &r).await
}
Ok(None) => Ok(::juniper::Value::null()),
Err(e) => Err(e),
}
};
use ::juniper::futures::future;
future::FutureExt::boxed(f)
}
_ => {
{
::std::rt::panic_fmt(::core::fmt::Arguments::new_v1(
&["Field ", " not found on type "],
&match (&field, &<Self as ::juniper::GraphQLType<__S>>::name(info)) {
_args => [
::core::fmt::ArgumentV1::new(_args.0, ::core::fmt::Display::fmt),
::core::fmt::ArgumentV1::new(_args.1, ::core::fmt::Debug::fmt),
],
},
))
};
}
}
}
In particular let res: &str = (|| self.name.as_ref())();
grabs self from the surrounding context.