-
Notifications
You must be signed in to change notification settings - Fork 434
Description
Is your feature request related to a problem? Please describe.
I'm always frustrated when I need to replace:
#[derive(juniper::GraphQLObject)]
struct Foo {
name: String
}
by
struct Foo {
name: String
}
#[juniper::graphql_object]
impl Foo {
fn computed(&self) -> String {
...
}
}
because I lose the nice default field accessor for name
and I have to manually add it to the impl
:
#[juniper::graphql_object]
impl Foo {
...
fn name(&self) -> &String {
&self.name
}
}
Describe the solution you'd like
I'd like for #[derive(juniper::GraphQLObject)]
and #[juniper::graphql_object]
to work together so I don't have to add the dummy accessors for every field in the impl
block.
I would imagine it is complicated to achieve this because both macros currently generate the same impl
block underneath.
Describe alternatives you've considered
I couldn't come up with any alternative to this 😿
Additional context
This is extra painful when wrapping REST interfaces which have many simple fields (like strings) and a few complex fields (references to other resources).
P.S.: juniper
is such a great crate! many thanks to all the contributors! 💯
Activity
LegNeato commentedon Mar 9, 2020
Could have sworn we had an enhancement request for this alrteady but can't find it. I agree this would be very nice to have.
As a workaround you can make your own macro or derive that spits out a
#[juniper::graphql_object]
for you with the boilerplate included.Victor-Savu commentedon Mar 9, 2020
@LegNeato Thanks for the quick reply! I quickly went through all of the open items before posting just because I was really expecting to see this issue :) I hope I didn't miss anything and if I did I'm sorry.
I will have a look at how to achieve that with my own macro for now. Would it be useful to leave this issue open and see if anyone else may want this feature?
LegNeato commentedon Mar 9, 2020
Yep!
gql!
procedural macro #592Victor-Savu commentedon Mar 29, 2020
I tried looking into rolling my own macro for generating the necessary fields and found a few things that I wanted to share in case you or someone else has an idea for how to best approach this.
The problem I ran into was that I could not solve this problem with a derive macro. In order to have since derive macros only apply to either the
struct
item or to theimpl
item so it would be impossible to get information about both without using a function-like procedural macro. I opened a draft PR #592 with an implementation that demonstrates the results.@LegNeato The PR is just to get an idea of whether you and the other juniper maintainers would be open to this direction.
Victor-Savu commentedon Apr 11, 2020
I had an idea how to just make
#[derive(GraphQLObject)]
and#[graphql_object])
work together, but the implementation depends on rust-lang/rfcs#1210 (or rather on#![feature(specialization)]
in nightly).Here is the general design:
We can have two intermediate traits:
GraphQLData
derrived from thestruct
/enum
by#[derive(GraphQLObject)]
; andGraphQLImpl
the implementation of which is generated from theimpl
block by#[graphql_object]
.The two traits would each specify similar methods as the
GraphQLType
. We can then have a default implementations of GraphQLType based on objects that implementGraphQLData
and a specialization for those that implementGraphQLImpl
. For coherence reasons, we need GraphQLImpl to require GraphQLData:Here is a mock implementation in the rust playground.
Victor-Savu commentedon Apr 11, 2020
The downside of the design in my comment above is that
impl GraphQLData
becomes necessary even for cases where users don't want to expose some (or any) of the fields in astruct
. Also, it is needed for rust enums as well. A hopefully convenient way to solve this is to provide default implementations for the methods inGraphQLData
such that the user would only need to write:to satisfy the condition and to hide all the members in
MyType
. This option is shown in the playground example as well.jmpunkt commentedon Apr 14, 2020
Another possibility is to use handler functions. The user defines structures which contain data and handler fields. For each data fields code is generated. A Handler field references a function, similar to Serde. However, this could require to remove non-async code first, because we can not distinguish between async and non-async functions.
Maybe this approach fits better, but I am not sure.
Victor-Savu commentedon Apr 16, 2020
@jmpunkt This sounds great as well! The design space for this problem seems bountiful.
I think if we can tolerate a bit more typing, we can specify when a handler is synchronous. The default could be async:
jmpunkt commentedon Apr 18, 2020
A current implementation which compiles (nothing tested) looks like the following:
A main different to the examples before, is that it is required to have a context and to use the type of the object rather than
self
.To fix this issue, an impl macro for this special case is required. This impl macro provides
self
binding and context handling. Therefore, the mod block would look very similar to an impl block. The macro would transform the function into same format as the example, but would use a similar input as the impl macro.Victor-Savu commentedon Apr 19, 2020
@jmpunkt I'm impressed and surprised. Was this possible all along? I thought I went over the book with a comb and couldn't find the graphql attribute macro on struct fields taking a Handler. Is this documented somewhere and I totally missed it?
jmpunkt commentedon Apr 19, 2020
No it is not possible with the code on the master branch. Due to the structure of Juniper, it does not require that much changes to support this behavior.
The sentence
refers to my testing branch.
zhenwenc commentedon May 1, 2020
I am slightly disagree the proposed approach above. For instance, I am using
diesel
to query the database:In the above example, I won't want to put the
full_name
field in User struct as there is no such column in theusers
table. As a workaround, I have to define another struct that doesn't contain thefull_name
field fordiesel
, which really just delegating the boilerplates...jmpunkt commentedon May 1, 2020
I did not think of that. Since the original idea was to reduce the boilerplate, what about this?
We define trivial fields inside graphql_object. The macro provides trivial implementations for the specified fields.
35 remaining items