-
Notifications
You must be signed in to change notification settings - Fork 435
Redesign interfaces (#1000) #1009
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
Conversation
Resolving some prior issues
But upon further digging I've found out that there is entire Redesign itselfFrom what I can tell this proposal covers all spec caveats
|
Yup, similarly to
Good finding 👍 We still can have the type as associated for type-level reffering and assertions, but the method signature is better be compliant with I do tend to think that this designt will evolve further over our codebase, because granularity over fields unlocks for us many longely desired capabilities (eventually we will be able to have multiple |
@ilslv as for the proposal itself:
As we don't have some third-party inputs, so the Hmm... but
These a better generated on object macro expansion, not interface. Ideally, GraphQL interface shouldn't explicitly mention its implementors (but implementorst do mention ther interfaces explicitly), though due to
The idea looks neat, but I do feel unsure about it. You're trying here to build subtyping on top of Rust trait system. That sounds like something quite troublesome in long prespective. As Rust trait system has no notion of subtyping, you propose to specify subtyping relation manually (like between I'd argue, that we do not need subtyping construct to check whether 2 different Rust types are considered the same GraphQL type. We better have here some projection into string literal ( Abusing May you look better into it and try to piggyback this part of design on I do realize that we still would need some form of subtyping for interfaces hierarchy. But it seems that it may be simplified for this particular case. |
@ilslv wait...
are these the same fields according to the spec? They're seem to be different ones. |
Yeah, of course:
Shiiii... 🤯 |
Now these make much more sense:
The thing that still bothers here, is that we express relations between Rust types here. While what we really need is to express subtyping relations between GraphQL types.
Maybe this one confuses me the most as you're abusing GraphQL type subtyping machinery to prove the fact that 2 different Rust types represent the same GraphQL type. That I've talked about:
Maybe for these particular assertions we need another machinery along? So we express GraphQL signatures inheritance via |
Yes, all of the above does work on stable Rust. Regarding error messages, there are not so readable, but better then nothing:
I've tried doing it with something like
This does work, until we wrap
Especially when you start stacking up those But I'll try to return to this design. |
I guess I've found a way to deal with subtyping in const assertions
|
@ilslv now you're talking! Regarding the
So now, instead of numbers like (To comply with GraphQL spec I would like also revert the Anything else seems fine. Elegant solution 👍 Side note: it would be also nice, if we name const asserting functions similarly to algos in spec. |
@ilslv another side note:
|
Actually, I don't think that
Main limitation is that we can't get
Solution should be const generics, but current limitations don't allow us to do that
And even in-place
|
@tyranron already tried that, but fundamental problems are the same: playground And moving |
@ilslv yeah, the problem is So ok, if we cannot provide other better solution at the moment, let's do it with encoding in numbers and place there a huge |
@tyranron I've played with that idea a bit, but no luck. We can't use nested tuples in
|
To sum up changes done in this PR. As GraphQL interfaces are more like Go's structural interfaces, rather than Rust traits that are more like typeclasses, we've decided to ditch
In case some of the fields/arguments are missing or have incompatible types user will get easy-to-understand error messages:
All those changes not only lay ground for future suport of interfaces implementing other interfaces, but also fully support June 2018 GraphQL spec features which weren't supported before:
In case additional field isn't nullable, user will get similar error mesage
Error messages are also understandable:
Also support for the explicit
ack: @LegNeato @davidpdrsn |
FCM
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ilslv nice job 👍
Tests and codegen still needs some adjustments and careful inspection, while the reflection
machinery feels good enough. Though, I cannot grasp it at once and need a few more times to review it.
@@ -652,49 +499,25 @@ mod trivial_async { | |||
async fn id(&self) -> &str; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I think we should factor out async_trait
here completely and just ignore async
keyworf in fn
signatures as it's not related to GraphQL types. On the current stable it will give an error, which is good, and once real async tratis will land into stable, we just don't bother.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this whole tests should test async
fields of GraphQL objects rather that of GraphQL interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tyranron the trouble with current approach is implementation that non-async
trait method can be implemented with non-async
object method only, while async
trait method can be implemented with both async
and non-async
object methods. This happens because non-async
methods are represented with both Field
and AsyncField
traits, while async
ones with AsyncField
only. I should definitely add pretty assertion for it.
So implementing non-async
trait method with async
object field will error.
#[graphql_interface]
trait Character {
fn id(&self) -> String;
}
impl Human {
// Errors
async fn id(&seld) -> String {
/// ...
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is possible to get rid of asyncness in traits, as they are represented with enum
s, which are implementing Field
and/or AsyncField
reflection traits.
Hm, actually I guess we can with some autoref-based specialisation, as we have all exact types. So we would panic on trying to resolve sync
enum method with async
impl.
UPD: yep, autoref-based specialisation works
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also looking at test case
#[graphql_object(scalar = S: ScalarValue, impl = CharacterValue<S>)]
impl Human {
async fn id<'a, S: ScalarValue>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str {
executor.look_ahead().field_name()
}
fn home_planet(&self) -> &str {
&self.home_planet
}
async fn info<'b>(&'b self, _arg: Option<i32>) -> &'b str {
&self.home_planet
}
}
I guess we can transform this into
#[graphql_object(scalar = S, impl = CharacterValue<S>)]
// No `: ScalarValue` ^
impl Human {
async fn id<'a, S: ScalarValue>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str {
executor.look_ahead().field_name()
}
fn home_planet(&self) -> &str {
&self.home_planet
}
async fn info<'b>(&'b self, _arg: Option<i32>) -> &'b str {
&self.home_planet
}
}
by searching for S
generic not only in impl
, but also in object methods
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we can transform this into
Not the case for this PR. Once we land it, we will reconsider all the attributes in a separate PR anyway.
But I do tend to have a visual separation between types and type params for library user.
the trouble with current approach is implementation that non-
async
trait method can be implemented with non-async
object method only, whileasync
trait method can be implemented with bothasync
and non-async
object methods. This happens because non-async
methods are represented with bothField
andAsyncField
traits, whileasync
ones withAsyncField
only. I should definitely add pretty assertion for it.
Well I don't think that deciding such things should be a library user burden. Ideally, we don't want sync/async coloring to be a thing here. We do want working it transparantely, providing a meaningful compilation error if something goes wrong.
So... as a GraphQL interface is just a bunch of fields, which are checked structuraly by signatures, the asyncness notion doesn't depend on the interface itself, it rather depends on how the objects implementing the this interface_ are resolved. So our job here is to have pretty const assertions and that's it.
@@ -721,20 +544,6 @@ mod trivial_async { | |||
.into(), | |||
} | |||
} | |||
|
|||
fn hero<S: ScalarValue + Send + Sync>(&self) -> Box<DynHero<'_, S>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With dyn
being ripped out we also shouldn't need to use something like #[graphql_object(scalar = S: ScalarValue + Send + Sync)]
here in tests, just #[graphql_object]
should be enough.
}}"#, | ||
interface, | ||
); | ||
let doc = r#"{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be const DOC: &str
to comply with other tests in this file.
} | ||
|
||
fn info(&self) -> &str; | ||
async fn info(&self) -> String; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And this explicit_async
section maybe ripped out too, as we don't have now notion of asyncness regarding GraphQL interfaces.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same goes for generic_async
and generic_lifetime_async
sections.
Nice job, can't wait for this feature, wish this be merged soon. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ilslv gj wd 👍
Please, pay attention in future to always place #[automatically_derived]
on the generated code as much as possible.
Part of #1000
Resolves #113
Partially resolves #814
Synopsis
Current approach to the interfaces most of the times works fine, but can't fully support GraphQL spec and may be misused.
Solution
Checklist
Draft:
prefixDraft:
prefix is removed