Skip to content
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

Adhere to specification for schema extension #2433

Open
wants to merge 4 commits into
base: series/2.x
Choose a base branch
from

Conversation

niodice
Copy link

@niodice niodice commented Oct 8, 2024

Looking into the GraphQL specification, I believe that schemas for federated graphs are being rendered in a slightly incorrect way. The specification states

A schema defines the initial root operation type for each kind of operation it supports: query, mutation, and subscription; this determines the place in the type system where those operations begin.

The query root operation type must be provided and must be an Object type.

What is important is that the final federated GraphQL schema obeys ☝️. However, subgraphs need not implement the full schema specification. Subgraphs which do not meet this criteria should use the extend keyword

Schema extensions are used to represent a schema which has been extended from an original schema. For example, this might be used by a GraphQL service which adds additional operation types, or additional directives to an existing schema.

I believe that this change more closely adheres to the correct specification.

@niodice niodice marked this pull request as draft October 8, 2024 18:23
@niodice
Copy link
Author

niodice commented Oct 8, 2024

@ghostdogpr hoping for some feedback here. Somebody more knowledgeable about the GQL specification noted that there are actually 2 conditions that we should check before considering the schema an extended schema:

  1. !hasTypes && directives.nonEmpty
  2. query.isEmpty && (mutation.nonEmpty || subscription.nonEmpty)
    • This is a valid check if the subgraph is a federated schema, but if it's just a standalone GQL schema, then I believe the schema is technically invalid, even if Caliban will generate a schema for the graph.

We can implement those rules, but developers may rely on the fact that caliban produces a non-extended schema for case (2) above. In either case, this change may be breaking.

I'm going to update the logic here to reflect the above checks, you can see my first attempt by looking at the commit history if you'd like

@niodice niodice marked this pull request as ready for review October 8, 2024 19:13
@ghostdogpr
Copy link
Owner

Pinging our expert in subgraphs @paulpdaniels to confirm

@@ -327,7 +327,8 @@ object DocumentRenderer extends Renderer[Document] {
definition match {
case SchemaDefinition(directives, query, mutation, subscription, description) =>
val hasTypes = query.nonEmpty || mutation.nonEmpty || subscription.nonEmpty
val isExtension = directives.nonEmpty && !hasTypes
val isExtension =
(!hasTypes && directives.nonEmpty) || query.isEmpty && (mutation.nonEmpty || subscription.nonEmpty)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on your comment, shouldn't this be:

Suggested change
(!hasTypes && directives.nonEmpty) || query.isEmpty && (mutation.nonEmpty || subscription.nonEmpty)
(!hasTypes && directives.nonEmpty) || (query.isEmpty && (mutation.nonEmpty || subscription.nonEmpty))

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whoops, I thought I had done it and the formatter stripped it, but I must have made a mistake. Fixed

@kyri-petrou
Copy link
Collaborator

I'm not sure I fully understand the reasoning behind (2) 🤔. Can you provide an example of a valid schema extension (in .graphql format) that would be considered an extension under the new rules but wouldn't before?

@paulpdaniels
Copy link
Collaborator

Is checking for a present Query part of our current validation step? If not then we may already be allowing invalid graphs to be generated.

@niodice
Copy link
Author

niodice commented Oct 16, 2024

I'm not sure I fully understand the reasoning behind (2) 🤔. Can you provide an example of a valid schema extension (in .graphql format) that would be considered an extension under the new rules but wouldn't before?

yes - this would be essentially any subgraph that has (1) only a mutation, (2) only a subscription, or (3) a mutation + subscription but no query. E.g:

"Directive which holds a freeform tag applied to an element."
directive @tag("The name of the tag to apply."
name: String!) repeatable on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION

extend schema @link(url: "https://specs.apollo.dev/federation/v2.6", import: ["@key", "@requires", "@provides", "@external", "@shareable", "@tag", "@inaccessible", "@override", "@extends", "@composeDirective", "@interfaceObject", "@authenticated", "@requiresScopes", "@policy"]) {
  mutation: Mutations
}

type Mutations {
  mutateFoo(fooId: String!): String @tag(name: "fooTag")
}

@niodice
Copy link
Author

niodice commented Nov 7, 2024

Wanted to circle back here to see if anyone can take a look at this change?

@ghostdogpr
Copy link
Owner

@kyri-petrou @paulpdaniels you guys can take another look?

@paulpdaniels
Copy link
Collaborator

Technically speaking I don't have any issues with this. My question above is, are we treating rendered vs interpreted schemas different? and are we ok with that?

For instance if we called interpreter instead of render are we returning a validation error in that case?

@ghostdogpr
Copy link
Owner

ghostdogpr commented Nov 11, 2024

We have a validation rule validateRootQuery that checks that the root query is defined and fail otherwise. Federation seems to always have it because there is a Query defined with a _service field. So the case mentioned above with no Query would never happen?

@kyri-petrou
Copy link
Collaborator

I guess I'm OK with this change. I would have preferred if we were able to determine whether this is a schema extension a bit more explicitly (rather than basing it on the absence of the query field in the schema), but if this is the best that we can do then that's fine with me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants