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

RFC: Default value validation & coercion #3049

Closed
wants to merge 1 commit into from

Conversation

leebyron
Copy link
Contributor

@leebyron leebyron commented Apr 25, 2021

Depends on #3086
Implements 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.


This is the final PR in a stack which builds up to supporting this RFC behavior change:


Here's a broad overview of the intended end state:

GraphQL Value Flow

@@ -98,7 +98,6 @@ const TestType = new GraphQLObjectType({
}),
fieldWithNestedInputObject: fieldWithInputArg({
type: TestNestedInputObject,
defaultValue: 'Hello World',
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This actually caused a bunch of failures in the existing suite, so that's promising!

src/type/validate.js Outdated Show resolved Hide resolved
if (isInputObjectType(fieldType)) {
const isNonNullField =
isNonNullType(field.type) && field.type.ofType === fieldType;
if (isNonNullField || !isEmptyValue(field.defaultValue)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This really just adds a condition, implementing this comment suggestion

I definitely need to write tests to ensure this is behaving as expected

// Default values are initially represented as internal values, "serialize"
// converts the internal value to an external value, and "parseValue" converts
// back to an internal value.
return type.parseValue(type.serialize(value));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

A key example of this is Enums, where the external (string) and internal (any) representations differ. Custom scalars can have the same property

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't this this is correct:
serialize is for coercing the values of resolved values (the return values from Resolver). It takes the result of a resolver and produces an result value ("result coercion" is the more correct term here).
parseValue is for coercing input runtime values (programming language specific values). The two use cases for that kind of coercing are variables values and default values. A GraphQLInputField contains a defaultValue which needs to be coerced via parseValue.

Copy link
Member

Choose a reason for hiding this comment

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

That would be a change in behaviour for graphql.js, but I think that change is desired - specifying that the default for an enum is "MY_CUSTOM_SORTER" rather than require('sorters').myCustomSorter (which may happen to be a function) makes sense to me - it's more in line with specifying the value via JSON within a variable.

I think Lee's solution may work to maintain the current behaviour - if you return an enum value (which may happen to be a function) from a resolver then that gets converted via serialize; similarly if you have the exact same enum value (i.e. a function) within defaultValue then using serialize to turn it back into the GraphQL enum makes sense to me.

Copy link
Contributor

@andimarek andimarek Apr 27, 2021

Choose a reason for hiding this comment

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

Let me expand a bit, why I think this can't work in general.

The possible output values of serialize can be completely disjunct from the input values of parseValue. They simply have an orthogonal purpose. parseValue will represent an "internal input value" and should accept a wide range of different inputs and return a good representation of the value to be received as input for Resolver.
The output of serialize on the other hand is the final result of a field.
In practice most Scalars probably accepts serialize values as input for parseValue, but this is not required at all.
I could define a Currency scalar for example which has a special CurrencyOutput as the result of serialize which is not accepted as input for parseValue.

Also there is nothing that actually forces you to implement to serialize in the first place. See https://github.com/jaydenseric/graphql-upload/blob/master/public/GraphQLUpload.js for an example.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks for clarifying Andi; that makes sense.

@benjie
Copy link
Member

benjie commented Apr 25, 2021

From a quick scan this looks good - the memoization is straightforward and leverages the flexibility of JS. I was expecting to do this with a schema WeakMap with a defaults map inside it, but your way is massively more efficient.

It might be worth adding an initial _coercedDefaultValue property to the relevant places to help with V8's HiddenClasses.

Can we move the defineArguments refactoring into a separate PR (and merge it) so that the diff is smaller?

When it comes to validation I'm going to have to dedicate a longer block of time to reviewing that.

@leebyron
Copy link
Contributor Author

leebyron commented Apr 26, 2021

It might be worth adding an initial _coercedDefaultValue property to the relevant places to help with V8's HiddenClasses.

Smart

Can we move the defineArguments refactoring into a separate PR (and merge it) so that the diff is smaller?

Definitely, good suggestion #3050

@andimarek
Copy link
Contributor

One interesting aspect here is that a default value declared via SDL is coerced twice:
Once via valueFromAST and then again via coerceInputValueImpl. Is my reading here correct?

I am not really sure this is a problem, but I wanted to call that out.

@leebyron
Copy link
Contributor Author

Thanks @andimarek - #3051

// which can throw to indicate failure. If it throws, maintain a reference
// to the original error.
try {
parseResult = type.serialize(inputValue);
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar to the other comment: serialize is the result coercion. Scalar and Enums check the validity of input values via parseValue.

@leebyron
Copy link
Contributor Author

Heads up: this diff is about to expand in scope.

After spending some time on this today - I'm realizing that #3051 and related comments are actually quite significant and difficult to untangle from this change. The fact that defaultValue is usually but not always stored as a coerced "internal" value is causing quite a number of issues in keeping this diff small and focused.

This issue is made most apparent by Enum values, which have a different representation externally (a string value or Enum literal) and internally (any value).

Some significant changes are necessary to represent defaultValue as a pre-coerced "external" value (eg a string value for Enums). I'm realizing that our functions for converting between values and AST are intermingled with coercion and serialization.

@andimarek
Copy link
Contributor

andimarek commented Apr 27, 2021

@leebyron we have actually a similar challenge in graphql java: the defaultValue is often a weird non coerced, but still mostly correct value.

IIRC one of the big roadblocks is the problem mentioned in #3051: if you defaultValue is a pre-coerced input value, how do we deal with it for Introspection and printing?

@leebyron
Copy link
Contributor Author

Updates in this last commit:

  • Rewrites default value circular reference checking as "detectDefaultValueCycle()"
  • Adds test suite for default value circular references
  • Moves default value validation to utility "validateInputValue()"
  • Adds "uncoerceDefaultValue()" to preserve behavior of existing programmatically provided default values
  • Rewrites "astFromValue()" to remove "uncoerce" and type checking behavior. It used to operate on "internal" coerced values, now it operates on assumed uncoerced values. (also re-writes a bunch of its test suite).
  • Extracts "validateInputValue()" from "coerceInputValue()" so it can be used separately

I don't think this is near complete yet, but wanted to get the work in progress up.

try {
// For leaf types (Scalars, Enums), serialize is the oppose of coercion
// (parseValue) and will produce an "external" value.
return namedType.serialize(value);
Copy link
Contributor

@andimarek andimarek Apr 28, 2021

Choose a reason for hiding this comment

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

as outlined in a previous comment I think this is wrong: we are misusing serialize here to produce a representation for an input value. We should introduce a specific function inputValueToAst for the Coercion and leave it to the scalar itself to determine a printable representation of its input values.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We talked about this, but writing it down here for posterity:

I agree this is a misuse. This "uncoerce" step should ideally not exist, and only does so to avoid introducing a breaking change to existing users.

The previous code path used astFromValue(defaultValue) - astFromValue misuses serialize in the same way. This carries over the misuse to preserve existing behavior but isolates it to this specific case.

The new code path is valueToLiteral(uncoerceDefaultValue(defaultValue))

Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure this will always preserve backwards compatibility.

Some scalars throw on serialize as a way of preventing them from being erroneously used as output values. While the example I know of should never have a default value, other examples might. See jaydenseric/graphql-upload#194

I would suggest that as part of the definitive solution that you appear on the cusp of delivering:
(A) the spec and the implementation should provide a mechanism of labeling leaf values as only valid within either output or input values

Separately, I would suggest:
(B) the implementation should introduce programmatic internalDefaultValue/externalDefaultValue options for arguments where only one may be defined, with a deprecation notice for the use of the previously ambiguous defaultValue. This would preserve the current behavior as best as possible, but also provide a clear upgrade path.

Copy link
Contributor Author

@leebyron leebyron May 6, 2021

Choose a reason for hiding this comment

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

I am not sure this will always preserve backwards compatibility.

Some scalars throw on serialize as a way of preventing them from being erroneously used as output values.

Since the previous codepath used valueToAST which also called serialize() on these, I believe such a scalar would already exhibit this behavior, so no change. This just moves the call to serialize().

(A) the spec and the implementation should provide a mechanism of labeling leaf values as only valid within either output or input values

This is an interesting idea! It's out of scope for this RFC, but I see the use case for giving GraphQL validation this information, rather than waiting to fail at execution time.

(B) the implementation should introduce programmatic internalDefaultValue/externalDefaultValue options for arguments where only one may be defined, with a deprecation notice for the use of the previously ambiguous defaultValue. This would preserve the current behavior as best as possible, but also provide a clear upgrade path.

I agree with this! I'd like for this "uncoerce" method to have a reasonably short shelf life and to be able to be removed in a future major version, but I'm looking for a solution that allows the end state to look correct. IMHO having to provide externalDefaultValue for the foreseeable future would be too confusing.

I'm not opposed to introducing a more modest breaking change, but would look to @IvanGoncharov for guidance on when and how to introduce that. Some ideas:

  1. Require defaultValue to validate as an external input value, but provide a new deprecatedCoercedDefaultValue field which uses this new method for preserving behavior.

    Any problematic internal defaultValue would need to manually edit their schema to either fix the value or use this new field. The schema validation error message could reference this field so fixing an existing schema is self-documenting.

  2. Provide a "flag" like "allowDeprecatedCoercedDefaultValues" when constructing a schema which applies the behavior currently in this PR. That would be a much easier change to make (one place, instead of per-call-site) but doesn't help highlight the long-term necessary changes and unnecessarily would run "uncoerce" on already uncoerced values.

I'll note that most defaultValue tend to be simple built-in scalars, so it's very likely that the vast majority will upgrade to a version including this change and not need to do anything at all. defaultValue for Enum or some Custom Scalars will need to be addressed.

I bet @andimarek has thoughts on this as well

Copy link
Contributor

Choose a reason for hiding this comment

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

I think option 1 is best and imho that it is very important to fix the reference implementation to reflect the spec properly.

Thanks for your additional comments above, much appreciated.

@andimarek
Copy link
Contributor

Let me say, that I am really happy that we tackle this issue. Great work @leebyron @benjie

In order to understand the other comments I made more clearly I will try to paint a more complete picture of what I believe is missing and we should do.

Currently we have three different coercion methods: parseLiteral, parseValue and serialize. (As I explained in #2357 I don't think these are great names, but this is another topic I will ignore here).

parseLiteral and parseValue produce "internal input values", while serialize accepts "internal output values" and produce a coerced result value. We are dealing here with two different sets of input values: while I understand that in practice there is often an overlap or they are even the same, in general we can't assume this. The other important aspect to highlight is that the domain of parseValue and parseLiteral is really the same: every value provided via SDL should also be possible programmatically and vice versa.

Default values for arguments or Input object fields can be created in two different ways: via SDL or programmatically directly.

A value specified via SDL can be coerced via parseLiteral into an "internal input value" and a programmatic provided value can be coerced via parseValue. As outlined in #3051 there is a challenge that SDL creation is leading to a double coercion: parseValue(parseLiteral(x)). I will come back to it later.

The missing part here becomes obvious when we look at Introspection default values or in SDL printing.
As specified by the spec default values in Introspection results should be printed as SDL:

defaultValue may return a String encoding (using the GraphQL language) of the default value

But we currently have no way of actually producing a Literal value from an "internal input value". This means there is actually no way of printing a custom Scalar default value correctly.

The solution I see is making the Scalar responsible for that by adding another coercion method: lets call it toLiteral for now.

The relationship between parseLiteral, parseValue and toLiteral would be that the result of parseLiteral and parseValue would be accepted by toLiteral and the result of toLiteral would be accepted by parseLiteral:

toLiteral(parseValue(x)) and toLiteral(parseLiteral(x)) and parseLiteral(toLiteral(x))

Regarding the double coercion problem mentioned above: parseValue(parseLiteral(x)) is not valid composing of functions in general: the result of parseLiteral doesn't have to be accepted by parseValue.

I think we must clearly define what GraphQLArgument.defaultValue,GraphQLInputObjectField.defaultValue means: is that an already coerced "internal input value" or is that an external input value which can be coerced via parseValue. The problem here is the SDL Literal values can't be transformed into an external input value in general (this would require the counterpart to toLiteral: a toValue method which then can be used as toValue(parseLiteral(x))) .
As alternative we could define defaultValue as Literal, which mean we can transform programmatic values via toLiteral(parseValue(x)) into it and then coerce it via parseLiteral. But I guess that would be a breaking change. I am not sure sure what the best solution here is tbh.

Looking for some feedback, especially if we agree on the fundamental aspects of what I described here. Thanks!

@leebyron
Copy link
Contributor Author

leebyron commented Apr 29, 2021 via email

@andimarek
Copy link
Contributor

andimarek commented Apr 29, 2021

I agree that defining defaultValue as an "external input value" is the closest definition to the current reality. And of course our aim should be not break anything currently working.

I am happy that you say agree in principal, but I think our mental models around Coercion are not completely the same. For example I think that the spec should be more clear about the different kinds of coercions and coercion is "underspecified" in the spec.

There is no one rule how to coerce input values, because we have two very different input coercion ways: from AST values which should be clearly defined, but also from programmatic values, which is very technology dependent. The combination of JS, JSON serialization and JS like GraphQL syntax makes it very easy to look at think they are the same, but I think it is very important to distinguish it clearly. As you said yourself:

I'm realizing that our functions for converting between values and AST are intermingled with coercion and serialization.

But in order to get more clarity let me give a specific example Scalar to make the discussion more concrete: I define a EmployeeReference scalar which is externally represented as Base64 encoded Number, but internally represented as {employeeNumber: <number>, department: <departmentId>} (this is JS a Object).

parseLiteral: (Any StringLiteral) => Base64 decode, lookup number and department, produce internal value
parseValue: (Any JS String) => Base64 decode, lookup number and department, produce internal value

Now if I define a SDL:

type Query  {
    salary(employee: EmployeeReference = "MTIzNA=="): String
}

The current PR would then go on and invokeparseLiteral and produce {employeeNumber: "123", department="HQ"} as "internal input value".
This would then be treated as "external input value" and then parseValue would try to parse it and it would fail, because it is not a base64 encoded string.

What is the current idea how we deal with that?

@andimarek
Copy link
Contributor

andimarek commented May 3, 2021

I want to share what GraphQL Java is planning to do about this issue in our code base, because fundamentally we have exactly the same challenge as graphql.js.

We have GraphQLArgument.defaultValue (the same problem applies for Input object, but lets focus only on Arguments, as it is literally the same problem) which are not guaranteed to be coerced. Or to put it differently: it is expected that defaultValue is an "internal input value". If the value is provided via SDL this works because when we parse the Schema we generate a coerced internal value (by calling parseLiteral ultimately). Same as in graphql.js via valueFromAst.

But we want to always coerce default values going forward, but also don't break existing usages. This is why we will make default value coercing optional: if you set GraphQLArgument.defaultValue the value will be marked as "we trust you, that this is a coerced internal input value". We will mark this method as deprecated and encourage users to provide us with "external input values" going forward, which we will coerce at execution time. This optional default value coercing will also the SDL code to continue as before.

This means defaultValue will be InternalInputValue | ExternalInputValue.

As explained before for the defaultValue printing challenge (for Introspection and printing SDL) we will introduce a toLiteral coercing method, which will allow us to convert internal input values into Literal, which we then use to produce a String. If defaultValue is InternalInputValue it will be applied directly: printLiteral(toLiteral(defaultValue)). If defaultValue is an external input value it will be: printLiteral(toLiteral(parseValue(defaultValue)))).

It may not look so nice as solution first, but this dual state of defaultValue being either a InternalInputValue or ExternalInputValue allows us to keep the existing behavior if you set the value programmatically, keep the existing code path for SDL schemas, but also make it more more correct by actually coercing default values if not explicitly prevented.

@leebyron
Copy link
Contributor Author

leebyron commented May 4, 2021

That mostly matches what I'm planning here - I just want to make sure we're clear on terminology because I think toLiteral is the wrong idea. Literals are a GraphQL language concept. I'd expect something named toLiteral to return either a GraphQL literal AST or a printed GraphQL literal. That leaps through a bunch of concepts at once.

This means defaultValue will be InternalInputValue | ExternalInputValue.

This exactly matches what I'm proposing here.

If defaultValue is an external input value it will be: printLiteral(toLiteral(parseValue(defaultValue)))).

This seems wrong if parseValue does input coercion. Introspection should print a pre-coerced external input value.

I think I need a flow chart to illustrate all the value states and the functions for moving between them. That might help guide the discussion

@leebyron
Copy link
Contributor Author

leebyron commented May 4, 2021

Another thing to add here - seems like we agree that our goal is to find a way to move to a better method of representing defaultValue while maintaining support for existing services.

The solution I see is making the Scalar responsible for that by adding another coercion method: lets call it toLiteral for now.

My primary problem with this is that existing services will have to define something new in order to continue to function. My goal is to introduce this without a breaking change or a change that requires additional work to adopt. Requiring scalars to implement this new function would hinder this goal

@andimarek
Copy link
Contributor

Maybe we can split this into two tasks. Introducing toLiteral is maybe not strictly required to solve the default coercion problem, but it is for #3051 in my understanding.

I'd expect something named toLiteral to return either a GraphQL literal AST or a printed GraphQL literal.

Yes absolutely:toLiteral should return an AST object. toLiteral is the inverse of parseLiteral.

If defaultValue is an external input value it will be: printLiteral(toLiteral(parseValue(defaultValue)))).

This seems wrong if parseValue does input coercion. Introspection should print a pre-coerced external input value.

That is exactly what toLiteral returns: it returns a pre coerced AST.

The flow for this expression is (from inside out)
"External input value"
-parseValue->
"Internal input value"
-toLiteral->
"External input value as AST/pre coerced AST"
-printLiteral->
"String representation of pre coerced default value in SDL"

My primary problem with this is that existing services will have to define something new in order to continue to function. My goal is to introduce this without a breaking change or a change that requires additional work to adopt. Requiring scalars to implement this new function would hinder this goal

I agree that requiring all Scalars suddenly to implement this is not a good approach. I am not suggesting to break everybody, but give Scalars at least the possibility to produce correct Ast representations. toLiteral could be optional and the current behavior could be the fallback for example.

@leebyron
Copy link
Contributor Author

leebyron commented May 4, 2021

EDIT: This graphic is no longer totally accurate:

#3074 (in the stack referenced at the original comment) implements "New Introspection Path for SDL defined Schema" - it's non breaking and fixes a related but different bug (#3051).

The "Input Value Un-coercion" has been changed to only be used to create validation error messages, not produce values which will be used at runtime.

Here's a broad overview of the intended end state: (See PR description)

Here's a focus on the default value introspection path before and after this RFC:

GraphQL default value introspection

@leebyron
Copy link
Contributor Author

leebyron commented May 4, 2021

Some things to note in those flow diagrams above:

  • The flow you're defining as toLiteral, or the a method for converting "Internal input value" to "External input value as AST", is represented as two steps here. First "Input Uncoerce" that converts an "Internal input value" to an "External input value" and then "Value to AST" which produces an External Literal AST from an External Value.

    GraphQL.js already defines the latter as a generally useful utility for working with GraphQL literals, and the former we would not want to expose as a utility since it's only purpose should be to solve this deprecated flow.

    We could combine these steps into one routine called toLiteral, but if we did we'd probably want to similarly isolate it's availability.

  • Input Coercion and Input Validation are defined twice (to your point above about parseValue and parseLiteral being the same domain). It should probably be safe to convert "External Input Value" to "GraphQL Literal AST" via "Value To AST" and then one use one of these paths, though that might have some performance implications. For coercion this is probably the right tradeoff. For validation, I'm exploring only needing a single definition that only applies on AST.

@andimarek
Copy link
Contributor

andimarek commented May 4, 2021

Thanks a lot for the picture @leebyron .

In order to get more clarity, let me just focus on one specific aspect which I don't fully agree with:

The box Ast to value (implemented in valueFromAst) uses parseLiteral to produce an "External input value" in your diagram.
The same parseLiteral is used in Input Literal coercion to produce an "internal input value".

I don't think both things can be true at the same time: parseLiteral produces an "internal input value" in my understanding and we can't use it to go from "Ast Literal" to "External Input Value".

What do you think?

@leebyron
Copy link
Contributor Author

leebyron commented May 4, 2021

I totally agree. This is currently broken, or at least poorly named. There's still a lot of work left to clean up this PR, but the next change coming will essentially rename "valueFromAST" to "coerceInputLiteral". GraphQL.js defineds "valueFromASTUntyped" which is a better fit for the "AST to Value" step in the picture

@leebyron leebyron force-pushed the coerce-default-value branch 4 times, most recently from 55e3e32 to 1259418 Compare May 6, 2021 03:59
yaacovCR pushed a commit to yaacovCR/graphql-js that referenced this pull request Sep 15, 2024
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 or both "value" and "literal" fields.

Since either of these fields may be set, new functions for resolving a value or literal from either have been added - `getLiteralDefaultValue` and `getCoercedDefaultValue` - these replace uses that either assumed a set value or were manually converting a value back to a literal.

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)
```
yaacovCR pushed a commit to yaacovCR/graphql-js that referenced this pull request Sep 15, 2024
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 or both "value" and "literal" fields.

Since either of these fields may be set, new functions for resolving a value or literal from either have been added - `getLiteralDefaultValue` and `getCoercedDefaultValue` - these replace uses that either assumed a set value or were manually converting a value back to a literal.

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)
```
yaacovCR pushed a commit to yaacovCR/graphql-js that referenced this pull request Sep 15, 2024
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 or both "value" and "literal" fields.

Since either of these fields may be set, new functions for resolving a value or literal from either have been added - `getLiteralDefaultValue` and `getCoercedDefaultValue` - these replace uses that either assumed a set value or were manually converting a value back to a literal.

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)
```
yaacovCR pushed a commit to yaacovCR/graphql-js that referenced this pull request Sep 16, 2024
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 or both "value" and "literal" fields.

Since either of these fields may be set, new functions for resolving a value or literal from either have been added - `getLiteralDefaultValue` and `getCoercedDefaultValue` - these replace uses that either assumed a set value or were manually converting a value back to a literal.

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)
```
yaacovCR pushed a commit to yaacovCR/graphql-js that referenced this pull request Sep 17, 2024
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. GraphQL behavior should not change, though error messages are now slightly different.
yaacovCR pushed a commit to yaacovCR/graphql-js that referenced this pull request Sep 17, 2024
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 or both "value" and "literal" fields.

Since either of these fields may be set, new functions for resolving a value or literal from either have been added - `getLiteralDefaultValue` and `getCoercedDefaultValue` - these replace uses that either assumed a set value or were manually converting a value back to a literal.

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)
```
yaacovCR pushed a commit to yaacovCR/graphql-js that referenced this pull request Sep 17, 2024
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 or both "value" and "literal" fields.

Since either of these fields may be set, new functions for resolving a value or literal from either have been added - `getLiteralDefaultValue` and `getCoercedDefaultValue` - these replace uses that either assumed a set value or were manually converting a value back to a literal.

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)
```
yaacovCR pushed a commit to yaacovCR/graphql-js that referenced this pull request Sep 17, 2024
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 or both "value" and "literal" fields.

Since either of these fields may be set, new functions for resolving a value or literal from either have been added - `getLiteralDefaultValue` and `getCoercedDefaultValue` - these replace uses that either assumed a set value or were manually converting a value back to a literal.

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)
```
yaacovCR pushed a commit to yaacovCR/graphql-js that referenced this pull request Sep 17, 2024
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. GraphQL behavior should not change, though error messages are now slightly different.
yaacovCR pushed a commit to yaacovCR/graphql-js that referenced this pull request Sep 17, 2024
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 or both "value" and "literal" fields.

Since either of these fields may be set, new functions for resolving a value or literal from either have been added - `getLiteralDefaultValue` and `getCoercedDefaultValue` - these replace uses that either assumed a set value or were manually converting a value back to a literal.

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)
```
yaacovCR pushed a commit to yaacovCR/graphql-js that referenced this pull request Sep 17, 2024
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. GraphQL behavior should not change, though error messages are now slightly different.
yaacovCR pushed a commit to yaacovCR/graphql-js that referenced this pull request Sep 17, 2024
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)
```
yaacovCR pushed a commit to yaacovCR/graphql-js that referenced this pull request Sep 17, 2024
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 pushed a commit to yaacovCR/graphql-js that referenced this pull request Sep 17, 2024
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)
```
yaacovCR pushed a commit to yaacovCR/graphql-js that referenced this pull request Sep 17, 2024
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 added a commit that referenced this pull request Sep 23, 2024
[#3074 rebased on
main](#3074).

Depends on #3809

@leebyron comments from original PR (edited, hopefully correctly):

> Fixes #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 (EDIT:
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 #3049.
> 
> **After this change:**
> 
> ```
> (SDL) --parse-> (defaultValue literal config) --print --> (SDL)
> ```

Co-authored-by: Lee Byron <[email protected]>
yaacovCR pushed a commit to yaacovCR/graphql-js that referenced this pull request Sep 29, 2024
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 pushed a commit to yaacovCR/graphql-js that referenced this pull request Oct 13, 2024
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 pushed a commit to yaacovCR/graphql-js that referenced this pull request Oct 15, 2024
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 added a commit that referenced this pull request Oct 15, 2024
…itionRule` (#4194)

### TLDR => let's consider moving validation of variable positions with
respect to one of into the `VariablesInAllowedPositionRule`, i.e. out of
the `ValuesOfCorrectTypeRule`.

<hr>

This work was pulled out of the work rebasing the Default Value
Coercion/Validation PR stack at #3814 on the OneOf and Experimental
Fragment Variables changes.

In that PR stack (work originally done by @leebyron within #3049), Lee
extracts the validation done by the `ValuesOfCorrectTypeRule` into a
`validateInputLiteral()` utility that can be called at validation-time
or at run-time. At validation time, `validateInputLiteral()` validates
statically, is not supplied variables **or their types**, and does not
validate variables, while at run-time it is supplied variables and does
validate them.

<hr>

With OneOf Input Objects, we have a situation in which Input Objects
will define technically nullable/optional input fields, but the addition
of the `@oneOf` directive tells the validator/executor that we have to
supply exactly one of these fields (and it cannot be given the value
`null`). In essence, we are saying that when `@oneOf` is applied, the
fields of the input object are no longer technically nullable positions,
although they are still optional. This was for a combination of type
reasoning, backwards compatibility, and simplicity, working overall
within the constraints of GraphQL where only nullable fields are
optional.

So, to validate variable usage with OneOf Input Object, we need to make
sure that a variable with a nullable type is not allowed to show up in a
field position on a OneOf Input Object. Where should this be done?
Currently, this is done within the `ValuesOfCorrectTypeRule`, which for
this purpose was modified to collect the operation variable definitions.
@leebyron 's changes in the abovementioned stack extracts this to
`validateInputLiteral()`, where we run into a problem, we don't have
access to the operation (or experimental fragment variables)! Lee's work
preserving variable sources and definitions organizes the definitions by
source, so if we are analyzing statically, we don't have the source or
the definitions.

There are two paths forwards.

One idea is to modify Lee's work, and split the definitions from the
sources, and supply them to `validateInputLiteral()` even when working
statically, which complicates the signature of a number of functions.

What if we take a step back, and ask ourselves if we should have really
modified `ValuesOfCorrectTypeRule` to collect all those operation
variable definitions? If we move this bit to
`VariablesInAllowedPositionRule`, then we avoid the entire complexity.
That's what this PR does.

<hr>

How did this happen anyway? Shouldn't it be clear from the spec change
in which validation rule the changes should have gone? Actually....not
exactly. According to [the proposed spec
changes](graphql/graphql-spec#825), this work
was not done within the `ValuesOfCorrectTypeRule` or the
`VariablesInAllowedPositionRule`, but instead within a wholly new "OneOf
Input Object Rule." That's not the way it got implemented, and in my
opinion for good reason! I'm planning on separately submit some comments
on the RFC to that effect, that we can eliminate the need for the new
rule, and fold the changes into the existing `ValuesOfCorrectTypeRule`
-- which basically says that if it can't coerce, it's not valid, and
because of the coercion changes does not require any actual new text --
except within the `VariablesInAllowedPositionRule`.

Anyway, TLDR TLDR => let's consider moving validation of variable
positions with respect to one of into the
`VariablesInAllowedPositionRule`, i.e. out of the
`ValuesOfCorrectTypeRule`. Discussion of allowed variable positions just
belongs within that rule, just look at the rule name. :)
yaacovCR pushed a commit to yaacovCR/graphql-js that referenced this pull request Oct 15, 2024
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 pushed a commit to yaacovCR/graphql-js that referenced this pull request Oct 15, 2024
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 pushed a commit to yaacovCR/graphql-js that referenced this pull request Oct 18, 2024
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 pushed a commit to yaacovCR/graphql-js that referenced this pull request Oct 18, 2024
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
Copy link
Contributor

Closed in favor of #3814

@yaacovCR yaacovCR closed this Oct 27, 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 <[email protected]>
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 spec RFC Implementation of a proposed change to the GraphQL specification
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants