-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Support of directives on fields and types during introspection #2221
Conversation
@@ -68,9 +68,10 @@ public static void addCodeForIntrospectionTypes(GraphQLCodeRegistry.Builder code | |||
codeRegistry.systemDataFetcher(systemCoordinates(TypeNameMetaFieldDef.getName()), TypeNameMetaFieldDefDataFetcher); | |||
codeRegistry.systemDataFetcher(systemCoordinates(TypeMetaFieldDef.getName()), TypeMetaFieldDefDataFetcher); | |||
|
|||
introspectionDataFetchers.forEach((coordinates, idf) -> codeRegistry.dataFetcher(coordinates, idf)); | |||
introspectionDataFetchers.forEach(codeRegistry::dataFetcher); |
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.
lamda syntax
@@ -295,7 +296,7 @@ private static String print(Object value, GraphQLInputType type) { | |||
return null; | |||
}; | |||
|
|||
private static final IntrospectionDataFetcher inputFieldsFetcher = environment -> { | |||
private static final IntrospectionDataFetcher<?> inputFieldsFetcher = environment -> { |
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.
IntrospectionDataFetcher<?> to stop IDEA warnings
…low __XXX new types
.name("name") | ||
.type(nonNull(GraphQLString))) | ||
.build(); | ||
} |
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.
Moved down from above statics
private static GraphQLDirective[] schemaDirectivesArray(GraphQLSchema existingSchema) { | ||
return existingSchema.schemaDirectives.getDirectives().toArray(new GraphQLDirective[0]); | ||
} | ||
|
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 think this has auto re-arranged the code some how - statics at the top etc..
Please show the |
Ooooh this is lovely 😲 I hope it becomes a thing in the wider GraphQL ecosystem. SDL based tools are terrible, and going back to introspection as the source of truth would be such a relief. |
https://spec.graphql.org/June2018/#sec-The-__Directive-Type
A query might be
|
So... you use existing Have you considered an option with brand new type that accurately describes the purpose of applied directive? Please see this page for my idea. Only 2 new introspection types and 6 new fields. In your solution I see 4 new introspection types and 4 new fields. Also keep in mind that even directive @documentation(link: String!) on [many_locations_here]
directive @length(min: Int, max: Int) on INPUT_FIELD | ARGUMENT_DEFINITION @documentation(link: "mysite.com/awesomeschemas/v1/length")
schema MySchema @documentation(link: "mysite.com/awesomeschemas/v1")
{
query: MyQuery
}
type MyQuery
{
field(arg: String! @length(10, 50)): String
} Then you end up with 6 new types and 6 new fields. At the same time, as I understand it, the |
@sungam3r - you might be right about the applied directives versus the defined directives. The reason i did what I did is that I aped what we did in the graphql-java runtime. That is we have a And then when directives are applied to schema elements we re-used the same type. This is a bit painful in one small way - that is the "arguments" of directive have values. No where else in the graphql type system do we have values in a schema context (we except default values - again on arguments) So we have this in graphql-java
This is the design principle we used and this is what we copied. It has always bugged me in other ways (defined versus applied directives) both in definition and at runtime I will read your doco and give it some more thought |
OK. I'm glad to see that we understand each other.
Exactly. Indeed directive definition and applied directive (to some element) are different types with different set of fields. I am very interested in the further development of this PR. Historically, our company has .NET development stack. Recently, due to the expansion, we have received many applications from other teams written in Java. We are now very close to the GraphQL.NET v4 release. In it, among other improvements, directive introspection will be implemented - documentation page that I post above. If you find the implementation we have made to be acceptable, then that will be great. Nevertheless, we still have time to change something. Waiting for your suggestions. If you choose to go our way with a separate type for the applied directive, then that may move us all closer to standardizing this behavior in the specification because two popular platforms implements introspection directives in the same way. |
So one suggestion I have is to use the |
@sungam3r - how about this for a suggestion in order for 2 projects (graphql-java and graphql .net) to be consistent
in general @andimarek and myself agree that your specific |
… inside the schema graph correctly
I considered this option. My thoughts were as follows.
Well... Honestly, having watched the development of the specification by the working group for a long time (and participated in the review), I generally doubt that in the coming years there will be work in this direction. No offense. Just reality. Better all this time people can work with non-standard parts of the schema in a more natural way. Please note that GraphQL.NET can work in 3 modes. First, experimental features are disabled by default. They need to be turned on. In addition, after turning them on, 2 operating modes are available. I described them on the documentation page that I gave above. All this allows you to maximally maintain backward compatibility with tooling. Also keep in mind that there is also a non-zero chance that the spec might start using the Next thought. When a new version of the spec is released, it will most likely be tied to the new major version of the application library (at least for GraphQL.NET, for the rest I will not confirm). I agree that code backward incompatibility is completely different from GraphQL request backward incompatibility. However, I thought it was OK if the experimental features were well documented so that people would explicitly turn them on and understand the implications. Thanks for the hint, by the way. I will add a note that in future versions these features may be changed/rolled back/removed in accordance with the evolution of the official specification. One final note. Whichever option we choose, it still goes "against" the specification, because unknown/new fields and types appear. I put the word against in quotation marks, because I was very surprised that due to the extension of introspection, tooling began to fail 😕 . This shouldn't happen. I find it a significant drawback that the specification does not explicitly say that introspection can be extended (or should not be extended which of course is worse). Tooling would use only that part of introspection (basic) that it can work with. And one question from me. type __DirectiveArgument {
name: String!
value: String <--- what do you think about this type?
} I'm still not sure. A directive can have complex argument values. So applied directive can too. For
I thought the same would apply for the directive argment value. Right? |
Let me think on your words a little more. I take your points about extra
types being added.
My vision however is BEYOND applied directives, could there be a case for
more custom introspection data that a server might do ( for example we run
a federated gateway at Atlassian where I work - could we put in more
Atlassian specific extensions for our tooling (eg supports ouath or some
such??)
So the `__XXXExtension` types are there as a well know "holder" place for
more extensions beyond what graphql-java might ship.
And one question from me.
I'm still not sure. A directive can have complex argument values.
The graphql original team had this problem with default values and they
solved it with string encoding. I think there is no other option (except
introducing a JSON / Map scalar and I reckon that's just as bad if not
worse)
Since you CANT know the structure of a directive value any schema for it is
going to be convoluted and too meta.
The string encoding is a neat (but slightly hacky) way to say "this is
graphql values encoded in a string" - tooling would need to decode them
This is what I did in our implementation - our
`graphql.language.AstValueHelper` is actually a port from graphql-js that
is used to make AST node values from objects and then we turn that into a
string by printing the AST node via
`graphql.language.AstPrinter#printAst(graphql.language.Node)`.
So yeah its a string with a special encoding and this idea is already
present in standard (graphql-js) graphql
…On Sun, 28 Feb 2021 at 20:41, Ivan Maximov ***@***.***> wrote:
I considered this option. My thoughts were as follows.
1. It's not good IMO that you have to create many new __XXXXExtensions
types for every existing introspection type (except for enumerations). This
"inflates" the schema. That is, if you add 2 more specific types then our
choice occurs between 8 new types + 6 new fields vs 2 new types + 6
new fields.
2. An additional nesting level (extensions field) is created for the
property. I see this more as a disadvantage, because such properties will
be perceived less naturally, although you can consider this an advantage
(it clear that this is NOT spec).
3. Redundant in meaning between extensions and directives fields. I
mean, the directives field already represents some sort of *extensions*.
The *applied* directive acts as a container for transferring any
meta-information (so, extension) to the client.
you are very unlikely to get a spec clash when they do decide to do it.
Well... Honestly, having watched the development of the specification by
the working group for a long time (and participated in the review), I
generally doubt that in the coming years there will be work in this
direction. No offense. Just reality. Better all this time people can work
with non-standard parts of the schema in a more natural way. Please note
that GraphQL.NET can work in 3 modes. First, experimental features are
disabled by default. They need to be turned on. In addition, after turning
them on, 2 operating modes are available. I described them on the
documentation page that I gave above. All this allows you to maximally
maintain backward compatibility with tooling. Also keep in mind that there
is also a non-zero chance that the spec might start using the extensions
fields (why not?). Therefore, it's a question whose solution will be in
conflict with spec.
Next thought. When a new version of the spec is released, it will most
likely be tied to the new major version of the application library (at
least for GraphQL.NET, for the rest I will not confirm). I agree that code
backward incompatibility is completely different from GraphQL request
backward incompatibility. However, I thought it was OK if the experimental
features were well documented so that people would explicitly turn them on
and understand the implications. Thanks for the hint, by the way. I will
add a note that in future versions these features may be changed/rolled
back/removed in accordance with the evolution of the official specification.
One final note. Whichever option we choose, it still goes "against" the
specification, because unknown/new fields and types appear. I put the word
*against* in quotation marks, because I was very surprised that due to
the extension of introspection, tooling began to fail 😕 . This shouldn't
happen. I find it a significant drawback that the specification does not
explicitly say that introspection can be extended (or should not be
extended which of course is worse). Tooling would use only that part of
introspection (basic) that it can work with.
And one question from me.
type __DirectiveArgument {
name: String!
value: String <--- what do you think about this type?
}
I'm still not sure. A directive can have complex argument values. So
*applied* directive can too. For __InputValue.defaultValue spec says
http://spec.graphql.org/June2018/#sec-The-__InputValue-Type that
defaultValue may return a String encoding (using the GraphQL language)
I thought the same would apply for the directive augment value. Right?
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#2221 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAMVWSJW7X7BOK3KIQ7UJPTTBIFVTANCNFSM4YD35AKQ>
.
|
Thanks!
All this fits into the concept of directives. Simplified example: directive @auth(schema: String!) on FIELD_DEFINITION
type Query {
field1: String @auth(schema: "Bearer")
field2: String @auth(schema: "BlaBla")
} That is, I do not see the point in the existence of such a concept as extensions. Directives are extensions. |
/// Note that this class describes only explicitly specified arguments. If the argument in the directive Our implementation will transfer the default value over to the "applied directive" and hence introspection WILL find a value for all defined args (including null for arguments with no default value) in short every argument for a directive (declared or not) will be presented. I think default values coming across is what one would generally expect. I acknowledge an argument for "not listing" arguments that are not declared and have no default value. But I dont think we will do it I is tricky for us to implement (we dont have a separate AppliedDirective class at runtime) and in a simplicity sense I am not sure it's the most simple solution. I think all args being listed is the most simple solution |
I have merged this to our master - this is not to say it cant be revisited before release but we want to get this into early snapshot releases so we can use it at my employer (Atlassian) and also for others to consume in this form |
The way we implemented it for Hot Chocolate now is that AppliedDirective represents the directive as is in the SDL, so no value coercion has happened. But this shows we should sit down together and hash out an early stage RFC so we all agree on the same thing. I already talke on that during the WG but we are also working on the next release which is why I did not follow up :) sorry for that. @sungam3r just as an info. We discussed formalizing this issue more into an early stage RFC so that we can work on it and see if this can be adopted for the spec. I think end of next week I can go back to this and we can hash out a form of collaboration on this. |
OK |
We turned on this PR in a local graphql-java based server inside my company (Atlassian) The front end team immediately pinged us because all their builds brok? Why??? Because they used Apollo CLI tools to download schema to generate client side bindings The Apollo CLI tools use graphql-js (the reference implemementation of graphql) to load up the schema
Digging down into the graphql-js code I see this
Sp in other words we have chosen (rightly in my opinion) to use This is a MAJOR blocker to this "common naming scheme" being useful, either in graphql.java or the .Net variants. I think the graphql-java team we are going to have to re-think this |
That is because of the spec does not allow |
But interesting enough this means you are using queries on these directive through UI tooling. We mostly are using this in schema stitching and schema federation where our gateway can query directives through introspection.. |
We have put the feature behind a feature flag
As did we in a way (we require code optin) but the fact is anyone who runs
up a graphql server with this on AND gives it out to front end teams who
use client side type generation etc.... that ends up calling graphql-js
will fall over on it as the schema being invalid.
(Apollo CLI is VERY VERY popular in the front end)
So I wanted to call out that "opt in on a server implementation" is not
enough because we can control the clients that generate themselves from
this opted in schema.
Right now my thoughts are that we will drop the `__` bit and call it
`AppliedDirectives` so that it can get pass this.
This has the drawback that is back into "user type space" and hence could
class with user types (as opposed to system types from a system
implementation) but it will survive in the wild of the front ends
…On Fri, 26 Mar 2021 at 20:05, Michael Staib ***@***.***> wrote:
That is because of the spec does not allow __ type names but in this case
the validation is wrong since it is introspection :) We have put the
feature behind a feature flag and it is disabled by default so most users
will not run into this. I will get into touch with Ivan and then we see how
we can move forward on this.
—
You are receiving this because you modified the open/close state.
Reply to this email directly, view it on GitHub
<#2221 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAMVWSNYNSIPJOGPVFKCZSLTFRE4TANCNFSM4YD35AKQ>
.
|
But interesting enough this means you are using queries on these
directive through UI tooling.
Yeah the front end tooling was trying to
* download a schema
* generate typescript client bindings to that schema
It ends up "running up a schema" via graphql-js code from the downloaded
schema.
…On Fri, 26 Mar 2021 at 20:12, Michael Staib ***@***.***> wrote:
But interesting enough this means you are using queries on these directive
through UI tooling. We mostly are using this in schema stitching and schema
federation where our gateway can query directives through introspection..
—
You are receiving this because you modified the open/close state.
Reply to this email directly, view it on GitHub
<#2221 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAMVWSLIXXIGBKB33GZ3KYDTFRFWZANCNFSM4YD35AKQ>
.
|
Lets wait for input of @IvanGoncharov since there might be an option to get graphql-js on board. |
This is also why I initially wanted a face to face meeting to discuss this and put it into a proper RFC. We still need to do that otherwise this will be dead before arrival. |
Yes. Standard introspection type name check as was expected. These errors can be seen in all GraphQL UIs - Playground, GraphiQL, Altair...
GraphQL.NET has this flag too. Even mode, see https://github.com/graphql-dotnet/graphql-dotnet/blob/master/src/GraphQL/SchemaExtensions.cs#L310 . So UI tooling can still work while server can accept introspection queries with additional fields.
IMO it's rather dirty hack. Also I note that in your company we use our own GraphQL client generator so there are no issues at all for us with additional introspection types/fields. |
I aggree changing the name would not make this feature more robust, it would just work around the validation. We need to get this properly in or it will not go far. |
IMO the right way would be to "fix" Apollo CLI tools to just ignore/skip unknown types/fields. |
Or make new types known ) |
I think we should discuss it online (either WG or a separate call). How about we make a Zoom call on the week after WG? |
I think that applied directive as such should be a first class citizen in introspection. We can talk about extensions but it is additional feature IMO. Applied directives can and should have strongly typed schema. Extensions have always been something like arbitrary map. |
I think we have to two strategies of attack here.
The above discussions are rightly about strategy 1 - the long term way for directives to be in schema elements with naming and shapes to be aligned with other Introspection elements. Regarding strategy 2) we need a way to cope with today. I notice in the graphql-js this code
So it chokes on any name starting with but a name like more extreme names could be In graphql-java today out opt in to these extensions is via code (not a system flag per se) You must do this
I am imagining some way to specify a prefix to this like in a strategy 2 work around sense.
So to reiterate - I want it to be clear there we (at least the graphql-java team) MUST consider 2 strategies. I suspect the lead time on WG + graphql-js fix ups is in the order of months - I don't think we can wait that long, because this capability has been needed for a long time and specifically my employer (Atlassian) needs it now. |
BTW: Playing around with this more I noticed that graphq-java itself wont roundtrip a schema that has
So I cant really say that graphql-js is acting badly alone. We obviously copied this (based on text of the error message) I did try with single So as a work around measure (aka strategy 2) I am keen to go towards a (The breaking change is a loose one as well - that is only people who used |
FWIW I've been using the single underscore prefix for SPQR internal directives and their argument types for years with no complaints. So I'm quite confident nothing in the common tool chain is squeamish about such names. |
@bbakerman So I cant really say that graphql-js is acting badly alone. We obviously copied this (based on text of the error message) we do the same since the spec is mandating this validation. http://spec.graphql.org/draft/#sec-Reserved-Names
http://spec.graphql.org/draft/#sec-Schema
http://spec.graphql.org/draft/#sec-Objects
and most importantly ... the type validation mandates it: http://spec.graphql.org/draft/#sec-Objects.Type-Validation But in this case its the introspection schema which should not be imported by the client anyway. When we export the schema to SDL these new types are not exported since they belong to the introspection. @IvanGoncharov lets setup a zoom call. I think this is the quickest way to exchange some ideas on this. |
@kaqqao the single underscore is often used by server implementations for system fields. Like with Apollo Federation. |
By the way regarding #2221 (comment) and broken tools like playground. In our company we have fixed this issue some time ago by patching one JS file from playground scripts. We injected code that adds new types to satisfy validation. Something like that:
|
This allows the Introspection system to get access to the directives on types and fields and other schema elements
Today the graphql spec does not allow you to get this information. You can know the directives in the system but not their usage on schema elements
This optional support class (you have to wire in the code to use it) make the introspection types like this
and hence now you can query for directives applied on schema elements AND the directives arguments values.
The shape of the types was chosen to match the graphql .NET implementation. This means that at least two major graphql implementations have the same shapes.
While not "graphql specification" per se this allows consumers of graphql servers to use introspection to work out what directives are applied to an element.
The spec discussion is here : graphql/graphql-spec#300