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

introduce GraphQLField, GraphQLInputField, GraphQLArgument, and GraphQLEnumValue #4288

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

yaacovCR
Copy link
Contributor

@yaacovCR yaacovCR commented Nov 7, 2024

this extracts logic from #3044 and #3145 (later rebased as #3807 and #3808) to implement more informative error messages without implementing the full schema coordinate RFC

This is a BREAKING CHANGE because these schema elements are now longer plain objects and function differently in various scenarios, for example with String(<schemaElement> JSON.stringifu(<schemaElement> and .toString() and .toJSON()

@yaacovCR yaacovCR requested a review from a team as a code owner November 7, 2024 11:43
Copy link

netlify bot commented Nov 7, 2024

Deploy Preview for compassionate-pike-271cb3 ready!

Name Link
🔨 Latest commit d96333d
🔍 Latest deploy log https://app.netlify.com/sites/compassionate-pike-271cb3/deploys/672dd8e0f89cea00087ab443
😎 Deploy Preview https://deploy-preview-4288--compassionate-pike-271cb3.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link

github-actions bot commented Nov 7, 2024

Hi @yaacovCR, I'm @github-actions bot happy to help you with this PR 👋

Supported commands

Please post this commands in separate comments and only one per comment:

  • @github-actions run-benchmark - Run benchmark comparing base and merge commits for this PR
  • @github-actions publish-pr-on-npm - Build package from this PR and publish it on NPM

@yaacovCR yaacovCR force-pushed the coordinates-without-coordinates branch from fdf0223 to f2d03c4 Compare November 7, 2024 11:58
@yaacovCR yaacovCR added the PR: breaking change 💥 implementation requires increase of "major" version number label Nov 7, 2024
@yaacovCR
Copy link
Contributor Author

yaacovCR commented Nov 7, 2024

updated description above and label to indicate that this is a BREAKING CHANGE

@@ -222,7 +226,7 @@ export function experimentalGetArgumentValues(
// execution. This is a runtime check to ensure execution does not
// continue with an invalid argument value.
throw new GraphQLError(
`Argument "${argDef.name}" of required type "${argType}" was not provided.`,
`Argument "${isArgument(argDef) ? argDef : argDef.name}" of required type "${argType}" was not provided.`,
Copy link
Member

Choose a reason for hiding this comment

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

Why would this not be an argument? This check feels a bit redundant given the parent check where we check whether it in fact is a required argument

Copy link
Member

@JoviDeCroock JoviDeCroock left a comment

Choose a reason for hiding this comment

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

Did a first pass, the change is rather large. What is driving this change, why have we not used the underlying branch, ... not sure I get the why.

This feels like quite a large breaking change, however I doubt people were using these non-classes extensively. The thing I however fear is that libraries like pothos/nexus/... might now be faced with a need for GraphQLField which might end up being a very large refactor.

@@ -272,7 +276,7 @@ export function experimentalGetArgumentValues(
valueNode,
argType,
(error, path) => {
error.message = `Argument "${argDef.name}" has invalid value${printPathArray(
error.message = `Argument "${isArgument(argDef) ? argDef : argDef.name}" has invalid value${printPathArray(
Copy link
Member

Choose a reason for hiding this comment

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

Similar question here but I guess it could be a VariableSignature? In which case argDef seems like an inappropriate name

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it could be a variable signature, which is "a kind of argument definition", but confusing. We should probably rename argDef, but I would guess that should be in a separate PR.

@yaacovCR
Copy link
Contributor Author

yaacovCR commented Nov 10, 2024

Did a first pass, the change is rather large. What is driving this change, why have we not used the underlying branch, ... not sure I get the why.

This feels like quite a large breaking change, however I doubt people were using these non-classes extensively. The thing I however fear is that libraries like pothos/nexus/... might now be faced with a need for GraphQLField which might end up being a very large refactor.

I think you are right to push back over here. Essentially, the reason the changes from #3145 were historically not taken up, was because the PR seemed even huger, with many changes to the error messages and the associated test files. I have been trying to break down the PR into smaller chunks, namely #4177 and this PR.

Basically, what I am attempting to do is separate #3145 from the PR it depends on #3044, which has to do more broadly with integrating schema coordinates into the grammar and utility set, and just focusing on improving the error messages.

I chose to keep the portions of #3145 that introduce the new classes, but instead of using a class hierarchy and directly referencing the "coordinate," instead just generally introduce a new property linking the schema element to its parent (fields to types, arguments to fields, etc) so that the PR could be more generic.

I can make this "less breaking" by simply introducing a new computed value (the coordinate or the parent) on the fields/arguments/enum values without introducing the new classes, but it's also a breaking change, as libraries like pothos and nexus would still have to be very aware of these new fields. So I considered introducing these new classes to be in some sense a better signpost that some underlying changes had happened than introducing a subtle new field that might be missed when upgrading. I might have to investigate more how pothos and nexus might be impacted to see which would make more sense.

Considering the change is breaking and the gain in functionality (better error messages) is not huge, we could consider just altogether foregoing this change. But if we do make a breaking change, v17 would be a good time for it. Maybe best to discuss at the next wg, do more research, etc.

@yaacovCR yaacovCR marked this pull request as draft November 10, 2024 13:06
@yaacovCR

This comment has been minimized.

Copy link

@github-actions publish-pr-on-npm

@yaacovCR The latest changes of this PR are available on NPM as
graphql@17.0.0-alpha.7.canary.pr.4288.b96419eebb37a65c76964ccd0994ab9d3b4e8215
Note: no gurantees provided so please use your own discretion.

Also you can depend on latest version built from this PR:
npm install --save graphql@canary-pr-4288

@yaacovCR
Copy link
Contributor Author

Note: I've investigated this in a general way by attempting to upgrade pothos to use v17, specifically, the canary version within this PR generated above.

I have essentially no significant downstream effects from this change, because pothos does "the right thing" when it creates/modifies types, by always actually rebuilding the types, without any unsafe modifications.

I have noticed quite a few breaking changes around the changes to default values:

  1. In v17, the provided defaultValue within arg config or input field config should be in the format of the external type/input type, not the internal representation.
  2. The defaultValue property on GraphQLArgument or GraphQLInputField is now a object of type DefaultValueUsage that stores either the preserved literal from SDL or the provided external value from code.

This is overall not super tricky to upgrade, but, as expected, requires upgrades of dependencies. Notably, within pothos, the tests rely on several packages/utilities, so to confirm everything is working as expected, we need to upgrade GraphQL Code Generator and at least mapSchema() from the graphql-tools ecosystem. The pothos-directives plugin relies on mapSchema(), which is currently v16-only, and the pothos-federation plugin relies on pothos-directives, so I was not able to probe that. All the other tests pass with some superficial changes. Note: I did not actually change the typings to reflect 1 above because my knowledge of internal pothos TS type wizardry is pretty poor, but I am assuming that would be a simple change.

Tagging @hayes just in case the above is of interest

@yaacovCR yaacovCR marked this pull request as ready for review November 15, 2024 09:29
@hayes
Copy link
Contributor

hayes commented Nov 15, 2024

thanks @yaacovCR for taking the time to explore upgrade process!

I am curious about defaultValue change. Is there a way to serialize input values from their parsed versions (so that we can keep this backwards compatible in Pothos).

One issue I'll have to figure out is that Pothos doesn't currently know anything about the external types, so we'd probably need to have a way to configure that for every scalar in order to make that work correctly.

I'm happy to hear overall the upgrade looks pretty straight forward (outside some deps used in tests)

@yaacovCR
Copy link
Contributor Author

I am curious about defaultValue change. Is there a way to serialize input values from their parsed versions (so that we can keep this backwards compatible in Pothos).

@hayes => FYI, I pushed #4296 to allow v17 to also work with the existing internal default values, deprecated supporting from them instead of removing support. I think that might help with the upgrade process overall, although it does mean that for a time libraries like pothos will also have to support both formats.1

Coercing the external values for the defaults to the input values can be done easily using the coerceInputValue() or the coerceInputLiteral functions, (although it's not necessarily safe until the schema has been validated, in particular default values may contain themselves recursively which would be quite unsafe).

If you want to safely turn an internal value into something external, we have no function to do this in v16 or v17. But if you want, you can do what's done unsafely in v16 for introspection, you can use astFromValue() to move from an internal value to an AST, which is always external, and can now be supplied as the default via the defaultValueLiteral option.

Footnotes

  1. To summarize, writing it out here, and then cross-posting on ease upgrade path for programmatic default values #4296:
    In v16, the available default value option on GraphQLArgumentConfig and GraphQLInputField was just defaultValue and that represented the internal value and so external values were never validated no matter how supplied. If the schema was built from SDL, we converted the default value AST into the internal value immediately by way of valueFromAST(), which just erased the default if it was invalid, if it was programmatically supplied, we just used whatever was suppllied.
    In v17 currently, prior to the proposed ease upgrade path for programmatic default values #4296, we adopted @leebyron 's work and replaced that with two options, defaultValue and defaultValueLiteral where the former was the external value programmatically supplied, and the lateral was the ValueNode from the schema built from buildSchema() or extendSchema(), validating both of these within validateSchema(), and so we retain on GraphQLArgument and GraphQLInputField types a single defaultValue property of type DefaultValueUsage which either has a value or a literal property.
    After ease upgrade path for programmatic default values #4296, we have both. On the config, to disambiguate, we use externalDefaultValue for the programmatically supplied option, so defaultValue as previously supplied stays the same. Within the objects, the new DefaultValueUsage object is stored under externalDefaultValue but the old internal default value if supplied will be under defaultValue. The new config options take precedence over the old.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
PR: breaking change 💥 implementation requires increase of "major" version number
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants