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

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

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 e4860de
🔍 Latest deploy log https://app.netlify.com/sites/compassionate-pike-271cb3/deploys/66e98b529869ca00087ee3ec
😎 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

Fixes graphql#3051

This change solves the problem of default values defined via SDL not always resolving correctly through introspection by preserving the original GraphQL literal in the schema definition. This changes argument and input field definitions `defaultValue` field from just the "value" to a new `GraphQLDefaultValueUsage` type which contains either -- but not both -- "value" and "literal" fields.

Here is the flow for how a default value defined in an SDL would be converted into a functional schema and back to an SDL:

**Before this change:**

```
(SDL) --parse-> (AST) --coerceInputLiteral--> (defaultValue config) --valueToAST--> (AST) --print --> (SDL)
```

`coerceInputLiteral` performs coercion which is a one-way function, and `valueToAST` is unsafe and set to be deprecated in graphql#3049.

**After this change:**

```
(SDL) --parse-> (defaultValue literal config) --print --> (SDL)
```
By way of introducing type `VariableValues`, allows `getVariableValues` to return both the coerced values as well as the original sources, which are then made available in `ExecutionContext`.
* Adds `valueToLiteral()` which takes an external value and translates it to a literal, allowing for custom scalars to define this behavior.

This also adds important changes to Input Coercion, especially for custom scalars:

* Addition of `parseConstLiteral()` to leaf types which operates in parallel to `parseLiteral()` but take `ConstValueNode` instead of `ValueNode` -- the second `variables` argument has been removed. For all built-in scalars this has no effect, but any custom scalars which use complex literals no longer need to do variable reconciliation manually (in fact most do not -- this has been an easy subtle bug to miss).

  This behavior is possible with the addition of `replaceVariables()`.
  `parseLiteral()` is no longer used internally and has been marked for deprecation.
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.
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.

2 participants