Skip to content

Self parameter not required in resolvers #1016

Closed
@tsoutsman

Description

@tsoutsman

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.

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingduplicateThis issue or pull request already exists

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions