Report defaultValue in introspection as a GraphQL literal - #625
Open
xperiandri wants to merge 2 commits into
Open
xperiandri wants to merge 2 commits into
xperiandri wants to merge 2 commits into
Conversation
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Recursive defaults can overflow the stack, and enum matching uses equality semantics inconsistent with runtime serialization.
Review effort: Balanced
Findings: 1
Open (2)
What changed in this PR
Fixes #623 by formatting introspection default values as valid GraphQL literals rather than JSON.
Changes:
- Adds type-aware literal formatting for scalars, enums, lists, and input objects.
- Integrates formatting into schema introspection and fixes option-array unwrapping.
- Adds regression and unit coverage plus release notes.
| File | Description |
|---|---|
src/FSharp.Data.GraphQL.Shared/ValueLiterals.fs |
Implements GraphQL literal formatting. |
src/FSharp.Data.GraphQL.Shared/Helpers/Reflection.fs |
Corrects option type detection. |
src/FSharp.Data.GraphQL.Shared/FSharp.Data.GraphQL.Shared.fsproj |
Includes the new module. |
src/FSharp.Data.GraphQL.Server/Schema.fs |
Uses literals for introspection defaults. |
tests/FSharp.Data.GraphQL.Tests/ValueLiteralsTests.fs |
Tests literal formatting behavior. |
tests/FSharp.Data.GraphQL.Tests/IntrospectionTests.fs |
Adds introspection regressions. |
tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj |
Includes the new tests. |
RELEASE_NOTES.md |
Documents both fixes. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+250
to
+252
| |> tryAppendSeparated builder (fun struct (field, fieldValue) -> | ||
| builder.Append(field.Name).Append(": ") |> ignore | ||
| tryAppendValue jsonOptions builder field.TypeDef fieldValue) |
| | Enum enumDef -> | ||
| match | ||
| enumDef.Options | ||
| |> Array.vtryFind (fun enumValue -> enumValue.Value = value) |
Test Results 9 files 9 suites 14m 47s ⏱️ Results for commit d3a9152. ♻️ This comment has been updated with latest results. |
xperiandri
force-pushed
the
introspection-default-value-literals
branch
from
October 4, 2026 14:46
328530f to
d3a9152
Compare
xperiandri
force-pushed
the
reflection-type-comparison
branch
from
October 4, 2026 20:19
faa08eb to
cd7e32d
Compare
The `defaultValue` of `__InputValue` is a GraphQL-formatted string, but the
schema serialized the default value to JSON with its `JsonOptions`, which
is a valid GraphQL literal only by chance: an enum value became its .NET
value, such as `{"Case":"Local"}` for an F# union case, the keys of an
input object were quoted, and the output coercion of a scalar was skipped.
Clients that build a schema from introspection, like graphql-js
`buildClientSchema`, parse every `defaultValue` and failed on it.
The new internal `ValueLiterals` module prints a value by its input type,
like `astFromValue` and `print` of graphql-js:
* an enum value as the name of its `EnumValue`;
* a scalar value as its output coercion serializes it: numbers, booleans
and escaped strings directly, and any other type, like a date, a GUID or
a URI, the way the JSON options of the schema serialize it;
* a list with every item printed by the item type, and a single value as
that item, which input coercion accepts in place of a list;
* an input object with the GraphQL names of its fields, read through the
properties the fields bind to, leaving the skipped skippable fields out;
* an `option` or `voption` unwrapped, and a missing one as `null`.
A default value without a GraphQL literal, such as a value outside of its
enum, a `NaN` float, a value its scalar cannot serialize or an input object
without a property for one of its fields, is not reported, while its input
stays nullable. Printing never throws, because the introspected schema
backs the validation of every request.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`Document.ToQueryString` printed a string value between quotes as it was, so a string with a quote, a backslash or a control character produced an invalid document. It now escapes them with `appendStringValue`, the string printer of `ValueLiterals`, which moves to `AstExtensions` so that both use it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri
force-pushed
the
introspection-default-value-literals
branch
from
October 4, 2026 20:22
d3a9152 to
5e95032
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Introspection reported the
defaultValueof an argument or an input object field serialized to JSON with the schema'sJsonOptions, while the spec defines it as "a GraphQL-formatted string" (§4.2.3). JSON is a valid GraphQL literal only by chance: an enum value became its .NET value, such as{"Case":"Local"}for an F# union case, the keys of an input object were quoted, and the output coercion of a scalar was skipped. Clients that build a schema from introspection, like graphql-jsbuildClientSchemaused by graphql-codegen, parse everydefaultValueand failed withSyntax Error: Expected Name, found String "Case".Fixes #623. Builds on #628, which this PR targets; it recognizes skippable values through the type checks of #628. Every commit builds on its own, so the PR is meant to be merged with rebase.
Changes
ValueLiteralsmodule ofFSharp.Data.GraphQL.Sharedprints a value by its input type, likeastFromValueandprintof graphql-js, andSchemareports default values with it:EnumValue, e.g.LOCAL;JsonOptionsserialize it;[LOCAL, REMOTE];SkippableInputfields out, e.g.{userId: "1", attendanceMode: REMOTE};optionorvoptionunwrapped, and a missing one asnull.NaNor an infinity, a value its scalar cannot or refuses to serialize, an input object without a property for one of its fields) is no longer reported, and its input stays nullable. Printing never throws: the introspected schema backs the validation of every request, so one bad default value would fail them all.Document.ToQueryStringprinted a string value between quotes as it was, so a string with a quote, a backslash or a control character produced an invalid document. The string printer moves fromValueLiteralstoAstExtensionsasappendStringValue, and both use it.Notes
printof graphql-js 16:{a: 1}and[1, 2], without the inner spacesToQueryStringputs in.IDis always printed as a string, while graphql-js prints an ID that looks like an integer as anInt; both are validIDinput.Verification
FSharp.Data.GraphQL.Tests: 786 passed, 5 skipped, 0 failed, including the newValueLiteralsTests, two introspection tests with the repro of Introspection emits JSON-serialized value asdefaultValueinstead of a GraphQL literal (breaks enum defaults) #623 and aToQueryStringtest with characters to escapeFSharp.Data.GraphQL.slnxwith SDK10.0.401: 0 warnings, 0 errors; the first commit alone builds too🤖 Generated with Claude Code