Skip to content

CoerceInputValue() #1179

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

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

CoerceInputValue() #1179

wants to merge 7 commits into from

Conversation

leebyron
Copy link
Collaborator

@leebyron leebyron commented Jul 4, 2025

As an alternative to #1058 and #1178 - this is a more substantial overhaul of how input value coercion is defined in favor of a system of composed algorithms.

A quick summary of what this introduces:

  • Introduces the concept of a coercion failure which has a similar "raised error" behavior, but is caught and translated into specific errors based on each callsite. That removes the awkwardness of "a request error is actually an execution error here".

  • A new "Input Coercion" section which defines CoerceInputValue(). This is the point of reusability which handles nullability and then forks out to each specific input type.

  • Algorithms for each kind of type's input coercion. Notably input objects and lists which have been under-defined (as an alt to [RFC] List coercion algorithm #1058).

  • A validation rule for default variable values, which I believe exists in graphql.js but was missing in spec.

Open Qs:

  • Is this the right place for the Input Coercion subsection?

  • Have I missed anything?

@leebyron leebyron requested review from benjie and a team July 4, 2025 00:20
@leebyron leebyron added the 💡 Proposal (RFC 1) RFC Stage 1 (See CONTRIBUTING.md) label Jul 4, 2025
Copy link

netlify bot commented Jul 4, 2025

Deploy Preview for graphql-spec-draft ready!

Name Link
🔨 Latest commit e9a4ec2
🔍 Latest deploy log https://app.netlify.com/projects/graphql-spec-draft/deploys/68689ba6d3b6c8000823d807
😎 Deploy Preview https://deploy-preview-1179--graphql-spec-draft.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 project configuration.

@leebyron leebyron force-pushed the lee/coerce-input-value branch 2 times, most recently from 1f26f1c to 5f8fa39 Compare July 4, 2025 00:56
@leebyron leebyron force-pushed the lee/coerce-input-value branch from 5f8fa39 to 4f40177 Compare July 4, 2025 01:13
@leebyron leebyron requested a review from mjmahone July 4, 2025 01:14
Copy link
Member

@benjie benjie left a comment

Choose a reason for hiding this comment

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

I really like the introduction of coercion failure. This is a solid change, and we should consider extracting it and fast-track merging it as an editorial change.

I like the more algorithmic approach to validation, I think it should add significant clarity.

However, I'm struggling to actually comprehend this as it currently stands - see my second comment for details.

(As an aside, I think that it might behoove us to add "pseudotypes" to our algorithms to make it much clearer exactly what the type of each variable in an algorithm is.)


- Assert: {IsInputType(type)}.
- If {type} is a Non-Null type:
- If {value} is {null}, raise a _coercion failure_.
Copy link
Member

Choose a reason for hiding this comment

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

I'm uncomfortable with how the CoerceInputValue algorithm seems to be fed values which are a mixture of literals (AST nodes) from the document (required for things inside of input objects/lists within value to be a "variable", right?) versus raw values from the client (required for its usage from CoerceVariableValues()). This feels like it is essentially mixing together GraphQL.js' parseLiteral and parseValue and not being very explicit about it?

If this is not the case, please could you explain further what it means for something within value to "be a variable" if it's also accepting raw values from CoerceVariableValues() which could be essentially anything (due to custom scalars) - or maybe point out where literals and values are handled separately and I've misread the algorithms.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You are correct. The spec groups literal values and externally provided values under the same umbrella when describing input coercion.

I share the unease around this, and maybe part of what we need is a better name for value though to be fair we already have this "variable is a kind of value" here https://spec.graphql.org/draft/#Value

It's true that we are quite hand-wavy about how externally provided values are handled, and some of that is necessary because we need to support different runtimes and serialization. Perhaps we need some description in this "input coercion" section (or the variables coercion section?) where we explain how we expect a GraphQL service to provide a way to represent their internal values as those GraphQL expects.

An example were we do spell some detail here is enums https://spec.graphql.org/draft/#sec-Enums.Input-Coercion where we need to answer the question of whether an enum should input coerce a string? The answer is only if there isn't a better option. GraphQL literals has a better option, EDN has a better option, JSON does not.

@@ -1649,11 +1725,47 @@ type of an Object or Interface field.

**Input Coercion**

CoerceInputObjectValue(type, value, variableValues):
Copy link
Member

Choose a reason for hiding this comment

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

Noting this does not (yet) handle oneOf, so we'll need to decide which order we are merging these. Rewriting for oneOf may require a bit of refactoring due to the way ResolveVariable works.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

oneof is, barring last few q's on the thread, ready to go - so I assume I'd be rebasing over it.

they are essentially different types, so I was thinking something like forking based on a directive conditional check to a CoerceInputOneOfValue() algo

@@ -502,10 +542,17 @@ information on the serialization of scalars in common JSON and other formats.

**Input Coercion**

CoerceScalarValue(scalarType, value):
Copy link
Member

Choose a reason for hiding this comment

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

The JSON scalar, discouraged though it may be, requires access to variableValues.

I'm okay with omitting this from the spec, but it's worth noting that GraphQL.js does pass variableValues to parseLiteral/parseValue.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah this is a reasonable compliance question - is it valid to consider variable values during scalar input coercion? I think it's okay to say yes, though I agree with you that this is a bit weird, it doesn't seem harmful.

The one consideration here is that we'd want to make sure we actually reference variableValues in the prose, I think we'd do that sort of like `Return the result of calling the internal method provided by the type system for determining the “input coercion” of {scalarType} given the value {value} (which may consider {variableValues})."

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 to taking variableValues into account: CoerceScalarValue(scalarType, value, variableValues)

It's late here and I don't have all the context any more but we might even want coercedVariableValues?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

wordy but more accurate :)


ResolveVariable(type, variableName, coercedVariableValues):

- If {coercedVariableValues} has an entry named {variableName}, let
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
- If {coercedVariableValues} has an entry named {variableName}, let
- If {coercedVariableValues} exists and has an entry named {variableName}, let

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think we could make this an assertion, right? The only time it doesn't exist is when we're coercing constant literals which do not have variables in them

Copy link
Member

@benjie benjie Jul 8, 2025

Choose a reason for hiding this comment

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

This may be different to your intention, but I figure we call this from validation too (e.g. in “values of correct type”) where variable values are unknown (null) so we need to assume they’re valid (by “all variables usages are allowed”) and thus we simply don’t access the variable when variableValues is explicitly null (which implies it is being called during validation?)

leebyron and others added 6 commits July 4, 2025 20:24
Co-authored-by: Benjie <benjie@jemjie.com>
Co-authored-by: Benjie <benjie@jemjie.com>
Co-authored-by: Benjie <benjie@jemjie.com>
Co-authored-by: Benjie <benjie@jemjie.com>
Co-authored-by: Benjie <benjie@jemjie.com>
Co-authored-by: Benjie <benjie@jemjie.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
💡 Proposal (RFC 1) RFC Stage 1 (See CONTRIBUTING.md)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants