FSharp.Data.GraphQL icon indicating copy to clipboard operation
FSharp.Data.GraphQL copied to clipboard

Invalid introspection schema when fields arguments are given default values

Open njlr opened this issue 3 years ago • 2 comments

Description

The library does not generate a valid introspection schema for arguments with default values. They do not appear to be escaped properly.

Repro steps

Here is an F# script that defines a simple schema and outputs the introspection JSON:

#r "nuget: FSharp.Data.GraphQL.Server, 1.0.7"
#r "nuget: Thoth.Json.Net, 5.0.0"

open System
open System.IO
open FSharp.Data.GraphQL
open FSharp.Data.GraphQL.Types
open FSharp.Data.GraphQL.Execution
open Thoth.Json.Net

// JSON stuff
module Encode =

  open System.Collections.Generic

  let rec private item (x : obj) =
    match x with
    | x when isNull x -> Encode.nil
    | :? Error as e -> graphQlError e
    | :? Guid as x -> Encode.guid x
    | :? int as i -> Encode.int i
    | :? int16 as i -> Encode.int16 i
    | :? int64 as i -> Encode.int64 i
    | :? string as s -> Encode.string s
    | :? float as x -> Encode.float x
    | :? float32 as x -> Encode.float32 x
    | :? decimal as x -> Encode.decimal x
    | :? DateTime as x -> Encode.datetime x
    | :? DateTimeOffset as x -> Encode.datetimeOffset x
    | :? bool as x -> Encode.bool x
    | :? (obj list) as xs -> Encode.list (xs |> Seq.map item |> Seq.toList)
    | :? (obj array) as xs -> Encode.array (xs |> Array.map item)
    | :? Map<string, obj> as xs ->
      xs
      |> Map.toSeq
      |> Seq.map (fun (k, v) -> k, item v)
      |> Seq.toList
      |> Encode.object
    | :? IDictionary<string, obj> as d ->
      Encode.object
        (
          d
          |> Seq.map (fun (KeyValue (k, v)) -> k, item v)
          |> Seq.toList
        )
    | :? (obj seq) as xs -> Encode.list (xs |> Seq.map item |> Seq.toList)
    | _ -> failwithf "Unsupported data item of type %A: %A" (x.GetType ()) x

  and graphQlError : Encoder<Error> =
    (fun (message, path) ->
      Encode.object
        [
          "message", Encode.string message
          "path", path |> List.map item |> Encode.list
        ])

  let graphQlOutput : Encoder<Output> = item

// Schema definition
let queryType =
  Define.Object<unit>(
    name = "Query",
    fields =
      [
        Define.Field(
          "foo",
          String,
          "Foo query",
          [
            Define.Input("bar", Nullable String, Some "this is the default value")
          ],
          (fun ctx x ->
            let bar = ctx.TryArg "bar"

            bar |> Option.defaultValue "no bar was given")
        )
      ]
  )

let schema = Schema (queryType)

// Introspection
let executor = Executor(schema)

let response =
  executor.AsyncExecute Introspection.IntrospectionQuery
  |> Async.RunSynchronously

let (Direct (output, _)) = response

let json =
  output
  |> Encode.graphQlOutput
  |> Encode.toString 2

// printfn "%A" json

File.WriteAllText("schema.json", json)

Here is a Node script that validates the schema:

// https://github.com/graphql/graphql-spec/issues/786#issuecomment-706195108

const fs = require("fs");
const { buildClientSchema, printSchema } = require("graphql");

const json = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));

if (json.errors) {
  throw new Error(
    `Errors in introspection: ${JSON.stringify(json.errors, null, 2)}`
  );
}

const schema = buildClientSchema(json.data);

console.log(printSchema(schema));

Expected behavior

# Generate schema.json
$ dotnet fsi ./Introspection.fsx

# Validate the schema
$ node ./validate-introspection-schema.js ./schema.json

The F# should generate a valid schema!

Actual behavior

The Node script fails:

GraphQLError [Object]: Syntax Error: Expected <EOF>, found Name "is".

The problem is that the default value in the schema JSON is not properly escaped.

We can manually escape it like this:

-                  "defaultValue": "this is the default value"
+                  "defaultValue": "\"this is the default value\""

And then the Node script works!

$ node ./validate-introspection-schema.js ./schema.json

"""Defers the resolution of this field or fragment"""
directive @defer on FIELD | FRAGMENT_DEFINITION | FRAGMENT_SPREAD | INLINE_FRAGMENT

"""Streams the resolution of this field or fragment"""
directive @stream on FIELD | FRAGMENT_DEFINITION | FRAGMENT_SPREAD | INLINE_FRAGMENT

"""Subscribes for live updates of this field or fragment"""
directive @live on FIELD | FRAGMENT_DEFINITION | FRAGMENT_SPREAD | INLINE_FRAGMENT

"""
The `Date` scalar type represents a Date value with Time component. The Date type appears in a JSON response as a String representation compatible with ISO-8601 format.
"""
scalar Date

"""
The `URI` scalar type represents a string resource identifier compatible with URI standard. The URI type appears in a JSON response as a String.
"""
scalar URI

type Query {
  """Foo query"""
  foo(bar: String = "this is the default value"): String!
}

Related information

  • Operating system: Ubuntu 20.04
  • Version: 1.0.7
  • .NET Runtime, CoreCLR or Mono Version: 6.0.101

This issue was also raised here: https://github.com/graphql/graphql-spec/issues/786

This issue may prevent the GraphIQL Docs panel from working, with error message:

Error fetching schema

njlr avatar Jan 07 '22 11:01 njlr

Simply a matter of escaping here?

https://github.com/fsprojects/FSharp.Data.GraphQL/blob/3cd0940df3150be671c284d205f9071a5d5950ed/src/FSharp.Data.GraphQL.Shared/Introspection.fs#L238

njlr avatar Jan 07 '22 11:01 njlr

Ah, I remember, I got this issue too

xperiandri avatar Oct 01 '22 11:10 xperiandri

Any chance this can be merged?

njlr avatar Aug 07 '23 16:08 njlr

As far as I remember it is fixed either in dev or in IGQLError branches

xperiandri avatar Aug 07 '23 17:08 xperiandri

I'm going to merge all that stuff this week

xperiandri avatar Aug 07 '23 17:08 xperiandri

I had a look. This needs to be migrated to the new changes in the IGQLError branch

xperiandri avatar Aug 07 '23 21:08 xperiandri

Fixed in #391

xperiandri avatar Nov 05 '23 15:11 xperiandri