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

Input Value Validation #3813

Merged
merged 1 commit into from
Oct 18, 2024
Merged

Conversation

yaacovCR
Copy link
Contributor

@yaacovCR yaacovCR commented Jan 1, 2023

#3086 rebased on main.

Depends on #3812

@leebyron comments from original PR:

Factors out input validation to reusable functions:

  • Introduces validateInputLiteral by extracting this behavior from ValuesOfCorrectTypeRule.
  • Introduces validateInputValue by extracting this behavior from coerceInputValue
  • Simplifies coerceInputValue to return early on validation error
  • Unifies error reporting between validateInputValue and validateInputLiteral, causing some error message strings to change, but error data (eg locations) are preserved.

These two parallel functions will be used to validate default values

Potentially breaking if you rely on the existing behavior of coerceInputValue to call a callback function, as the call signature has changed. GraphQL behavior should not change, though error messages are now slightly different.

Note: also breaking if you rely on the default callback function to throw. Grossly similar behavior is available with validateInputValue().

@netlify
Copy link

netlify bot commented Jan 1, 2023

Deploy Preview for compassionate-pike-271cb3 ready!

Name Link
🔨 Latest commit 7ee722c
🔍 Latest deploy log https://app.netlify.com/sites/compassionate-pike-271cb3/deploys/67121b0ba121e800085537d7
😎 Deploy Preview https://deploy-preview-3813--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.

@github-actions
Copy link

github-actions bot commented Jan 1, 2023

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 requested a review from leebyron January 2, 2023 12:20
@yaacovCR yaacovCR added the PR: breaking change 💥 implementation requires increase of "major" version number label Jan 2, 2023
@yaacovCR yaacovCR force-pushed the input-validation-rebased branch 6 times, most recently from 1d1b883 to c10741a Compare February 6, 2023 12:54
@yaacovCR yaacovCR force-pushed the input-validation-rebased branch 3 times, most recently from c7f677b to 2f37daa Compare May 31, 2023 12:05
@yaacovCR
Copy link
Contributor Author

yaacovCR commented Mar 22, 2024

@leebyron @erikkessler1 @benjie et al

I hit a wrinkle rebasing this on main considering the addition of @oneOf.

The spec proposal for @oneOf appropriately adds the following:

- If {value} is a variable:
  - Let {variableName} be the name of {variable}.
  - Let {variableDefinition} be the {VariableDefinition} named
    {variableName} defined within {operation}.
  - Let {variableType} be the expected type of {variableDefinition}.
  - {variableType} must be a non-null type.

This validation logic was implemented in #3513 within the ValuesOfCorrectTypeRule.

This Input Validation PR extracts the logic for ValuesOfCorrectTypeRule into a separate function validateInputLiteral with TS signature:

/**
 * Validate that the provided input literal is allowed for this type, collecting
 * all errors via a callback function.
 *
 * If variable values are not provided, the literal is validated statically
 * (not assuming that those variables are missing runtime values).
 */
export function validateInputLiteral(
  valueNode: ValueNode,
  type: GraphQLInputType,
  variables: Maybe<VariableValues>,
  onError: (error: GraphQLError, path: ReadonlyArray<string | number>) => void,
  path?: Path,
): void

To implement the additional validation for OneOf Input Types mentioned above , we need to pass the operation's variable definitions to validateInputLiteral.

It might seem that this is very doable, as because of #3811, further up in this PR stack, variables is now of new type VariableValues that preserves the variable source -- and its definition. However, variables is not supplied by the implementation of ValuesForCorrectType, and in fact the presence of absence of variables is what changes the behavior above in terms of the difference between static or runtime validation referenced in the comment above.

Validation for @oneOf seems to be the first case where we can statically check something about the variable definition without actually having a variable value.

