-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add support for directives on directives #907
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
base: main
Are you sure you want to change the base?
Conversation
✔️ Deploy Preview for graphql-spec-draft ready! 🔨 Explore the source changes: 93eb694 🔍 Inspect the deploy log: https://app.netlify.com/sites/graphql-spec-draft/deploys/61a8ffa89a70010007283c1d 😎 Browse the preview: https://deploy-preview-907--graphql-spec-draft.netlify.app |
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.
A directive definition must not contain the use of a directive that references itself indirectly by referencing a Type or Directive which transitively includes a reference to this directive.
This text needs to be modified to cover direct reference to itself (directive @foo on DIRECTIVE_DEFINITION @foo
). Maybe something like:
A directive definition must not contain the use of a directive that references itself directly, or indirectly by referencing a Type or Directive which transitively includes a reference to this directive.
why are we so scared of these recursive definitions? things like that happen in real progr languages, no trouble. Why all this word soup to prohibit smth like that - if there's potential trouble, it would be a choice of the server-side dev, he wants it for some reason - he would handle the consequences if any |
GraphQL is supposed to be non-Turing Complete, to provide a guarantee to the server that, outside of the code used by Resolvers, the program itself will halt. The requirement for directives on directives to not form a cycle may or may not be required to keep GraphQL from becoming Turing Complete, but whenever we are adding new constructs to the language, by default we usually try to avoid recursion in the first pass to reduce Turing Completeness considerations from the RFC. |
Turing?! complete?! what are you talking about?! it is not a programming language. Guarantee to the server?!!! I am a server-side programmer, and I can tell you nobody other than myself, no spec can guarantee anything about my server code, will it complete, crash or loop forever. Spec can't guarantee anything, especially about server implementation code, period. What Spec can do is introduce unnecessary, hard to understand restrictions that leave with one question - why is this word soup there at all |
This is really interesting? Is there a reference in the spec or other originating material documenting this? In the spec, I just see a statement I read as noting that GraphQL is not a programming language capable of arbitrary computation, but nothing per se that we have to avoid recursion. I see the point you are raising in terms of guarantees of completion when resolvers are properly implemented, but perhaps not every recursion application instance would prohibit this. Maybe it would? I guess I am wondering if the specifiedBy directive could recursively apply to itself, for example, without non-halting concerns… |
I guess the question is: considering some recursive directive references could be nonparsable, nonsensical, etc, etc, should all such references be disallowed? Considering that even some non recursive directive references have the same problem and require separate validation, I am not sure that recursion itself has to be forbidden. maybe it should for now for simplicity, of course! That would be my preference, until a compelling use case presents itself |
That seems to already exist, see https://spec.graphql.org/draft/#sel-EAHnBTEBAAAEDDA_FtwX I think putting directives at the end of the list of locations isn't consistent with similar syntax of Union types union Example @directive =
| TypeA
| TypeB
| TypeC compare to: directive @directive(arg: Type) on
| Field
| Fragment @deprecated It also reads like the deprecation is applying to the Putting it after the argument list would be more consistent with directives on FieldDefinitions: directive @directive(arg: Type) @deprecated on
| Field
| Fragment In the case where there are no args and repeatable: directive @directive @deprecated repeatable on
| Field
| Fragment Either way, I still don't love this syntax. The repeated |
re syntax: Would it be crazy to generally allow directives to be used within docstrings? For directive definitions, this could then be the only way to apply a directive in order to keep the syntax clean. |
What are potential use-cases for directives that transform the actual behavior of an existing directive? In the WG we mentioned While discussing this I noticed a lot of similarities to JSDOC What if those "metadata enrichment directives" could instead be part of the field/argument/directive description string? directive @deprecated(reason: String) on TYPE_DESCRIPTION, FIELD_DESCRIPTION, ARGUMENT_DESCRIPTION
type User {
"""
This is the id
"""
id: ID!
"""
This is the name
@deprecated(reason: "This is deprecated.")
"""
name: String!
}
"""
@deprecated(reason: "This is deprecated.")
"""
directive @bar on FIELD Edit: @fotoetienne was faster than me |
could we consider allowing d-o-ds to apply to individual locations? a use case is when a directive is directive @explode(arg: Type) @specifiedBy(url: X) on
| FIELD
| FRAGMENT @deprecated the
|
I have a couple of directives that modify the behavior of the contiguous directive(s) in the query/SDL:
These are useful to apply a directive that does not expect a List. For instance, let's say that directive @titleCase on FIELD | FIELD_DEFINITION
type Query {
siteName: String
@titleCase
} Then it can receive type Query {
categoryNames: [String]
@forEach
@titleCase
} In the query: query {
categoryNames
@forEach
@titleCase
} These directives can also nest each other, as many times as needed. For instance, if the input is query {
customerAllNames
@forEach
@forEach
@titleCase
} I use [
{
"name": "Leo",
"email": "leo@ponga.com"
},
{
"name": "Manuel",
"email": "manuel@ponga.com"
}
] I can then apply query {
userData: getJSON(url: "https://...")
@forEach
@advancePointerInArrayOrObject(path: "name")
@titleCase
} These directives have an type Query {
categoryNames: [String]
@forEach(affect: [1, 2])
@titleCase
@translate(from: "en", to: "fr")
} By default Btw, I just published a more detailed article explaining all this, here. |
Every time I see an example such as this: type Post {
categories: [String] @forEach @titleCase
}
directive @forEach on FIELD_DEFINITION
directive @titleCaseon FIELD_DEFINITION I immediately think that the following might be the better solution: enum StringFormat {
titleCase
upperCase
snakeCase
}
type Post {
categories(format: StringFormat): [String]
} My main reasons:
The following cannot be caught by GraphQL validation: type Post {
a: Int @titleCase
} |
@n1ru4l I acknowledge that not everybody likes this solution, but I don't think it makes the use case invalid. There are examples of things we can do in different ways in GraphQL, which can work better for different applications, and they are all valid. For instance:
In what context would My concern is to not have to update the resolvers whenever the client has a novel type input, such as Even if you prefer an alternative solution, don't you think that |
I think we got two threads mixed up here. The original post suggests allowing directives on directive definitions. That seems logical and unambiguous to me. The later post by @leoloso (with @foreach and @titleCase) suggests directives on directive instances in queries. This is quite different I think. My biggest problem with this: having query {
customerAllNames
@forEach
@titleCase
} the idea is that in this case titleCase is applied to forEach? and why not both directives apply to customerAllNames field?! what's the rule to distinguish the case that the second directive is applied to previous directive, not the field? |
@rivantsov You have a point! The question was about directives modifying the behavior of other directives, which happens with Here is an idea to implement a similar concept using A directive directive @titleCase on FIELD | FIELD_DEFINITION
# It converts an input of type `[String]` into an array of `String`, passes each downstream to the composed directive, and finally recreates the original array
directive @forEach on FIELD | FIELD_DEFINITION
# Create a new directive from composing 2 or more directives
directive @composeDirectives($directiveNames: [String!]!) on DIRECTIVE_DEFINITION
# Create a new directive combining the behavior of other directives, just through SDL
directive @forEachTitleCase on FIELD | FIELD_DEFINITION
@composeDirectives(directiveNames: ["forEach", "titleCase"]) (This solution will not work for my server, though, since it's code-first, so I need to stick to using |
I suggest allowing to specify directives on directives with the following syntax:
Motivation
Allow applying directive-exclusive features (e.g.
@specifyBy
,@deprecate
) on directives.Validation
We already have a rule in place that prevent cycles in directives
https://spec.graphql.org/draft/#sel-HAHnBTDDABAB_F_kC
So validation is already in place and already paid "complexity cost".