We have some options!

  1. We can validate the variable definition when used with @oneOf at runtime (Ed: I don't think this is a great option).
  2. We can collect and pass the variableDefinitions separately to validateInputLiteral as an additional parameter.
  3. We can reorganize the VariableValues interface such that the definitions are stored separately from the actual values. See next comment for a potential option:

All input (pun-intended) welcome!

@yaacovCR
Copy link
Contributor Author

yaacovCR commented Mar 22, 2024

Current:

export interface VariableValues {
  readonly sources: ReadOnlyObjMap<VariableValueSource>;
  readonly coerced: ReadOnlyObjMap<unknown>;
}

interface VariableValueSource {
  readonly variable: VariableDefinitionNode;
  readonly type: GraphQLInputType;
  readonly value: unknown;
}

Proposal:

export interface VariableValues {
  readonly definitions: ReadOnlyObjMap<VariableDefinitionNode>;
  readonly values?: ReadOnlyObjMap<VariableValueValues> | undefined;
}

export interface VariableValueValues {
  readonly sources: ReadOnlyObjMap<VariableValueSource>;
  readonly coerced: ReadOnlyObjMap<unknown>;
}

interface VariableValueSource {
  readonly type: GraphQLInputType;
  readonly value: unknown;
}

@yaacovCR
Copy link
Contributor Author

Validation for @oneOf seems to be the first case where we can statically check something about the variable definition without actually having a variable value.

Correction: this is definitely not the case, there is a wholly separate validation rule specifying that variables should be of the correct type. My current suggestion actually is to pull the variable validation from one-of altogether, and let whether we allow nullable variables in non-nullable positions re: oneOf be decided by the general rule.

See: https://github.com/graphql/graphql-spec/pull/825/files#r1538875839

@yaacovCR
Copy link
Contributor Author

yaacovCR commented Sep 29, 2024

This PR relies on #4194 to perform @oneOf variable validation, moving it to VariablesInAllowedPositionRule, a fourth option besides the ones above, and one that has independent appeal for me, as suggested in the later comment above => @oneOf variable validation should be treated the same as variables of other values.

Hopefully, #4194 will be discussed at an upcoming GraphQL WG meeting as it moves closer to Stage 3. If #4194 is not accepted as part of this process, this PR will require greater refiguring.

@benjie
Copy link
Member

benjie commented Oct 17, 2024

@yaacovCR
Copy link
Contributor Author

If so, are there remaining complexities?

I don't think so! Just need some approvals :)

Factors out input validation to reusable functions:

* Introduces `validateInputLiteral` by extracting this behavior from `ValuesOfCorrectTypeRule`.
* Introduces `validateInputValue` by extracting this behavior from `coerceInputValue`
* Simplifies `coerceInputValue` to return early on validation error
* Unifies error reporting between `validateInputValue` and `validateInputLiteral`, causing some error message strings to change, but error data (eg locations) are preserved.

These two parallel functions will be used to validate default values in graphql#3049

Potentially breaking if you rely on the existing behavior of `coerceInputValue` to call a callback function, as the call signature has changed, or to throw with the default callback function. Grossly similar behavior is available with `validateInputValue()`, but with a separate function. GraphQL behavior should not change, though error messages are now slightly different.
@yaacovCR yaacovCR merged commit beff1d5 into graphql:main Oct 18, 2024
20 checks passed
@yaacovCR yaacovCR deleted the input-validation-rebased branch October 18, 2024 08:29
@yaacovCR yaacovCR mentioned this pull request Oct 18, 2024
yaacovCR added a commit that referenced this pull request Oct 27, 2024
[#3049 rebased on
main](#3049).

This is the last rebased PR from the original PR stack concluding with
#3049.

* Rebased: #3809 [Original: #3092]
* Rebased: #3810 [Original: #3074]
* Rebased: #3811 [Original: #3077]
* Rebased: #3812 [Original: #3065]
* Rebased: #3813 [Original: #3086]
* Rebased: #3814 (this PR) [Original: #3049]

Update: #3044 and #3145 have been separated from this stack.

Changes from original PR:
1. `astFromValue()` is deprecated instead of being removed.

@leebyron comments from #3049, the original PR:
> Implements
[graphql/graphql-spec#793](graphql/graphql-spec#793)
> 
> * BREAKING: Changes default values from being represented as an
assumed-coerced "internal input value" to a pre-coerced "external input
value" (See chart below).
> This allows programmatically provided default values to be represented
in the same domain as values sent to the service via variable values,
and allows it to have well defined methods for both transforming into a
printed GraphQL literal string for introspection / schema printing (via
`valueToLiteral()`) or coercing into an "internal input value" for use
at runtime (via `coerceInputValue()`)
> To support this change in value type, this PR adds two related
behavioral changes:
>   
> * Adds coercion of default values from external to internal at runtime
(within `coerceInputValue()`)
> * Removes `astFromValue()`, replacing it with `valueToLiteral()` for
use in introspection and schema printing. `astFromValue()` performed
unsafe "uncoercion" to convert an "Internal input value" directly to a
"GraphQL Literal AST", where `valueToLiteral()` performs a well defined
transform from "External input value" to "GraphQL Literal AST".
> * Adds validation of default values during schema validation.
> Since assumed-coerced "internal" values may not pass "external"
validation (for example, Enum values), an additional validation error
has been included which attempts to detect this case and report a
strategy for resolution.
> 
> Here's a broad overview of the intended end state:
> 
> ![GraphQL Value
Flow](https://user-images.githubusercontent.com/50130/118379946-51ac5300-b593-11eb-839f-c483ecfbc875.png)

---------

Co-authored-by: Lee Byron <lee@leebyron.com>
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.

4 participants